Skip to content

Commit 83c9732

Browse files
committed
Auto merge of #116270 - cjgillot:gvn-aggregate, r=oli-obk,RalfJung
See through aggregates in GVN This PR is extracted from #111344 The first 2 commit are cleanups to avoid repeated work. I propose to stop removing useless assignments as part of this pass, and let a later `SimplifyLocals` do it. This makes tests easier to read (among others). The next 3 commits add a constant folding mechanism to the GVN pass, presented in #116012. ~This pass is designed to only use global allocations, to avoid any risk of accidental modification of the stored state.~ The following commits implement opportunistic simplifications, in particular: - projections of aggregates: `MyStruct { x: a }.x` gets replaced by `a`, works with enums too; - projections of arrays: `[a, b][0]` becomes `a`; - projections of repeat expressions: `[a; N][x]` becomes `a`; - transform arrays of equal operands into a repeat rvalue. Fixes rust-lang/miri#3090 r? `@oli-obk`
2 parents 160fd3c + 24be433 commit 83c9732

File tree

51 files changed

+4299
-2588
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+4299
-2588
lines changed

Diff for: compiler/rustc_const_eval/src/interpret/discriminant.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//! Functions for reading and writing discriminants of multi-variant layouts (enums and coroutines).
22
3-
use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt, TyAndLayout};
4-
use rustc_middle::{mir, ty};
3+
use rustc_middle::mir;
4+
use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt};
5+
use rustc_middle::ty::{self, Ty};
56
use rustc_target::abi::{self, TagEncoding};
67
use rustc_target::abi::{VariantIdx, Variants};
78

@@ -244,11 +245,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
244245

245246
pub fn discriminant_for_variant(
246247
&self,
247-
layout: TyAndLayout<'tcx>,
248+
ty: Ty<'tcx>,
248249
variant: VariantIdx,
249250
) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> {
250-
let discr_layout = self.layout_of(layout.ty.discriminant_ty(*self.tcx))?;
251-
let discr_value = match layout.ty.discriminant_for_variant(*self.tcx, variant) {
251+
let discr_layout = self.layout_of(ty.discriminant_ty(*self.tcx))?;
252+
let discr_value = match ty.discriminant_for_variant(*self.tcx, variant) {
252253
Some(discr) => {
253254
// This type actually has discriminants.
254255
assert_eq!(discr.ty, discr_layout.ty);

Diff for: compiler/rustc_const_eval/src/interpret/intern.rs

+36
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,42 @@ pub fn intern_const_alloc_recursive<
450450
Ok(())
451451
}
452452

453+
/// Intern `ret`. This function assumes that `ret` references no other allocation.
454+
#[instrument(level = "debug", skip(ecx))]
455+
pub fn intern_const_alloc_for_constprop<
456+
'mir,
457+
'tcx: 'mir,
458+
T,
459+
M: CompileTimeMachine<'mir, 'tcx, T>,
460+
>(
461+
ecx: &mut InterpCx<'mir, 'tcx, M>,
462+
alloc_id: AllocId,
463+
) -> InterpResult<'tcx, ()> {
464+
// Move allocation to `tcx`.
465+
let Some((_, mut alloc)) = ecx.memory.alloc_map.remove(&alloc_id) else {
466+
// Pointer not found in local memory map. It is either a pointer to the global
467+
// map, or dangling.
468+
if ecx.tcx.try_get_global_alloc(alloc_id).is_none() {
469+
throw_ub!(DeadLocal)
470+
}
471+
// The constant is already in global memory. Do nothing.
472+
return Ok(());
473+
};
474+
475+
alloc.mutability = Mutability::Not;
476+
477+
// We are not doing recursive interning, so we don't currently support provenance.
478+
// (If this assertion ever triggers, we should just implement a
479+
// proper recursive interning loop.)
480+
assert!(alloc.provenance().ptrs().is_empty());
481+
482+
// Link the alloc id to the actual allocation
483+
let alloc = ecx.tcx.mk_const_alloc(alloc);
484+
ecx.tcx.set_alloc_id_memory(alloc_id, alloc);
485+
486+
Ok(())
487+
}
488+
453489
impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !>>
454490
InterpCx<'mir, 'tcx, M>
455491
{

Diff for: compiler/rustc_const_eval/src/interpret/intrinsics.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
218218
sym::discriminant_value => {
219219
let place = self.deref_pointer(&args[0])?;
220220
let variant = self.read_discriminant(&place)?;
221-
let discr = self.discriminant_for_variant(place.layout, variant)?;
221+
let discr = self.discriminant_for_variant(place.layout.ty, variant)?;
222222
self.write_immediate(*discr, dest)?;
223223
}
224224
sym::exact_div => {

Diff for: compiler/rustc_const_eval/src/interpret/memory.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1011,7 +1011,7 @@ impl<'tcx, 'a, Prov: Provenance, Extra, Bytes: AllocBytes> AllocRef<'a, 'tcx, Pr
10111011
}
10121012

10131013
/// Returns whether the allocation has provenance anywhere in the range of the `AllocRef`.
1014-
pub(crate) fn has_provenance(&self) -> bool {
1014+
pub fn has_provenance(&self) -> bool {
10151015
!self.alloc.provenance().range_empty(self.range, &self.tcx)
10161016
}
10171017
}

Diff for: compiler/rustc_const_eval/src/interpret/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ mod visitor;
2121
pub use rustc_middle::mir::interpret::*; // have all the `interpret` symbols in one place: here
2222

2323
pub use self::eval_context::{Frame, FrameInfo, InterpCx, StackPopCleanup};
24-
pub use self::intern::{intern_const_alloc_recursive, InternKind};
24+
pub use self::intern::{
25+
intern_const_alloc_for_constprop, intern_const_alloc_recursive, InternKind,
26+
};
2527
pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, StackPopJump};
2628
pub use self::memory::{AllocKind, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind};
2729
pub use self::operand::{ImmTy, Immediate, OpTy, Readable};

Diff for: compiler/rustc_const_eval/src/interpret/operand.rs

+10
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,16 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> {
169169
ImmTy { imm: val.into(), layout }
170170
}
171171

172+
#[inline]
173+
pub fn from_scalar_pair(a: Scalar<Prov>, b: Scalar<Prov>, layout: TyAndLayout<'tcx>) -> Self {
174+
debug_assert!(
175+
matches!(layout.abi, Abi::ScalarPair(..)),
176+
"`ImmTy::from_scalar_pair` on non-scalar-pair layout"
177+
);
178+
let imm = Immediate::ScalarPair(a, b);
179+
ImmTy { imm, layout }
180+
}
181+
172182
#[inline(always)]
173183
pub fn from_immediate(imm: Immediate<Prov>, layout: TyAndLayout<'tcx>) -> Self {
174184
debug_assert!(

Diff for: compiler/rustc_const_eval/src/interpret/step.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
297297
Discriminant(place) => {
298298
let op = self.eval_place_to_op(place, None)?;
299299
let variant = self.read_discriminant(&op)?;
300-
let discr = self.discriminant_for_variant(op.layout, variant)?;
300+
let discr = self.discriminant_for_variant(op.layout.ty, variant)?;
301301
self.write_immediate(*discr, &dest)?;
302302
}
303303
}

Diff for: compiler/rustc_middle/src/mir/consts.rs

+50
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,24 @@ impl<'tcx> ConstValue<'tcx> {
172172
let end = end.try_into().unwrap();
173173
Some(data.inner().inspect_with_uninit_and_ptr_outside_interpreter(start..end))
174174
}
175+
176+
/// Check if a constant may contain provenance information. This is used by MIR opts.
177+
/// Can return `true` even if there is no provenance.
178+
pub fn may_have_provenance(&self, tcx: TyCtxt<'tcx>, size: Size) -> bool {
179+
match *self {
180+
ConstValue::ZeroSized | ConstValue::Scalar(Scalar::Int(_)) => return false,
181+
ConstValue::Scalar(Scalar::Ptr(..)) => return true,
182+
// It's hard to find out the part of the allocation we point to;
183+
// just conservatively check everything.
184+
ConstValue::Slice { data, meta: _ } => !data.inner().provenance().ptrs().is_empty(),
185+
ConstValue::Indirect { alloc_id, offset } => !tcx
186+
.global_alloc(alloc_id)
187+
.unwrap_memory()
188+
.inner()
189+
.provenance()
190+
.range_empty(super::AllocRange::from(offset..offset + size), &tcx),
191+
}
192+
}
175193
}
176194

177195
///////////////////////////////////////////////////////////////////////////
@@ -485,6 +503,38 @@ impl<'tcx> Const<'tcx> {
485503
_ => Self::Ty(c),
486504
}
487505
}
506+
507+
/// Return true if any evaluation of this constant always returns the same value,
508+
/// taking into account even pointer identity tests.
509+
pub fn is_deterministic(&self) -> bool {
510+
// Some constants may generate fresh allocations for pointers they contain,
511+
// so using the same constant twice can yield two different results:
512+
// - valtrees purposefully generate new allocations
513+
// - ConstValue::Slice also generate new allocations
514+
match self {
515+
Const::Ty(c) => match c.kind() {
516+
ty::ConstKind::Param(..) => true,
517+
// A valtree may be a reference. Valtree references correspond to a
518+
// different allocation each time they are evaluated. Valtrees for primitive
519+
// types are fine though.
520+
ty::ConstKind::Value(_) => c.ty().is_primitive(),
521+
ty::ConstKind::Unevaluated(..) | ty::ConstKind::Expr(..) => false,
522+
// Should not appear in runtime MIR.
523+
ty::ConstKind::Infer(..)
524+
| ty::ConstKind::Bound(..)
525+
| ty::ConstKind::Placeholder(..)
526+
| ty::ConstKind::Error(..) => bug!(),
527+
},
528+
Const::Unevaluated(..) => false,
529+
// If the same slice appears twice in the MIR, we cannot guarantee that we will
530+
// give the same `AllocId` to the data.
531+
Const::Val(ConstValue::Slice { .. }, _) => false,
532+
Const::Val(
533+
ConstValue::ZeroSized | ConstValue::Scalar(_) | ConstValue::Indirect { .. },
534+
_,
535+
) => true,
536+
}
537+
}
488538
}
489539

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

