Stream: t-compiler/help

Topic: Calls in MIR


Edd Barrett (Nov 25 2019 at 11:26, on Zulip):

Hi everyone,

I have some questions about calls in MIR which I've been unable to tease out from the compiler source code.

I'm looking at a call site in the optimised MIR of a program. The call target is a TyKind::FnDef, which is expressed as a DefId and a SubstsRef. Using println debugging, I've determined that the DefId in question is never code-genned. That's likely because the callee in question is a trait function with no default implementation. The function does have a concrete implementation elsewhere (in the same crate) though.

Nonetheless I was quite surprised that a dynamic call to a trait function is a TyKind::FnDef. I thought that these kinds of calls would be distinguished from normal static calls via another variant (perhaps TyKind::FnPtr?).

So are calls to trait functions indistinguishable from normal static calls at the MIR level? It occurs to me that the trait in question has only one implementation. I wonder if that has something to do with it? Perhaps the compiler "promotes" the dynamic call to a static call?

Next, supposing there's a way to know if any given call is to a trait function, is there a way (inside the compiler) to enumerate the potential callees?

Context: I'm working on something which requires the ability to be able to distinguish a function with no MIR from a function which is a "stub" (no default impl) in a trait.

Thanks!

Matthew Jasper (Nov 25 2019 at 12:38, on Zulip):

Virtual calls (<dyn Trait as Trait>::method) are turned into a reading a function pointer from the vtable and then calling it.

Matthew Jasper (Nov 25 2019 at 12:40, on Zulip):

Other trait methods are codegened with the DefId from the impl, while in MIR the may use the DefId from the trait item.

Matthew Jasper (Nov 25 2019 at 12:43, on Zulip):

What do you mean by a function with no mir? One where it isn't available? Or one where it never existed on the first place?

Edd Barrett (Nov 25 2019 at 13:17, on Zulip):

Hi Matthew,

Other trait methods are codegened with the DefId from the impl, while in MIR the may use the DefId from the trait item.

Right, so I suppose I'm seeing the latter. At least, I see no TyKind::FnPtr in the target.

Is there a way to identify if a call is to a trait item, and if so, enumerate its impls? That'd be very useful.

What do you mean by a function with no mir?

In my case, it's functions where tcx.is_mir_available() returns true after all passes have run, and right before the codegen.

Thanks

Edd Barrett (Nov 25 2019 at 13:32, on Zulip):

while in MIR the may use the DefId from the trait item.

And I guess, is there a way to resolve the call to a concrete function?

Matthew Jasper (Nov 25 2019 at 14:09, on Zulip):

while in MIR the may use the DefId from the trait item.

And I guess, is there a way to resolve the call to a concrete function?

ty::Instance::resolve

Matthew Jasper (Nov 25 2019 at 14:10, on Zulip):

Well, that requires running during monomorphization.

Edd Barrett (Nov 25 2019 at 14:26, on Zulip):

I've already experimented with Instance::resolve(), but figured it wasn't right due to:

error: internal compiler error: src/librustc/traits/codegen/mod.rs:53: Encountered error `Unimplemented`
selecting `Binder(<F as std::ops::Fn<(f64,)>>)` during codegen

I also tried tcx.subst_and_normalize_erasing_regions(). Whilst this didn't crash, it didn't resolve the DefId in question either.

Well, that requires running during monomorphization.

Can you tell me more about this?

Edd Barrett (Nov 25 2019 at 15:11, on Zulip):

By the way, I just realised that I am actually running my code after codegen, not before (in case that changes anything).

I run after:

codegen_backend.codegen_crate(...)
Edd Barrett (Nov 25 2019 at 16:39, on Zulip):

Well, that requires running during monomorphization.

I guess this relates to:

error: internal compiler error: src/librustc/traits/codegen/mod.rs:127: Encountered errors `[FulfillmentE
rror(Obligation(predicate=Binder(TraitPredicate(<R as std::marker::Sized>)), depth=1),Unimplemented)]` re
solving bounds after type-checking

i.e. I'm resolve()ing too late :(

Matthew Jasper (Nov 25 2019 at 20:06, on Zulip):

Hi Matthew,

Other trait methods are codegened with the DefId from the impl, while in MIR the may use the DefId from the trait item.

Right, so I suppose I'm seeing the latter. At least, I see no TyKind::FnPtr in the target.

Is there a way to identify if a call is to a trait item, and if so, enumerate its impls? That'd be very useful.

