diff options
5 files changed, 276 insertions, 40 deletions
diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs index 403d658bccd..5ec9d13f7d2 100644 --- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs +++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs @@ -6,7 +6,7 @@ use rustc_const_eval::const_eval::CheckAlignment; use rustc_const_eval::interpret::{ImmTy, Immediate, InterpCx, OpTy, Projectable}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::DefKind; -use rustc_middle::mir::interpret::{ConstValue, Scalar}; +use rustc_middle::mir::interpret::{AllocId, ConstAllocation, ConstValue, InterpResult, Scalar}; use rustc_middle::mir::visit::{MutVisitor, NonMutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::layout::TyAndLayout; @@ -15,6 +15,7 @@ use rustc_mir_dataflow::value_analysis::{ Map, PlaceIndex, State, TrackElem, ValueAnalysis, ValueAnalysisWrapper, ValueOrPlace, }; use rustc_mir_dataflow::{lattice::FlatSet, Analysis, Results, ResultsVisitor}; +use rustc_span::def_id::DefId; use rustc_span::{Span, DUMMY_SP}; use rustc_target::abi::{Align, FieldIdx, VariantIdx}; @@ -78,7 +79,7 @@ struct ConstAnalysis<'a, 'tcx> { } impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { - type Value = FlatSet<ScalarInt>; + type Value = FlatSet<Scalar>; const NAME: &'static str = "ConstAnalysis"; @@ -182,7 +183,7 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { if let Some(overflow_target) = overflow_target { let overflow = match overflow { FlatSet::Top => FlatSet::Top, - FlatSet::Elem(overflow) => FlatSet::Elem(overflow.into()), + FlatSet::Elem(overflow) => FlatSet::Elem(Scalar::from_bool(overflow)), FlatSet::Bottom => FlatSet::Bottom, }; // We have flooded `target` earlier. @@ -204,7 +205,7 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { && let ty::Array(_, len) = operand_ty.ty.kind() && let Some(len) = ConstantKind::Ty(*len).eval(self.tcx, self.param_env).try_to_scalar_int() { - state.insert_value_idx(target_len, FlatSet::Elem(len), self.map()); + state.insert_value_idx(target_len, FlatSet::Elem(len.into()), self.map()); } } _ => self.super_assign(target, rvalue, state), @@ -222,7 +223,7 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { if let ty::Array(_, len) = place_ty.ty.kind() { ConstantKind::Ty(*len) .eval(self.tcx, self.param_env) - .try_to_scalar_int() + .try_to_scalar() .map_or(FlatSet::Top, FlatSet::Elem) } else if let [ProjectionElem::Deref] = place.projection[..] { state.get_len(place.local.into(), self.map()) @@ -281,7 +282,7 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { .bytes(), _ => return ValueOrPlace::Value(FlatSet::Top), }; - ScalarInt::try_from_target_usize(val, self.tcx).map_or(FlatSet::Top, FlatSet::Elem) + FlatSet::Elem(Scalar::from_target_usize(val, &self.tcx)) } Rvalue::Discriminant(place) => state.get_discr(place.as_ref(), self.map()), _ => return self.super_rvalue(rvalue, state), @@ -297,7 +298,7 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { constant .literal .eval(self.tcx, self.param_env) - .try_to_scalar_int() + .try_to_scalar() .map_or(FlatSet::Top, FlatSet::Elem) } @@ -339,14 +340,21 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { /// The caller must have flooded `place`. fn assign_operand( &self, - state: &mut State<FlatSet<ScalarInt>>, + state: &mut State<FlatSet<Scalar>>, place: PlaceIndex, operand: &Operand<'tcx>, ) { match operand { Operand::Copy(rhs) | Operand::Move(rhs) => { if let Some(rhs) = self.map.find(rhs.as_ref()) { - state.insert_place_idx(place, rhs, &self.map) + state.insert_place_idx(place, rhs, &self.map); + } else if rhs.projection.first() == Some(&PlaceElem::Deref) + && let FlatSet::Elem(pointer) = state.get(rhs.local.into(), &self.map) + && let rhs_ty = self.local_decls[rhs.local].ty + && let Ok(rhs_layout) = self.tcx.layout_of(self.param_env.and(rhs_ty)) + { + let op = ImmTy::from_scalar(pointer, rhs_layout).into(); + self.assign_constant(state, place, op, &rhs.projection); } } Operand::Constant(box constant) => { @@ -363,7 +371,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { #[instrument(level = "trace", skip(self, state))] fn assign_constant( &self, - state: &mut State<FlatSet<ScalarInt>>, + state: &mut State<FlatSet<Scalar>>, place: PlaceIndex, mut operand: OpTy<'tcx>, projection: &[PlaceElem<'tcx>], @@ -371,7 +379,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { for &(mut proj_elem) in projection { if let PlaceElem::Index(index) = proj_elem { if let FlatSet::Elem(index) = state.get(index.into(), &self.map) - && let Ok(offset) = index.try_to_target_usize(self.tcx) + && let Ok(offset) = index.to_target_usize(&self.tcx) && let Some(min_length) = offset.checked_add(1) { proj_elem = PlaceElem::ConstantIndex { offset, min_length, from_end: false }; @@ -406,7 +414,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { &mut |place, op| { if let Ok(imm) = self.ecx.read_immediate_raw(op) && let Some(imm) = imm.right() - && let Immediate::Scalar(Scalar::Int(scalar)) = *imm + && let Immediate::Scalar(scalar) = *imm { state.insert_value_idx(place, FlatSet::Elem(scalar), &self.map); } @@ -418,11 +426,11 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { fn binary_op( &self, - state: &mut State<FlatSet<ScalarInt>>, + state: &mut State<FlatSet<Scalar>>, op: BinOp, left: &Operand<'tcx>, right: &Operand<'tcx>, - ) -> (FlatSet<ScalarInt>, FlatSet<bool>) { + ) -> (FlatSet<Scalar>, FlatSet<bool>) { let left = self.eval_operand(left, state); let right = self.eval_operand(right, state); @@ -431,9 +439,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { // Both sides are known, do the actual computation. (FlatSet::Elem(left), FlatSet::Elem(right)) => { match self.ecx.overflowing_binary_op(op, &left, &right) { - Ok((Scalar::Int(val), overflow, _)) => { - (FlatSet::Elem(val), FlatSet::Elem(overflow)) - } + Ok((val, overflow, _)) => (FlatSet::Elem(val), FlatSet::Elem(overflow)), _ => (FlatSet::Top, FlatSet::Top), } } @@ -445,9 +451,6 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { } let arg_scalar = const_arg.to_scalar(); - let Ok(arg_scalar) = arg_scalar.try_to_int() else { - return (FlatSet::Top, FlatSet::Top); - }; let Ok(arg_value) = arg_scalar.to_bits(layout.size) else { return (FlatSet::Top, FlatSet::Top); }; @@ -473,7 +476,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { fn eval_operand( &self, op: &Operand<'tcx>, - state: &mut State<FlatSet<ScalarInt>>, + state: &mut State<FlatSet<Scalar>>, ) -> FlatSet<ImmTy<'tcx>> { let value = match self.handle_operand(op, state) { ValueOrPlace::Value(value) => value, @@ -492,24 +495,24 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { } } - fn eval_discriminant(&self, enum_ty: Ty<'tcx>, variant_index: VariantIdx) -> Option<ScalarInt> { + fn eval_discriminant(&self, enum_ty: Ty<'tcx>, variant_index: VariantIdx) -> Option<Scalar> { if !enum_ty.is_enum() { return None; } let discr = enum_ty.discriminant_for_variant(self.tcx, variant_index)?; let discr_layout = self.tcx.layout_of(self.param_env.and(discr.ty)).ok()?; - let discr_value = ScalarInt::try_from_uint(discr.val, discr_layout.size)?; + let discr_value = Scalar::try_from_uint(discr.val, discr_layout.size)?; Some(discr_value) } - fn wrap_immediate(&self, imm: Immediate) -> FlatSet<ScalarInt> { + fn wrap_immediate(&self, imm: Immediate) -> FlatSet<Scalar> { match imm { - Immediate::Scalar(Scalar::Int(scalar)) => FlatSet::Elem(scalar), + Immediate::Scalar(scalar) => FlatSet::Elem(scalar), _ => FlatSet::Top, } } - fn wrap_immty(&self, val: ImmTy<'tcx>) -> FlatSet<ScalarInt> { + fn wrap_immty(&self, val: ImmTy<'tcx>) -> FlatSet<Scalar> { self.wrap_immediate(*val) } } @@ -550,7 +553,7 @@ impl<'mir, 'tcx> ResultsVisitor<'mir, 'tcx, Results<'tcx, ValueAnalysisWrapper<ConstAnalysis<'_, 'tcx>>>> for CollectAndPatch<'tcx, '_> { - type FlowState = State<FlatSet<ScalarInt>>; + type FlowState = State<FlatSet<Scalar>>; fn visit_statement_before_primary_effect( &mut self, @@ -580,14 +583,10 @@ impl<'mir, 'tcx> // Don't overwrite the assignment if it already uses a constant (to keep the span). } StatementKind::Assign(box (place, _)) => { - match state.get(place.as_ref(), &results.analysis.0.map) { - FlatSet::Top => (), - FlatSet::Elem(value) => { - self.assignments.insert(location, value); - } - FlatSet::Bottom => { - // This assignment is either unreachable, or an uninitialized value is assigned. - } + if let FlatSet::Elem(Scalar::Int(value)) = + state.get(place.as_ref(), &results.analysis.0.map) + { + self.assignments.insert(location, value); } } _ => (), @@ -657,7 +656,7 @@ impl<'tcx> MutVisitor<'tcx> for CollectAndPatch<'tcx, '_> { } struct OperandCollector<'tcx, 'map, 'locals, 'a> { - state: &'a State<FlatSet<ScalarInt>>, + state: &'a State<FlatSet<Scalar>>, visitor: &'a mut CollectAndPatch<'tcx, 'locals>, map: &'map Map, } @@ -665,7 +664,7 @@ struct OperandCollector<'tcx, 'map, 'locals, 'a> { impl<'tcx> Visitor<'tcx> for OperandCollector<'tcx, '_, '_, '_> { fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { if let Some(place) = operand.place() { - if let FlatSet::Elem(value) = self.state.get(place.as_ref(), self.map) { + if let FlatSet::Elem(Scalar::Int(value)) = self.state.get(place.as_ref(), self.map) { self.visitor.before_effect.insert((location, place), value); } else if !place.projection.is_empty() { // Try to propagate into `Index` projections. @@ -676,7 +675,7 @@ impl<'tcx> Visitor<'tcx> for OperandCollector<'tcx, '_, '_, '_> { fn visit_local(&mut self, local: Local, ctxt: PlaceContext, location: Location) { if let PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy | NonMutatingUseContext::Move) = ctxt - && let FlatSet::Elem(value) = self.state.get(local.into(), self.map) + && let FlatSet::Elem(Scalar::Int(value)) = self.state.get(local.into(), self.map) { self.visitor.before_effect.insert((location, local.into()), value); } @@ -685,6 +684,34 @@ impl<'tcx> Visitor<'tcx> for OperandCollector<'tcx, '_, '_, '_> { struct DummyMachine; +/// Macro for machine-specific `InterpError` without allocation. +/// (These will never be shown to the user, but they help diagnose ICEs.) +macro_rules! throw_machine_stop_str { + ($($tt:tt)*) => {{ + // We make a new local type for it. The type itself does not carry any information, + // but its vtable (for the `MachineStopType` trait) does. + #[derive(Debug)] + struct Zst; + // Printing this type shows the desired string. + impl std::fmt::Display for Zst { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, $($tt)*) + } + } + impl rustc_middle::mir::interpret::MachineStopType for Zst { + fn diagnostic_message(&self) -> rustc_errors::DiagnosticMessage { + self.to_string().into() + } + + fn add_args( + self: Box<Self>, + _: &mut dyn FnMut(std::borrow::Cow<'static, str>, rustc_errors::DiagnosticArgValue<'static>), + ) {} + } + throw_machine_stop!(Zst) + }}; +} + impl<'mir, 'tcx: 'mir> rustc_const_eval::interpret::Machine<'mir, 'tcx> for DummyMachine { rustc_const_eval::interpret::compile_time_machine!(<'mir, 'tcx>); type MemoryKind = !; @@ -714,6 +741,27 @@ impl<'mir, 'tcx: 'mir> rustc_const_eval::interpret::Machine<'mir, 'tcx> for Dumm unimplemented!() } + fn before_access_global( + _tcx: TyCtxt<'tcx>, + _machine: &Self, + _alloc_id: AllocId, + alloc: ConstAllocation<'tcx>, + _static_def_id: Option<DefId>, + is_write: bool, + ) -> InterpResult<'tcx> { + if is_write { + throw_machine_stop_str!("can't write to global"); + } + + // If the static allocation is mutable, then we can't const prop it as its content + // might be different at runtime. + if alloc.inner().mutability.is_mut() { + throw_machine_stop_str!("can't access mutable globals in ConstProp"); + } + + Ok(()) + } + fn find_mir_or_eval_fn( _ecx: &mut InterpCx<'mir, 'tcx, Self>, _instance: ty::Instance<'tcx>, diff --git a/tests/mir-opt/dataflow-const-prop/enum.rs b/tests/mir-opt/dataflow-const-prop/enum.rs index a70c0abee5b..6b745707592 100644 --- a/tests/mir-opt/dataflow-const-prop/enum.rs +++ b/tests/mir-opt/dataflow-const-prop/enum.rs @@ -4,6 +4,7 @@ use std::intrinsics::mir::*; +#[derive(Copy, Clone)] enum E { V1(i32), V2(i32) @@ -22,6 +23,17 @@ fn constant() { let x = match e { E::V1(x) => x, E::V2(x) => x }; } +// EMIT_MIR enum.statics.DataflowConstProp.diff +fn statics() { + static C: E = E::V1(0); + let e = C; + let x = match e { E::V1(x) => x, E::V2(x) => x }; + + static RC: &E = &E::V2(4); + let e = RC; + let x = match e { E::V1(x) => x, E::V2(x) => x }; +} + #[rustc_layout_scalar_valid_range_start(1)] #[rustc_nonnull_optimization_guaranteed] struct NonZeroUsize(usize); @@ -71,6 +83,7 @@ fn multiple(x: bool, i: u8) { fn main() { simple(); constant(); + statics(); mutate_discriminant(); multiple(false, 5); } diff --git a/tests/mir-opt/dataflow-const-prop/enum.statics.DataflowConstProp.diff b/tests/mir-opt/dataflow-const-prop/enum.statics.DataflowConstProp.diff new file mode 100644 index 00000000000..63799b3bac3 --- /dev/null +++ b/tests/mir-opt/dataflow-const-prop/enum.statics.DataflowConstProp.diff @@ -0,0 +1,126 @@ +- // MIR for `statics` before DataflowConstProp ++ // MIR for `statics` after DataflowConstProp + + fn statics() -> () { + let mut _0: (); + let _1: E; + let mut _2: &E; + let mut _4: isize; + let mut _8: &&E; + let mut _10: isize; + scope 1 { + debug e => _1; + let _3: i32; + let _5: i32; + let _6: i32; + scope 2 { + debug x => _3; + let _7: &E; + scope 5 { + debug e => _7; + let _9: &i32; + let _11: &i32; + let _12: &i32; + scope 6 { + debug x => _9; + } + scope 7 { + debug x => _11; + } + scope 8 { + debug x => _12; + } + } + } + scope 3 { + debug x => _5; + } + scope 4 { + debug x => _6; + } + } + + bb0: { + StorageLive(_1); + StorageLive(_2); + _2 = const {alloc1: &E}; + _1 = (*_2); + StorageDead(_2); + StorageLive(_3); +- _4 = discriminant(_1); +- switchInt(move _4) -> [0: bb3, 1: bb1, otherwise: bb2]; ++ _4 = const 0_isize; ++ switchInt(const 0_isize) -> [0: bb3, 1: bb1, otherwise: bb2]; + } + + bb1: { + StorageLive(_6); + _6 = ((_1 as V2).0: i32); + _3 = _6; + StorageDead(_6); + goto -> bb4; + } + + bb2: { + unreachable; + } + + bb3: { + StorageLive(_5); +- _5 = ((_1 as V1).0: i32); +- _3 = _5; ++ _5 = const 0_i32; ++ _3 = const 0_i32; + StorageDead(_5); + goto -> bb4; + } + + bb4: { + StorageLive(_7); + StorageLive(_8); + _8 = const {alloc2: &&E}; + _7 = (*_8); + StorageDead(_8); + StorageLive(_9); + _10 = discriminant((*_7)); + switchInt(move _10) -> [0: bb6, 1: bb5, otherwise: bb2]; + } + + bb5: { + StorageLive(_12); + _12 = &(((*_7) as V2).0: i32); + _9 = &(*_12); + StorageDead(_12); + goto -> bb7; + } + + bb6: { + StorageLive(_11); + _11 = &(((*_7) as V1).0: i32); + _9 = _11; + StorageDead(_11); + goto -> bb7; + } + + bb7: { + _0 = const (); + StorageDead(_9); + StorageDead(_7); + StorageDead(_3); + StorageDead(_1); + return; + } + } + + alloc2 (static: RC, size: 8, align: 8) { + ╾───────alloc14───────╼ │ ╾──────╼ + } + + alloc14 (size: 8, align: 4) { + 01 00 00 00 04 00 00 00 │ ........ + } + + alloc1 (static: statics::C, size: 8, align: 4) { + 00 00 00 00 00 00 00 00 │ ........ + } + diff --git a/tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.diff b/tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.diff index d65221158e4..89e37cc04dc 100644 --- a/tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.diff +++ b/tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.diff @@ -8,6 +8,12 @@ let mut _5: i32; let mut _6: i32; let mut _11: BigStruct; + let mut _16: &&BigStruct; + let mut _17: &BigStruct; + let mut _18: &BigStruct; + let mut _19: &BigStruct; + let mut _20: &BigStruct; + let mut _21: &BigStruct; scope 1 { debug s => _1; let _2: i32; @@ -25,6 +31,16 @@ debug b => _8; debug c => _9; debug d => _10; + let _12: S; + let _13: u8; + let _14: f32; + let _15: S; + scope 5 { + debug a => _12; + debug b => _13; + debug c => _14; + debug d => _15; + } } } } @@ -55,7 +71,7 @@ StorageLive(_11); _11 = const _; StorageLive(_7); -- _7 = move (_11.0: S); +- _7 = (_11.0: S); + _7 = const S(1_i32); StorageLive(_8); - _8 = (_11.1: u8); @@ -64,10 +80,30 @@ - _9 = (_11.2: f32); + _9 = const 7f32; StorageLive(_10); -- _10 = move (_11.3: S); +- _10 = (_11.3: S); + _10 = const S(13_i32); StorageDead(_11); + StorageLive(_16); + _16 = const {alloc1: &&BigStruct}; + _17 = deref_copy (*_16); + StorageLive(_12); + _18 = deref_copy (*_16); + _12 = ((*_18).0: S); + StorageLive(_13); + _19 = deref_copy (*_16); + _13 = ((*_19).1: u8); + StorageLive(_14); + _20 = deref_copy (*_16); + _14 = ((*_20).2: f32); + StorageLive(_15); + _21 = deref_copy (*_16); + _15 = ((*_21).3: S); + StorageDead(_16); _0 = const (); + StorageDead(_15); + StorageDead(_14); + StorageDead(_13); + StorageDead(_12); StorageDead(_10); StorageDead(_9); StorageDead(_8); @@ -79,3 +115,11 @@ } } + alloc1 (static: STAT, size: 8, align: 8) { + ╾───────alloc15───────╼ │ ╾──────╼ + } + + alloc15 (size: 16, align: 4) { + 01 00 00 00 00 00 e0 40 0d 00 00 00 05 __ __ __ │ .......@.....░░░ + } + diff --git a/tests/mir-opt/dataflow-const-prop/struct.rs b/tests/mir-opt/dataflow-const-prop/struct.rs index d2cd697db25..c171ed74c54 100644 --- a/tests/mir-opt/dataflow-const-prop/struct.rs +++ b/tests/mir-opt/dataflow-const-prop/struct.rs @@ -1,7 +1,9 @@ // unit-test: DataflowConstProp +#[derive(Copy, Clone)] struct S(i32); +#[derive(Copy, Clone)] struct BigStruct(S, u8, f32, S); // EMIT_MIR struct.main.DataflowConstProp.diff @@ -13,4 +15,7 @@ fn main() { const VAL: BigStruct = BigStruct(S(1), 5, 7., S(13)); let BigStruct(a, b, c, d) = VAL; + + static STAT: &BigStruct = &BigStruct(S(1), 5, 7., S(13)); + let BigStruct(a, b, c, d) = *STAT; } |
