Change:
<ins> // compile-flags: -Z mir-opt-level=2 // Due to a bug in propagating scalar pairs the assertion below used to fail. In the expected // outputs below, after ConstProp this is how _2 would look like with the bug: // //     _2 = (const Scalar(0x00) : (), const 0u8); // // Which has the wrong type. fn encode(this: ((), u8, u8)) { assert!(this.2 == 0); } fn main() { encode(((), 0, 0)); } // END RUST SOURCE // START rustc.main.ConstProp.before.mir //  bb0: { //      ... //      _3 = (); //      _2 = (move _3, const 0u8, const 0u8); //      ... //      _1 = const encode(move _2) -> bb1; //      ... //  } // END rustc.main.ConstProp.before.mir // START rustc.main.ConstProp.after.mir //  bb0: { //      ... //      _3 = const Scalar(<ZST>) : (); //      _2 = (move _3, const 0u8, const 0u8); //      ... //      _1 = const encode(move _2) -> bb1; //      ... //  } // END rustc.main.ConstProp.after.mir </ins>

Discussion:
Build: ICE:
improves the situation a little bit but it's still not enough. The problem is (as accurately identified by in ) when we have something like the layout of it says this has one field, but mir value of evaluating this is a , so we can't propagate value of this expression correctly using the type because the type says this has one field but evaluation of it is two values. Two options: Ignore types entirely, if something evaluates to a pair then represent it as a pair regardless of the type. Use ABI of the layout Either way, this will effect resolution of too. says in the issue: I don't know how to fix this while also doing what says above. Currently I use types so in PR the MIR we generate for after const-prop is , but if we do any of the options above we'll get (note that the type changed, we no longer have the zst ). does this make sense? How should I proceed here? I think first option would allow more constant propagation. Both options would change logical type of values in some cases.
Ok, so there are multiple things at play here. We have a total of 3 representations that can be represented as: via , by creating an (virtual const eval memory) that contains the bits of as they would show up on the target at runtime with a single element of either const or of an from a local that contains this value . This can only happing during const evaluation and will never show up in MIR or constants. It is represented as a , because that's the layout of it. Our current situation is that we convert scalarpairs directly to two element aggregates. This is wrong though, because while the memory representation of a value may be a scalar pair, the MIR needs to build all levels of a value irrelevant of its memory representation. The MIR just looks at the fields that are there, not at the layout. Side note: the correct way to get a field from an is to turn it into an and call . We could pull out the immediate specific logic () into a separate function if we end up needing to access fields of s. I don't believe we'll end up in a happy situation if we start doing this, because as shown in option 2. above, we'd need to generate more locals with the component values.

So... My suggestion is to instead always create an . So we keep doing what we're doing for s (create a ConstValue::Scalar), because that works for all types with layout. For other types there are a few things that can be done: and become everything else becomes by moving their content into an . And the fun thing about it, you don't have to write any code. You can just make public and call it. This means we stop calling in and instead call there. Now this may cause two other problems: may regress our peak memory usage, because we may suddenly create (and intern) a lot of s that are quickly optimized out. I don't know yet how to address this. I'm not sure if this can actually happen, but I presume that in the MIR as will end up as a constant with its value. Or constructing a super large union with a small variant will allocate the entire union's memory. I think these cause a lot of memory usage in llvm anyway, and there are ways we can resolve some of these situations with more fancy const eval tricks (we already have a trick for all bits being Undef) panics if the allocation is not already interned. We don't want to eagerly intern all allocations in an though. I think we can avoid overeager interning by calling and if we got an , make the from that and use the original otherwise. This makes sure we "normalize" the to its variant, thus eliminating all that are not strictly necessary for representation. After this normalization we can eagerly intern all s found in the and then call

This reduced testcase is getting miscompiled with . I bisected it to . Rust version:
cc
Slightly simpler reproducer fails the same way:
I may be talking nonsense (first time looking at a mir dump), but I think in : should've been initialized similar to (something like ), but it's not.
Looking at MIR dumps, before constant propagation this is how is initialized: After: So the last field disappears.
I think the bug is in these lines: In this program when visiting substs is so the rvalue needs to be not .
Confirmed that commenting out this block of code fixes the issue. Not sure how to proceed. If we want to do this optimization for scalar pairs then I think we'll have to filter out zero-sized types the value's type in . Would that make sense?
Since we are making the decision on something that came from a layout, but then use the type to get at said field, we should probably figure out a layout based scheme for this. You can ask the layout for a field count, then iterate and use the method to get a layout for each field. If it's zst, create a value from nothing, if not, fetch the next value from the scalar pair. If there are three fields, we need three entries even if one of them is zst
Thanks I have a fix, PR coming.