Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move MIR towards a single kind of local #36752

Merged
merged 15 commits into from
Sep 29, 2016
Merged

Move MIR towards a single kind of local #36752

merged 15 commits into from
Sep 29, 2016

Conversation

jonas-schievink
Copy link
Contributor

@jonas-schievink jonas-schievink commented Sep 26, 2016

This PR modifies MIR to handle function arguments (Arg), user-defined variable bindings (Var), compiler-generated temporaries (Tmp), as well as the return value pointer equally. All of them are replaced with a single Local type, a few functions for iterating over different kinds of locals, and a way to get the kind of local we're dealing with (mainly used in the constant qualification/propagation passes).

I haven't managed to fix one remaining issue: A StorageDead not getting emitted for a variable (see the TODO in the test). If that's fixed, this is basically good to go. Found the issue (an off-by-one error), fix incoming.

r? @eddyb for changes to constant qualification and propagation I'm not quite sure about

If MIR is for a "rust-call" ABI function, the last arg would always
have `spread` set to `true`. Move this flag into `Mir` instead.
Copy link
Member

@eddyb eddyb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM modulo nits. cc @rust-lang/compiler In case anyone has anything to say against it.

/// is passed as its individual components at the LLVM level.
///
/// This is used for the "rust-call" ABI.
pub spread_last_arg: bool,
Copy link
Member

@eddyb eddyb Sep 26, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather have an Option<Local> here instead, special-casing the last argument is a step backwards for VG.

/// Returns an iterator over all function arguments.
#[inline]
pub fn arg_iter<'a>(&'a self) -> impl Iterator<Item=Local> + 'a {
(1..self.arg_count+1).map(Local::new)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to borrow self.


/// Returns an iterator over all temporaries.
#[inline]
pub fn temp_iter<'a>(&'a self) -> impl Iterator<Item=Local> + 'a {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usual conversion would be temps, I believe (same for the ones below).

}
repr::Operand::Consume(_) |
repr::Operand::Consume(ref lval) => match *lval {
repr::Lvalue::Local(local) if mir.local_kind(local) == LocalKind::Temp => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think this code should care. cc @pnkfelix

Lvalue::Var(var) => Some(Local::new(var.index() + self.arg_count)),
Lvalue::Temp(temp) => {
Some(Local::new(temp.index() + self.arg_count + self.var_count))
if let Lvalue::Local(local) = *lvalue {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet! I'm sorry @pcwalton had to write this in the first place, but, this is all in hindsight.

block: BasicBlock::new(0),
statement_index: usize::MAX
});
self.assign(Lvalue::ReturnPointer, rvalue, span);

self.assign(Lvalue::Local(RETURN_POINTER), rvalue, span);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be able to change assign to take a Local.

let retptr = allocate_local(mir::RETURN_POINTER);
iter::once(retptr)
.chain(args.into_iter())
.chain(mir.var_and_temp_iter().map(&mut allocate_local))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not move allocate_local into the map call?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has gone through a refactoring, I'll remove the &mut.

// return = Baz { x: tmp0, y: const F32(0), z: const false }; // scope ...
// local2 = local1; // scope 0 at main.rs:8:8: 8:9
// local3 = local2; // scope 1 at main.rs:9:14: 9:15
// local0 = Baz { x: local3, y: const F32(0), z: const false }; // scope ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Losing the names is unfortunate, do you think we could recover them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean what kind of local each one is? We could recover that, but the full MIR dump already contains variable names so you can differentiate temps and variables. The full dump also has a comment on local0 explaining that it's the return pointer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's harder to read though. Maybe if it was %5 like in LLVM or _5, it'd be easier. But still.
I guess there's no easy way to recover that information due to how it's printed.

@jonas-schievink jonas-schievink changed the title [WIP] Move MIR towards a single kind of local Move MIR towards a single kind of local Sep 26, 2016
/// Returns an iterator over all user-defined variables and compiler-generated temporaries (all
/// locals that are neither arguments nor the return pointer).
#[inline]
pub fn vars_and_temps_iter<'a>(&'a self) -> impl Iterator<Item=Local> + 'a {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also doesn't need 'a.

