MIR: Excessive temporary variables and assignments

Good day!
Explain please why at MIR creation rust is using temporary variable and excessive assignment when it seems is no need for this:

fn main() {
    let _x = false;
    let _y;
    _y = _x;
}

(Playground)

Here is MIR fragment:

 bb0: {
        StorageLive(_1);
        _1 = const false;
        StorageLive(_2);
        StorageLive(_3);
        _3 = _1;
        _2 = move _3;
        StorageDead(_3);
        StorageDead(_2);
        StorageDead(_1);
        return;
    }

Why temporary variable _3 was created? Isn't it enough to immediately assign _2 = _1?

3 Likes

To me, it is more of a mystery why StorageLive(_2) was created for let _y;
when no type and value were yet available.
No doubt something will be said about copying items on the stack but even so, one copy should not need two more locations?

This is just a guess but,
MIR is just an internal representation of the compiler. Its generation is not optimized for such efficiency because it later optimized in LLVM passes but more optimized for implementation simplicity.
So the answer to the question is probably because it was simpler to implement in that way and there is no real reason.

4 Likes

There are probably more complex cases, such as types with Drop or construction of struct literals, that may require some kind of two-phase assignment. I wouldn't be surprised if complex MIR was generated in all cases just to handle the special cases.

2 Likes

_1 is a bool, which is Copy. So when it's read out of, it's copied (_3 = _1) and then that copy is moved into the target (_2 = move _3). It's not necessary, but it's easier to generate that way than special-casing situations where it's not needed. LLVM can optimize it away just fine (in either mem2reg or just in SSA building), so it being generates this way is fine.

The MIR-opt group might add a pass to clean this up in future. I tried doing so once in the past: https://github.com/rust-lang/rust/pull/46440

1 Like

@ scottmcm Thanks.
It's unlicky that the reason of closing your commit (https://github.com/rust-lang/rust/pull/46440) was remain hidden:

Closing after IRC discussion, as eddyb's figured out how to make full NRVO sound.

I can imagine there are tons of such excessive assignments in MIR's of many real apps.
Isn't such optimization can reduce a other MIR-passes work?

You can try -Zmir-opt-level=2 to enable copyprop pass, which also removes redundant assignments (though it is reported that the optimization itself is slow: https://github.com/rust-lang/rust/issues/36673).

Thanks a lot. I confirm.

I see a work around CopyPropagation optimizing was freezed at 11.2016. And that issue still stay open...

I noticed that these assignments are not actually "excessive", at least when debuginfo is enabled.

_3 = _1;                         // bb0[4]: scope 2 at src/main.rs:4:10: 4:12
_2 = move _3;                    // bb0[5]: scope 2 at src/main.rs:4:5: 4:12

They have different spans, which are all propagated to LLVM IR.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.