GaloisInc / mir-json

Plugin for rustc to dump MIR in JSON format
Apache License 2.0
9 stars 2 forks source link

Implementations don't say which traits they are for. #4

Closed sweirich closed 5 years ago

sweirich commented 5 years ago

This is a bit obscure so not high priority. Trait methods can be called using a static type. However, mir-json does not include the right name in the output.

For example, this module (from mir-json's test/conc_eval/traits/static_two.rs) defines a trait with two different implementations.

enum S {}
enum U {}

trait T {
    fn g() -> u32;
}

impl T for S {
    fn g() -> u32 { 42 }
}

impl T for U {
    fn g() -> u32 { 1 }
}

fn f(_: ()) -> u32 {
    U::g()
}

In the output, the two implementations are called {{impl}}[0] and {{impl}}[1] respectively, but there is no associations between these names and the types. However, the type argument in the body of f is the only way to tell which function should be called.

fn ::{{impl}}[0]::g[0]() -> u32  {
   let mut _0 : u32;
   bb0: {
      _0 = use(42);
      return;
   }
}
fn ::{{impl}}[1]::g[0]() -> u32  {
   let mut _0 : u32;
   bb0: {
      _0 = use(1);
      return;
   }
}
fn ::f[0](_1 : ()) -> u32  {
   let mut _0 : u32;
   bb0: {
      call(_0 = ::T[0]::g[0]<::U[0]<>>()
          ,bb1)
   }
   bb1: {
      return;
   }
sweirich commented 5 years ago

This is also important when there are associated types:

trait FIndex {

    type Output : ?Sized;

    fn findex(&self, i:usize) -> &Self::Output; 

}

impl FIndex for [u8] {
    type Output = u8;
    fn findex(&self, i:usize) -> &u8 {
        &self[i]
    }
}

When I match up the implementation of the trait, I need to know what the type Output is for that implementation. The information that "type Output<[u8]> = u8" doesn't appear in mir-json.

spernsteiner commented 5 years ago

I think the JSON now contains all the information that's necessary to resolve trait-method calls. There's a new impls array at top level that contains information on each trait impl in the crate. (It currently doesn't include inherent impls, because inherent method calls are already resolved, but I don't think it would be hard to add them if necessary.)

An impls entry looks like this (from your first example above):

{
    "name": "::{{impl}}[0]",
    // Indicates the trait this impl implements.  `substs` gives the type
    // arguments for the trait, which may refer to the type parameters of the
    // impl.
    "trait_ref": {
        "substs": [
            { "kind": "Adt", "name": "::S[0]", "substs": [] }
        ],
        "trait": "::T[0]"
    }
    // Generics (and predicates) for the impl block, for `impl<T: Clone> ...`.
    "generics": {
        "params": []
    },
    "predicates": {
        "predicates": []
    },
    // The impl-item definitions.
    "items": [
        {
            "kind": "Method",
            // The def path of the impl-item.  For `Method`s, this should match
            // the name of the corresponding entry in `fns`.
            "name": "::{{impl}}[0]::g[0]",
            // The def path of the trait-item that this impl-item implements.
            // If there is no impl-item that `implements` a particular
            // trait-item, that means the impl uses the default from the trait.
            "implements": "::T[0]::g[0]",
            // Generics for the method itself.  For `Method`s, these should
            // match the generics of the `fn`.  This consists of the generics
            // inherited from the impl (if any), followed by any generics
            // declared on the impl-item itself.
            "generics": {
                "params": []
            },
            "predicates": {
                "predicates": []
            },
            // The `signature` field is specific to `Method`s.
            "signature": {
                "inputs": [],
                "output": { "kind": "Uint", "uintkind": { "kind": "u32" } }
            }
        }
    ],
},

Now for an example like this:

trait T {
  fn g<B>();
  fn h<B>() {}
}

impl<A> T for Vec<A> {
  fn g<B>() {}
  // Use default for `h`
}

fn f() {
  // In crux-mir, this is `::T::g::<Vec<u8>, bool>()`
  <Vec<u8> as T>::g::<bool>();
  <Vec<u8> as T>::h::<bool>();
}

Resolution of the call to T::g can be done as follows:

  1. Identify the trait that contains ::T::g, which is ::T. Split the type arguments at the call site into the trait args and method args. In this case, the trait has 1 type parameter (Self), so the first type argument (Vec<u8>) is for the trait, and the remaining argument (bool) is for the method.
  2. Find an impl whose trait_ref.trait is ::T and whose trait_ref.substs unify with the trait arguments. In this case, that's ::{{impl}}[0], and unification assigns u8 to the impl's type parameter A.
  3. Find the method in the impl's items array whose implements field references the trait method being called (::T::g). This item is called ::{{impl}}[0]::g.
  4. The call dispatches to the located method ::{{impl}}[0]::g, with type arguments <u8, bool>. The u8 comes from the impl type arguments found by unification, and the bool is from the method type arguments.

And for T::h:

  1. Same as before
  2. Same as before
  3. There's no entry in items that implements ::T::h, because the impl is using the default from the trait declaration.
  4. The call dispatches to the default implementation ::T::h, with type arguments <Vec<u8>, bool>, which are exactly the original trait and method type arguments from the call site.

I haven't looked closely at associated type projections, but hopefully resolving those should work similarly.

sweirich commented 5 years ago

Thanks!
I've noticed that "implements" is not always included with every item. For example, given this trait declaration

   pub trait Def: Sized {
        fn def() -> Self;
    }

the following impl conforms to your description above

  impl Def for u8 {
        #[inline]
        #[doc = "Returns default value of `0`"]
        fn def() -> u8 { 0 }
    } 

but if I use a macro to generate the impl, then I don't get the "implements" component of the item

     macro_rules! default_impl {
        ($t:ty, $v:expr, $doc:tt) => {
            impl Def for $t {
                #[inline]
                #[doc = $doc]
                fn def() -> $t { $v }
            }
        }
     }
    default_impl! { (), (), "Returns the default value of `()`" }    
spernsteiner commented 5 years ago

I believe this is fixed - mir-json now produces implements fields for both impl items in your example.