Skip to content

Commit ff8b054

Browse files
committed
Disambiguate non-deterministic constants.
1 parent 488ce66 commit ff8b054

File tree

7 files changed

+238
-53
lines changed

7 files changed

+238
-53
lines changed

compiler/rustc_middle/src/mir/consts.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,40 @@ impl<'tcx> Const<'tcx> {
497497
_ => Self::Ty(c),
498498
}
499499
}
500+
501+
/// Return true if any evaluation of this constant returns the same value.
502+
pub fn is_deterministic(&self) -> bool {
503+
// Some constants may contain pointers. We need to preserve the provenance of these
504+
// pointers, but not all constants guarantee this:
505+
// - valtrees purposefully do not;
506+
// - ConstValue::Slice does not either.
507+
match self {
508+
Const::Ty(c) => match c.kind() {
509+
ty::ConstKind::Value(valtree) => match valtree {
510+
// This is just an integer, keep it.
511+
ty::ValTree::Leaf(_) => true,
512+
// This branch may be a reference. Valtree references correspond to a
513+
// different allocation each time they are evaluated.
514+
ty::ValTree::Branch(_) => false,
515+
},
516+
ty::ConstKind::Param(..) => true,
517+
ty::ConstKind::Unevaluated(..) | ty::ConstKind::Expr(..) => false,
518+
// Should not appear in runtime MIR.
519+
ty::ConstKind::Infer(..)
520+
| ty::ConstKind::Bound(..)
521+
| ty::ConstKind::Placeholder(..)
522+
| ty::ConstKind::Error(..) => bug!(),
523+
},
524+
Const::Unevaluated(..) => false,
525+
// If the same slice appears twice in the MIR, we cannot guarantee that we will
526+
// give the same `AllocId` to the data.
527+
Const::Val(ConstValue::Slice { .. }, _) => false,
528+
Const::Val(
529+
ConstValue::ZeroSized | ConstValue::Scalar(_) | ConstValue::Indirect { .. },
530+
_,
531+
) => true,
532+
}
533+
}
500534
}
501535

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

compiler/rustc_mir_transform/src/gvn.rs

Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,35 @@
5252
//! _a = *_b // _b is &Freeze
5353
//! _c = *_b // replaced by _c = _a
5454
//! ```
55+
//!
56+
//! # Determinism of constant propagation
57+
//!
58+
//! When registering a new `Value`, we attempt to opportunistically evaluate it as a constant.
59+
//! The evaluated form is inserted in `evaluated` as an `OpTy` or `None` if evaluation failed.
60+
//!
61+
//! The difficulty is non-deterministic evaluation of MIR constants. Some `Const` can have
62+
//! different runtime values each time they are evaluated. This is the case with
63+
//! `Const::Slice` which have a new pointer each time they are evaluated, and constants that
64+
//! contain a fn pointer (`AllocId` pointing to a `GlobalAlloc::Function`) pointing to a different
65+
//! symbol in each codegen unit.
66+
//!
67+
//! Meanwhile, we want to be able to read indirect constants. For instance:
68+
//! ```
69+
//! static A: &'static &'static u8 = &&63;
70+
//! fn foo() -> u8 {
71+
//! **A // We want to replace by 63.
72+
//! }
73+
//! fn bar() -> u8 {
74+
//! b"abc"[1] // We want to replace by 'b'.
75+
//! }
76+
//! ```
77+
//!
78+
//! The `Value::Constant` variant stores a possibly unevaluated constant. Evaluating that constant
79+
//! may be non-deterministic. When that happens, we assign a disambiguator to ensure that we do not
80+
//! merge the constants. See `duplicate_slice` test in `gvn.rs`.
81+
//!
82+
//! Second, when writing constants in MIR, we do not write `Const::Slice` or `Const`
83+
//! that contain `AllocId`s.
5584
5685
use rustc_const_eval::interpret::{intern_const_alloc_for_constprop, MemoryKind};
5786
use rustc_const_eval::interpret::{ImmTy, InterpCx, OpTy, Projectable, Scalar};
@@ -161,7 +190,12 @@ enum Value<'tcx> {
161190
/// The `usize` is a counter incremented by `new_opaque`.
162191
Opaque(usize),
163192
/// Evaluated or unevaluated constant value.
164-
Constant(Const<'tcx>),
193+
Constant {
194+
value: Const<'tcx>,
195+
/// Some constants do not have a deterministic value. To avoid merging two instances of the
196+
/// same `Const`, we assign them an additional integer index.
197+
disambiguator: usize,
198+
},
165199
/// An aggregate value, either tuple/closure/struct/enum.
166200
/// This does not contain unions, as we cannot reason with the value.
167201
Aggregate(AggregateTy<'tcx>, VariantIdx, Vec<VnIndex>),
@@ -288,8 +322,24 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
288322
}
289323
}
290324

