From f01b2f938bb51c108fefca069816b72f24582992 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Thu, 6 Nov 2025 23:10:28 +0000 Subject: [PATCH 01/10] Try harder to evaluate constants. --- compiler/rustc_mir_transform/src/gvn.rs | 49 ++++++++++++------- ...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 +- 11 files changed, 45 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 6507a5194adda..cb9b8b5d7f0ce 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1002,21 +1002,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)] @@ -1831,14 +1829,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. @@ -1852,8 +1864,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/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 88c77832a4e18..61cc233bcb528 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(copy _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 8a6e7fd35ccd2..655762567da32 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(copy _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 ce10f4bb247a8..ca47d2c580b50 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(copy _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 b19f2438d0225..9239c365537c7 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(copy _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 54b07a31ffe78065171c925580badc75836d85f5 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Thu, 6 Nov 2025 23:00:23 +0000 Subject: [PATCH 02/10] Constants of primitive types are always deterministic. --- compiler/rustc_middle/src/mir/consts.rs | 20 +++++++++---------- ...ace.PreCodegen.after.32bit.panic-abort.mir | 20 ++++++++----------- ...ce.PreCodegen.after.32bit.panic-unwind.mir | 20 ++++++++----------- ...ace.PreCodegen.after.64bit.panic-abort.mir | 20 ++++++++----------- ...ce.PreCodegen.after.64bit.panic-unwind.mir | 20 ++++++++----------- tests/mir-opt/pre-codegen/drop_boxed_slice.rs | 5 ++--- 6 files changed, 44 insertions(+), 61 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 f8e575f490b0c..f4794a974bf22 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,7 +8,7 @@ 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 _9: (); + let _8: (); scope 3 { scope 4 { scope 17 (inlined Layout::size) { @@ -26,7 +26,7 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 23 (inlined ::deallocate) { scope 24 (inlined std::alloc::Global::deallocate_impl) { scope 25 (inlined std::alloc::Global::deallocate_impl_runtime) { - let mut _8: *mut u8; + let mut _7: *mut u8; scope 26 (inlined Layout::size) { } scope 27 (inlined NonNull::::as_ptr) { @@ -47,7 +47,7 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } scope 7 (inlined Layout::for_value_raw::<[T]>) { let mut _5: usize; - let mut _7: std::ptr::Alignment; + let mut _6: std::ptr::Alignment; scope 8 { scope 16 (inlined #[track_caller] Layout::from_size_alignment_unchecked) { } @@ -55,7 +55,6 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 9 (inlined size_of_val_raw::<[T]>) { } scope 10 (inlined std::ptr::Alignment::of_val_raw::<[T]>) { - let _6: usize; scope 11 { scope 13 (inlined #[track_caller] std::ptr::Alignment::new_unchecked) { scope 14 (inlined core::ub_checks::check_language_ub) { @@ -82,22 +81,19 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } bb1: { - StorageLive(_6); - _6 = const ::ALIGN; - _7 = copy _6 as std::ptr::Alignment (Transmute); - StorageDead(_6); + _6 = const ::ALIGN as std::ptr::Alignment (Transmute); StorageDead(_4); switchInt(copy _5) -> [0: bb4, otherwise: bb2]; } bb2: { - StorageLive(_8); - _8 = copy _3 as *mut u8 (PtrToPtr); - _9 = alloc::alloc::__rust_dealloc(move _8, move _5, move _7) -> [return: bb3, unwind unreachable]; + StorageLive(_7); + _7 = copy _3 as *mut u8 (PtrToPtr); + _8 = alloc::alloc::__rust_dealloc(move _7, move _5, move _6) -> [return: bb3, unwind unreachable]; } bb3: { - StorageDead(_8); + StorageDead(_7); 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 f8e575f490b0c..f4794a974bf22 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,7 +8,7 @@ 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 _9: (); + let _8: (); scope 3 { scope 4 { scope 17 (inlined Layout::size) { @@ -26,7 +26,7 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 23 (inlined ::deallocate) { scope 24 (inlined std::alloc::Global::deallocate_impl) { scope 25 (inlined std::alloc::Global::deallocate_impl_runtime) { - let mut _8: *mut u8; + let mut _7: *mut u8; scope 26 (inlined Layout::size) { } scope 27 (inlined NonNull::::as_ptr) { @@ -47,7 +47,7 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } scope 7 (inlined Layout::for_value_raw::<[T]>) { let mut _5: usize; - let mut _7: std::ptr::Alignment; + let mut _6: std::ptr::Alignment; scope 8 { scope 16 (inlined #[track_caller] Layout::from_size_alignment_unchecked) { } @@ -55,7 +55,6 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 9 (inlined size_of_val_raw::<[T]>) { } scope 10 (inlined std::ptr::Alignment::of_val_raw::<[T]>) { - let _6: usize; scope 11 { scope 13 (inlined #[track_caller] std::ptr::Alignment::new_unchecked) { scope 14 (inlined core::ub_checks::check_language_ub) { @@ -82,22 +81,19 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } bb1: { - StorageLive(_6); - _6 = const ::ALIGN; - _7 = copy _6 as std::ptr::Alignment (Transmute); - StorageDead(_6); + _6 = const ::ALIGN as std::ptr::Alignment (Transmute); StorageDead(_4); switchInt(copy _5) -> [0: bb4, otherwise: bb2]; } bb2: { - StorageLive(_8); - _8 = copy _3 as *mut u8 (PtrToPtr); - _9 = alloc::alloc::__rust_dealloc(move _8, move _5, move _7) -> [return: bb3, unwind unreachable]; + StorageLive(_7); + _7 = copy _3 as *mut u8 (PtrToPtr); + _8 = alloc::alloc::__rust_dealloc(move _7, move _5, move _6) -> [return: bb3, unwind unreachable]; } bb3: { - StorageDead(_8); + StorageDead(_7); 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 f8e575f490b0c..f4794a974bf22 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,7 +8,7 @@ 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 _9: (); + let _8: (); scope 3 { scope 4 { scope 17 (inlined Layout::size) { @@ -26,7 +26,7 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 23 (inlined ::deallocate) { scope 24 (inlined std::alloc::Global::deallocate_impl) { scope 25 (inlined std::alloc::Global::deallocate_impl_runtime) { - let mut _8: *mut u8; + let mut _7: *mut u8; scope 26 (inlined Layout::size) { } scope 27 (inlined NonNull::::as_ptr) { @@ -47,7 +47,7 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } scope 7 (inlined Layout::for_value_raw::<[T]>) { let mut _5: usize; - let mut _7: std::ptr::Alignment; + let mut _6: std::ptr::Alignment; scope 8 { scope 16 (inlined #[track_caller] Layout::from_size_alignment_unchecked) { } @@ -55,7 +55,6 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 9 (inlined size_of_val_raw::<[T]>) { } scope 10 (inlined std::ptr::Alignment::of_val_raw::<[T]>) { - let _6: usize; scope 11 { scope 13 (inlined #[track_caller] std::ptr::Alignment::new_unchecked) { scope 14 (inlined core::ub_checks::check_language_ub) { @@ -82,22 +81,19 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } bb1: { - StorageLive(_6); - _6 = const ::ALIGN; - _7 = copy _6 as std::ptr::Alignment (Transmute); - StorageDead(_6); + _6 = const ::ALIGN as std::ptr::Alignment (Transmute); StorageDead(_4); switchInt(copy _5) -> [0: bb4, otherwise: bb2]; } bb2: { - StorageLive(_8); - _8 = copy _3 as *mut u8 (PtrToPtr); - _9 = alloc::alloc::__rust_dealloc(move _8, move _5, move _7) -> [return: bb3, unwind unreachable]; + StorageLive(_7); + _7 = copy _3 as *mut u8 (PtrToPtr); + _8 = alloc::alloc::__rust_dealloc(move _7, move _5, move _6) -> [return: bb3, unwind unreachable]; } bb3: { - StorageDead(_8); + StorageDead(_7); 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 f8e575f490b0c..f4794a974bf22 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,7 +8,7 @@ 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 _9: (); + let _8: (); scope 3 { scope 4 { scope 17 (inlined Layout::size) { @@ -26,7 +26,7 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 23 (inlined ::deallocate) { scope 24 (inlined std::alloc::Global::deallocate_impl) { scope 25 (inlined std::alloc::Global::deallocate_impl_runtime) { - let mut _8: *mut u8; + let mut _7: *mut u8; scope 26 (inlined Layout::size) { } scope 27 (inlined NonNull::::as_ptr) { @@ -47,7 +47,7 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } scope 7 (inlined Layout::for_value_raw::<[T]>) { let mut _5: usize; - let mut _7: std::ptr::Alignment; + let mut _6: std::ptr::Alignment; scope 8 { scope 16 (inlined #[track_caller] Layout::from_size_alignment_unchecked) { } @@ -55,7 +55,6 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 9 (inlined size_of_val_raw::<[T]>) { } scope 10 (inlined std::ptr::Alignment::of_val_raw::<[T]>) { - let _6: usize; scope 11 { scope 13 (inlined #[track_caller] std::ptr::Alignment::new_unchecked) { scope 14 (inlined core::ub_checks::check_language_ub) { @@ -82,22 +81,19 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } bb1: { - StorageLive(_6); - _6 = const ::ALIGN; - _7 = copy _6 as std::ptr::Alignment (Transmute); - StorageDead(_6); + _6 = const ::ALIGN as std::ptr::Alignment (Transmute); StorageDead(_4); switchInt(copy _5) -> [0: bb4, otherwise: bb2]; } bb2: { - StorageLive(_8); - _8 = copy _3 as *mut u8 (PtrToPtr); - _9 = alloc::alloc::__rust_dealloc(move _8, move _5, move _7) -> [return: bb3, unwind unreachable]; + StorageLive(_7); + _7 = copy _3 as *mut u8 (PtrToPtr); + _8 = alloc::alloc::__rust_dealloc(move _7, move _5, move _6) -> [return: bb3, unwind unreachable]; } bb3: { - StorageDead(_8); + StorageDead(_7); 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 ae10cfb0b1713..9e56b310e8188 100644 --- a/tests/mir-opt/pre-codegen/drop_boxed_slice.rs +++ b/tests/mir-opt/pre-codegen/drop_boxed_slice.rs @@ -9,8 +9,7 @@ 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: = alloc::alloc::__rust_dealloc({{.+}}, move [[SIZE]], move [[B]]) -> + // CHECK: [[ALIGN:_.+]] = const ::ALIGN as std::ptr::Alignment (Transmute); + // CHECK: = alloc::alloc::__rust_dealloc({{.+}}, move [[SIZE]], move [[ALIGN]]) -> std::ptr::drop_in_place(ptr) } From fd1a413a30a7686aa52b07ae2b50091186ee2651 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Fri, 28 Nov 2025 23:27:21 +0000 Subject: [PATCH 03/10] 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 1ddf683521d38821864b993a91b3efc7d084cae9 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Tue, 16 Dec 2025 01:14:00 +0000 Subject: [PATCH 04/10] 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 cb9b8b5d7f0ce..3dc827dac47f0 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1862,7 +1862,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 92e2d078c008f7345dcd8d0c67a92c6c1d20d4c1 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Wed, 17 Dec 2025 00:37:19 +0000 Subject: [PATCH 05/10] 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 3dc827dac47f0..3caec75d80c5a 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; @@ -487,7 +490,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) @@ -522,14 +525,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 }) } @@ -1729,6 +1732,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>, @@ -1804,7 +1846,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); } @@ -1862,7 +1913,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 6f0004f9f5744ed1325780053ecc7be16880286b Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Sat, 10 Jan 2026 14:32:39 +0000 Subject: [PATCH 06/10] 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 3caec75d80c5a..8ceb9cc6a44a1 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 918a98ab548ced011bbc231090d65f96b4331f40 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Sat, 10 Jan 2026 14:33:15 +0000 Subject: [PATCH 07/10] 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 8ceb9cc6a44a1..ae3b2507395ec 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}; @@ -1800,7 +1801,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)); @@ -1812,7 +1813,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() { @@ -1843,7 +1844,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) @@ -1910,7 +1911,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 b56e5b34a18872dffb073b9328139992997ecfb9 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Sat, 10 Jan 2026 14:41:12 +0000 Subject: [PATCH 08/10] 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 ae3b2507395ec..4b8c6d9c8b31a 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1840,25 +1840,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> { @@ -1912,7 +1894,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 223be90a998e638614ca46c89cd54e41bcefb696 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Sun, 11 Jan 2026 18:18:31 +0000 Subject: [PATCH 09/10] 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 4b8c6d9c8b31a..6a858fc93261f 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}; From 3f4ce076f225c4be3c94bcb5b5d29c186258e9ee Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Tue, 17 Feb 2026 10:12:42 -0300 Subject: [PATCH 10/10] Update compiler/rustc_mir_transform/src/gvn.rs Co-authored-by: Ralf Jung --- compiler/rustc_mir_transform/src/gvn.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 6a858fc93261f..2ad5090f484bf 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1735,6 +1735,10 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> { /// always returns the same value, taking into account even pointer identity tests. /// /// In other words, this answers: is "cloning" the `Const` ok? +/// +/// This returns `false` for constants that synthesize new `AllocId` when they are instantiated. +/// It is `true` for anything else, since a given `AllocId` *does* have a unique runtime value +/// within the scope of a single MIR body. fn is_deterministic(c: Const<'_>) -> bool { // Primitive types cannot contain provenance and always have the same value. if c.ty().is_primitive() {