Diff for: compiler/rustc_mir_transform/src/dataflow_const_prop.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,8 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
406406
TrackElem::Variant(idx) => self.ecx.project_downcast(op, idx).ok(),
407407
TrackElem::Discriminant => {
408408
let variant = self.ecx.read_discriminant(op).ok()?;
409-
let discr_value = self.ecx.discriminant_for_variant(op.layout, variant).ok()?;
409+
let discr_value =
410+
self.ecx.discriminant_for_variant(op.layout.ty, variant).ok()?;
410411
Some(discr_value.into())
411412
}
412413
TrackElem::DerefLen => {
@@ -507,7 +508,8 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
507508
return None;
508509
}
509510
let enum_ty_layout = self.tcx.layout_of(self.param_env.and(enum_ty)).ok()?;
510-
let discr_value = self.ecx.discriminant_for_variant(enum_ty_layout, variant_index).ok()?;
511+
let discr_value =
512+
self.ecx.discriminant_for_variant(enum_ty_layout.ty, variant_index).ok()?;
511513
Some(discr_value.to_scalar())
512514
}
513515

@@ -854,7 +856,7 @@ impl<'tcx> Visitor<'tcx> for OperandCollector<'tcx, '_, '_, '_> {
854856
}
855857
}
856858

857-
struct DummyMachine;
859+
pub(crate) struct DummyMachine;
858860

859861
impl<'mir, 'tcx: 'mir> rustc_const_eval::interpret::Machine<'mir, 'tcx> for DummyMachine {
860862
rustc_const_eval::interpret::compile_time_machine!(<'mir, 'tcx>);

0 commit comments

Comments
 (0)