What do you mean by a function with no mir?

In my case, it's functions where tcx.is_mir_available() returns true after all passes have run, and right before the codegen.

Thanks

You can list all impls with tcx.trait_of_item and tcx.all_impls. Then tcx.associated_items will give an iterator over the associated items for each impl. Which can then be filters by ident and kind. (See https://github.com/rust-lang/rust/blob/781866f3a95da0e4a35151cc24523f186ce19c79/src/librustc/traits/specialize/specialization_graph.rs#L440)

Edd Barrett (Nov 26 2019 at 11:53, on Zulip):

Thanks Matthew. I'll have a play around and see if this works for me.

Thanks so much for the pointers.

Edd Barrett (Nov 26 2019 at 16:32, on Zulip):

Hi Matthew,

I was able to enumerate the associated items of the trait, so thanks for this :)

Just to be clear: there's no way of knowing which of the candidates will actually be called without "being the codegen"?

Matthew Jasper (Nov 26 2019 at 20:54, on Zulip):

It's actually rustc_mir::monomorphize::collector that does this. You could probably copy the implementation there if the collect_and_partition_mono_items query isn't providing enough information.

Edd Barrett (Nov 27 2019 at 12:26, on Zulip):

It's actually rustc_mir::monomorphize::collector that does this.

The function responsible for resolving the call sites in the collector is visit_fn_use(). I changed it to cache the results of resolutions into a hashmap keyed by instance (in the tcx. yeah, I know, gross). That way I can access them later.

So the function now looks like this:

fn visit_fn_use<'tcx>(
    tcx: TyCtxt<'tcx>,
    ty: Ty<'tcx>,
    is_direct_call: bool,
    output: &mut Vec<MonoItem<'tcx>>,
) {
    if let ty::FnDef(def_id, substs) = ty.kind {
        let resolver = if is_direct_call {
            ty::Instance::resolve
        } else {
            ty::Instance::resolve_for_fn_ptr
        };
        let instance = resolver(tcx, ty::ParamEnv::reveal_all(), def_id, substs).unwrap();

        let mut resolutions = tcx.call_resolution_map.borrow_mut();
        if resolutions.is_none() {
            *resolutions = Some(FxHashMap::default());
        }
        let pre_inst = Instance::new(def_id, substs);

        if tcx.def_path_str(def_id).contains("stop_tracing") {
            dbg!(&pre_inst);
            dbg!(instance);
        }

        resolutions.as_mut().unwrap().insert(pre_inst, instance);

        visit_instance_use(tcx, instance, is_direct_call, output);
    }
}

And as you can see, I have some dbg! in there so I can see how my problem call-site is being resolved.

[src/librustc_mir/monomorphize/collector.rs:738] &pre_inst = Instance {
    def: Item(
        DefId(0:156 ~ yktrace[72ea]::ThreadTracerImpl[0]::stop_tracing[0]),
    ),
    substs: [
        dyn ThreadTracerImpl,
    ],
}
[src/librustc_mir/monomorphize/collector.rs:739] instance = Instance {
    def: Virtual(
        DefId(0:156 ~ yktrace[72ea]::ThreadTracerImpl[0]::stop_tracing[0]),
        0,
    ),
    substs: [
        dyn ThreadTracerImpl,
    ],
}

Seems that the call remains unresolved? I was expecting substs to be empty after resolution. But if that were always the case, resolve() would return only a DefId. Seems I'm missing something...

I think this has something to do with codegen_call_terminator() in src/librustc_codegen_ssa/mir/block.rs where there appears to a second layer of resolution?

Matthew Jasper (Nov 27 2019 at 12:38, on Zulip):

Oh, it's an actual virtual call. The concrete method gets recorded through a path like coerce to trait object ("unsize coercion") -> vtable -> method.

Edd Barrett (Nov 27 2019 at 13:25, on Zulip):

Oh, it's an actual virtual call. The concrete method gets recorded through a path like coerce to trait object ("unsize coercion") -> vtable -> method.

So the target is only decided at runtime, right?

Matthew Jasper (Nov 27 2019 at 13:45, on Zulip):

Yes

Edd Barrett (Nov 27 2019 at 17:07, on Zulip):

Great!

Thanks for your patience with this. I have much to learn :)

bjorn3 (Dec 02 2019 at 14:37, on Zulip):

Edit: didn't read your comment completely.

Last update: Dec 12 2019 at 01:35UTC