325+
fn insert_constant(&mut self, value: Const<'tcx>) -> Option<VnIndex> {
326+
let disambiguator = if value.is_deterministic() {
327+
// The constant is deterministic, no need to disambiguate.
328+
0
329+
} else {
330+
// Multiple mentions of this constant will yield different values,
331+
// so assign a different `disambiguator` to ensure they do not get the same `VnIndex`.
332+
let next_opaque = self.next_opaque.as_mut()?;
333+
let disambiguator = *next_opaque;
334+
*next_opaque += 1;
335+
disambiguator
336+
};
337+
Some(self.insert(Value::Constant { value, disambiguator }))
338+
}
339+
291340
fn insert_scalar(&mut self, scalar: Scalar, ty: Ty<'tcx>) -> VnIndex {
292-
self.insert(Value::Constant(Const::from_scalar(self.tcx, scalar, ty)))
341+
self.insert_constant(Const::from_scalar(self.tcx, scalar, ty))
342+
.expect("scalars are deterministic")
293343
}
294344

295345
#[instrument(level = "trace", skip(self), ret)]
@@ -300,7 +350,9 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
300350
// Do not bother evaluating repeat expressions. This would uselessly consume memory.
301351
Repeat(..) => return None,
302352

303-
Constant(ref constant) => self.ecx.eval_mir_constant(constant, None, None).ok()?,
353+
Constant { ref value, disambiguator: _ } => {
354+
self.ecx.eval_mir_constant(value, None, None).ok()?
355+
}
304356
Aggregate(kind, variant, ref fields) => {
305357
let fields = fields
306358
.iter()
@@ -657,7 +709,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
657709
match *operand {
658710
Operand::Constant(ref mut constant) => {
659711
let const_ = constant.const_.normalize(self.tcx, self.param_env);
660-
Some(self.insert(Value::Constant(const_)))
712+
self.insert_constant(const_)
661713
}
662714
Operand::Copy(ref mut place) | Operand::Move(ref mut place) => {
663715
let value = self.simplify_place_value(place, location)?;
@@ -691,7 +743,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
691743
Value::Repeat(op, amount)
692744
}
693745
Rvalue::NullaryOp(op, ty) => Value::NullaryOp(op, ty),
694-
Rvalue::Aggregate(..) => self.simplify_aggregate(rvalue, location)?,
746+
Rvalue::Aggregate(..) => return self.simplify_aggregate(rvalue, location),
695747
Rvalue::Ref(_, borrow_kind, ref mut place) => {
696748
self.simplify_place_projection(place, location);
697749
return self.new_pointer(*place, AddressKind::Ref(borrow_kind));
@@ -757,7 +809,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
757809
&mut self,
758810
rvalue: &mut Rvalue<'tcx>,
759811
location: Location,
760-
) -> Option<Value<'tcx>> {
812+
) -> Option<VnIndex> {
761813
let Rvalue::Aggregate(box ref kind, ref mut fields) = *rvalue else { bug!() };
762814

763815
let tcx = self.tcx;
@@ -774,8 +826,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
774826

775827
if is_zst {
776828
let ty = rvalue.ty(self.local_decls, tcx);
777-
let value = Value::Constant(Const::zero_sized(ty));
778-
return Some(value);
829+
return self.insert_constant(Const::zero_sized(ty));
779830
}
780831
}
781832

@@ -814,12 +865,11 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
814865
*rvalue = Rvalue::Repeat(Operand::Copy(local.into()), len);
815866
self.reused_locals.insert(local);
816867
}
817-
return Some(Value::Repeat(first, len));
868+
return Some(self.insert(Value::Repeat(first, len)));
818869
}
819870
}
820871

821-
let value = Value::Aggregate(ty, variant_index, fields);
822-
Some(value)
872+
Some(self.insert(Value::Aggregate(ty, variant_index, fields)))
823873
}
824874
}
825875

@@ -882,39 +932,12 @@ impl<'tcx> VnState<'_, 'tcx> {
882932
/// If `index` is a `Value::Constant`, return the `Constant` to be put in the MIR.
883933
fn try_as_constant(&mut self, index: VnIndex) -> Option<ConstOperand<'tcx>> {
884934
// This was already constant in MIR, do not change it.
885-
if let Value::Constant(const_) = *self.get(index) {
886-
// Some constants may contain pointers. We need to preserve the provenance of these
887-
// pointers, but not all constants guarantee this:
888-
// - valtrees purposefully do not;
889-
// - ConstValue::Slice does not either.
890-
let const_ok = match const_ {
891-
Const::Ty(c) => match c.kind() {
892-
ty::ConstKind::Value(valtree) => match valtree {
893-
// This is just an integer, keep it.
894-
ty::ValTree::Leaf(_) => true,
895-
ty::ValTree::Branch(_) => false,
896-
},
897-
ty::ConstKind::Param(..)
898-
| ty::ConstKind::Unevaluated(..)
899-
| ty::ConstKind::Expr(..) => true,
900-
// Should not appear in runtime MIR.
901-
ty::ConstKind::Infer(..)
902-
| ty::ConstKind::Bound(..)
903-
| ty::ConstKind::Placeholder(..)
904-
| ty::ConstKind::Error(..) => bug!(),
905-
},
906-
Const::Unevaluated(..) => true,
907-
// If the same slice appears twice in the MIR, we cannot guarantee that we will
908-
// give the same `AllocId` to the data.
909-
Const::Val(ConstValue::Slice { .. }, _) => false,
910-
Const::Val(
911-
ConstValue::ZeroSized | ConstValue::Scalar(_) | ConstValue::Indirect { .. },
912-
_,
913-
) => true,
914-
};
915-
if const_ok {
916-
return Some(ConstOperand { span: rustc_span::DUMMY_SP, user_ty: None, const_ });
917-
}
935+
if let Value::Constant { value, disambiguator: _ } = *self.get(index)
936+
// If the constant is not deterministic, adding an additional mention of it in MIR will
937+
// not give the same value as the former mention.
938+
&& value.is_deterministic()
939+
{
940+
return Some(ConstOperand { span: rustc_span::DUMMY_SP, user_ty: None, const_: value });
918941
}
919942

920943
let op = self.evaluated[index].as_ref()?;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
- // MIR for `duplicate_slice` before GVN
2+
+ // MIR for `duplicate_slice` after GVN
3+
4+
fn duplicate_slice() -> (bool, bool) {
5+
let mut _0: (bool, bool);
6+
let mut _1: u128;
7+
let mut _2: u128;
8+
let mut _3: u128;
9+
let mut _4: u128;
10+
let mut _5: &str;
11+
let mut _6: &str;
12+
let mut _7: (&str,);
13+
let mut _8: &str;
14+
let mut _9: bool;
15+
let mut _10: bool;
16+
17+
bb0: {
18+
_7 = (const "a",);
19+
_1 = (_7.0: &str) as u128 (Transmute);
20+
_5 = identity::<&str>((_7.0: &str)) -> [return: bb1, unwind unreachable];
21+
}
22+
23+
bb1: {
24+
_3 = _5 as u128 (Transmute);
25+
_8 = const "a";
26+
_2 = _8 as u128 (Transmute);
27+
_6 = identity::<&str>(_8) -> [return: bb2, unwind unreachable];
28+
}
29+
30+
bb2: {
31+
_4 = _6 as u128 (Transmute);
32+
_9 = Eq(_1, _2);
33+
_10 = Eq(_3, _4);
34+
_0 = (_9, _10);
35+
return;
36+
}
37+
}
38+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
- // MIR for `duplicate_slice` before GVN
2+
+ // MIR for `duplicate_slice` after GVN
3+
4+
fn duplicate_slice() -> (bool, bool) {
5+
let mut _0: (bool, bool);
6+
let mut _1: u128;
7+
let mut _2: u128;
8+
let mut _3: u128;
9+
let mut _4: u128;
10+
let mut _5: &str;
11+
let mut _6: &str;
12+
let mut _7: (&str,);
13+
let mut _8: &str;
14+
let mut _9: bool;
15+
let mut _10: bool;
16+
17+
bb0: {
18+
_7 = (const "a",);
19+
_1 = (_7.0: &str) as u128 (Transmute);
20+
_5 = identity::<&str>((_7.0: &str)) -> [return: bb1, unwind continue];
21+
}
22+
23+
bb1: {
24+
_3 = _5 as u128 (Transmute);
25+
_8 = const "a";
26+
_2 = _8 as u128 (Transmute);
27+
_6 = identity::<&str>(_8) -> [return: bb2, unwind continue];
28+
}
29+
30+
bb2: {
31+
_4 = _6 as u128 (Transmute);
32+
_9 = Eq(_1, _2);
33+
_10 = Eq(_3, _4);
34+
_0 = (_9, _10);
35+
return;
36+
}
37+
}
38+

tests/mir-opt/gvn.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
// skip-filecheck
22
// unit-test: GVN
33
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
4+
// only-64bit
45

56
#![feature(raw_ref_op)]
67
#![feature(rustc_attrs)]
8+
#![feature(custom_mir)]
9+
#![feature(core_intrinsics)]
710
#![allow(unconditional_panic)]
811

12+
use std::intrinsics::mir::*;
13+
use std::mem::transmute;
14+
915
struct S<T>(T);
1016

1117
fn subexpression_elimination(x: u64, y: u64, mut z: u64) {
@@ -225,11 +231,49 @@ fn slices() {
225231
let t = s; // This should be the same pointer, so cannot be a `Const::Slice`.
226232
opaque(t);
227233
assert_eq!(s.as_ptr(), t.as_ptr());
228-
let u = unsafe { std::mem::transmute::<&str, &[u8]>(s) };
234+
let u = unsafe { transmute::<&str, &[u8]>(s) };
229235
opaque(u);
230236
assert_eq!(s.as_ptr(), u.as_ptr());
231237
}
232238

239+
#[custom_mir(dialect = "analysis")]
240+
fn duplicate_slice() -> (bool, bool) {
241+
mir!(
242+
let au: u128;
243+
let bu: u128;
244+
let cu: u128;
245+
let du: u128;
246+
let c: &str;
247+
let d: &str;
248+
{
249+
let a = ("a",);
250+
Call(au = transmute::<_, u128>(a.0), bb1)
251+
}
252+
bb1 = {
253+
Call(c = identity(a.0), bb2)
254+
}
255+
bb2 = {
256+
Call(cu = transmute::<_, u128>(c), bb3)
257+
}
258+
bb3 = {
259+
let b = "a"; // This slice is different from `a.0`.
260+
Call(bu = transmute::<_, u128>(b), bb4) // Hence `bu` is not `au`.
261+
}
262+
bb4 = {
263+
Call(d = identity(b), bb5) // This returns a copy of `b`, which is not `a`.
264+
}
265+
bb5 = {
266+
Call(du = transmute::<_, u128>(d), bb6)
267+
}
268+
bb6 = {
269+
let direct = au == bu; // Must not fold to `true`...
270+
let indirect = cu == du; // ...as this will not.
271+
RET = (direct, indirect);
272+
Return()
273+
}
274+
)
275+
}
276+
233277
fn aggregates() {
234278
let a_array: S<[u8; 0]> = S([]);
235279
let b_array: S<[u16; 0]> = S([]); // This must not be merged with `a_array`.
@@ -253,12 +297,19 @@ fn main() {
253297
references(5);
254298
dereferences(&mut 5, &6, &S(7));
255299
slices();
300+
let (direct, indirect) = duplicate_slice();
301+
assert_eq!(direct, indirect);
256302
aggregates();
257303
}
258304

259305
#[inline(never)]
260306
fn opaque(_: impl Sized) {}
261307

308+
#[inline(never)]
309+
fn identity<T>(x: T) -> T {
310+
x
311+
}
312+
262313
// EMIT_MIR gvn.subexpression_elimination.GVN.diff
263314
// EMIT_MIR gvn.wrap_unwrap.GVN.diff
264315
// EMIT_MIR gvn.repeated_index.GVN.diff
@@ -270,4 +321,5 @@ fn opaque(_: impl Sized) {}
270321
// EMIT_MIR gvn.references.GVN.diff
271322
// EMIT_MIR gvn.dereferences.GVN.diff
272323
// EMIT_MIR gvn.slices.GVN.diff
324+
// EMIT_MIR gvn.duplicate_slice.GVN.diff
273325
// EMIT_MIR gvn.aggregates.GVN.diff

0 commit comments

Comments
 (0)