Change:
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

Discussion:
ScalarMaybeUndef::Scalar(one), ScalarMaybeUndef::Scalar(two) ) => { <ins> // Found a value represented as a pair. For now only do cont-prop if type of // Rvalue is also a pair with two scalars. The more general case is more // complicated to implement so we'll do it later. </ins> let ty = &value.layout.ty.kind; <ins> // Only do it for tuples </ins> if let ty::Tuple(substs) = ty { <del> *rval = Rvalue::Aggregate( Box::new(AggregateKind::Tuple), vec![ self.operand_from_scalar( one, substs[0].expect_ty(), source_info.span ), self.operand_from_scalar( two, substs[1].expect_ty(), source_info.span ), ], ); </del> <ins> // Only do it if tuple is also a pair with two scalars if substs.len() == 2 { let opt_ty1_ty2 = self.use_ecx(source_info, |this| { let ty1 = substs[0].expect_ty(); let ty2 = substs[1].expect_ty(); let ty_is_scalar = |ty| { this.ecx .layout_of(ty) .ok() .map(|ty| ty.details.abi.is_scalar()) == Some(true) }; if ty_is_scalar(ty1) && ty_is_scalar(ty2) { Ok(Some((ty1, ty2))) } else { Ok(None) } }); if let Some(Some((ty1, ty2))) = opt_ty1_ty2 { *rval = Rvalue::Aggregate( Box::new(AggregateKind::Tuple), vec![ self.operand_from_scalar( one, ty1, source_info.span ), self.operand_from_scalar( two, ty2, source_info.span ), ], ); } } </ins> } }, _ => { }

_ => false, } } <ins> /// Returns `true` is this is a scalar type pub fn is_scalar(&self) -> bool { match *self { Abi::Scalar(_) => true, _ => false, } } </ins> } rustc_index::newtype_index! {

<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>

<ins> // compile-flags: -Z mir-opt-level=2 // This used to ICE in const-prop fn test(this: ((u8, u8),)) { assert!((this.0).0 == 1); } fn main() { test(((1, 2),)); } // Important bit is parameter passing so we only check that below // END RUST SOURCE // START rustc.main.ConstProp.before.mir //  bb0: { //      ... //      _3 = (const 1u8, const 2u8); //      _2 = (move _3,); //      ... //      _1 = const test(move _2) -> bb1; //      ... //  } // END rustc.main.ConstProp.before.mir // START rustc.main.ConstProp.after.mir //  bb0: { //      ... //      _3 = (const 1u8, const 2u8); //      _2 = (move _3,); //      ... //      _1 = const test(move _2) -> bb1; //      ... //  } // END rustc.main.ConstProp.after.mir </ins>

<ins> // compile-flags: -Z mir-opt-level=2 // build-pass // This used to ICE in const-prop fn foo() { let bar = |_| { }; let _ = bar("a"); } fn main() { foo(); } </ins>