// they are the two sub-fields of a single aggregate field.
let meta = &fcx.fn_ty.args[idx];
if let Some(spread_local) = mir.spread_arg {
if local == spread_local {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can write this as if Some(local) == mir.spread_arg.

@solson
Copy link
Member

solson commented Sep 28, 2016

LGTM, but the loss of names in the MIR is somewhat annoying. If we can't easily recover the original names, then since all locals are now printed the same, I'd prefer to make the prefix short, like @eddyb's _5 suggestion.

@jonas-schievink
Copy link
Contributor Author

We could recover the local kind (by threading a &Mir through the printer), but I actually like this way better. I also don't know how we should print this information without confusing the reader. Just replacing local with arg etc. doesn't seem appropriate since it suggests that arguments, variables and temps are still different things. Adding it to the comment might be worth a shot, but comments aren't placed on arguments.

For the record, here's a "full" MIR dump for a small test program after this patch:

fn testroutine(local1: u8, local2: u8) -> u8 {
    let mut local0: u8;                  // return pointer
    scope 1 {
        let local3: u8;                  // "start" in scope 1 at test.rs:1:16: 1:21
        let local4: u8;                  // "end" in scope 1 at test.rs:1:27: 1:30
    }
    let mut local5: std::iter::Map<std::ops::Range<u8>, [closure@test.rs:2:24: 2:33]>;
    let mut local6: std::ops::Range<u8>;
    let mut local7: u8;
    let mut local8: u8;
    let mut local9: u8;
    let mut local10: u8;
    let mut local11: (u8, bool);
    let mut local12: [closure@test.rs:2:24: 2:33];

    bb0: {
        StorageLive(local3);             // scope 0 at test.rs:1:16: 1:21
        local3 = local1;                 // scope 0 at test.rs:1:16: 1:21
        StorageLive(local4);             // scope 0 at test.rs:1:27: 1:30
        local4 = local2;                 // scope 0 at test.rs:1:27: 1:30
        StorageLive(local5);             // scope 1 at test.rs:2:5: 2:34
        StorageLive(local6);             // scope 1 at test.rs:2:5: 2:19
        StorageLive(local7);             // scope 1 at test.rs:2:6: 2:11
        StorageLive(local8);             // scope 1 at test.rs:2:6: 2:11
        local8 = local3;                 // scope 1 at test.rs:2:6: 2:11
        local7 = local8;                 // scope 1 at test.rs:2:6: 2:11
        StorageLive(local9);             // scope 1 at test.rs:2:13: 2:18
        StorageLive(local10);            // scope 1 at test.rs:2:13: 2:16
        local10 = local4;                // scope 1 at test.rs:2:13: 2:16
        local11 = CheckedAdd(local10, const 1u8); // scope 1 at test.rs:2:13: 2:18
        assert(!(local11.1: bool), "attempt to add with overflow") -> bb1; // scope 1 at test.rs:2:13: 2:18
    }

    bb1: {
        local9 = (local11.0: u8);        // scope 1 at test.rs:2:13: 2:18
        local6 = std::ops::Range::<u8> { start: local7, end: local9 }; // scope 1 at test.rs:2:6: 2:18
        StorageLive(local12);            // scope 1 at test.rs:2:24: 2:33
        local12 = [closure@test.rs:2:24: 2:33]; // scope 1 at test.rs:2:24: 2:33
        local5 = <std::ops::Range<u8> as std::iter::Iterator>::map::<u8, [closure@test.rs:2:24: 2:33]>(local6, local12) -> bb2; // scope 1 at test.rs:2:5: 2:34
    }

    bb2: {
        local0 = <std::iter::Map<std::ops::Range<u8>, [closure@test.rs:2:24: 2:33]> as std::iter::Iterator>::sum::<u8>(local5) -> bb3; // scope 1 at test.rs:2:5: 2:40
    }

    bb3: {
        StorageDead(local5);             // scope 1 at test.rs:2:5: 2:34
        StorageDead(local12);            // scope 1 at test.rs:2:24: 2:33
        StorageDead(local6);             // scope 1 at test.rs:2:5: 2:19
        StorageDead(local9);             // scope 1 at test.rs:2:13: 2:18
        StorageDead(local10);            // scope 1 at test.rs:2:13: 2:16
        StorageDead(local7);             // scope 1 at test.rs:2:6: 2:11
        StorageDead(local8);             // scope 1 at test.rs:2:6: 2:11
        StorageDead(local4);             // scope 0 at test.rs:1:27: 1:30
        StorageDead(local3);             // scope 0 at test.rs:1:16: 1:21
        return;                          // scope 1 at test.rs:1:1: 3:2
    }
}

fn testroutine::{{closure}}(local1: &mut [closure@test.rs:2:24: 2:33], local2: u8) -> u8 {
    let mut local0: u8;                  // return pointer
    scope 1 {
        let local3: u8;                  // "i" in scope 1 at test.rs:2:25: 2:26
    }
    let mut local4: u8;
    let mut local5: u8;
    let mut local6: (u8, bool);

    bb0: {
        StorageLive(local3);             // scope 0 at test.rs:2:25: 2:26
        local3 = local2;                 // scope 0 at test.rs:2:25: 2:26
        StorageLive(local4);             // scope 1 at test.rs:2:28: 2:29
        local4 = local3;                 // scope 1 at test.rs:2:28: 2:29
        StorageLive(local5);             // scope 1 at test.rs:2:32: 2:33
        local5 = local3;                 // scope 1 at test.rs:2:32: 2:33
        local6 = CheckedMul(local4, local5); // scope 1 at test.rs:2:28: 2:33
        assert(!(local6.1: bool), "attempt to multiply with overflow") -> bb1; // scope 1 at test.rs:2:28: 2:33
    }

    bb1: {
        local0 = (local6.0: u8);         // scope 1 at test.rs:2:28: 2:33
        StorageDead(local5);             // scope 1 at test.rs:2:32: 2:33
        StorageDead(local4);             // scope 1 at test.rs:2:28: 2:29
        StorageDead(local3);             // scope 0 at test.rs:2:25: 2:26
        return;                          // scope 1 at test.rs:2:24: 2:33
    }
}
fn testroutine(start: u8, end: u8) -> u8 {
    (start..end+1).map(|i| i * i).sum()
}

@jonas-schievink
Copy link
Contributor Author

We could possibly annotate each localN with the local kind, so you'd end up with local0{return}, local1{arg0}, local3{var0 "start"} and local7{tmp3} or something like that.

There's no need for a long prefix, since there's nothing to distinguish
anymore.
They're ignored by the test runner, so let's not suggest that they
matter
@eddyb
Copy link
Member

eddyb commented Sep 28, 2016

@bors r+

@bors
Copy link
Contributor

bors commented Sep 28, 2016

📌 Commit d2c8893 has been approved by eddyb

@bors
Copy link
Contributor

bors commented Sep 29, 2016

⌛ Testing commit d2c8893 with merge ff67da6...

bors added a commit that referenced this pull request Sep 29, 2016
Move MIR towards a single kind of local

This PR modifies MIR to handle function arguments (`Arg`), user-defined variable bindings (`Var`), compiler-generated temporaries (`Tmp`), as well as the return value pointer equally. All of them are replaced with a single `Local` type, a few functions for iterating over different kinds of locals, and a way to get the kind of local we're dealing with (mainly used in the constant qualification/propagation passes).

~~I haven't managed to fix one remaining issue: A `StorageDead` not getting emitted for a variable (see the `TODO` in the test). If that's fixed, this is basically good to go.~~ Found the issue (an off-by-one error), fix incoming.

r? @eddyb for changes to constant qualification and propagation I'm not quite sure about
@jonas-schievink jonas-schievink deleted the vartmparg branch September 29, 2016 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants