From b4905394664d062a110fb22404a592c6c9ad56cd Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Thu, 6 Nov 2025 23:10:28 +0000 Subject: [PATCH 1/9] Try harder to evaluate constants. --- compiler/rustc_mir_transform/src/gvn.rs | 49 ++++++++++++------- .../boxes.main.GVN.panic-abort.diff | 3 +- .../boxes.main.GVN.panic-unwind.diff | 3 +- ...onential_common.GVN.32bit.panic-abort.diff | 4 +- ...nential_common.GVN.32bit.panic-unwind.diff | 4 +- ...onential_common.GVN.64bit.panic-abort.diff | 4 +- ...nential_common.GVN.64bit.panic-unwind.diff | 4 +- ...onst_eval_polymorphic.no_optimize.GVN.diff | 3 +- tests/mir-opt/gvn_const_eval_polymorphic.rs | 2 +- ...ecked_shl.PreCodegen.after.panic-abort.mir | 2 +- ...cked_shl.PreCodegen.after.panic-unwind.mir | 2 +- ...p_forward.PreCodegen.after.panic-abort.mir | 2 +- ..._forward.PreCodegen.after.panic-unwind.mir | 2 +- 13 files changed, 49 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 820998eed1005..5f9255104ee50 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1004,21 +1004,19 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> { operand: &mut Operand<'tcx>, location: Location, ) -> Option { - match *operand { - Operand::RuntimeChecks(c) => { - Some(self.insert(self.tcx.types.bool, Value::RuntimeChecks(c))) - } - Operand::Constant(ref constant) => Some(self.insert_constant(constant.const_)), + let value = match *operand { + Operand::RuntimeChecks(c) => self.insert(self.tcx.types.bool, Value::RuntimeChecks(c)), + Operand::Constant(ref constant) => self.insert_constant(constant.const_), Operand::Copy(ref mut place) | Operand::Move(ref mut place) => { - let value = self.simplify_place_value(place, location)?; - if let Some(const_) = self.try_as_constant(value) { - *operand = Operand::Constant(Box::new(const_)); - } else if let Value::RuntimeChecks(c) = self.get(value) { - *operand = Operand::RuntimeChecks(c); - } - Some(value) + self.simplify_place_value(place, location)? } + }; + if let Some(const_) = self.try_as_constant(value) { + *operand = Operand::Constant(Box::new(const_)); + } else if let Value::RuntimeChecks(c) = self.get(value) { + *operand = Operand::RuntimeChecks(c); } + Some(value) } #[instrument(level = "trace", skip(self), ret)] @@ -1796,14 +1794,28 @@ impl<'tcx> VnState<'_, '_, 'tcx> { /// If `index` is a `Value::Constant`, return the `Constant` to be put in the MIR. fn try_as_constant(&mut self, index: VnIndex) -> Option> { - // This was already constant in MIR, do not change it. If the constant is not - // deterministic, adding an additional mention of it in MIR will not give the same value as - // the former mention. - if let Value::Constant { value, disambiguator: None } = self.get(index) { - debug_assert!(value.is_deterministic()); + let value = self.get(index); + + // This was already an *evaluated* constant in MIR, do not change it. + if let Value::Constant { value, disambiguator: None } = value + && let Const::Val(..) = value + { + return Some(ConstOperand { span: DUMMY_SP, user_ty: None, const_: value }); + } + + if let Some(value) = self.try_as_evaluated_constant(index) { + return Some(ConstOperand { span: DUMMY_SP, user_ty: None, const_: value }); + } + + // We failed to provide an evaluated form, fallback to using the unevaluated constant. + if let Value::Constant { value, disambiguator: None } = value { return Some(ConstOperand { span: DUMMY_SP, user_ty: None, const_: value }); } + None + } + + fn try_as_evaluated_constant(&mut self, index: VnIndex) -> Option> { let op = self.eval_to_const(index)?; if op.layout.is_unsized() { // Do not attempt to propagate unsized locals. @@ -1817,8 +1829,7 @@ impl<'tcx> VnState<'_, '_, 'tcx> { // FIXME: remove this hack once https://github.com/rust-lang/rust/issues/79738 is fixed. assert!(!value.may_have_provenance(self.tcx, op.layout.size)); - let const_ = Const::Val(value, op.layout.ty); - Some(ConstOperand { span: DUMMY_SP, user_ty: None, const_ }) + Some(Const::Val(value, op.layout.ty)) } /// Construct a place which holds the same value as `index` and for which all locals strictly diff --git a/tests/mir-opt/const_prop/boxes.main.GVN.panic-abort.diff b/tests/mir-opt/const_prop/boxes.main.GVN.panic-abort.diff index 95eaf18b4703b..c68805f119ea7 100644 --- a/tests/mir-opt/const_prop/boxes.main.GVN.panic-abort.diff +++ b/tests/mir-opt/const_prop/boxes.main.GVN.panic-abort.diff @@ -22,7 +22,8 @@ - StorageLive(_2); + nop; StorageLive(_3); - _4 = alloc::alloc::exchange_malloc(const ::SIZE, const ::ALIGN) -> [return: bb1, unwind unreachable]; +- _4 = alloc::alloc::exchange_malloc(const ::SIZE, const ::ALIGN) -> [return: bb1, unwind unreachable]; ++ _4 = alloc::alloc::exchange_malloc(const 4_usize, const 4_usize) -> [return: bb1, unwind unreachable]; } bb1: { diff --git a/tests/mir-opt/const_prop/boxes.main.GVN.panic-unwind.diff b/tests/mir-opt/const_prop/boxes.main.GVN.panic-unwind.diff index 6d8d3a0dcfe29..7fef8af08d400 100644 --- a/tests/mir-opt/const_prop/boxes.main.GVN.panic-unwind.diff +++ b/tests/mir-opt/const_prop/boxes.main.GVN.panic-unwind.diff @@ -22,7 +22,8 @@ - StorageLive(_2); + nop; StorageLive(_3); - _4 = alloc::alloc::exchange_malloc(const ::SIZE, const ::ALIGN) -> [return: bb1, unwind continue]; +- _4 = alloc::alloc::exchange_malloc(const ::SIZE, const ::ALIGN) -> [return: bb1, unwind continue]; ++ _4 = alloc::alloc::exchange_malloc(const 4_usize, const 4_usize) -> [return: bb1, unwind continue]; } bb1: { diff --git a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-abort.diff b/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-abort.diff index 6baa902b6f4bd..40630e7ad9712 100644 --- a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-abort.diff +++ b/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-abort.diff @@ -47,7 +47,7 @@ StorageLive(_20); StorageLive(_21); _21 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _20 = BitAnd(move _21, const core::fmt::flags::SIGN_PLUS_FLAG); + _20 = BitAnd(move _21, const 2097152_u32); StorageDead(_21); _4 = Ne(move _20, const 0_u32); StorageDead(_20); @@ -72,7 +72,7 @@ StorageLive(_22); StorageLive(_23); _23 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _22 = BitAnd(move _23, const core::fmt::flags::PRECISION_FLAG); + _22 = BitAnd(move _23, const 268435456_u32); StorageDead(_23); switchInt(move _22) -> [0: bb10, otherwise: bb11]; } diff --git a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-unwind.diff b/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-unwind.diff index 36540e038654f..29a5b06ae7ec8 100644 --- a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-unwind.diff +++ b/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-unwind.diff @@ -47,7 +47,7 @@ StorageLive(_20); StorageLive(_21); _21 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _20 = BitAnd(move _21, const core::fmt::flags::SIGN_PLUS_FLAG); + _20 = BitAnd(move _21, const 2097152_u32); StorageDead(_21); _4 = Ne(move _20, const 0_u32); StorageDead(_20); @@ -72,7 +72,7 @@ StorageLive(_22); StorageLive(_23); _23 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _22 = BitAnd(move _23, const core::fmt::flags::PRECISION_FLAG); + _22 = BitAnd(move _23, const 268435456_u32); StorageDead(_23); switchInt(move _22) -> [0: bb10, otherwise: bb11]; } diff --git a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-abort.diff b/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-abort.diff index 41c350f3eaeb5..56e08fcce8082 100644 --- a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-abort.diff +++ b/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-abort.diff @@ -47,7 +47,7 @@ StorageLive(_20); StorageLive(_21); _21 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _20 = BitAnd(move _21, const core::fmt::flags::SIGN_PLUS_FLAG); + _20 = BitAnd(move _21, const 2097152_u32); StorageDead(_21); _4 = Ne(move _20, const 0_u32); StorageDead(_20); @@ -72,7 +72,7 @@ StorageLive(_22); StorageLive(_23); _23 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _22 = BitAnd(move _23, const core::fmt::flags::PRECISION_FLAG); + _22 = BitAnd(move _23, const 268435456_u32); StorageDead(_23); switchInt(move _22) -> [0: bb10, otherwise: bb11]; } diff --git a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-unwind.diff b/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-unwind.diff index b839bf81eaf45..dd4c5cf5d65d6 100644 --- a/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-unwind.diff +++ b/tests/mir-opt/funky_arms.float_to_exponential_common.GVN.64bit.panic-unwind.diff @@ -47,7 +47,7 @@ StorageLive(_20); StorageLive(_21); _21 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _20 = BitAnd(move _21, const core::fmt::flags::SIGN_PLUS_FLAG); + _20 = BitAnd(move _21, const 2097152_u32); StorageDead(_21); _4 = Ne(move _20, const 0_u32); StorageDead(_20); @@ -72,7 +72,7 @@ StorageLive(_22); StorageLive(_23); _23 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32); - _22 = BitAnd(move _23, const core::fmt::flags::PRECISION_FLAG); + _22 = BitAnd(move _23, const 268435456_u32); StorageDead(_23); switchInt(move _22) -> [0: bb10, otherwise: bb11]; } diff --git a/tests/mir-opt/gvn_const_eval_polymorphic.no_optimize.GVN.diff b/tests/mir-opt/gvn_const_eval_polymorphic.no_optimize.GVN.diff index a91561ba304b8..92158682c9993 100644 --- a/tests/mir-opt/gvn_const_eval_polymorphic.no_optimize.GVN.diff +++ b/tests/mir-opt/gvn_const_eval_polymorphic.no_optimize.GVN.diff @@ -5,7 +5,8 @@ let mut _0: bool; bb0: { - _0 = Eq(const no_optimize::::{constant#0}, const no_optimize::::{constant#1}); +- _0 = Eq(const no_optimize::::{constant#0}, const no_optimize::::{constant#1}); ++ _0 = Eq(const no_optimize::::{constant#0}, const true); return; } } diff --git a/tests/mir-opt/gvn_const_eval_polymorphic.rs b/tests/mir-opt/gvn_const_eval_polymorphic.rs index 7320ad947ff2e..722720b2a6f07 100644 --- a/tests/mir-opt/gvn_const_eval_polymorphic.rs +++ b/tests/mir-opt/gvn_const_eval_polymorphic.rs @@ -51,7 +51,7 @@ fn optimize_false() -> bool { // EMIT_MIR gvn_const_eval_polymorphic.no_optimize.GVN.diff fn no_optimize() -> bool { // CHECK-LABEL: fn no_optimize( - // CHECK: _0 = Eq(const no_optimize::::{constant#0}, const no_optimize::::{constant#1}); + // CHECK: _0 = Eq(const no_optimize::::{constant#0}, const true); // CHECK-NEXT: return; (const { type_name_contains_i32(&generic::) }) == const { true } } diff --git a/tests/mir-opt/pre-codegen/checked_ops.checked_shl.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/checked_ops.checked_shl.PreCodegen.after.panic-abort.mir index 18eeb8e4d3b61..c97c9b45d6ad5 100644 --- a/tests/mir-opt/pre-codegen/checked_ops.checked_shl.PreCodegen.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/checked_ops.checked_shl.PreCodegen.after.panic-abort.mir @@ -17,7 +17,7 @@ fn checked_shl(_1: u32, _2: u32) -> Option { bb0: { StorageLive(_3); - _3 = Lt(copy _2, const core::num::::BITS); + _3 = Lt(copy _2, const 32_u32); switchInt(move _3) -> [0: bb1, otherwise: bb2]; } diff --git a/tests/mir-opt/pre-codegen/checked_ops.checked_shl.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/checked_ops.checked_shl.PreCodegen.after.panic-unwind.mir index 18eeb8e4d3b61..c97c9b45d6ad5 100644 --- a/tests/mir-opt/pre-codegen/checked_ops.checked_shl.PreCodegen.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/checked_ops.checked_shl.PreCodegen.after.panic-unwind.mir @@ -17,7 +17,7 @@ fn checked_shl(_1: u32, _2: u32) -> Option { bb0: { StorageLive(_3); - _3 = Lt(copy _2, const core::num::::BITS); + _3 = Lt(copy _2, const 32_u32); switchInt(move _3) -> [0: bb1, otherwise: bb2]; } diff --git a/tests/mir-opt/pre-codegen/checked_ops.step_forward.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/checked_ops.step_forward.PreCodegen.after.panic-abort.mir index 83478e60b5d4e..7b681946bee1e 100644 --- a/tests/mir-opt/pre-codegen/checked_ops.step_forward.PreCodegen.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/checked_ops.step_forward.PreCodegen.after.panic-abort.mir @@ -72,7 +72,7 @@ fn step_forward(_1: u16, _2: usize) -> u16 { } bb6: { - assert(!const true, "attempt to compute `{} + {}`, which would overflow", const core::num::::MAX, const 1_u16) -> [success: bb7, unwind unreachable]; + assert(!const true, "attempt to compute `{} + {}`, which would overflow", const u16::MAX, const 1_u16) -> [success: bb7, unwind unreachable]; } bb7: { diff --git a/tests/mir-opt/pre-codegen/checked_ops.step_forward.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/checked_ops.step_forward.PreCodegen.after.panic-unwind.mir index ac7a6e0445191..a0137b23ec4f4 100644 --- a/tests/mir-opt/pre-codegen/checked_ops.step_forward.PreCodegen.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/checked_ops.step_forward.PreCodegen.after.panic-unwind.mir @@ -72,7 +72,7 @@ fn step_forward(_1: u16, _2: usize) -> u16 { } bb6: { - assert(!const true, "attempt to compute `{} + {}`, which would overflow", const core::num::::MAX, const 1_u16) -> [success: bb7, unwind continue]; + assert(!const true, "attempt to compute `{} + {}`, which would overflow", const u16::MAX, const 1_u16) -> [success: bb7, unwind continue]; } bb7: { From b1dec9a46628540c5a3f62c87274656e3c03af99 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Thu, 6 Nov 2025 23:00:23 +0000 Subject: [PATCH 2/9] Constants of primitive types are always deterministic. --- compiler/rustc_middle/src/mir/consts.rs | 20 ++++++------- ...ace.PreCodegen.after.32bit.panic-abort.mir | 30 ++++++++----------- ...ce.PreCodegen.after.32bit.panic-unwind.mir | 30 ++++++++----------- ...ace.PreCodegen.after.64bit.panic-abort.mir | 30 ++++++++----------- ...ce.PreCodegen.after.64bit.panic-unwind.mir | 30 ++++++++----------- tests/mir-opt/pre-codegen/drop_boxed_slice.rs | 5 ++-- 6 files changed, 64 insertions(+), 81 deletions(-) diff --git a/compiler/rustc_middle/src/mir/consts.rs b/compiler/rustc_middle/src/mir/consts.rs index afe39e4481efc..715e6e2917fc2 100644 --- a/compiler/rustc_middle/src/mir/consts.rs +++ b/compiler/rustc_middle/src/mir/consts.rs @@ -478,6 +478,11 @@ impl<'tcx> Const<'tcx> { /// Return true if any evaluation of this constant always returns the same value, /// taking into account even pointer identity tests. pub fn is_deterministic(&self) -> bool { + // Primitive types cannot contain provenance and always have the same value. + if self.ty().is_primitive() { + return true; + } + // Some constants may generate fresh allocations for pointers they contain, // so using the same constant twice can yield two different results. // Notably, valtrees purposefully generate new allocations. @@ -487,24 +492,19 @@ impl<'tcx> Const<'tcx> { // A valtree may be a reference. Valtree references correspond to a // different allocation each time they are evaluated. Valtrees for primitive // types are fine though. - ty::ConstKind::Value(cv) => cv.ty.is_primitive(), - ty::ConstKind::Unevaluated(..) | ty::ConstKind::Expr(..) => false, + ty::ConstKind::Value(..) + | ty::ConstKind::Expr(..) + | ty::ConstKind::Unevaluated(..) // This can happen if evaluation of a constant failed. The result does not matter // much since compilation is doomed. - ty::ConstKind::Error(..) => false, + | ty::ConstKind::Error(..) => false, // Should not appear in runtime MIR. ty::ConstKind::Infer(..) | ty::ConstKind::Bound(..) | ty::ConstKind::Placeholder(..) => bug!(), }, Const::Unevaluated(..) => false, - Const::Val( - ConstValue::Slice { .. } - | ConstValue::ZeroSized - | ConstValue::Scalar(_) - | ConstValue::Indirect { .. }, - _, - ) => true, + Const::Val(..) => true, } } } diff --git a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-abort.mir b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-abort.mir index 013361d1d2fb8..5ba98f1b23636 100644 --- a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-abort.mir @@ -8,9 +8,9 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { let _2: std::ptr::NonNull<[T]>; let mut _3: *mut [T]; let mut _4: *const [T]; - let _11: (); + let _10: (); scope 3 { - let _8: std::ptr::alignment::AlignmentEnum; + let _7: std::ptr::alignment::AlignmentEnum; scope 4 { scope 12 (inlined Layout::size) { } @@ -27,13 +27,13 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 18 (inlined ::deallocate) { scope 19 (inlined std::alloc::Global::deallocate_impl) { scope 20 (inlined std::alloc::Global::deallocate_impl_runtime) { - let mut _9: *mut u8; + let mut _8: *mut u8; scope 21 (inlined Layout::size) { } scope 22 (inlined NonNull::::as_ptr) { } scope 23 (inlined std::alloc::dealloc) { - let mut _10: usize; + let mut _9: usize; scope 24 (inlined Layout::size) { } scope 25 (inlined Layout::align) { @@ -51,10 +51,9 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } scope 7 (inlined Layout::for_value_raw::<[T]>) { let mut _5: usize; - let mut _6: usize; scope 8 { scope 11 (inlined #[track_caller] Layout::from_size_align_unchecked) { - let mut _7: std::ptr::Alignment; + let mut _6: std::ptr::Alignment; } } scope 9 (inlined size_of_val_raw::<[T]>) { @@ -72,32 +71,29 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { StorageLive(_4); _3 = copy _2 as *mut [T] (Transmute); _4 = copy _2 as *const [T] (Transmute); - StorageLive(_6); _5 = std::intrinsics::size_of_val::<[T]>(move _4) -> [return: bb1, unwind unreachable]; } bb1: { - _6 = const ::ALIGN; - StorageLive(_7); - _7 = copy _6 as std::ptr::Alignment (Transmute); - _8 = move (_7.0: std::ptr::alignment::AlignmentEnum); - StorageDead(_7); + StorageLive(_6); + _6 = const ::ALIGN as std::ptr::Alignment (Transmute); + _7 = move (_6.0: std::ptr::alignment::AlignmentEnum); StorageDead(_6); StorageDead(_4); switchInt(copy _5) -> [0: bb4, otherwise: bb2]; } bb2: { + StorageLive(_8); + _8 = copy _3 as *mut u8 (PtrToPtr); StorageLive(_9); - _9 = copy _3 as *mut u8 (PtrToPtr); - StorageLive(_10); - _10 = discriminant(_8); - _11 = alloc::alloc::__rust_dealloc(move _9, move _5, move _10) -> [return: bb3, unwind unreachable]; + _9 = discriminant(_7); + _10 = alloc::alloc::__rust_dealloc(move _8, move _5, move _9) -> [return: bb3, unwind unreachable]; } bb3: { - StorageDead(_10); StorageDead(_9); + StorageDead(_8); goto -> bb4; } diff --git a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-unwind.mir b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-unwind.mir index 013361d1d2fb8..5ba98f1b23636 100644 --- a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-unwind.mir @@ -8,9 +8,9 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { let _2: std::ptr::NonNull<[T]>; let mut _3: *mut [T]; let mut _4: *const [T]; - let _11: (); + let _10: (); scope 3 { - let _8: std::ptr::alignment::AlignmentEnum; + let _7: std::ptr::alignment::AlignmentEnum; scope 4 { scope 12 (inlined Layout::size) { } @@ -27,13 +27,13 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 18 (inlined ::deallocate) { scope 19 (inlined std::alloc::Global::deallocate_impl) { scope 20 (inlined std::alloc::Global::deallocate_impl_runtime) { - let mut _9: *mut u8; + let mut _8: *mut u8; scope 21 (inlined Layout::size) { } scope 22 (inlined NonNull::::as_ptr) { } scope 23 (inlined std::alloc::dealloc) { - let mut _10: usize; + let mut _9: usize; scope 24 (inlined Layout::size) { } scope 25 (inlined Layout::align) { @@ -51,10 +51,9 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } scope 7 (inlined Layout::for_value_raw::<[T]>) { let mut _5: usize; - let mut _6: usize; scope 8 { scope 11 (inlined #[track_caller] Layout::from_size_align_unchecked) { - let mut _7: std::ptr::Alignment; + let mut _6: std::ptr::Alignment; } } scope 9 (inlined size_of_val_raw::<[T]>) { @@ -72,32 +71,29 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { StorageLive(_4); _3 = copy _2 as *mut [T] (Transmute); _4 = copy _2 as *const [T] (Transmute); - StorageLive(_6); _5 = std::intrinsics::size_of_val::<[T]>(move _4) -> [return: bb1, unwind unreachable]; } bb1: { - _6 = const ::ALIGN; - StorageLive(_7); - _7 = copy _6 as std::ptr::Alignment (Transmute); - _8 = move (_7.0: std::ptr::alignment::AlignmentEnum); - StorageDead(_7); + StorageLive(_6); + _6 = const ::ALIGN as std::ptr::Alignment (Transmute); + _7 = move (_6.0: std::ptr::alignment::AlignmentEnum); StorageDead(_6); StorageDead(_4); switchInt(copy _5) -> [0: bb4, otherwise: bb2]; } bb2: { + StorageLive(_8); + _8 = copy _3 as *mut u8 (PtrToPtr); StorageLive(_9); - _9 = copy _3 as *mut u8 (PtrToPtr); - StorageLive(_10); - _10 = discriminant(_8); - _11 = alloc::alloc::__rust_dealloc(move _9, move _5, move _10) -> [return: bb3, unwind unreachable]; + _9 = discriminant(_7); + _10 = alloc::alloc::__rust_dealloc(move _8, move _5, move _9) -> [return: bb3, unwind unreachable]; } bb3: { - StorageDead(_10); StorageDead(_9); + StorageDead(_8); goto -> bb4; } diff --git a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-abort.mir b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-abort.mir index 013361d1d2fb8..5ba98f1b23636 100644 --- a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-abort.mir @@ -8,9 +8,9 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { let _2: std::ptr::NonNull<[T]>; let mut _3: *mut [T]; let mut _4: *const [T]; - let _11: (); + let _10: (); scope 3 { - let _8: std::ptr::alignment::AlignmentEnum; + let _7: std::ptr::alignment::AlignmentEnum; scope 4 { scope 12 (inlined Layout::size) { } @@ -27,13 +27,13 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 18 (inlined ::deallocate) { scope 19 (inlined std::alloc::Global::deallocate_impl) { scope 20 (inlined std::alloc::Global::deallocate_impl_runtime) { - let mut _9: *mut u8; + let mut _8: *mut u8; scope 21 (inlined Layout::size) { } scope 22 (inlined NonNull::::as_ptr) { } scope 23 (inlined std::alloc::dealloc) { - let mut _10: usize; + let mut _9: usize; scope 24 (inlined Layout::size) { } scope 25 (inlined Layout::align) { @@ -51,10 +51,9 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } scope 7 (inlined Layout::for_value_raw::<[T]>) { let mut _5: usize; - let mut _6: usize; scope 8 { scope 11 (inlined #[track_caller] Layout::from_size_align_unchecked) { - let mut _7: std::ptr::Alignment; + let mut _6: std::ptr::Alignment; } } scope 9 (inlined size_of_val_raw::<[T]>) { @@ -72,32 +71,29 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { StorageLive(_4); _3 = copy _2 as *mut [T] (Transmute); _4 = copy _2 as *const [T] (Transmute); - StorageLive(_6); _5 = std::intrinsics::size_of_val::<[T]>(move _4) -> [return: bb1, unwind unreachable]; } bb1: { - _6 = const ::ALIGN; - StorageLive(_7); - _7 = copy _6 as std::ptr::Alignment (Transmute); - _8 = move (_7.0: std::ptr::alignment::AlignmentEnum); - StorageDead(_7); + StorageLive(_6); + _6 = const ::ALIGN as std::ptr::Alignment (Transmute); + _7 = move (_6.0: std::ptr::alignment::AlignmentEnum); StorageDead(_6); StorageDead(_4); switchInt(copy _5) -> [0: bb4, otherwise: bb2]; } bb2: { + StorageLive(_8); + _8 = copy _3 as *mut u8 (PtrToPtr); StorageLive(_9); - _9 = copy _3 as *mut u8 (PtrToPtr); - StorageLive(_10); - _10 = discriminant(_8); - _11 = alloc::alloc::__rust_dealloc(move _9, move _5, move _10) -> [return: bb3, unwind unreachable]; + _9 = discriminant(_7); + _10 = alloc::alloc::__rust_dealloc(move _8, move _5, move _9) -> [return: bb3, unwind unreachable]; } bb3: { - StorageDead(_10); StorageDead(_9); + StorageDead(_8); goto -> bb4; } diff --git a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-unwind.mir b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-unwind.mir index 013361d1d2fb8..5ba98f1b23636 100644 --- a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-unwind.mir @@ -8,9 +8,9 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { let _2: std::ptr::NonNull<[T]>; let mut _3: *mut [T]; let mut _4: *const [T]; - let _11: (); + let _10: (); scope 3 { - let _8: std::ptr::alignment::AlignmentEnum; + let _7: std::ptr::alignment::AlignmentEnum; scope 4 { scope 12 (inlined Layout::size) { } @@ -27,13 +27,13 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 18 (inlined ::deallocate) { scope 19 (inlined std::alloc::Global::deallocate_impl) { scope 20 (inlined std::alloc::Global::deallocate_impl_runtime) { - let mut _9: *mut u8; + let mut _8: *mut u8; scope 21 (inlined Layout::size) { } scope 22 (inlined NonNull::::as_ptr) { } scope 23 (inlined std::alloc::dealloc) { - let mut _10: usize; + let mut _9: usize; scope 24 (inlined Layout::size) { } scope 25 (inlined Layout::align) { @@ -51,10 +51,9 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } scope 7 (inlined Layout::for_value_raw::<[T]>) { let mut _5: usize; - let mut _6: usize; scope 8 { scope 11 (inlined #[track_caller] Layout::from_size_align_unchecked) { - let mut _7: std::ptr::Alignment; + let mut _6: std::ptr::Alignment; } } scope 9 (inlined size_of_val_raw::<[T]>) { @@ -72,32 +71,29 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { StorageLive(_4); _3 = copy _2 as *mut [T] (Transmute); _4 = copy _2 as *const [T] (Transmute); - StorageLive(_6); _5 = std::intrinsics::size_of_val::<[T]>(move _4) -> [return: bb1, unwind unreachable]; } bb1: { - _6 = const ::ALIGN; - StorageLive(_7); - _7 = copy _6 as std::ptr::Alignment (Transmute); - _8 = move (_7.0: std::ptr::alignment::AlignmentEnum); - StorageDead(_7); + StorageLive(_6); + _6 = const ::ALIGN as std::ptr::Alignment (Transmute); + _7 = move (_6.0: std::ptr::alignment::AlignmentEnum); StorageDead(_6); StorageDead(_4); switchInt(copy _5) -> [0: bb4, otherwise: bb2]; } bb2: { + StorageLive(_8); + _8 = copy _3 as *mut u8 (PtrToPtr); StorageLive(_9); - _9 = copy _3 as *mut u8 (PtrToPtr); - StorageLive(_10); - _10 = discriminant(_8); - _11 = alloc::alloc::__rust_dealloc(move _9, move _5, move _10) -> [return: bb3, unwind unreachable]; + _9 = discriminant(_7); + _10 = alloc::alloc::__rust_dealloc(move _8, move _5, move _9) -> [return: bb3, unwind unreachable]; } bb3: { - StorageDead(_10); StorageDead(_9); + StorageDead(_8); goto -> bb4; } diff --git a/tests/mir-opt/pre-codegen/drop_boxed_slice.rs b/tests/mir-opt/pre-codegen/drop_boxed_slice.rs index 83b10f8bd6888..15d3af8e7a5ba 100644 --- a/tests/mir-opt/pre-codegen/drop_boxed_slice.rs +++ b/tests/mir-opt/pre-codegen/drop_boxed_slice.rs @@ -9,9 +9,8 @@ pub unsafe fn generic_in_place(ptr: *mut Box<[T]>) { // CHECK-LABEL: fn generic_in_place(_1: *mut Box<[T]>) // CHECK: (inlined as Drop>::drop) // CHECK: [[SIZE:_.+]] = std::intrinsics::size_of_val::<[T]> - // CHECK: [[ALIGN:_.+]] = const ::ALIGN; - // CHECK: [[B:_.+]] = copy [[ALIGN]] as std::ptr::Alignment (Transmute); - // CHECK: [[C:_.+]] = move ([[B]].0: std::ptr::alignment::AlignmentEnum); + // CHECK: [[ALIGN:_.+]] = const ::ALIGN as std::ptr::Alignment (Transmute); + // CHECK: [[C:_.+]] = move ([[ALIGN]].0: std::ptr::alignment::AlignmentEnum); // CHECK: [[D:_.+]] = discriminant([[C]]); // CHECK: = alloc::alloc::__rust_dealloc({{.+}}, move [[SIZE]], move [[D]]) -> std::ptr::drop_in_place(ptr) From 45fc5931db16e321ecd52a65cb2fcdf8b34b9852 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Fri, 28 Nov 2025 23:27:21 +0000 Subject: [PATCH 3/9] Bless incremental. --- tests/incremental/hashes/enum_constructors.rs | 2 +- tests/incremental/hashes/struct_constructors.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/incremental/hashes/enum_constructors.rs b/tests/incremental/hashes/enum_constructors.rs index dee485681e447..5962727b3af0b 100644 --- a/tests/incremental/hashes/enum_constructors.rs +++ b/tests/incremental/hashes/enum_constructors.rs @@ -65,7 +65,7 @@ pub fn change_field_order_struct_like() -> Enum { #[cfg(not(any(cfail1,cfail4)))] #[rustc_clean(cfg="cfail2", except="opt_hir_owner_nodes,typeck")] #[rustc_clean(cfg="cfail3")] -#[rustc_clean(cfg="cfail5", except="opt_hir_owner_nodes,typeck,optimized_mir")] +#[rustc_clean(cfg="cfail5", except="opt_hir_owner_nodes,typeck")] #[rustc_clean(cfg="cfail6")] // FIXME(michaelwoerister):Interesting. I would have thought that that changes the MIR. And it // would if it were not all constants diff --git a/tests/incremental/hashes/struct_constructors.rs b/tests/incremental/hashes/struct_constructors.rs index da7abe049d982..b2f8d116b1e94 100644 --- a/tests/incremental/hashes/struct_constructors.rs +++ b/tests/incremental/hashes/struct_constructors.rs @@ -62,7 +62,7 @@ pub fn change_field_order_regular_struct() -> RegularStruct { #[cfg(not(any(cfail1,cfail4)))] #[rustc_clean(cfg="cfail2", except="opt_hir_owner_nodes,typeck")] #[rustc_clean(cfg="cfail3")] -#[rustc_clean(cfg="cfail5", except="opt_hir_owner_nodes,typeck,optimized_mir")] +#[rustc_clean(cfg="cfail5", except="opt_hir_owner_nodes,typeck")] #[rustc_clean(cfg="cfail6")] pub fn change_field_order_regular_struct() -> RegularStruct { RegularStruct { From c411e059f9cea2b4015a9f96ff71c4df3d3ee82e Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Tue, 16 Dec 2025 01:14:00 +0000 Subject: [PATCH 4/9] Elaborate comments. --- compiler/rustc_middle/src/mir/consts.rs | 52 +++++++++---------------- compiler/rustc_mir_transform/src/gvn.rs | 2 +- 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/compiler/rustc_middle/src/mir/consts.rs b/compiler/rustc_middle/src/mir/consts.rs index 715e6e2917fc2..5949eaf2efcd6 100644 --- a/compiler/rustc_middle/src/mir/consts.rs +++ b/compiler/rustc_middle/src/mir/consts.rs @@ -183,21 +183,16 @@ impl ConstValue { /// Check if a constant may contain provenance information. This is used by MIR opts. /// Can return `true` even if there is no provenance. - pub fn may_have_provenance(&self, tcx: TyCtxt<'_>, size: Size) -> bool { + pub fn may_have_provenance(&self, tcx: TyCtxt<'_>, size: Option) -> bool { match *self { ConstValue::ZeroSized | ConstValue::Scalar(Scalar::Int(_)) => return false, - ConstValue::Scalar(Scalar::Ptr(..)) => return true, - // It's hard to find out the part of the allocation we point to; - // just conservatively check everything. - ConstValue::Slice { alloc_id, meta: _ } => { - !tcx.global_alloc(alloc_id).unwrap_memory().inner().provenance().ptrs().is_empty() + ConstValue::Scalar(Scalar::Ptr(..)) | ConstValue::Slice { .. } => return true, + ConstValue::Indirect { alloc_id, offset } => { + let allocation = tcx.global_alloc(alloc_id).unwrap_memory().inner(); + let end = if let Some(size) = size { offset + size } else { allocation.size() }; + let provenance_map = allocation.provenance(); + !provenance_map.range_empty(AllocRange::from(offset..end), &tcx) } - ConstValue::Indirect { alloc_id, offset } => !tcx - .global_alloc(alloc_id) - .unwrap_memory() - .inner() - .provenance() - .range_empty(AllocRange::from(offset..offset + size), &tcx), } } @@ -475,35 +470,26 @@ impl<'tcx> Const<'tcx> { Self::Val(val, ty) } - /// Return true if any evaluation of this constant always returns the same value, - /// taking into account even pointer identity tests. + /// Return true if any evaluation of this constant in the same MIR body + /// always returns the same value, taking into account even pointer identity tests. + /// + /// In other words, this answers: is "cloning" the mir::ConstOperand ok? pub fn is_deterministic(&self) -> bool { // Primitive types cannot contain provenance and always have the same value. if self.ty().is_primitive() { return true; } - // Some constants may generate fresh allocations for pointers they contain, - // so using the same constant twice can yield two different results. - // Notably, valtrees purposefully generate new allocations. match self { - Const::Ty(_, c) => match c.kind() { - ty::ConstKind::Param(..) => true, - // A valtree may be a reference. Valtree references correspond to a - // different allocation each time they are evaluated. Valtrees for primitive - // types are fine though. - ty::ConstKind::Value(..) - | ty::ConstKind::Expr(..) - | ty::ConstKind::Unevaluated(..) - // This can happen if evaluation of a constant failed. The result does not matter - // much since compilation is doomed. - | ty::ConstKind::Error(..) => false, - // Should not appear in runtime MIR. - ty::ConstKind::Infer(..) - | ty::ConstKind::Bound(..) - | ty::ConstKind::Placeholder(..) => bug!(), - }, + // Some constants may generate fresh allocations for pointers they contain, + // so using the same constant twice can yield two different results. + // Notably, valtrees purposefully generate new allocations. + Const::Ty(..) => false, + // We do not know the contents, so don't attempt to do anything clever. Const::Unevaluated(..) => false, + // When an evaluated contant contains provenance, it is encoded as an `AllocId`. + // Cloning the constant will reuse the same `AllocId`. If this is in the same MIR + // body, this same `AllocId` will result in the same pointer in codegen. Const::Val(..) => true, } } diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 5f9255104ee50..5399034c2da24 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1827,7 +1827,7 @@ impl<'tcx> VnState<'_, '_, 'tcx> { // Check that we do not leak a pointer. // Those pointers may lose part of their identity in codegen. // FIXME: remove this hack once https://github.com/rust-lang/rust/issues/79738 is fixed. - assert!(!value.may_have_provenance(self.tcx, op.layout.size)); + assert!(!value.may_have_provenance(self.tcx, Some(op.layout.size))); Some(Const::Val(value, op.layout.ty)) } From 7cb5ce841c2630897a97b1e59454c0bdef2e9480 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Wed, 17 Dec 2025 00:37:19 +0000 Subject: [PATCH 5/9] Move methods to gvn.rs. --- compiler/rustc_middle/src/mir/consts.rs | 39 ------------- compiler/rustc_mir_transform/src/gvn.rs | 75 +++++++++++++++++++++---- 2 files changed, 63 insertions(+), 51 deletions(-) diff --git a/compiler/rustc_middle/src/mir/consts.rs b/compiler/rustc_middle/src/mir/consts.rs index 5949eaf2efcd6..de3ef6deca1fc 100644 --- a/compiler/rustc_middle/src/mir/consts.rs +++ b/compiler/rustc_middle/src/mir/consts.rs @@ -181,21 +181,6 @@ impl ConstValue { Some(data.inner().inspect_with_uninit_and_ptr_outside_interpreter(start..end)) } - /// Check if a constant may contain provenance information. This is used by MIR opts. - /// Can return `true` even if there is no provenance. - pub fn may_have_provenance(&self, tcx: TyCtxt<'_>, size: Option) -> bool { - match *self { - ConstValue::ZeroSized | ConstValue::Scalar(Scalar::Int(_)) => return false, - ConstValue::Scalar(Scalar::Ptr(..)) | ConstValue::Slice { .. } => return true, - ConstValue::Indirect { alloc_id, offset } => { - let allocation = tcx.global_alloc(alloc_id).unwrap_memory().inner(); - let end = if let Some(size) = size { offset + size } else { allocation.size() }; - let provenance_map = allocation.provenance(); - !provenance_map.range_empty(AllocRange::from(offset..end), &tcx) - } - } - } - /// Check if a constant only contains uninitialized bytes. pub fn all_bytes_uninit(&self, tcx: TyCtxt<'_>) -> bool { let ConstValue::Indirect { alloc_id, .. } = self else { @@ -469,30 +454,6 @@ impl<'tcx> Const<'tcx> { let val = ConstValue::Scalar(s); Self::Val(val, ty) } - - /// Return true if any evaluation of this constant in the same MIR body - /// always returns the same value, taking into account even pointer identity tests. - /// - /// In other words, this answers: is "cloning" the mir::ConstOperand ok? - pub fn is_deterministic(&self) -> bool { - // Primitive types cannot contain provenance and always have the same value. - if self.ty().is_primitive() { - return true; - } - - match self { - // Some constants may generate fresh allocations for pointers they contain, - // so using the same constant twice can yield two different results. - // Notably, valtrees purposefully generate new allocations. - Const::Ty(..) => false, - // We do not know the contents, so don't attempt to do anything clever. - Const::Unevaluated(..) => false, - // When an evaluated contant contains provenance, it is encoded as an `AllocId`. - // Cloning the constant will reuse the same `AllocId`. If this is in the same MIR - // body, this same `AllocId` will result in the same pointer in codegen. - Const::Val(..) => true, - } - } } /// An unevaluated (potentially generic) constant used in MIR. diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 5399034c2da24..ccb904d98f10e 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -61,10 +61,10 @@ //! The evaluated form is inserted in `evaluated` as an `OpTy` or `None` if evaluation failed. //! //! The difficulty is non-deterministic evaluation of MIR constants. Some `Const` can have -//! different runtime values each time they are evaluated. This is the case with -//! `Const::Slice` which have a new pointer each time they are evaluated, and constants that -//! contain a fn pointer (`AllocId` pointing to a `GlobalAlloc::Function`) pointing to a different -//! symbol in each codegen unit. +//! different runtime values each time they are evaluated. This used to be the case with +//! `ConstValue::Slice` which have a new pointer each time they are evaluated, and is still the +//! case with valtrees that generate a new allocation each time they are used. This is checked by +//! `is_deterministic`. //! //! Meanwhile, we want to be able to read indirect constants. For instance: //! ``` @@ -81,8 +81,11 @@ //! may be non-deterministic. When that happens, we assign a disambiguator to ensure that we do not //! merge the constants. See `duplicate_slice` test in `gvn.rs`. //! -//! Second, when writing constants in MIR, we do not write `Const::Slice` or `Const` -//! that contain `AllocId`s. +//! Conversely, some constants cannot cross crate boundaries, which could happen because of +//! inlining. For instance, constants that contain a fn pointer (`AllocId` pointing to a +//! `GlobalAlloc::Function`) point to a different symbol in each codegen unit. To avoid this, +//! when writing constants in MIR, we do not write `Const`s that contain `AllocId`s. This is +//! checked by `may_have_provenance`. use std::borrow::Cow; use std::hash::{Hash, Hasher}; @@ -103,7 +106,7 @@ use rustc_hir::def::DefKind; use rustc_index::bit_set::DenseBitSet; use rustc_index::{IndexVec, newtype_index}; use rustc_middle::bug; -use rustc_middle::mir::interpret::GlobalAlloc; +use rustc_middle::mir::interpret::{AllocRange, GlobalAlloc}; use rustc_middle::mir::visit::*; use rustc_middle::mir::*; use rustc_middle::ty::layout::HasTypingEnv; @@ -491,7 +494,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> { #[instrument(level = "trace", skip(self), ret)] fn insert_constant(&mut self, value: Const<'tcx>) -> VnIndex { - if value.is_deterministic() { + if is_deterministic(value) { // The constant is deterministic, no need to disambiguate. let constant = Value::Constant { value, disambiguator: None }; self.insert(value.ty(), constant) @@ -526,14 +529,14 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> { fn insert_bool(&mut self, flag: bool) -> VnIndex { // Booleans are deterministic. let value = Const::from_bool(self.tcx, flag); - debug_assert!(value.is_deterministic()); + debug_assert!(is_deterministic(value)); self.insert(self.tcx.types.bool, Value::Constant { value, disambiguator: None }) } fn insert_scalar(&mut self, ty: Ty<'tcx>, scalar: Scalar) -> VnIndex { // Scalars are deterministic. let value = Const::from_scalar(self.tcx, scalar, ty); - debug_assert!(value.is_deterministic()); + debug_assert!(is_deterministic(value)); self.insert(ty, Value::Constant { value, disambiguator: None }) } @@ -1694,6 +1697,45 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> { } } +/// Return true if any evaluation of this constant in the same MIR body +/// always returns the same value, taking into account even pointer identity tests. +/// +/// In other words, this answers: is "cloning" the `Const` ok? +fn is_deterministic(c: Const<'_>) -> bool { + // Primitive types cannot contain provenance and always have the same value. + if c.ty().is_primitive() { + return true; + } + + match c { + // Some constants may generate fresh allocations for pointers they contain, + // so using the same constant twice can yield two different results. + // Notably, valtrees purposefully generate new allocations. + Const::Ty(..) => false, + // We do not know the contents, so don't attempt to do anything clever. + Const::Unevaluated(..) => false, + // When an evaluated constant contains provenance, it is encoded as an `AllocId`. + // Cloning the constant will reuse the same `AllocId`. If this is in the same MIR + // body, this same `AllocId` will result in the same pointer in codegen. + Const::Val(..) => true, + } +} + +/// Check if a constant may contain provenance information. +/// Can return `true` even if there is no provenance. +fn may_have_provenance(tcx: TyCtxt<'_>, value: ConstValue, size: Size) -> bool { + match value { + ConstValue::ZeroSized | ConstValue::Scalar(Scalar::Int(_)) => return false, + ConstValue::Scalar(Scalar::Ptr(..)) | ConstValue::Slice { .. } => return true, + ConstValue::Indirect { alloc_id, offset } => !tcx + .global_alloc(alloc_id) + .unwrap_memory() + .inner() + .provenance() + .range_empty(AllocRange::from(offset..offset + size), &tcx), + } +} + fn op_to_prop_const<'tcx>( ecx: &mut InterpCx<'tcx, DummyMachine>, op: &OpTy<'tcx>, @@ -1769,7 +1811,16 @@ fn op_to_prop_const<'tcx>( // Check that we do not leak a pointer. // Those pointers may lose part of their identity in codegen. // FIXME: remove this hack once https://github.com/rust-lang/rust/issues/79738 is fixed. - if ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner().provenance().ptrs().is_empty() { + if ecx + .tcx + .global_alloc(alloc_id) + .unwrap_memory() + .inner() + .provenance() + .provenances() + .next() + .is_none() + { return Some(value); } @@ -1827,7 +1878,7 @@ impl<'tcx> VnState<'_, '_, 'tcx> { // Check that we do not leak a pointer. // Those pointers may lose part of their identity in codegen. // FIXME: remove this hack once https://github.com/rust-lang/rust/issues/79738 is fixed. - assert!(!value.may_have_provenance(self.tcx, Some(op.layout.size))); + assert!(!may_have_provenance(self.tcx, value, op.layout.size)); Some(Const::Val(value, op.layout.ty)) } From 9a7eabcbfcb56ae0f73cd73ad2d6b1f127453db5 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Sat, 10 Jan 2026 14:32:39 +0000 Subject: [PATCH 6/9] Update comment. --- compiler/rustc_mir_transform/src/gvn.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index ccb904d98f10e..bdb16523057a9 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -61,10 +61,8 @@ //! The evaluated form is inserted in `evaluated` as an `OpTy` or `None` if evaluation failed. //! //! The difficulty is non-deterministic evaluation of MIR constants. Some `Const` can have -//! different runtime values each time they are evaluated. This used to be the case with -//! `ConstValue::Slice` which have a new pointer each time they are evaluated, and is still the -//! case with valtrees that generate a new allocation each time they are used. This is checked by -//! `is_deterministic`. +//! different runtime values each time they are evaluated. This happens with valtrees that +//! generate a new allocation each time they are used. This is checked by `is_deterministic`. //! //! Meanwhile, we want to be able to read indirect constants. For instance: //! ``` @@ -81,7 +79,7 @@ //! may be non-deterministic. When that happens, we assign a disambiguator to ensure that we do not //! merge the constants. See `duplicate_slice` test in `gvn.rs`. //! -//! Conversely, some constants cannot cross crate boundaries, which could happen because of +//! Conversely, some constants cannot cross function boundaries, which could happen because of //! inlining. For instance, constants that contain a fn pointer (`AllocId` pointing to a //! `GlobalAlloc::Function`) point to a different symbol in each codegen unit. To avoid this, //! when writing constants in MIR, we do not write `Const`s that contain `AllocId`s. This is From 1a4f7d3e60eb94bdb0269f81f52db21eae8b56da Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Sat, 10 Jan 2026 14:33:15 +0000 Subject: [PATCH 7/9] Correct issue number. --- compiler/rustc_mir_transform/src/gvn.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index bdb16523057a9..9b74c51994b64 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -83,7 +83,8 @@ //! inlining. For instance, constants that contain a fn pointer (`AllocId` pointing to a //! `GlobalAlloc::Function`) point to a different symbol in each codegen unit. To avoid this, //! when writing constants in MIR, we do not write `Const`s that contain `AllocId`s. This is -//! checked by `may_have_provenance`. +//! checked by `may_have_provenance`. See https://github.com/rust-lang/rust/issues/128775 for more +//! information. use std::borrow::Cow; use std::hash::{Hash, Hasher}; @@ -1765,7 +1766,7 @@ fn op_to_prop_const<'tcx>( if !scalar.try_to_scalar_int().is_ok() { // Check that we do not leak a pointer. // Those pointers may lose part of their identity in codegen. - // FIXME: remove this hack once https://github.com/rust-lang/rust/issues/79738 is fixed. + // FIXME: remove this hack once https://github.com/rust-lang/rust/issues/128775 is fixed. return None; } return Some(ConstValue::Scalar(scalar)); @@ -1777,7 +1778,7 @@ fn op_to_prop_const<'tcx>( let (size, _align) = ecx.size_and_align_of_val(&mplace).discard_err()??; // Do not try interning a value that contains provenance. - // Due to https://github.com/rust-lang/rust/issues/79738, doing so could lead to bugs. + // Due to https://github.com/rust-lang/rust/issues/128775, doing so could lead to bugs. // FIXME: remove this hack once that issue is fixed. let alloc_ref = ecx.get_ptr_alloc(mplace.ptr(), size).discard_err()??; if alloc_ref.has_provenance() { @@ -1808,7 +1809,7 @@ fn op_to_prop_const<'tcx>( // Check that we do not leak a pointer. // Those pointers may lose part of their identity in codegen. - // FIXME: remove this hack once https://github.com/rust-lang/rust/issues/79738 is fixed. + // FIXME: remove this hack once https://github.com/rust-lang/rust/issues/128775 is fixed. if ecx .tcx .global_alloc(alloc_id) @@ -1875,7 +1876,7 @@ impl<'tcx> VnState<'_, '_, 'tcx> { // Check that we do not leak a pointer. // Those pointers may lose part of their identity in codegen. - // FIXME: remove this hack once https://github.com/rust-lang/rust/issues/79738 is fixed. + // FIXME: remove this hack once https://github.com/rust-lang/rust/issues/128775 is fixed. assert!(!may_have_provenance(self.tcx, value, op.layout.size)); Some(Const::Val(value, op.layout.ty)) From 04cb15904160c1170c76dc55b63bdb8bc596392d Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Sat, 10 Jan 2026 14:41:12 +0000 Subject: [PATCH 8/9] Use may_have_provenance. --- compiler/rustc_mir_transform/src/gvn.rs | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 9b74c51994b64..085054e1be8a3 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1805,25 +1805,7 @@ fn op_to_prop_const<'tcx>( // Everything failed: create a new allocation to hold the data. let alloc_id = ecx.intern_with_temp_alloc(op.layout, |ecx, dest| ecx.copy_op(op, dest)).discard_err()?; - let value = ConstValue::Indirect { alloc_id, offset: Size::ZERO }; - - // Check that we do not leak a pointer. - // Those pointers may lose part of their identity in codegen. - // FIXME: remove this hack once https://github.com/rust-lang/rust/issues/128775 is fixed. - if ecx - .tcx - .global_alloc(alloc_id) - .unwrap_memory() - .inner() - .provenance() - .provenances() - .next() - .is_none() - { - return Some(value); - } - - None + Some(ConstValue::Indirect { alloc_id, offset: Size::ZERO }) } impl<'tcx> VnState<'_, '_, 'tcx> { @@ -1877,7 +1859,9 @@ impl<'tcx> VnState<'_, '_, 'tcx> { // Check that we do not leak a pointer. // Those pointers may lose part of their identity in codegen. // FIXME: remove this hack once https://github.com/rust-lang/rust/issues/128775 is fixed. - assert!(!may_have_provenance(self.tcx, value, op.layout.size)); + if may_have_provenance(self.tcx, value, op.layout.size) { + return None; + } Some(Const::Val(value, op.layout.ty)) } From 60b3703f5d642e6bff6ac839f1f7c54ed455a349 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Sun, 11 Jan 2026 18:18:31 +0000 Subject: [PATCH 9/9] Tidy. --- compiler/rustc_mir_transform/src/gvn.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 085054e1be8a3..7b83923b1293d 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -83,8 +83,8 @@ //! inlining. For instance, constants that contain a fn pointer (`AllocId` pointing to a //! `GlobalAlloc::Function`) point to a different symbol in each codegen unit. To avoid this, //! when writing constants in MIR, we do not write `Const`s that contain `AllocId`s. This is -//! checked by `may_have_provenance`. See https://github.com/rust-lang/rust/issues/128775 for more -//! information. +//! checked by `may_have_provenance`. See for +//! more information. use std::borrow::Cow; use std::hash::{Hash, Hasher};