GaloisInc / mir-json

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

"Promoted" values #12

Open sweirich opened 5 years ago

sweirich commented 5 years ago

The following example


fn k(x : u32) -> u32 {
    x + 1
}

fn app<G>(x : &G, y : u32) -> u32
  where G : Fn(u32) -> u32 {
    x(y)
}

fn f(_: ()) -> u32 {
    let d = 32;
    app(&k,d)
}

Generates a use of a Promoted lvalue in the definition of the f function (in the definition of _4).

fn ::f (_1_mut : ()) -> u32  {
   let mut _1 : ();
   let mut _0 : u32;
   let const _2 : u32;
   let mut _3 : &fnDef ::k<>;
   let mut _4 : &fnDef ::k<>;
   let mut _5 : u32;
   bb0: {
      _1 = use(_1_mut);
      StorageLive(_2);
      _2 = use(32);
      StorageLive(_3);
      StorageLive(_4);
      _4 = &Promoted(promoted[0]);
      _3 = use(_4);
      StorageLive(_5);
      _5 = use(_2);
      call(_0 = ::app<fnDef ::k<>,projection [::ops::function::FnOnce::Output<fnDef ::k<>,(u32)>]>(_3
                                                                                                  ,_5)
          ,bb1)
   }
   bb1: {
      StorageDead(_5);
      StorageDead(_3);
      StorageDead(_2);
      StorageDead(_4);
      return;
   }
}

This promoted value is a constant function pointer to the k function, but that isn't clear from its definition. Should there be another definition in the MIR output that links promoted[0] to k?

spernsteiner commented 5 years ago

mir-json now outputs information on statics, including promoted statics.

The short version is:

Note that each static has only a single DefId, which is used in all three locations (the Place, the entry in fns, and the entry in statics).

Now for the long version:

Places:

Place::Static is now represented like this:

{
    "kind": "Static",
    "def_id": "::S[0]",
    "ty": {
        "kind": "Ref",
        "mutability": {
            "kind": "MutImmutable"
        },
        "ty": {
            "kind": "Uint",
            "uintkind": {
                "kind": "usize"
            }
        }
    }
}

Place::Promoted is now represented like this:

{
    "kind": "Promoted",
    "index": 1,
    "ty": {
        "kind": "Uint",
        "uintkind": {
            "kind": "usize"
        }
    }
}

index is an index into the promoted table of the containing function.

MIR:

MIR function bodies in fns now contain a table called promoted, which lists the DefId of each promoted static from that MIR:

{
    "name": "::f[0]",
    // other fields...
    "promoted": [
        "::f[0]::{{promoted}}[0]",
        "::f[0]::{{promoted}}[1]"
    ]
}

So the previous { "kind": "Promoted", "index": 1, ... } Place would be a reference to the promoted static ::f[0]::{{promoted}}[1], and an entry with "name": "::f[0]::{{promoted}}[1]" should be present in both fns and statics.

Statics:

There is a new top-level table, statics, which contains an entry for each static item and each promoted static in the crate. This is in addition to the entries that statics already had in fns, so each static now has two entries in the JSON: an entry in fns representing a zero-argument function that returns the static's initial value, and an entry in statics with the same name that contains (I believe) the necessary metadata to allocate storage for the static.

The statics entry for a static item looks like this:

{
    "name": "::S[0]",
    "mutable": false,       // Will be `true` for a `static mut` item
    "ty": {
        "kind": "Ref",
        "mutability": {
            "kind": "MutImmutable"
        },
        "ty": {
            "kind": "Uint",
            "uintkind": {
                "kind": "usize"
            }
        }
    }
}

The entry for a promoted static additionally has a back-reference to the function that produced it. Not sure if this is useful, but just in case you need it:

{
    "name": "::f[0]::{{promoted}}[1]",
    "ty": ...,          // as above
    "mutable": ...,     // as above
    "promoted_from": "::f[0]",
    "promoted_index": 1,
},
spernsteiner commented 5 years ago

Note, in case you haven't seen it before, Rust's handling of function pointers is a bit unusual. If you write this:

fn f() { ... }
let fp = f;

then the type of fp is not fn() (the type of pointers to nullary functions returning unit), but rather fn() {f}, which encodes the fact that it's a pointer specifically to the function f into the type (internally, this is written TyKind::FnDef(<f's DefId>)), and which takes up zero bytes at run time. So, in the MIR initializer function for the promoted static in by your example above, the reference to f will likely appear only in the type of the zero-sized constant, and not in any Consts or Rvalues.

sweirich commented 5 years ago

Thanks! I've incorporated this update into mir-verifier. I'd hate for this comment to get lost though---it's good documentation for mir-json.

Thanks also for the info about Rust function pointers. We do have one (currently-failing) test case that uses a promoted function pointer. However, the type translation needs to be able to look up the type of 'f' to translate TyFnDef correctly. I'll need to do some refactoring to make this info available in the right place, so I can fix that test case.