Skip to content

Commit eeb94be

Browse files
committed
Auto merge of #149366 - cjgillot:gvn-primitive, r=RalfJung
GVN: consider constants of primitive types as deterministic *[View all comments](https://triagebot.infra.rust-lang.org/gh-comments/rust-lang/rust/pull/149366)* GVN separates MIR constants into deterministic and non-deterministic constants. Deterministic constants are defined here as: gives the exact same value each time it is evaluated. This was mainly useful because of `ConstValue::Slice` that generated an extra `AllocId` each time it appeared in the MIR. That variant has been removed. This is still useful for valtrees that hold references, which generate a fresh `AllocId` for each evaluation. This PR proposes to consider all constants of primitive type to be deterministic. If a constant of primitive type passes validation, then it does not contain provenance, so we have no risk of having a reference becoming different `AllocId`s. In particular, valtrees only are leaves. r? @ghost for perf
2 parents d3e8bd9 + a2c3ebf commit eeb94be

24 files changed

Lines changed: 185 additions & 201 deletions

compiler/rustc_middle/src/mir/consts.rs

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -181,26 +181,6 @@ impl ConstValue {
181181
Some(data.inner().inspect_with_uninit_and_ptr_outside_interpreter(start..end))
182182
}
183183

184-
/// Check if a constant may contain provenance information. This is used by MIR opts.
185-
/// Can return `true` even if there is no provenance.
186-
pub fn may_have_provenance(&self, tcx: TyCtxt<'_>, size: Size) -> bool {
187-
match *self {
188-
ConstValue::ZeroSized | ConstValue::Scalar(Scalar::Int(_)) => return false,
189-
ConstValue::Scalar(Scalar::Ptr(..)) => return true,
190-
// It's hard to find out the part of the allocation we point to;
191-
// just conservatively check everything.
192-
ConstValue::Slice { alloc_id, meta: _ } => {
193-
!tcx.global_alloc(alloc_id).unwrap_memory().inner().provenance().ptrs().is_empty()
194-
}
195-
ConstValue::Indirect { alloc_id, offset } => !tcx
196-
.global_alloc(alloc_id)
197-
.unwrap_memory()
198-
.inner()
199-
.provenance()
200-
.range_empty(AllocRange::from(offset..offset + size), &tcx),
201-
}
202-
}
203-
204184
/// Check if a constant only contains uninitialized bytes.
205185
pub fn all_bytes_uninit(&self, tcx: TyCtxt<'_>) -> bool {
206186
let ConstValue::Indirect { alloc_id, .. } = self else {
@@ -474,39 +454,6 @@ impl<'tcx> Const<'tcx> {
474454
let val = ConstValue::Scalar(s);
475455
Self::Val(val, ty)
476456
}
477-
478-
/// Return true if any evaluation of this constant always returns the same value,
479-
/// taking into account even pointer identity tests.
480-
pub fn is_deterministic(&self) -> bool {
481-
// Some constants may generate fresh allocations for pointers they contain,
482-
// so using the same constant twice can yield two different results.
483-
// Notably, valtrees purposefully generate new allocations.
484-
match self {
485-
Const::Ty(_, c) => match c.kind() {
486-
ty::ConstKind::Param(..) => true,
487-
// A valtree may be a reference. Valtree references correspond to a
488-
// different allocation each time they are evaluated. Valtrees for primitive
489-
// types are fine though.
490-
ty::ConstKind::Value(cv) => cv.ty.is_primitive(),
491-
ty::ConstKind::Unevaluated(..) | ty::ConstKind::Expr(..) => false,
492-
// This can happen if evaluation of a constant failed. The result does not matter
493-
// much since compilation is doomed.
494-
ty::ConstKind::Error(..) => false,
495-
// Should not appear in runtime MIR.
496-
ty::ConstKind::Infer(..)
497-
| ty::ConstKind::Bound(..)
498-
| ty::ConstKind::Placeholder(..) => bug!(),
499-
},
500-
Const::Unevaluated(..) => false,
501-
Const::Val(
502-
ConstValue::Slice { .. }
503-
| ConstValue::ZeroSized
504-
| ConstValue::Scalar(_)
505-
| ConstValue::Indirect { .. },
506-
_,
507-
) => true,
508-
}
509-
}
510457
}
511458

512459
/// An unevaluated (potentially generic) constant used in MIR.

compiler/rustc_mir_transform/src/gvn.rs

Lines changed: 92 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,8 @@
6161
//! The evaluated form is inserted in `evaluated` as an `OpTy` or `None` if evaluation failed.
6262
//!
6363
//! The difficulty is non-deterministic evaluation of MIR constants. Some `Const` can have
64-
//! different runtime values each time they are evaluated. This is the case with
65-
//! `Const::Slice` which have a new pointer each time they are evaluated, and constants that
66-
//! contain a fn pointer (`AllocId` pointing to a `GlobalAlloc::Function`) pointing to a different
67-
//! symbol in each codegen unit.
64+
//! different runtime values each time they are evaluated. This happens with valtrees that
65+
//! generate a new allocation each time they are used. This is checked by `is_deterministic`.
6866
//!
6967
//! Meanwhile, we want to be able to read indirect constants. For instance:
7068
//! ```
@@ -81,8 +79,12 @@
8179
//! may be non-deterministic. When that happens, we assign a disambiguator to ensure that we do not
8280
//! merge the constants. See `duplicate_slice` test in `gvn.rs`.
8381
//!
84-
//! Second, when writing constants in MIR, we do not write `Const::Slice` or `Const`
85-
//! that contain `AllocId`s.
82+
//! Conversely, some constants cannot cross function boundaries, which could happen because of
83+
//! inlining. For instance, constants that contain a fn pointer (`AllocId` pointing to a
84+
//! `GlobalAlloc::Function`) point to a different symbol in each codegen unit. To avoid this,
85+
//! when writing constants in MIR, we do not write `Const`s that contain `AllocId`s. This is
86+
//! checked by `may_have_provenance`. See <https://github.com/rust-lang/rust/issues/128775> for
87+
//! more information.
8688
8789
use std::borrow::Cow;
8890
use std::hash::{Hash, Hasher};
@@ -103,7 +105,7 @@ use rustc_hir::def::DefKind;
103105
use rustc_index::bit_set::DenseBitSet;
104106
use rustc_index::{IndexVec, newtype_index};
105107
use rustc_middle::bug;
106-
use rustc_middle::mir::interpret::GlobalAlloc;
108+
use rustc_middle::mir::interpret::{AllocRange, GlobalAlloc};
107109
use rustc_middle::mir::visit::*;
108110
use rustc_middle::mir::*;
109111
use rustc_middle::ty::layout::HasTypingEnv;
@@ -487,7 +489,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
487489

488490
#[instrument(level = "trace", skip(self), ret)]
489491
fn insert_constant(&mut self, value: Const<'tcx>) -> VnIndex {
490-
if value.is_deterministic() {
492+
if is_deterministic(value) {
491493
// The constant is deterministic, no need to disambiguate.
492494
let constant = Value::Constant { value, disambiguator: None };
493495
self.insert(value.ty(), constant)
@@ -522,14 +524,14 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
522524
fn insert_bool(&mut self, flag: bool) -> VnIndex {
523525
// Booleans are deterministic.
524526
let value = Const::from_bool(self.tcx, flag);
525-
debug_assert!(value.is_deterministic());
527+
debug_assert!(is_deterministic(value));
526528
self.insert(self.tcx.types.bool, Value::Constant { value, disambiguator: None })
527529
}
528530

529531
fn insert_scalar(&mut self, ty: Ty<'tcx>, scalar: Scalar) -> VnIndex {
530532
// Scalars are deterministic.
531533
let value = Const::from_scalar(self.tcx, scalar, ty);
532-
debug_assert!(value.is_deterministic());
534+
debug_assert!(is_deterministic(value));
533535
self.insert(ty, Value::Constant { value, disambiguator: None })
534536
}
535537

@@ -1002,21 +1004,19 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
10021004
operand: &mut Operand<'tcx>,
10031005
location: Location,
10041006
) -> Option<VnIndex> {
1005-
match *operand {
1006-
Operand::RuntimeChecks(c) => {
1007-
Some(self.insert(self.tcx.types.bool, Value::RuntimeChecks(c)))
1008-
}
1009-
Operand::Constant(ref constant) => Some(self.insert_constant(constant.const_)),
1007+
let value = match *operand {
1008+
Operand::RuntimeChecks(c) => self.insert(self.tcx.types.bool, Value::RuntimeChecks(c)),
1009+
Operand::Constant(ref constant) => self.insert_constant(constant.const_),
10101010
Operand::Copy(ref mut place) | Operand::Move(ref mut place) => {
1011-
let value = self.simplify_place_value(place, location)?;
1012-
if let Some(const_) = self.try_as_constant(value) {
1013-
*operand = Operand::Constant(Box::new(const_));
1014-
} else if let Value::RuntimeChecks(c) = self.get(value) {
1015-
*operand = Operand::RuntimeChecks(c);
1016-
}
1017-
Some(value)
1011+
self.simplify_place_value(place, location)?
10181012
}
1013+
};
1014+
if let Some(const_) = self.try_as_constant(value) {
1015+
*operand = Operand::Constant(Box::new(const_));
1016+
} else if let Value::RuntimeChecks(c) = self.get(value) {
1017+
*operand = Operand::RuntimeChecks(c);
10191018
}
1019+
Some(value)
10201020
}
10211021

10221022
#[instrument(level = "trace", skip(self), ret)]
@@ -1731,6 +1731,49 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
17311731
}
17321732
}
17331733

1734+
/// Return true if any evaluation of this constant in the same MIR body
1735+
/// always returns the same value, taking into account even pointer identity tests.
1736+
///
1737+
/// In other words, this answers: is "cloning" the `Const` ok?
1738+
///
1739+
/// This returns `false` for constants that synthesize new `AllocId` when they are instantiated.
1740+
/// It is `true` for anything else, since a given `AllocId` *does* have a unique runtime value
1741+
/// within the scope of a single MIR body.
1742+
fn is_deterministic(c: Const<'_>) -> bool {
1743+
// Primitive types cannot contain provenance and always have the same value.
1744+
if c.ty().is_primitive() {
1745+
return true;
1746+
}
1747+
1748+
match c {
1749+
// Some constants may generate fresh allocations for pointers they contain,
1750+
// so using the same constant twice can yield two different results.
1751+
// Notably, valtrees purposefully generate new allocations.
1752+
Const::Ty(..) => false,
1753+
// We do not know the contents, so don't attempt to do anything clever.
1754+
Const::Unevaluated(..) => false,
1755+
// When an evaluated constant contains provenance, it is encoded as an `AllocId`.
1756+
// Cloning the constant will reuse the same `AllocId`. If this is in the same MIR
1757+
// body, this same `AllocId` will result in the same pointer in codegen.
1758+
Const::Val(..) => true,
1759+
}
1760+
}
1761+
1762+
/// Check if a constant may contain provenance information.
1763+
/// Can return `true` even if there is no provenance.
1764+
fn may_have_provenance(tcx: TyCtxt<'_>, value: ConstValue, size: Size) -> bool {
1765+
match value {
1766+
ConstValue::ZeroSized | ConstValue::Scalar(Scalar::Int(_)) => return false,
1767+
ConstValue::Scalar(Scalar::Ptr(..)) | ConstValue::Slice { .. } => return true,
1768+
ConstValue::Indirect { alloc_id, offset } => !tcx
1769+
.global_alloc(alloc_id)
1770+
.unwrap_memory()
1771+
.inner()
1772+
.provenance()
1773+
.range_empty(AllocRange::from(offset..offset + size), &tcx),
1774+
}
1775+
}
1776+
17341777
fn op_to_prop_const<'tcx>(
17351778
ecx: &mut InterpCx<'tcx, DummyMachine>,
17361779
op: &OpTy<'tcx>,
@@ -1762,7 +1805,7 @@ fn op_to_prop_const<'tcx>(
17621805
if !scalar.try_to_scalar_int().is_ok() {
17631806
// Check that we do not leak a pointer.
17641807
// Those pointers may lose part of their identity in codegen.
1765-
// FIXME: remove this hack once https://github.com/rust-lang/rust/issues/79738 is fixed.
1808+
// FIXME: remove this hack once https://github.com/rust-lang/rust/issues/128775 is fixed.
17661809
return None;
17671810
}
17681811
return Some(ConstValue::Scalar(scalar));
@@ -1774,7 +1817,7 @@ fn op_to_prop_const<'tcx>(
17741817
let (size, _align) = ecx.size_and_align_of_val(&mplace).discard_err()??;
17751818

17761819
// Do not try interning a value that contains provenance.
1777-
// Due to https://github.com/rust-lang/rust/issues/79738, doing so could lead to bugs.
1820+
// Due to https://github.com/rust-lang/rust/issues/128775, doing so could lead to bugs.
17781821
// FIXME: remove this hack once that issue is fixed.
17791822
let alloc_ref = ecx.get_ptr_alloc(mplace.ptr(), size).discard_err()??;
17801823
if alloc_ref.has_provenance() {
@@ -1801,16 +1844,7 @@ fn op_to_prop_const<'tcx>(
18011844
// Everything failed: create a new allocation to hold the data.
18021845
let alloc_id =
18031846
ecx.intern_with_temp_alloc(op.layout, |ecx, dest| ecx.copy_op(op, dest)).discard_err()?;
1804-
let value = ConstValue::Indirect { alloc_id, offset: Size::ZERO };
1805-
1806-
// Check that we do not leak a pointer.
1807-
// Those pointers may lose part of their identity in codegen.
1808-
// FIXME: remove this hack once https://github.com/rust-lang/rust/issues/79738 is fixed.
1809-
if ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner().provenance().ptrs().is_empty() {
1810-
return Some(value);
1811-
}
1812-
1813-
None
1847+
Some(ConstValue::Indirect { alloc_id, offset: Size::ZERO })
18141848
}
18151849

18161850
impl<'tcx> VnState<'_, '_, 'tcx> {
@@ -1831,14 +1865,28 @@ impl<'tcx> VnState<'_, '_, 'tcx> {
18311865

18321866
/// If `index` is a `Value::Constant`, return the `Constant` to be put in the MIR.
18331867
fn try_as_constant(&mut self, index: VnIndex) -> Option<ConstOperand<'tcx>> {
1834-
// This was already constant in MIR, do not change it. If the constant is not
1835-
// deterministic, adding an additional mention of it in MIR will not give the same value as
1836-
// the former mention.
1837-
if let Value::Constant { value, disambiguator: None } = self.get(index) {
1838-
debug_assert!(value.is_deterministic());
1868+
let value = self.get(index);
1869+
1870+
// This was already an *evaluated* constant in MIR, do not change it.
1871+
if let Value::Constant { value, disambiguator: None } = value
1872+
&& let Const::Val(..) = value
1873+
{
18391874
return Some(ConstOperand { span: DUMMY_SP, user_ty: None, const_: value });
18401875
}
18411876

1877+
if let Some(value) = self.try_as_evaluated_constant(index) {
1878+
return Some(ConstOperand { span: DUMMY_SP, user_ty: None, const_: value });
1879+
}
1880+
1881+
// We failed to provide an evaluated form, fallback to using the unevaluated constant.
1882+
if let Value::Constant { value, disambiguator: None } = value {
1883+
return Some(ConstOperand { span: DUMMY_SP, user_ty: None, const_: value });
1884+
}
1885+
1886+
None
1887+
}
1888+
1889+
fn try_as_evaluated_constant(&mut self, index: VnIndex) -> Option<Const<'tcx>> {
18421890
let op = self.eval_to_const(index)?;
18431891
if op.layout.is_unsized() {
18441892
// Do not attempt to propagate unsized locals.
@@ -1849,11 +1897,12 @@ impl<'tcx> VnState<'_, '_, 'tcx> {
18491897

18501898
// Check that we do not leak a pointer.
18511899
// Those pointers may lose part of their identity in codegen.
1852-
// FIXME: remove this hack once https://github.com/rust-lang/rust/issues/79738 is fixed.
1853-
assert!(!value.may_have_provenance(self.tcx, op.layout.size));
1900+
// FIXME: remove this hack once https://github.com/rust-lang/rust/issues/128775 is fixed.
1901+
if may_have_provenance(self.tcx, value, op.layout.size) {
1902+
return None;
1903+
}
18541904

1855-
let const_ = Const::Val(value, op.layout.ty);
1856-
Some(ConstOperand { span: DUMMY_SP, user_ty: None, const_ })
1905+
Some(Const::Val(value, op.layout.ty))
18571906
}
18581907

18591908
/// Construct a place which holds the same value as `index` and for which all locals strictly

tests/incremental/hashes/enum_constructors.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ pub fn change_field_order_struct_like() -> Enum {
6565
#[cfg(not(any(cfail1,cfail4)))]
6666
#[rustc_clean(cfg="cfail2", except="opt_hir_owner_nodes,typeck")]
6767
#[rustc_clean(cfg="cfail3")]
68-
#[rustc_clean(cfg="cfail5", except="opt_hir_owner_nodes,typeck,optimized_mir")]
68+
#[rustc_clean(cfg="cfail5", except="opt_hir_owner_nodes,typeck")]
6969
#[rustc_clean(cfg="cfail6")]
7070
// FIXME(michaelwoerister):Interesting. I would have thought that that changes the MIR. And it
7171
// would if it were not all constants

tests/incremental/hashes/struct_constructors.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub fn change_field_order_regular_struct() -> RegularStruct {
6262
#[cfg(not(any(cfail1,cfail4)))]
6363
#[rustc_clean(cfg="cfail2", except="opt_hir_owner_nodes,typeck")]
6464
#[rustc_clean(cfg="cfail3")]
65-
#[rustc_clean(cfg="cfail5", except="opt_hir_owner_nodes,typeck,optimized_mir")]
65+
#[rustc_clean(cfg="cfail5", except="opt_hir_owner_nodes,typeck")]
6666
#[rustc_clean(cfg="cfail6")]
6767
pub fn change_field_order_regular_struct() -> RegularStruct {
6868
RegularStruct {

tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.32bit.panic-abort.diff

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@
5050
+ _4 = const ();
5151
StorageLive(_12);
5252
StorageLive(_13);
53-
_13 = boxed::box_new_uninit(const <() as std::mem::SizedTypeProperties>::LAYOUT) -> [return: bb2, unwind unreachable];
53+
- _13 = boxed::box_new_uninit(const <() as std::mem::SizedTypeProperties>::LAYOUT) -> [return: bb2, unwind unreachable];
54+
+ _13 = boxed::box_new_uninit(const Layout {{ size: 0_usize, align: std::ptr::Alignment {{ _inner_repr_trick: std::ptr::alignment::AlignmentEnum::_Align1Shl0 }} }}) -> [return: bb2, unwind unreachable];
5455
}
5556

5657
bb1: {
@@ -103,5 +104,9 @@
103104
+ nop;
104105
drop(_3) -> [return: bb1, unwind unreachable];
105106
}
107+
+ }
108+
+
109+
+ ALLOC0 (size: 8, align: 4) {
110+
+ 01 00 00 00 00 00 00 00 │ ........
106111
}
107112

tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.64bit.panic-abort.diff

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@
5050
+ _4 = const ();
5151
StorageLive(_12);
5252
StorageLive(_13);
53-
_13 = boxed::box_new_uninit(const <() as std::mem::SizedTypeProperties>::LAYOUT) -> [return: bb2, unwind unreachable];
53+
- _13 = boxed::box_new_uninit(const <() as std::mem::SizedTypeProperties>::LAYOUT) -> [return: bb2, unwind unreachable];
54+
+ _13 = boxed::box_new_uninit(const Layout {{ size: 0_usize, align: std::ptr::Alignment {{ _inner_repr_trick: std::ptr::alignment::AlignmentEnum::_Align1Shl0 }} }}) -> [return: bb2, unwind unreachable];
5455
}
5556

5657
bb1: {
@@ -103,5 +104,9 @@
103104
+ nop;
104105
drop(_3) -> [return: bb1, unwind unreachable];
105106
}
107+
+ }
108+
+
109+
+ ALLOC0 (size: 16, align: 8) {
110+
+ 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │ ................
106111
}
107112

tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-abort.diff

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
StorageLive(_20);
4848
StorageLive(_21);
4949
_21 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32);
50-
_20 = BitAnd(move _21, const core::fmt::flags::SIGN_PLUS_FLAG);
50+
_20 = BitAnd(move _21, const 2097152_u32);
5151
StorageDead(_21);
5252
_4 = Ne(move _20, const 0_u32);
5353
StorageDead(_20);
@@ -72,7 +72,7 @@
7272
StorageLive(_22);
7373
StorageLive(_23);
7474
_23 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32);
75-
_22 = BitAnd(move _23, const core::fmt::flags::PRECISION_FLAG);
75+
_22 = BitAnd(move _23, const 268435456_u32);
7676
StorageDead(_23);
7777
switchInt(copy _22) -> [0: bb10, otherwise: bb11];
7878
}

tests/mir-opt/funky_arms.float_to_exponential_common.GVN.32bit.panic-unwind.diff

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
StorageLive(_20);
4848
StorageLive(_21);
4949
_21 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32);
50-
_20 = BitAnd(move _21, const core::fmt::flags::SIGN_PLUS_FLAG);
50+
_20 = BitAnd(move _21, const 2097152_u32);
5151
StorageDead(_21);
5252
_4 = Ne(move _20, const 0_u32);
5353
StorageDead(_20);
@@ -72,7 +72,7 @@
7272
StorageLive(_22);
7373
StorageLive(_23);
7474
_23 = copy (((*_1).0: std::fmt::FormattingOptions).0: u32);
75-
_22 = BitAnd(move _23, const core::fmt::flags::PRECISION_FLAG);
75+
_22 = BitAnd(move _23, const 268435456_u32);
7676
StorageDead(_23);
7777
switchInt(copy _22) -> [0: bb10, otherwise: bb11];
7878
}

0 commit comments

Comments
 (0)