diff --git a/compiler/rustc_graphviz/src/lib.rs b/compiler/rustc_graphviz/src/lib.rs index 3c1bb553266..401d3f6689c 100644 --- a/compiler/rustc_graphviz/src/lib.rs +++ b/compiler/rustc_graphviz/src/lib.rs @@ -471,7 +471,11 @@ pub trait Labeller<'a> { /// Escape tags in such a way that it is suitable for inclusion in a /// Graphviz HTML label. pub fn escape_html(s: &str) -> String { - s.replace('&', "&").replace('\"', """).replace('<', "<").replace('>', ">") + s.replace('&', "&") + .replace('\"', """) + .replace('<', "<") + .replace('>', ">") + .replace('\n', "
") } impl<'a> LabelText<'a> { diff --git a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs index 579fe68a149..c9d5601f207 100644 --- a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs +++ b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs @@ -475,7 +475,10 @@ where r#"{state}"#, colspan = this.style.num_state_columns(), fmt = fmt, - state = format!("{:?}", DebugWithAdapter { this: state, ctxt: analysis }), + state = dot::escape_html(&format!( + "{:?}", + DebugWithAdapter { this: state, ctxt: analysis } + )), ) }) } diff --git a/compiler/rustc_mir_dataflow/src/lib.rs b/compiler/rustc_mir_dataflow/src/lib.rs index b471d04fd60..7f40cfca32f 100644 --- a/compiler/rustc_mir_dataflow/src/lib.rs +++ b/compiler/rustc_mir_dataflow/src/lib.rs @@ -41,6 +41,7 @@ pub mod move_paths; pub mod rustc_peek; pub mod storage; pub mod un_derefer; +pub mod value_analysis; pub(crate) mod indexes { pub(crate) use super::move_paths::MovePathIndex; diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs new file mode 100644 index 00000000000..1dcea430a0f --- /dev/null +++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs @@ -0,0 +1,677 @@ +//! This module provides a framework on top of the normal MIR dataflow framework to simplify the +//! implementation of analyses that track the values stored in places of interest. +//! +//! The default methods of [`ValueAnalysis`] (prefixed with `super_` instead of `handle_`) +//! provide some behavior that should be valid for all abstract domains that are based only on the +//! value stored in a certain place. On top of these default rules, an implementation should +//! override some of the `handle_` methods. For an example, see `ConstAnalysis`. +//! +//! An implementation must also provide a [`Map`]. Before the anaylsis begins, all places that +//! should be tracked during the analysis must be registered. The set of tracked places cannot be +//! changed during the analysis. + +use std::fmt::{Debug, Formatter}; + +use rustc_data_structures::fx::FxHashMap; +use rustc_index::vec::IndexVec; +use rustc_middle::mir::*; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_target::abi::VariantIdx; + +use crate::{ + fmt::DebugWithContext, lattice::FlatSet, Analysis, AnalysisDomain, CallReturnPlaces, + JoinSemiLattice, SwitchIntEdgeEffects, +}; + +pub trait ValueAnalysis<'tcx> { + /// For each place of interest, the analysis tracks a value of the given type. + type Value: Clone + JoinSemiLattice + HasBottom + HasTop; + + const NAME: &'static str; + + fn map(&self) -> ⤅ + + fn handle_statement(&self, statement: &Statement<'tcx>, state: &mut State) { + self.super_statement(statement, state) + } + + fn super_statement(&self, statement: &Statement<'tcx>, state: &mut State) { + match &statement.kind { + StatementKind::Assign(box (place, rvalue)) => { + self.handle_assign(*place, rvalue, state); + } + StatementKind::SetDiscriminant { .. } => { + // Could tread this as writing a constant to a pseudo-place. + } + StatementKind::CopyNonOverlapping(..) => { + // FIXME: What to do here? + } + StatementKind::StorageLive(..) + | StatementKind::StorageDead(..) + | StatementKind::Deinit(_) => { + // Could perhaps use these. + } + StatementKind::Nop + | StatementKind::Retag(..) + | StatementKind::FakeRead(..) + | StatementKind::Coverage(..) + | StatementKind::AscribeUserType(..) => (), + } + } + + fn handle_assign( + &self, + target: Place<'tcx>, + rvalue: &Rvalue<'tcx>, + state: &mut State, + ) { + self.super_assign(target, rvalue, state) + } + + fn super_assign( + &self, + target: Place<'tcx>, + rvalue: &Rvalue<'tcx>, + state: &mut State, + ) { + match rvalue { + Rvalue::Ref(_, BorrowKind::Shared, place) => { + let target_deref = self + .map() + .find(target.as_ref()) + .and_then(|target| self.map().apply_elem(target, ProjElem::Deref)); + let place = self.map().find(place.as_ref()); + match (target_deref, place) { + (Some(target_deref), Some(place)) => { + state.assign_idx(target_deref, ValueOrPlace::Place(place), self.map()) + } + _ => (), + } + } + Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => { + state.flood(place.as_ref(), self.map(), Self::Value::top()); + } + _ => { + let result = self.handle_rvalue(rvalue, state); + state.assign(target.as_ref(), result, self.map()); + } + } + } + + fn handle_rvalue( + &self, + rvalue: &Rvalue<'tcx>, + state: &mut State, + ) -> ValueOrPlace { + self.super_rvalue(rvalue, state) + } + + fn super_rvalue( + &self, + rvalue: &Rvalue<'tcx>, + state: &mut State, + ) -> ValueOrPlace { + match rvalue { + Rvalue::Use(operand) => self.handle_operand(operand, state), + Rvalue::CopyForDeref(place) => self.handle_operand(&Operand::Copy(*place), state), + Rvalue::Ref(..) | Rvalue::AddressOf(..) => { + bug!("this rvalue must be handled by handle_assign() or super_assign()") + } + _ => { + // FIXME: Check that other Rvalues really have no side-effect. + ValueOrPlace::Unknown + } + } + } + + fn handle_operand( + &self, + operand: &Operand<'tcx>, + state: &mut State, + ) -> ValueOrPlace { + self.super_operand(operand, state) + } + + fn super_operand( + &self, + operand: &Operand<'tcx>, + state: &mut State, + ) -> ValueOrPlace { + match operand { + Operand::Constant(box constant) => { + ValueOrPlace::Value(self.handle_constant(constant, state)) + } + Operand::Copy(place) | Operand::Move(place) => { + // Do want want to handle moves different? Could flood place with bottom. + self.map() + .find(place.as_ref()) + .map(ValueOrPlace::Place) + .unwrap_or(ValueOrPlace::Unknown) + } + } + } + + fn handle_constant( + &self, + constant: &Constant<'tcx>, + state: &mut State, + ) -> Self::Value { + self.super_constant(constant, state) + } + + fn super_constant( + &self, + _constant: &Constant<'tcx>, + _state: &mut State, + ) -> Self::Value { + Self::Value::top() + } + + fn handle_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State) { + self.super_terminator(terminator, state) + } + + fn super_terminator(&self, _terminator: &Terminator<'tcx>, _state: &mut State) {} + + fn handle_call_return( + &self, + return_places: CallReturnPlaces<'_, 'tcx>, + state: &mut State, + ) { + self.super_call_return(return_places, state) + } + + fn super_call_return( + &self, + return_places: CallReturnPlaces<'_, 'tcx>, + state: &mut State, + ) { + return_places.for_each(|place| { + state.flood(place.as_ref(), self.map(), Self::Value::top()); + }) + } + + fn handle_switch_int( + &self, + discr: &Operand<'tcx>, + apply_edge_effects: &mut impl SwitchIntEdgeEffects>, + ) { + self.super_switch_int(discr, apply_edge_effects) + } + + fn super_switch_int( + &self, + _discr: &Operand<'tcx>, + _apply_edge_effects: &mut impl SwitchIntEdgeEffects>, + ) { + } + + fn wrap(self) -> ValueAnalysisWrapper + where + Self: Sized, + { + ValueAnalysisWrapper(self) + } +} + +pub struct ValueAnalysisWrapper(pub T); + +impl<'tcx, T: ValueAnalysis<'tcx>> AnalysisDomain<'tcx> for ValueAnalysisWrapper { + type Domain = State; + + type Direction = crate::Forward; + + const NAME: &'static str = T::NAME; + + fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain { + State(IndexVec::from_elem_n(T::Value::bottom(), self.0.map().value_count)) + } + + fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { + for arg in body.args_iter() { + state.flood(PlaceRef { local: arg, projection: &[] }, self.0.map(), T::Value::top()); + } + } +} + +impl<'tcx, T> Analysis<'tcx> for ValueAnalysisWrapper +where + T: ValueAnalysis<'tcx>, +{ + fn apply_statement_effect( + &self, + state: &mut Self::Domain, + statement: &Statement<'tcx>, + _location: Location, + ) { + self.0.handle_statement(statement, state); + } + + fn apply_terminator_effect( + &self, + state: &mut Self::Domain, + terminator: &Terminator<'tcx>, + _location: Location, + ) { + self.0.handle_terminator(terminator, state); + } + + fn apply_call_return_effect( + &self, + state: &mut Self::Domain, + _block: BasicBlock, + return_places: crate::CallReturnPlaces<'_, 'tcx>, + ) { + self.0.handle_call_return(return_places, state) + } + + fn apply_switch_int_edge_effects( + &self, + _block: BasicBlock, + discr: &Operand<'tcx>, + apply_edge_effects: &mut impl SwitchIntEdgeEffects, + ) { + self.0.handle_switch_int(discr, apply_edge_effects) + } +} + +rustc_index::newtype_index!( + pub struct PlaceIndex {} +); + +rustc_index::newtype_index!( + struct ValueIndex {} +); + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct State(IndexVec); + +impl State { + pub fn flood_all(&mut self, value: V) { + self.0.raw.fill(value); + } + + pub fn flood(&mut self, place: PlaceRef<'_>, map: &Map, value: V) { + if let Some(root) = map.find(place) { + self.flood_idx(root, map, value); + } + } + + pub fn flood_idx(&mut self, place: PlaceIndex, map: &Map, value: V) { + map.preorder_invoke(place, &mut |place| { + if let Some(vi) = map.places[place].value_index { + self.0[vi] = value.clone(); + } + }); + } + + pub fn assign_place_idx(&mut self, target: PlaceIndex, source: PlaceIndex, map: &Map) { + if let Some(target_value) = map.places[target].value_index { + if let Some(source_value) = map.places[source].value_index { + self.0[target_value] = self.0[source_value].clone(); + } else { + self.0[target_value] = V::top(); + } + } + for target_child in map.children(target) { + // Try to find corresponding child in source. + let projection = map.places[target_child].proj_elem.unwrap(); + if let Some(source_child) = map.projections.get(&(source, projection)) { + self.assign_place_idx(target_child, *source_child, map); + } else { + self.flood_idx(target_child, map, V::top()); + } + } + } + + pub fn assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlace, map: &Map) { + if let Some(target) = map.find(target) { + self.assign_idx(target, result, map); + } + } + + pub fn assign_idx(&mut self, target: PlaceIndex, result: ValueOrPlace, map: &Map) { + match result { + ValueOrPlace::Value(value) => { + // FIXME: What if not all tracked projections are overwritten? Can this happen? + if let Some(value_index) = map.places[target].value_index { + self.0[value_index] = value; + } + } + ValueOrPlace::Place(source) => self.assign_place_idx(target, source, map), + ValueOrPlace::Unknown => { + self.flood_idx(target, map, V::top()); + } + } + } + + pub fn get(&self, place: PlaceRef<'_>, map: &Map) -> V { + map.find(place).map(|place| self.get_idx(place, map)).unwrap_or(V::top()) + } + + pub fn get_idx(&self, place: PlaceIndex, map: &Map) -> V { + map.places[place].value_index.map(|v| self.0[v].clone()).unwrap_or(V::top()) + } +} + +impl JoinSemiLattice for State { + fn join(&mut self, other: &Self) -> bool { + self.0.join(&other.0) + } +} + +#[derive(Debug)] +pub struct Map { + locals: IndexVec>, + projections: FxHashMap<(PlaceIndex, ProjElem), PlaceIndex>, + places: IndexVec, + value_count: usize, +} + +impl Map { + pub fn new() -> Self { + Self { + locals: IndexVec::new(), + projections: FxHashMap::default(), + places: IndexVec::new(), + value_count: 0, + } + } + + /// Register all places with suitable types up to a certain derefence depth (to prevent cycles). + pub fn register_with_filter<'tcx>( + &mut self, + tcx: TyCtxt<'tcx>, + source: &impl HasLocalDecls<'tcx>, + max_derefs: u32, + mut filter: impl FnMut(Ty<'tcx>) -> bool, + ) { + let mut projection = Vec::new(); + for (local, decl) in source.local_decls().iter_enumerated() { + self.register_with_filter_rec( + tcx, + max_derefs, + local, + &mut projection, + decl.ty, + &mut filter, + ); + } + } + + fn register_with_filter_rec<'tcx>( + &mut self, + tcx: TyCtxt<'tcx>, + max_derefs: u32, + local: Local, + projection: &mut Vec>, + ty: Ty<'tcx>, + filter: &mut impl FnMut(Ty<'tcx>) -> bool, + ) { + if filter(ty) { + self.register(local, projection) + .expect("projection should only contain convertible elements"); + } + if max_derefs > 0 { + if let Some(ty::TypeAndMut { ty, .. }) = ty.builtin_deref(false) { + projection.push(PlaceElem::Deref); + self.register_with_filter_rec(tcx, max_derefs - 1, local, projection, ty, filter); + projection.pop(); + } + } + iter_fields(ty, tcx, |variant, field, ty| { + if let Some(variant) = variant { + projection.push(PlaceElem::Downcast(None, variant)); + } + projection.push(PlaceElem::Field(field, ty)); + self.register_with_filter_rec(tcx, max_derefs, local, projection, ty, filter); + projection.pop(); + if variant.is_some() { + projection.pop(); + } + }); + } + + pub fn register<'tcx>( + &mut self, + local: Local, + projection: &[PlaceElem<'tcx>], + ) -> Result<(), ()> { + // Get the base index of the local. + let mut index = + *self.locals.get_or_insert_with(local, || self.places.push(PlaceInfo::new(None))); + + // Apply the projection. + for &elem in projection { + let elem = elem.try_into()?; + index = *self.projections.entry((index, elem)).or_insert_with(|| { + // Prepend new child to the linked list. + let next = self.places.push(PlaceInfo::new(Some(elem))); + self.places[next].next_sibling = self.places[index].first_child; + self.places[index].first_child = Some(next); + next + }); + } + + // Allocate a value slot if it doesn't have one. + if self.places[index].value_index.is_none() { + self.places[index].value_index = Some(self.value_count.into()); + self.value_count += 1; + } + + Ok(()) + } + + pub fn apply_elem(&self, place: PlaceIndex, elem: ProjElem) -> Option { + self.projections.get(&(place, elem)).copied() + } + + pub fn find(&self, place: PlaceRef<'_>) -> Option { + let mut index = *self.locals.get(place.local)?.as_ref()?; + + for &elem in place.projection { + index = self.apply_elem(index, elem.try_into().ok()?)?; + } + + Some(index) + } + + pub fn children(&self, parent: PlaceIndex) -> impl Iterator + '_ { + Children::new(self, parent) + } + + pub fn preorder_invoke(&self, root: PlaceIndex, f: &mut impl FnMut(PlaceIndex)) { + f(root); + for child in self.children(root) { + self.preorder_invoke(child, f); + } + } +} + +#[derive(Debug)] +struct PlaceInfo { + next_sibling: Option, + first_child: Option, + /// The projection used to go from parent to this node (only None for root). + proj_elem: Option, + value_index: Option, +} + +impl PlaceInfo { + fn new(proj_elem: Option) -> Self { + Self { next_sibling: None, first_child: None, proj_elem, value_index: None } + } +} + +struct Children<'a> { + map: &'a Map, + next: Option, +} + +impl<'a> Children<'a> { + fn new(map: &'a Map, parent: PlaceIndex) -> Self { + Self { map, next: map.places[parent].first_child } + } +} + +impl<'a> Iterator for Children<'a> { + type Item = PlaceIndex; + + fn next(&mut self) -> Option { + match self.next { + Some(child) => { + self.next = self.map.places[child].next_sibling; + Some(child) + } + None => None, + } + } +} + +// FIXME: See if we can get rid of `Unknown`. +pub enum ValueOrPlace { + Value(V), + Place(PlaceIndex), + Unknown, +} + +pub trait HasBottom { + fn bottom() -> Self; +} + +pub trait HasTop { + fn top() -> Self; +} + +impl HasBottom for FlatSet { + fn bottom() -> Self { + Self::Bottom + } +} + +impl HasTop for FlatSet { + fn top() -> Self { + Self::Top + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum ProjElem { + Deref, + Field(Field), + Downcast(VariantIdx), +} + +impl TryFrom> for ProjElem { + type Error = (); + + fn try_from(value: ProjectionElem) -> Result { + match value { + ProjectionElem::Deref => Ok(ProjElem::Deref), + ProjectionElem::Field(field, _) => Ok(ProjElem::Field(field)), + ProjectionElem::Downcast(_, variant) => Ok(ProjElem::Downcast(variant)), + _ => Err(()), + } + } +} + +fn iter_fields<'tcx>( + ty: Ty<'tcx>, + tcx: TyCtxt<'tcx>, + mut f: impl FnMut(Option, Field, Ty<'tcx>), +) { + match ty.kind() { + ty::Tuple(list) => { + for (field, ty) in list.iter().enumerate() { + f(None, field.into(), ty); + } + } + ty::Adt(def, substs) => { + for (v_index, v_def) in def.variants().iter_enumerated() { + for (f_index, f_def) in v_def.fields.iter().enumerate() { + let field_ty = tcx.normalize_erasing_regions( + ty::ParamEnv::reveal_all(), + f_def.ty(tcx, substs), + ); + f(Some(v_index), f_index.into(), field_ty); + } + } + } + ty::Closure(_, substs) => { + iter_fields(substs.as_closure().tupled_upvars_ty(), tcx, f); + } + _ => (), + } +} + +fn debug_with_context_rec( + place: PlaceIndex, + place_str: &str, + new: &State, + old: Option<&State>, + map: &Map, + f: &mut Formatter<'_>, +) -> std::fmt::Result { + if let Some(value) = map.places[place].value_index { + match old { + None => writeln!(f, "{}: {:?}", place_str, new.0[value])?, + Some(old) => { + if new.0[value] != old.0[value] { + writeln!(f, "\u{001f}-{}: {:?}", place_str, old.0[value])?; + writeln!(f, "\u{001f}+{}: {:?}", place_str, new.0[value])?; + } + } + } + } + + for child in map.children(place) { + let info_elem = map.places[child].proj_elem.unwrap(); + let child_place_str = match info_elem { + ProjElem::Deref => format!("*{}", place_str), + ProjElem::Field(field) => { + if place_str.starts_with("*") { + format!("({}).{}", place_str, field.index()) + } else { + format!("{}.{}", place_str, field.index()) + } + } + ProjElem::Downcast(variant) => format!("({} as #{})", place_str, variant.index()), + }; + debug_with_context_rec(child, &child_place_str, new, old, map, f)?; + } + + Ok(()) +} + +fn debug_with_context( + new: &State, + old: Option<&State>, + map: &Map, + f: &mut Formatter<'_>, +) -> std::fmt::Result { + for (local, place) in map.locals.iter_enumerated() { + if let Some(place) = place { + debug_with_context_rec(*place, &format!("{:?}", local), new, old, map, f)?; + } + } + Ok(()) +} + +impl<'tcx, T> DebugWithContext> for State +where + T: ValueAnalysis<'tcx>, + T::Value: Debug, +{ + fn fmt_with(&self, ctxt: &ValueAnalysisWrapper, f: &mut Formatter<'_>) -> std::fmt::Result { + debug_with_context(self, None, ctxt.0.map(), f) + } + + fn fmt_diff_with( + &self, + old: &Self, + ctxt: &ValueAnalysisWrapper, + f: &mut Formatter<'_>, + ) -> std::fmt::Result { + debug_with_context(self, Some(old), ctxt.0.map(), f) + } +} diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs new file mode 100644 index 00000000000..f1220f634ac --- /dev/null +++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs @@ -0,0 +1,490 @@ +use rustc_const_eval::interpret::{ConstValue, ImmTy, Immediate, InterpCx, Scalar}; +use rustc_data_structures::fx::FxHashMap; +use rustc_middle::mir::visit::{MutVisitor, Visitor}; +use rustc_middle::mir::*; +use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt}; +use rustc_mir_dataflow::value_analysis::{Map, ProjElem, State, ValueAnalysis, ValueOrPlace}; +use rustc_mir_dataflow::{lattice::FlatSet, Analysis, ResultsVisitor, SwitchIntEdgeEffects}; +use rustc_span::DUMMY_SP; + +use crate::MirPass; + +pub struct DataflowConstProp; + +impl<'tcx> MirPass<'tcx> for DataflowConstProp { + fn is_enabled(&self, sess: &rustc_session::Session) -> bool { + // Choose different minimum level? + sess.mir_opt_level() >= 4 + } + + fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + // Decide which places to track during the analysis. + let mut map = Map::new(); + map.register_with_filter(tcx, body, 3, |ty| ty.is_scalar() && !ty.is_unsafe_ptr()); + + // Perform the actual dataflow analysis. + let analysis = ConstAnalysis::new(tcx, body, map); + let results = analysis.wrap().into_engine(tcx, body).iterate_to_fixpoint(); + + // Collect results and patch the body afterwards. + let mut visitor = CollectAndPatch::new(tcx, &results.analysis.0.map); + results.visit_reachable_with(body, &mut visitor); + visitor.visit_body(body); + } +} + +// FIXME: Consider support for discriminants, mutable references, arrays and slices. +struct ConstAnalysis<'tcx> { + map: Map, + tcx: TyCtxt<'tcx>, + ecx: InterpCx<'tcx, 'tcx, DummyMachine>, + param_env: ty::ParamEnv<'tcx>, +} + +impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'tcx> { + type Value = FlatSet>; + + const NAME: &'static str = "ConstAnalysis"; + + fn map(&self) -> &Map { + &self.map + } + + fn handle_assign( + &self, + target: Place<'tcx>, + rvalue: &Rvalue<'tcx>, + state: &mut State, + ) { + match rvalue { + Rvalue::CheckedBinaryOp(op, box (left, right)) => { + let target = self.map().find(target.as_ref()); + let value_target = target.and_then(|target| { + self.map().apply_elem(target, ProjElem::Field(0_u32.into())) + }); + let overflow_target = target.and_then(|target| { + self.map().apply_elem(target, ProjElem::Field(1_u32.into())) + }); + + if value_target.is_some() || overflow_target.is_some() { + let (val, overflow) = self.binary_op(state, *op, left, right); + + if let Some(value_target) = value_target { + state.assign_idx(value_target, ValueOrPlace::Value(val), self.map()); + } + if let Some(overflow_target) = overflow_target { + state.assign_idx( + overflow_target, + ValueOrPlace::Value(overflow), + self.map(), + ); + } + } + } + _ => self.super_assign(target, rvalue, state), + } + } + + fn handle_rvalue( + &self, + rvalue: &Rvalue<'tcx>, + state: &mut State, + ) -> ValueOrPlace { + match rvalue { + Rvalue::Cast(CastKind::Misc, operand, ty) => { + let operand = self.eval_operand(operand, state); + match operand { + FlatSet::Elem(operand) => self + .ecx + .misc_cast(&operand, *ty) + .map(|result| ValueOrPlace::Value(self.wrap_immediate(result, *ty))) + .unwrap_or(ValueOrPlace::Unknown), + _ => ValueOrPlace::Unknown, + } + } + Rvalue::BinaryOp(op, box (left, right)) => { + let (val, _overflow) = self.binary_op(state, *op, left, right); + // FIXME: Just ignore overflow here? + ValueOrPlace::Value(val) + } + Rvalue::UnaryOp(op, operand) => match self.eval_operand(operand, state) { + FlatSet::Elem(value) => self + .ecx + .unary_op(*op, &value) + .map(|val| ValueOrPlace::Value(self.wrap_immty(val))) + .unwrap_or(ValueOrPlace::Value(FlatSet::Top)), + FlatSet::Bottom => ValueOrPlace::Value(FlatSet::Bottom), + FlatSet::Top => ValueOrPlace::Value(FlatSet::Top), + }, + _ => self.super_rvalue(rvalue, state), + } + } + + fn handle_constant( + &self, + constant: &Constant<'tcx>, + _state: &mut State, + ) -> Self::Value { + constant + .literal + .eval(self.tcx, self.param_env) + .try_to_scalar() + .and_then(|scalar| scalar.try_to_int().ok()) + .map(|value| FlatSet::Elem(Const::Scalar(value, constant.ty()))) + .unwrap_or(FlatSet::Top) + } + + fn handle_switch_int( + &self, + discr: &Operand<'tcx>, + apply_edge_effects: &mut impl SwitchIntEdgeEffects>, + ) { + // FIXME: The dataflow framework only provides the state if we call `apply()`, which makes + // this more inefficient than it has to be. + // FIXME: Perhaps we rather need a proper unreachability flag for every block. + let mut discr_value = None; + let mut handled = false; + apply_edge_effects.apply(|state, target| { + let discr_value = match discr_value { + Some(value) => value, + None => { + let value = match self.handle_operand(discr, state) { + ValueOrPlace::Value(value) => value, + ValueOrPlace::Place(place) => state.get_idx(place, self.map()), + ValueOrPlace::Unknown => FlatSet::Top, + }; + let result = match value { + FlatSet::Top => FlatSet::Top, + FlatSet::Elem(Const::Scalar(scalar, _)) => { + FlatSet::Elem(scalar.assert_bits(scalar.size())) + } + FlatSet::Bottom => FlatSet::Bottom, + }; + discr_value = Some(result); + result + } + }; + + let FlatSet::Elem(choice) = discr_value else { + // Do nothing if we don't know which branch will be taken. + return + }; + + if target.value.map(|n| n == choice).unwrap_or(!handled) { + // Branch is taken. Has no effect on state. + handled = true; + } else { + // Branch is not taken, we can flood everything. + state.flood_all(FlatSet::Bottom); + } + }) + } +} + +#[derive(Clone, PartialEq, Eq)] +enum Const<'tcx> { + // FIXME: If there won't be any other cases, make it a struct. + Scalar(ScalarInt, Ty<'tcx>), +} + +impl<'tcx> std::fmt::Debug for Const<'tcx> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + Self::Scalar(scalar, ty) => { + std::fmt::Display::fmt(&ConstantKind::Val(ConstValue::Scalar(scalar.into()), ty), f) + } + } + } +} + +impl<'tcx> ConstAnalysis<'tcx> { + pub fn new(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, map: Map) -> Self { + Self { + map, + tcx, + ecx: InterpCx::new(tcx, DUMMY_SP, ty::ParamEnv::empty(), DummyMachine), + param_env: tcx.param_env(body.source.def_id()), + } + } + + fn binary_op( + &self, + state: &mut State>>, + op: BinOp, + left: &Operand<'tcx>, + right: &Operand<'tcx>, + ) -> (FlatSet>, FlatSet>) { + let left = self.eval_operand(left, state); + let right = self.eval_operand(right, state); + match (left, right) { + (FlatSet::Elem(left), FlatSet::Elem(right)) => { + match self.ecx.overflowing_binary_op(op, &left, &right) { + Ok((val, overflow, ty)) => { + let val = val + .try_to_int() + .ok() + .map(|val| self.wrap_scalar(val, ty)) + .unwrap_or(FlatSet::Top); + let overflow = self.wrap_scalar(overflow.into(), self.tcx.types.bool); + (val, overflow) + } + _ => (FlatSet::Top, FlatSet::Top), + } + } + (FlatSet::Bottom, _) | (_, FlatSet::Bottom) => (FlatSet::Bottom, FlatSet::Bottom), + (_, _) => { + // Could attempt some algebraic simplifcations here. + (FlatSet::Top, FlatSet::Top) + } + } + } + + fn eval_operand( + &self, + op: &Operand<'tcx>, + state: &mut State>>, + ) -> FlatSet> { + let value = match self.handle_operand(op, state) { + ValueOrPlace::Value(value) => value, + ValueOrPlace::Place(place) => state.get_idx(place, &self.map), + ValueOrPlace::Unknown => FlatSet::Top, + }; + match value { + FlatSet::Top => FlatSet::Top, + FlatSet::Elem(Const::Scalar(value, ty)) => { + let layout = self + .tcx + .layout_of(ty::ParamEnv::empty().and(ty)) + .expect("this should not happen"); // FIXME + FlatSet::Elem(ImmTy::from_scalar(value.into(), layout)) + } + FlatSet::Bottom => FlatSet::Bottom, + } + } + + fn wrap_scalar(&self, scalar: ScalarInt, ty: Ty<'tcx>) -> FlatSet> { + FlatSet::Elem(Const::Scalar(scalar, ty)) + } + + fn wrap_immediate(&self, imm: Immediate, ty: Ty<'tcx>) -> FlatSet> { + match imm { + Immediate::Scalar(Scalar::Int(scalar)) => self.wrap_scalar(scalar, ty), + _ => FlatSet::Top, + } + } + + fn wrap_immty(&self, val: ImmTy<'tcx>) -> FlatSet> { + self.wrap_immediate(*val, val.layout.ty) + } +} + +struct CollectAndPatch<'tcx, 'map> { + tcx: TyCtxt<'tcx>, + map: &'map Map, + before_effect: FxHashMap<(Location, Place<'tcx>), Const<'tcx>>, + assignments: FxHashMap>, +} + +impl<'tcx, 'map> CollectAndPatch<'tcx, 'map> { + fn new(tcx: TyCtxt<'tcx>, map: &'map Map) -> Self { + Self { tcx, map, before_effect: FxHashMap::default(), assignments: FxHashMap::default() } + } + + fn make_operand(&self, constant: Const<'tcx>) -> Operand<'tcx> { + let Const::Scalar(scalar, ty) = constant; + Operand::Constant(Box::new(Constant { + span: DUMMY_SP, + user_ty: None, + literal: ConstantKind::Val(ConstValue::Scalar(scalar.into()), ty), + })) + } +} + +impl<'mir, 'tcx, 'map> ResultsVisitor<'mir, 'tcx> for CollectAndPatch<'tcx, 'map> { + type FlowState = State>>; + + fn visit_statement_before_primary_effect( + &mut self, + state: &Self::FlowState, + statement: &'mir Statement<'tcx>, + location: Location, + ) { + match &statement.kind { + StatementKind::Assign(box (_, rvalue)) => { + OperandCollector { state, visitor: self }.visit_rvalue(rvalue, location); + } + _ => (), + } + } + + fn visit_statement_after_primary_effect( + &mut self, + state: &Self::FlowState, + statement: &'mir Statement<'tcx>, + location: Location, + ) { + match statement.kind { + StatementKind::Assign(box (place, _)) => match state.get(place.as_ref(), self.map) { + FlatSet::Top => (), + FlatSet::Elem(value) => { + self.assignments.insert(location, value); + } + FlatSet::Bottom => { + // This statement is not reachable. Do nothing, it will (hopefully) be removed. + } + }, + _ => (), + } + } + + fn visit_terminator_before_primary_effect( + &mut self, + state: &Self::FlowState, + terminator: &'mir Terminator<'tcx>, + location: Location, + ) { + OperandCollector { state, visitor: self }.visit_terminator(terminator, location); + } +} + +impl<'tcx, 'map> MutVisitor<'tcx> for CollectAndPatch<'tcx, 'map> { + fn tcx<'a>(&'a self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) { + if let Some(value) = self.assignments.get(&location) { + match &mut statement.kind { + StatementKind::Assign(box (_, rvalue)) => { + *rvalue = Rvalue::Use(self.make_operand(value.clone())); + } + _ => bug!("found assignment info for non-assign statement"), + } + } else { + self.super_statement(statement, location); + } + } + + fn visit_operand(&mut self, operand: &mut Operand<'tcx>, location: Location) { + match operand { + Operand::Copy(place) | Operand::Move(place) => { + if let Some(value) = self.before_effect.get(&(location, *place)) { + *operand = self.make_operand(value.clone()); + } + } + _ => (), + } + } +} + +struct OperandCollector<'tcx, 'map, 'a> { + state: &'a State>>, + visitor: &'a mut CollectAndPatch<'tcx, 'map>, +} + +impl<'tcx, 'map, 'a> Visitor<'tcx> for OperandCollector<'tcx, 'map, 'a> { + fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { + match operand { + Operand::Copy(place) | Operand::Move(place) => { + match self.state.get(place.as_ref(), self.visitor.map) { + FlatSet::Top => (), + FlatSet::Elem(value) => { + self.visitor.before_effect.insert((location, *place), value); + } + FlatSet::Bottom => { + // This only happens if this location is unreachable. + } + } + } + _ => (), + } + } +} + +struct DummyMachine; + +impl<'mir, 'tcx> rustc_const_eval::interpret::Machine<'mir, 'tcx> for DummyMachine { + rustc_const_eval::interpret::compile_time_machine!(<'mir, 'tcx>); + type MemoryKind = !; + const PANIC_ON_ALLOC_FAIL: bool = true; + + fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool { + unimplemented!() + } + + fn enforce_validity(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool { + unimplemented!() + } + + fn find_mir_or_eval_fn( + _ecx: &mut InterpCx<'mir, 'tcx, Self>, + _instance: ty::Instance<'tcx>, + _abi: rustc_target::spec::abi::Abi, + _args: &[rustc_const_eval::interpret::OpTy<'tcx, Self::Provenance>], + _destination: &rustc_const_eval::interpret::PlaceTy<'tcx, Self::Provenance>, + _target: Option, + _unwind: rustc_const_eval::interpret::StackPopUnwind, + ) -> interpret::InterpResult<'tcx, Option<(&'mir Body<'tcx>, ty::Instance<'tcx>)>> { + unimplemented!() + } + + fn call_intrinsic( + _ecx: &mut InterpCx<'mir, 'tcx, Self>, + _instance: ty::Instance<'tcx>, + _args: &[rustc_const_eval::interpret::OpTy<'tcx, Self::Provenance>], + _destination: &rustc_const_eval::interpret::PlaceTy<'tcx, Self::Provenance>, + _target: Option, + _unwind: rustc_const_eval::interpret::StackPopUnwind, + ) -> interpret::InterpResult<'tcx> { + unimplemented!() + } + + fn assert_panic( + _ecx: &mut InterpCx<'mir, 'tcx, Self>, + _msg: &rustc_middle::mir::AssertMessage<'tcx>, + _unwind: Option, + ) -> interpret::InterpResult<'tcx> { + unimplemented!() + } + + fn binary_ptr_op( + _ecx: &InterpCx<'mir, 'tcx, Self>, + _bin_op: BinOp, + _left: &rustc_const_eval::interpret::ImmTy<'tcx, Self::Provenance>, + _right: &rustc_const_eval::interpret::ImmTy<'tcx, Self::Provenance>, + ) -> interpret::InterpResult<'tcx, (interpret::Scalar, bool, Ty<'tcx>)> { + unimplemented!() + } + + fn expose_ptr( + _ecx: &mut InterpCx<'mir, 'tcx, Self>, + _ptr: interpret::Pointer, + ) -> interpret::InterpResult<'tcx> { + unimplemented!() + } + + fn init_frame_extra( + _ecx: &mut InterpCx<'mir, 'tcx, Self>, + _frame: rustc_const_eval::interpret::Frame<'mir, 'tcx, Self::Provenance>, + ) -> interpret::InterpResult< + 'tcx, + rustc_const_eval::interpret::Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>, + > { + unimplemented!() + } + + fn stack<'a>( + _ecx: &'a InterpCx<'mir, 'tcx, Self>, + ) -> &'a [rustc_const_eval::interpret::Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>] + { + unimplemented!() + } + + fn stack_mut<'a>( + _ecx: &'a mut InterpCx<'mir, 'tcx, Self>, + ) -> &'a mut Vec< + rustc_const_eval::interpret::Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>, + > { + unimplemented!() + } +} diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 4791be1306c..692eeddfb98 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -54,6 +54,7 @@ mod const_goto; mod const_prop; mod const_prop_lint; mod coverage; +mod dataflow_const_prop; mod dead_store_elimination; mod deaggregator; mod deduce_param_attrs; @@ -569,6 +570,7 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { // // FIXME(#70073): This pass is responsible for both optimization as well as some lints. &const_prop::ConstProp, + &dataflow_const_prop::DataflowConstProp, // // Const-prop runs unconditionally, but doesn't mutate the MIR at mir-opt-level=0. &const_debuginfo::ConstDebugInfo, diff --git a/src/test/mir-opt/dataflow-const-prop/cast.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/cast.main.DataflowConstProp.diff new file mode 100644 index 00000000000..950b0518e0b --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/cast.main.DataflowConstProp.diff @@ -0,0 +1,41 @@ +- // MIR for `main` before DataflowConstProp ++ // MIR for `main` after DataflowConstProp + + fn main() -> () { + let mut _0: (); // return place in scope 0 at $DIR/cast.rs:+0:11: +0:11 + let _1: i32; // in scope 0 at $DIR/cast.rs:+1:9: +1:10 + let mut _3: u8; // in scope 0 at $DIR/cast.rs:+2:13: +2:22 + let mut _4: i32; // in scope 0 at $DIR/cast.rs:+2:13: +2:16 + let mut _5: &i32; // in scope 0 at $DIR/cast.rs:+2:14: +2:16 + scope 1 { + debug a => _1; // in scope 1 at $DIR/cast.rs:+1:9: +1:10 + let _2: u8; // in scope 1 at $DIR/cast.rs:+2:9: +2:10 + scope 2 { + debug b => _2; // in scope 2 at $DIR/cast.rs:+2:9: +2:10 + } + } + + bb0: { + StorageLive(_1); // scope 0 at $DIR/cast.rs:+1:9: +1:10 + _1 = const 257_i32; // scope 0 at $DIR/cast.rs:+1:13: +1:16 + StorageLive(_2); // scope 1 at $DIR/cast.rs:+2:9: +2:10 + StorageLive(_3); // scope 1 at $DIR/cast.rs:+2:13: +2:22 + StorageLive(_4); // scope 1 at $DIR/cast.rs:+2:13: +2:16 + StorageLive(_5); // scope 1 at $DIR/cast.rs:+2:14: +2:16 + _5 = &_1; // scope 1 at $DIR/cast.rs:+2:14: +2:16 +- _4 = (*_5); // scope 1 at $DIR/cast.rs:+2:13: +2:16 +- _3 = move _4 as u8 (Misc); // scope 1 at $DIR/cast.rs:+2:13: +2:22 ++ _4 = const 257_i32; // scope 1 at $DIR/cast.rs:+2:13: +2:16 ++ _3 = const 1_u8; // scope 1 at $DIR/cast.rs:+2:13: +2:22 + StorageDead(_4); // scope 1 at $DIR/cast.rs:+2:21: +2:22 +- _2 = Add(move _3, const 1_u8); // scope 1 at $DIR/cast.rs:+2:13: +2:26 ++ _2 = const 2_u8; // scope 1 at $DIR/cast.rs:+2:13: +2:26 + StorageDead(_3); // scope 1 at $DIR/cast.rs:+2:25: +2:26 + StorageDead(_5); // scope 1 at $DIR/cast.rs:+2:26: +2:27 + _0 = const (); // scope 0 at $DIR/cast.rs:+0:11: +3:2 + StorageDead(_2); // scope 1 at $DIR/cast.rs:+3:1: +3:2 + StorageDead(_1); // scope 0 at $DIR/cast.rs:+3:1: +3:2 + return; // scope 0 at $DIR/cast.rs:+3:2: +3:2 + } + } + diff --git a/src/test/mir-opt/dataflow-const-prop/cast.rs b/src/test/mir-opt/dataflow-const-prop/cast.rs new file mode 100644 index 00000000000..bf5838cb89d --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/cast.rs @@ -0,0 +1,7 @@ +// unit-test: DataflowConstProp + +// EMIT_MIR cast.main.DataflowConstProp.diff +fn main() { + let a = 257; + let b = *&a as u8 + 1; +} diff --git a/src/test/mir-opt/dataflow-const-prop/cell.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/cell.main.DataflowConstProp.diff new file mode 100644 index 00000000000..e947f084970 --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/cell.main.DataflowConstProp.diff @@ -0,0 +1,12 @@ +- // MIR for `main` before DataflowConstProp ++ // MIR for `main` after DataflowConstProp + + fn main() -> () { + let mut _0: (); // return place in scope 0 at $DIR/cell.rs:+0:11: +0:11 + + bb0: { + _0 = const (); // scope 0 at $DIR/cell.rs:+0:11: +2:2 + return; // scope 0 at $DIR/cell.rs:+2:2: +2:2 + } + } + diff --git a/src/test/mir-opt/dataflow-const-prop/cell.rs b/src/test/mir-opt/dataflow-const-prop/cell.rs new file mode 100644 index 00000000000..82a4c346595 --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/cell.rs @@ -0,0 +1,6 @@ +// unit-test: DataflowConstProp + +// EMIT_MIR cell.main.DataflowConstProp.diff +fn main() { + // FIXME: Is it possible to build something with UnsafeCell? +} diff --git a/src/test/mir-opt/dataflow-const-prop/checked.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/checked.main.DataflowConstProp.diff new file mode 100644 index 00000000000..39bc506adac --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/checked.main.DataflowConstProp.diff @@ -0,0 +1,81 @@ +- // MIR for `main` before DataflowConstProp ++ // MIR for `main` after DataflowConstProp + + fn main() -> () { + let mut _0: (); // return place in scope 0 at $DIR/checked.rs:+0:11: +0:11 + let _1: i32; // in scope 0 at $DIR/checked.rs:+1:9: +1:10 + let mut _4: i32; // in scope 0 at $DIR/checked.rs:+3:13: +3:14 + let mut _5: i32; // in scope 0 at $DIR/checked.rs:+3:17: +3:18 + let mut _6: (i32, bool); // in scope 0 at $DIR/checked.rs:+3:13: +3:18 + let mut _9: i32; // in scope 0 at $DIR/checked.rs:+6:13: +6:14 + let mut _10: (i32, bool); // in scope 0 at $DIR/checked.rs:+6:13: +6:18 + scope 1 { + debug a => _1; // in scope 1 at $DIR/checked.rs:+1:9: +1:10 + let _2: i32; // in scope 1 at $DIR/checked.rs:+2:9: +2:10 + scope 2 { + debug b => _2; // in scope 2 at $DIR/checked.rs:+2:9: +2:10 + let _3: i32; // in scope 2 at $DIR/checked.rs:+3:9: +3:10 + scope 3 { + debug c => _3; // in scope 3 at $DIR/checked.rs:+3:9: +3:10 + let _7: i32; // in scope 3 at $DIR/checked.rs:+5:9: +5:10 + scope 4 { + debug d => _7; // in scope 4 at $DIR/checked.rs:+5:9: +5:10 + let _8: i32; // in scope 4 at $DIR/checked.rs:+6:9: +6:10 + scope 5 { + debug e => _8; // in scope 5 at $DIR/checked.rs:+6:9: +6:10 + } + } + } + } + } + + bb0: { + StorageLive(_1); // scope 0 at $DIR/checked.rs:+1:9: +1:10 + _1 = const 1_i32; // scope 0 at $DIR/checked.rs:+1:13: +1:14 + StorageLive(_2); // scope 1 at $DIR/checked.rs:+2:9: +2:10 + _2 = const 2_i32; // scope 1 at $DIR/checked.rs:+2:13: +2:14 + StorageLive(_3); // scope 2 at $DIR/checked.rs:+3:9: +3:10 + StorageLive(_4); // scope 2 at $DIR/checked.rs:+3:13: +3:14 +- _4 = _1; // scope 2 at $DIR/checked.rs:+3:13: +3:14 ++ _4 = const 1_i32; // scope 2 at $DIR/checked.rs:+3:13: +3:14 + StorageLive(_5); // scope 2 at $DIR/checked.rs:+3:17: +3:18 +- _5 = _2; // scope 2 at $DIR/checked.rs:+3:17: +3:18 +- _6 = CheckedAdd(_4, _5); // scope 2 at $DIR/checked.rs:+3:13: +3:18 +- assert(!move (_6.1: bool), "attempt to compute `{} + {}`, which would overflow", move _4, move _5) -> bb1; // scope 2 at $DIR/checked.rs:+3:13: +3:18 ++ _5 = const 2_i32; // scope 2 at $DIR/checked.rs:+3:17: +3:18 ++ _6 = CheckedAdd(const 1_i32, const 2_i32); // scope 2 at $DIR/checked.rs:+3:13: +3:18 ++ assert(!const false, "attempt to compute `{} + {}`, which would overflow", const 1_i32, const 2_i32) -> bb1; // scope 2 at $DIR/checked.rs:+3:13: +3:18 + } + + bb1: { +- _3 = move (_6.0: i32); // scope 2 at $DIR/checked.rs:+3:13: +3:18 ++ _3 = const 3_i32; // scope 2 at $DIR/checked.rs:+3:13: +3:18 + StorageDead(_5); // scope 2 at $DIR/checked.rs:+3:17: +3:18 + StorageDead(_4); // scope 2 at $DIR/checked.rs:+3:17: +3:18 + StorageLive(_7); // scope 3 at $DIR/checked.rs:+5:9: +5:10 +- _7 = const core::num::::MAX; // scope 3 at $DIR/checked.rs:+5:13: +5:21 ++ _7 = const i32::MAX; // scope 3 at $DIR/checked.rs:+5:13: +5:21 + StorageLive(_8); // scope 4 at $DIR/checked.rs:+6:9: +6:10 + StorageLive(_9); // scope 4 at $DIR/checked.rs:+6:13: +6:14 +- _9 = _7; // scope 4 at $DIR/checked.rs:+6:13: +6:14 +- _10 = CheckedAdd(_9, const 1_i32); // scope 4 at $DIR/checked.rs:+6:13: +6:18 +- assert(!move (_10.1: bool), "attempt to compute `{} + {}`, which would overflow", move _9, const 1_i32) -> bb2; // scope 4 at $DIR/checked.rs:+6:13: +6:18 ++ _9 = const i32::MAX; // scope 4 at $DIR/checked.rs:+6:13: +6:14 ++ _10 = CheckedAdd(const i32::MAX, const 1_i32); // scope 4 at $DIR/checked.rs:+6:13: +6:18 ++ assert(!const true, "attempt to compute `{} + {}`, which would overflow", const i32::MAX, const 1_i32) -> bb2; // scope 4 at $DIR/checked.rs:+6:13: +6:18 + } + + bb2: { +- _8 = move (_10.0: i32); // scope 4 at $DIR/checked.rs:+6:13: +6:18 ++ _8 = const i32::MIN; // scope 4 at $DIR/checked.rs:+6:13: +6:18 + StorageDead(_9); // scope 4 at $DIR/checked.rs:+6:17: +6:18 + _0 = const (); // scope 0 at $DIR/checked.rs:+0:11: +7:2 + StorageDead(_8); // scope 4 at $DIR/checked.rs:+7:1: +7:2 + StorageDead(_7); // scope 3 at $DIR/checked.rs:+7:1: +7:2 + StorageDead(_3); // scope 2 at $DIR/checked.rs:+7:1: +7:2 + StorageDead(_2); // scope 1 at $DIR/checked.rs:+7:1: +7:2 + StorageDead(_1); // scope 0 at $DIR/checked.rs:+7:1: +7:2 + return; // scope 0 at $DIR/checked.rs:+7:2: +7:2 + } + } + diff --git a/src/test/mir-opt/dataflow-const-prop/checked.rs b/src/test/mir-opt/dataflow-const-prop/checked.rs new file mode 100644 index 00000000000..ae457af72c0 --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/checked.rs @@ -0,0 +1,13 @@ +// unit-test: DataflowConstProp +// compile-flags: -C overflow-checks=on + +// EMIT_MIR checked.main.DataflowConstProp.diff +#[allow(arithmetic_overflow)] +fn main() { + let a = 1; + let b = 2; + let c = a + b; + + let d = i32::MAX; + let e = d + 1; +} diff --git a/src/test/mir-opt/dataflow-const-prop/if.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/if.main.DataflowConstProp.diff new file mode 100644 index 00000000000..951e5c5b9ad --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/if.main.DataflowConstProp.diff @@ -0,0 +1,118 @@ +- // MIR for `main` before DataflowConstProp ++ // MIR for `main` after DataflowConstProp + + fn main() -> () { + let mut _0: (); // return place in scope 0 at $DIR/if.rs:+0:11: +0:11 + let _1: i32; // in scope 0 at $DIR/if.rs:+2:9: +2:10 + let mut _3: bool; // in scope 0 at $DIR/if.rs:+3:16: +3:24 + let mut _4: i32; // in scope 0 at $DIR/if.rs:+3:16: +3:19 + let mut _5: &i32; // in scope 0 at $DIR/if.rs:+3:17: +3:19 + let mut _7: i32; // in scope 0 at $DIR/if.rs:+4:13: +4:14 + let mut _9: bool; // in scope 0 at $DIR/if.rs:+6:16: +6:24 + let mut _10: i32; // in scope 0 at $DIR/if.rs:+6:16: +6:19 + let mut _11: &i32; // in scope 0 at $DIR/if.rs:+6:17: +6:19 + let mut _12: i32; // in scope 0 at $DIR/if.rs:+6:38: +6:39 + let mut _14: i32; // in scope 0 at $DIR/if.rs:+7:13: +7:14 + scope 1 { + debug a => _1; // in scope 1 at $DIR/if.rs:+2:9: +2:10 + let _2: i32; // in scope 1 at $DIR/if.rs:+3:9: +3:10 + scope 2 { + debug b => _2; // in scope 2 at $DIR/if.rs:+3:9: +3:10 + let _6: i32; // in scope 2 at $DIR/if.rs:+4:9: +4:10 + scope 3 { + debug c => _6; // in scope 3 at $DIR/if.rs:+4:9: +4:10 + let _8: i32; // in scope 3 at $DIR/if.rs:+6:9: +6:10 + scope 4 { + debug d => _8; // in scope 4 at $DIR/if.rs:+6:9: +6:10 + let _13: i32; // in scope 4 at $DIR/if.rs:+7:9: +7:10 + scope 5 { + debug e => _13; // in scope 5 at $DIR/if.rs:+7:9: +7:10 + } + } + } + } + } + + bb0: { + StorageLive(_1); // scope 0 at $DIR/if.rs:+2:9: +2:10 + _1 = const 1_i32; // scope 0 at $DIR/if.rs:+2:13: +2:14 + StorageLive(_2); // scope 1 at $DIR/if.rs:+3:9: +3:10 + StorageLive(_3); // scope 1 at $DIR/if.rs:+3:16: +3:24 + StorageLive(_4); // scope 1 at $DIR/if.rs:+3:16: +3:19 + StorageLive(_5); // scope 1 at $DIR/if.rs:+3:17: +3:19 + _5 = &_1; // scope 1 at $DIR/if.rs:+3:17: +3:19 +- _4 = (*_5); // scope 1 at $DIR/if.rs:+3:16: +3:19 +- _3 = Eq(move _4, const 1_i32); // scope 1 at $DIR/if.rs:+3:16: +3:24 ++ _4 = const 1_i32; // scope 1 at $DIR/if.rs:+3:16: +3:19 ++ _3 = const true; // scope 1 at $DIR/if.rs:+3:16: +3:24 + StorageDead(_5); // scope 1 at $DIR/if.rs:+3:23: +3:24 + StorageDead(_4); // scope 1 at $DIR/if.rs:+3:23: +3:24 +- switchInt(move _3) -> [false: bb2, otherwise: bb1]; // scope 1 at $DIR/if.rs:+3:16: +3:24 ++ switchInt(const true) -> [false: bb2, otherwise: bb1]; // scope 1 at $DIR/if.rs:+3:16: +3:24 + } + + bb1: { + _2 = const 2_i32; // scope 1 at $DIR/if.rs:+3:27: +3:28 + goto -> bb3; // scope 1 at $DIR/if.rs:+3:13: +3:41 + } + + bb2: { + _2 = const 3_i32; // scope 1 at $DIR/if.rs:+3:38: +3:39 + goto -> bb3; // scope 1 at $DIR/if.rs:+3:13: +3:41 + } + + bb3: { + StorageDead(_3); // scope 1 at $DIR/if.rs:+3:40: +3:41 + StorageLive(_6); // scope 2 at $DIR/if.rs:+4:9: +4:10 + StorageLive(_7); // scope 2 at $DIR/if.rs:+4:13: +4:14 + _7 = _2; // scope 2 at $DIR/if.rs:+4:13: +4:14 + _6 = Add(move _7, const 1_i32); // scope 2 at $DIR/if.rs:+4:13: +4:18 + StorageDead(_7); // scope 2 at $DIR/if.rs:+4:17: +4:18 + StorageLive(_8); // scope 3 at $DIR/if.rs:+6:9: +6:10 + StorageLive(_9); // scope 3 at $DIR/if.rs:+6:16: +6:24 + StorageLive(_10); // scope 3 at $DIR/if.rs:+6:16: +6:19 + StorageLive(_11); // scope 3 at $DIR/if.rs:+6:17: +6:19 + _11 = &_1; // scope 3 at $DIR/if.rs:+6:17: +6:19 +- _10 = (*_11); // scope 3 at $DIR/if.rs:+6:16: +6:19 +- _9 = Eq(move _10, const 1_i32); // scope 3 at $DIR/if.rs:+6:16: +6:24 ++ _10 = const 1_i32; // scope 3 at $DIR/if.rs:+6:16: +6:19 ++ _9 = const true; // scope 3 at $DIR/if.rs:+6:16: +6:24 + StorageDead(_11); // scope 3 at $DIR/if.rs:+6:23: +6:24 + StorageDead(_10); // scope 3 at $DIR/if.rs:+6:23: +6:24 +- switchInt(move _9) -> [false: bb5, otherwise: bb4]; // scope 3 at $DIR/if.rs:+6:16: +6:24 ++ switchInt(const true) -> [false: bb5, otherwise: bb4]; // scope 3 at $DIR/if.rs:+6:16: +6:24 + } + + bb4: { +- _8 = _1; // scope 3 at $DIR/if.rs:+6:27: +6:28 ++ _8 = const 1_i32; // scope 3 at $DIR/if.rs:+6:27: +6:28 + goto -> bb6; // scope 3 at $DIR/if.rs:+6:13: +6:45 + } + + bb5: { + StorageLive(_12); // scope 3 at $DIR/if.rs:+6:38: +6:39 + _12 = _1; // scope 3 at $DIR/if.rs:+6:38: +6:39 + _8 = Add(move _12, const 1_i32); // scope 3 at $DIR/if.rs:+6:38: +6:43 + StorageDead(_12); // scope 3 at $DIR/if.rs:+6:42: +6:43 + goto -> bb6; // scope 3 at $DIR/if.rs:+6:13: +6:45 + } + + bb6: { + StorageDead(_9); // scope 3 at $DIR/if.rs:+6:44: +6:45 + StorageLive(_13); // scope 4 at $DIR/if.rs:+7:9: +7:10 + StorageLive(_14); // scope 4 at $DIR/if.rs:+7:13: +7:14 +- _14 = _8; // scope 4 at $DIR/if.rs:+7:13: +7:14 +- _13 = Add(move _14, const 1_i32); // scope 4 at $DIR/if.rs:+7:13: +7:18 ++ _14 = const 1_i32; // scope 4 at $DIR/if.rs:+7:13: +7:14 ++ _13 = const 2_i32; // scope 4 at $DIR/if.rs:+7:13: +7:18 + StorageDead(_14); // scope 4 at $DIR/if.rs:+7:17: +7:18 + _0 = const (); // scope 0 at $DIR/if.rs:+0:11: +8:2 + StorageDead(_13); // scope 4 at $DIR/if.rs:+8:1: +8:2 + StorageDead(_8); // scope 3 at $DIR/if.rs:+8:1: +8:2 + StorageDead(_6); // scope 2 at $DIR/if.rs:+8:1: +8:2 + StorageDead(_2); // scope 1 at $DIR/if.rs:+8:1: +8:2 + StorageDead(_1); // scope 0 at $DIR/if.rs:+8:1: +8:2 + return; // scope 0 at $DIR/if.rs:+8:2: +8:2 + } + } + diff --git a/src/test/mir-opt/dataflow-const-prop/if.rs b/src/test/mir-opt/dataflow-const-prop/if.rs new file mode 100644 index 00000000000..8df89080477 --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/if.rs @@ -0,0 +1,12 @@ +// unit-test: DataflowConstProp + +// EMIT_MIR if.main.DataflowConstProp.diff +fn main() { + // This does not work (yet). Needs perhaps additional state to track unreachability. + let a = 1; + let b = if *&a == 1 { 2 } else { 3 }; + let c = b + 1; + + let d = if *&a == 1 { a } else { a + 1 }; + let e = d + 1; +} diff --git a/src/test/mir-opt/dataflow-const-prop/promoted.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/promoted.main.DataflowConstProp.diff new file mode 100644 index 00000000000..d5a17d08356 --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/promoted.main.DataflowConstProp.diff @@ -0,0 +1,29 @@ +- // MIR for `main` before DataflowConstProp ++ // MIR for `main` after DataflowConstProp + + fn main() -> () { + let mut _0: (); // return place in scope 0 at $DIR/promoted.rs:+0:11: +0:11 + let _1: i32; // in scope 0 at $DIR/promoted.rs:+2:9: +2:10 + let mut _2: &i32; // in scope 0 at $DIR/promoted.rs:+2:14: +2:17 + let _3: i32; // in scope 0 at $DIR/promoted.rs:+2:15: +2:17 + let mut _4: &i32; // in scope 0 at $DIR/promoted.rs:+2:14: +2:17 + scope 1 { + debug a => _1; // in scope 1 at $DIR/promoted.rs:+2:9: +2:10 + } + + bb0: { + StorageLive(_1); // scope 0 at $DIR/promoted.rs:+2:9: +2:10 + StorageLive(_2); // scope 0 at $DIR/promoted.rs:+2:14: +2:17 + _4 = const main::promoted[0]; // scope 0 at $DIR/promoted.rs:+2:14: +2:17 + // mir::Constant + // + span: $DIR/promoted.rs:6:14: 6:17 + // + literal: Const { ty: &i32, val: Unevaluated(main, [], Some(promoted[0])) } + _2 = &(*_4); // scope 0 at $DIR/promoted.rs:+2:14: +2:17 + _1 = (*_2); // scope 0 at $DIR/promoted.rs:+2:13: +2:17 + StorageDead(_2); // scope 0 at $DIR/promoted.rs:+2:17: +2:18 + _0 = const (); // scope 0 at $DIR/promoted.rs:+0:11: +3:2 + StorageDead(_1); // scope 0 at $DIR/promoted.rs:+3:1: +3:2 + return; // scope 0 at $DIR/promoted.rs:+3:2: +3:2 + } + } + diff --git a/src/test/mir-opt/dataflow-const-prop/promoted.rs b/src/test/mir-opt/dataflow-const-prop/promoted.rs new file mode 100644 index 00000000000..1653cf33344 --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/promoted.rs @@ -0,0 +1,7 @@ +// unit-test: DataflowConstProp + +// EMIT_MIR promoted.main.DataflowConstProp.diff +fn main() { + // This does not work because `&42` gets promoted. + let a = *&42; +} diff --git a/src/test/mir-opt/dataflow-const-prop/recursive.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/recursive.main.DataflowConstProp.diff new file mode 100644 index 00000000000..953628cff06 --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/recursive.main.DataflowConstProp.diff @@ -0,0 +1,133 @@ +- // MIR for `main` before DataflowConstProp ++ // MIR for `main` after DataflowConstProp + + fn main() -> () { + let mut _0: (); // return place in scope 0 at $DIR/recursive.rs:+0:11: +0:11 + let _1: S; // in scope 0 at $DIR/recursive.rs:+1:9: +1:10 + let mut _3: &S; // in scope 0 at $DIR/recursive.rs:+2:20: +2:22 + let _4: &S; // in scope 0 at $DIR/recursive.rs:+2:20: +2:22 + let mut _6: &S; // in scope 0 at $DIR/recursive.rs:+3:20: +3:22 + let _7: &S; // in scope 0 at $DIR/recursive.rs:+3:20: +3:22 + let mut _9: isize; // in scope 0 at $DIR/recursive.rs:+5:9: +5:18 + let mut _11: isize; // in scope 0 at $DIR/recursive.rs:+6:13: +6:22 + let mut _13: isize; // in scope 0 at $DIR/recursive.rs:+7:17: +7:28 + let mut _15: !; // in scope 0 at $DIR/recursive.rs:+8:22: +8:43 + let mut _16: !; // in scope 0 at $DIR/recursive.rs:+10:18: +10:39 + let mut _17: !; // in scope 0 at $DIR/recursive.rs:+12:14: +12:35 + let mut _18: &S; // in scope 0 at $DIR/recursive.rs:+6:20: +6:21 + let mut _19: &S; // in scope 0 at $DIR/recursive.rs:+6:20: +6:21 + scope 1 { + debug a => _1; // in scope 1 at $DIR/recursive.rs:+1:9: +1:10 + let _2: S; // in scope 1 at $DIR/recursive.rs:+2:9: +2:10 + scope 2 { + debug b => _2; // in scope 2 at $DIR/recursive.rs:+2:9: +2:10 + let _5: S; // in scope 2 at $DIR/recursive.rs:+3:9: +3:10 + scope 3 { + debug c => _5; // in scope 3 at $DIR/recursive.rs:+3:9: +3:10 + let _8: u32; // in scope 3 at $DIR/recursive.rs:+4:9: +4:10 + let _10: &S; // in scope 3 at $DIR/recursive.rs:+5:16: +5:17 + scope 4 { + debug d => _8; // in scope 4 at $DIR/recursive.rs:+4:9: +4:10 + } + scope 5 { + debug b => _10; // in scope 5 at $DIR/recursive.rs:+5:16: +5:17 + let _12: &&S; // in scope 5 at $DIR/recursive.rs:+6:20: +6:21 + scope 6 { + debug a => _12; // in scope 6 at $DIR/recursive.rs:+6:20: +6:21 + let _14: &u32; // in scope 6 at $DIR/recursive.rs:+7:24: +7:27 + scope 7 { + debug num => _14; // in scope 7 at $DIR/recursive.rs:+7:24: +7:27 + } + } + } + } + } + } + + bb0: { + StorageLive(_1); // scope 0 at $DIR/recursive.rs:+1:9: +1:10 + Deinit(_1); // scope 0 at $DIR/recursive.rs:+1:13: +1:22 + ((_1 as Num).0: u32) = const 0_u32; // scope 0 at $DIR/recursive.rs:+1:13: +1:22 + discriminant(_1) = 1; // scope 0 at $DIR/recursive.rs:+1:13: +1:22 + StorageLive(_2); // scope 1 at $DIR/recursive.rs:+2:9: +2:10 + StorageLive(_3); // scope 1 at $DIR/recursive.rs:+2:20: +2:22 + StorageLive(_4); // scope 1 at $DIR/recursive.rs:+2:20: +2:22 + _4 = &_1; // scope 1 at $DIR/recursive.rs:+2:20: +2:22 + _3 = &(*_4); // scope 1 at $DIR/recursive.rs:+2:20: +2:22 + Deinit(_2); // scope 1 at $DIR/recursive.rs:+2:13: +2:23 + ((_2 as Rec).0: &S) = move _3; // scope 1 at $DIR/recursive.rs:+2:13: +2:23 + discriminant(_2) = 0; // scope 1 at $DIR/recursive.rs:+2:13: +2:23 + StorageDead(_3); // scope 1 at $DIR/recursive.rs:+2:22: +2:23 + StorageDead(_4); // scope 1 at $DIR/recursive.rs:+2:23: +2:24 + StorageLive(_5); // scope 2 at $DIR/recursive.rs:+3:9: +3:10 + StorageLive(_6); // scope 2 at $DIR/recursive.rs:+3:20: +3:22 + StorageLive(_7); // scope 2 at $DIR/recursive.rs:+3:20: +3:22 + _7 = &_2; // scope 2 at $DIR/recursive.rs:+3:20: +3:22 + _6 = &(*_7); // scope 2 at $DIR/recursive.rs:+3:20: +3:22 + Deinit(_5); // scope 2 at $DIR/recursive.rs:+3:13: +3:23 + ((_5 as Rec).0: &S) = move _6; // scope 2 at $DIR/recursive.rs:+3:13: +3:23 + discriminant(_5) = 0; // scope 2 at $DIR/recursive.rs:+3:13: +3:23 + StorageDead(_6); // scope 2 at $DIR/recursive.rs:+3:22: +3:23 + StorageDead(_7); // scope 2 at $DIR/recursive.rs:+3:23: +3:24 + StorageLive(_8); // scope 3 at $DIR/recursive.rs:+4:9: +4:10 + _9 = discriminant(_5); // scope 3 at $DIR/recursive.rs:+4:19: +4:20 + switchInt(move _9) -> [0_isize: bb2, otherwise: bb1]; // scope 3 at $DIR/recursive.rs:+4:13: +4:20 + } + + bb1: { + StorageLive(_17); // scope 3 at $DIR/recursive.rs:+12:14: +12:35 + _17 = exit(const 0_i32); // scope 3 at $DIR/recursive.rs:+12:14: +12:35 + // mir::Constant + // + span: $DIR/recursive.rs:21:14: 21:32 + // + literal: Const { ty: fn(i32) -> ! {exit}, val: Value() } + } + + bb2: { + StorageLive(_10); // scope 3 at $DIR/recursive.rs:+5:16: +5:17 + _10 = ((_5 as Rec).0: &S); // scope 3 at $DIR/recursive.rs:+5:16: +5:17 + _11 = discriminant((*_10)); // scope 5 at $DIR/recursive.rs:+5:28: +5:29 + switchInt(move _11) -> [0_isize: bb4, otherwise: bb3]; // scope 5 at $DIR/recursive.rs:+5:22: +5:29 + } + + bb3: { + StorageLive(_16); // scope 5 at $DIR/recursive.rs:+10:18: +10:39 + _16 = exit(const 0_i32); // scope 5 at $DIR/recursive.rs:+10:18: +10:39 + // mir::Constant + // + span: $DIR/recursive.rs:19:18: 19:36 + // + literal: Const { ty: fn(i32) -> ! {exit}, val: Value() } + } + + bb4: { + StorageLive(_12); // scope 5 at $DIR/recursive.rs:+6:20: +6:21 + _12 = &(((*_10) as Rec).0: &S); // scope 5 at $DIR/recursive.rs:+6:20: +6:21 + _18 = deref_copy (*_12); // scope 6 at $DIR/recursive.rs:+6:32: +6:33 + _13 = discriminant((*_18)); // scope 6 at $DIR/recursive.rs:+6:32: +6:33 + switchInt(move _13) -> [1_isize: bb6, otherwise: bb5]; // scope 6 at $DIR/recursive.rs:+6:26: +6:33 + } + + bb5: { + StorageLive(_15); // scope 6 at $DIR/recursive.rs:+8:22: +8:43 + _15 = exit(const 0_i32); // scope 6 at $DIR/recursive.rs:+8:22: +8:43 + // mir::Constant + // + span: $DIR/recursive.rs:17:22: 17:40 + // + literal: Const { ty: fn(i32) -> ! {exit}, val: Value() } + } + + bb6: { + StorageLive(_14); // scope 6 at $DIR/recursive.rs:+7:24: +7:27 + _19 = deref_copy (*_12); // scope 6 at $DIR/recursive.rs:+7:24: +7:27 + _14 = &(((*_19) as Num).0: u32); // scope 6 at $DIR/recursive.rs:+7:24: +7:27 +- _8 = (*_14); // scope 7 at $DIR/recursive.rs:+7:32: +7:36 ++ _8 = const 0_u32; // scope 7 at $DIR/recursive.rs:+7:32: +7:36 + StorageDead(_14); // scope 6 at $DIR/recursive.rs:+7:35: +7:36 + StorageDead(_12); // scope 5 at $DIR/recursive.rs:+9:13: +9:14 + StorageDead(_10); // scope 3 at $DIR/recursive.rs:+11:9: +11:10 + _0 = const (); // scope 0 at $DIR/recursive.rs:+0:11: +14:2 + StorageDead(_8); // scope 3 at $DIR/recursive.rs:+14:1: +14:2 + StorageDead(_5); // scope 2 at $DIR/recursive.rs:+14:1: +14:2 + StorageDead(_2); // scope 1 at $DIR/recursive.rs:+14:1: +14:2 + StorageDead(_1); // scope 0 at $DIR/recursive.rs:+14:1: +14:2 + return; // scope 0 at $DIR/recursive.rs:+14:2: +14:2 + } + } + diff --git a/src/test/mir-opt/dataflow-const-prop/recursive.rs b/src/test/mir-opt/dataflow-const-prop/recursive.rs new file mode 100644 index 00000000000..0eda1239ecd --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/recursive.rs @@ -0,0 +1,23 @@ +// unit-test: DataflowConstProp + +enum S<'a> { + Rec(&'a S<'a>), + Num(u32), +} + +// EMIT_MIR recursive.main.DataflowConstProp.diff +fn main() { + let a = S::Num(0); + let b = S::Rec(&a); + let c = S::Rec(&b); + let d = match c { + S::Rec(b) => match b { + S::Rec(a) => match a { + S::Num(num) => *num, + _ => std::process::exit(0), + }, + _ => std::process::exit(0), + }, + _ => std::process::exit(0), + }; +} diff --git a/src/test/mir-opt/dataflow-const-prop/ref.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/ref.main.DataflowConstProp.diff new file mode 100644 index 00000000000..6af381617f9 --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/ref.main.DataflowConstProp.diff @@ -0,0 +1,89 @@ +- // MIR for `main` before DataflowConstProp ++ // MIR for `main` after DataflowConstProp + + fn main() -> () { + let mut _0: (); // return place in scope 0 at $DIR/ref.rs:+0:11: +0:11 + let _1: i32; // in scope 0 at $DIR/ref.rs:+1:9: +1:10 + let mut _4: bool; // in scope 0 at $DIR/ref.rs:+3:16: +3:43 + let mut _5: u32; // in scope 0 at $DIR/ref.rs:+3:16: +3:38 + let mut _6: u32; // in scope 0 at $DIR/ref.rs:+3:16: +3:34 + let mut _7: bool; // in scope 0 at $DIR/ref.rs:+3:16: +3:38 + let _8: &i32; // in scope 0 at $DIR/ref.rs:+3:58: +3:60 + let mut _10: i32; // in scope 0 at $DIR/ref.rs:+4:13: +4:15 + scope 1 { + debug a => _1; // in scope 1 at $DIR/ref.rs:+1:9: +1:10 + let _2: i32; // in scope 1 at $DIR/ref.rs:+2:9: +2:10 + scope 2 { + debug b => _2; // in scope 2 at $DIR/ref.rs:+2:9: +2:10 + let _3: &i32; // in scope 2 at $DIR/ref.rs:+3:9: +3:10 + scope 3 { + debug c => _3; // in scope 3 at $DIR/ref.rs:+3:9: +3:10 + let _9: i32; // in scope 3 at $DIR/ref.rs:+4:9: +4:10 + scope 4 { + debug d => _9; // in scope 4 at $DIR/ref.rs:+4:9: +4:10 + } + } + } + } + + bb0: { + StorageLive(_1); // scope 0 at $DIR/ref.rs:+1:9: +1:10 + _1 = const 0_i32; // scope 0 at $DIR/ref.rs:+1:13: +1:14 + StorageLive(_2); // scope 1 at $DIR/ref.rs:+2:9: +2:10 + _2 = const 0_i32; // scope 1 at $DIR/ref.rs:+2:13: +2:14 + StorageLive(_3); // scope 2 at $DIR/ref.rs:+3:9: +3:10 + StorageLive(_4); // scope 2 at $DIR/ref.rs:+3:16: +3:43 + StorageLive(_5); // scope 2 at $DIR/ref.rs:+3:16: +3:38 + StorageLive(_6); // scope 2 at $DIR/ref.rs:+3:16: +3:34 + _6 = id() -> bb1; // scope 2 at $DIR/ref.rs:+3:16: +3:34 + // mir::Constant + // + span: $DIR/ref.rs:7:16: 7:32 + // + literal: Const { ty: fn() -> u32 {id}, val: Value() } + } + + bb1: { +- _7 = Eq(const 2_u32, const 0_u32); // scope 2 at $DIR/ref.rs:+3:16: +3:38 +- assert(!move _7, "attempt to calculate the remainder of `{}` with a divisor of zero", _6) -> bb2; // scope 2 at $DIR/ref.rs:+3:16: +3:38 ++ _7 = const false; // scope 2 at $DIR/ref.rs:+3:16: +3:38 ++ assert(!const false, "attempt to calculate the remainder of `{}` with a divisor of zero", _6) -> bb2; // scope 2 at $DIR/ref.rs:+3:16: +3:38 + } + + bb2: { + _5 = Rem(move _6, const 2_u32); // scope 2 at $DIR/ref.rs:+3:16: +3:38 + StorageDead(_6); // scope 2 at $DIR/ref.rs:+3:37: +3:38 + _4 = Eq(move _5, const 0_u32); // scope 2 at $DIR/ref.rs:+3:16: +3:43 + StorageDead(_5); // scope 2 at $DIR/ref.rs:+3:42: +3:43 + switchInt(move _4) -> [false: bb4, otherwise: bb3]; // scope 2 at $DIR/ref.rs:+3:16: +3:43 + } + + bb3: { + _3 = &_1; // scope 2 at $DIR/ref.rs:+3:46: +3:48 + goto -> bb5; // scope 2 at $DIR/ref.rs:+3:13: +3:62 + } + + bb4: { + StorageLive(_8); // scope 2 at $DIR/ref.rs:+3:58: +3:60 + _8 = &_2; // scope 2 at $DIR/ref.rs:+3:58: +3:60 + _3 = &(*_8); // scope 2 at $DIR/ref.rs:+3:58: +3:60 + StorageDead(_8); // scope 2 at $DIR/ref.rs:+3:61: +3:62 + goto -> bb5; // scope 2 at $DIR/ref.rs:+3:13: +3:62 + } + + bb5: { + StorageDead(_4); // scope 2 at $DIR/ref.rs:+3:61: +3:62 + StorageLive(_9); // scope 3 at $DIR/ref.rs:+4:9: +4:10 + StorageLive(_10); // scope 3 at $DIR/ref.rs:+4:13: +4:15 +- _10 = (*_3); // scope 3 at $DIR/ref.rs:+4:13: +4:15 +- _9 = Add(move _10, const 1_i32); // scope 3 at $DIR/ref.rs:+4:13: +4:19 ++ _10 = const 0_i32; // scope 3 at $DIR/ref.rs:+4:13: +4:15 ++ _9 = const 1_i32; // scope 3 at $DIR/ref.rs:+4:13: +4:19 + StorageDead(_10); // scope 3 at $DIR/ref.rs:+4:18: +4:19 + _0 = const (); // scope 0 at $DIR/ref.rs:+0:11: +5:2 + StorageDead(_9); // scope 3 at $DIR/ref.rs:+5:1: +5:2 + StorageDead(_3); // scope 2 at $DIR/ref.rs:+5:1: +5:2 + StorageDead(_2); // scope 1 at $DIR/ref.rs:+5:1: +5:2 + StorageDead(_1); // scope 0 at $DIR/ref.rs:+5:1: +5:2 + return; // scope 0 at $DIR/ref.rs:+5:2: +5:2 + } + } + diff --git a/src/test/mir-opt/dataflow-const-prop/ref.rs b/src/test/mir-opt/dataflow-const-prop/ref.rs new file mode 100644 index 00000000000..6da613fc516 --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/ref.rs @@ -0,0 +1,9 @@ +// unit-test: DataflowConstProp + +// EMIT_MIR ref.main.DataflowConstProp.diff +fn main() { + let a = 0; + let b = 0; + let c = if std::process::id() % 2 == 0 { &a } else { &b }; + let d = *c + 1; +} diff --git a/src/test/mir-opt/dataflow-const-prop/ref_mut.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/ref_mut.main.DataflowConstProp.diff new file mode 100644 index 00000000000..fe5bdd07d4a --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/ref_mut.main.DataflowConstProp.diff @@ -0,0 +1,77 @@ +- // MIR for `main` before DataflowConstProp ++ // MIR for `main` after DataflowConstProp + + fn main() -> () { + let mut _0: (); // return place in scope 0 at $DIR/ref_mut.rs:+0:11: +0:11 + let mut _1: i32; // in scope 0 at $DIR/ref_mut.rs:+1:9: +1:14 + let mut _7: &i32; // in scope 0 at $DIR/ref_mut.rs:+9:10: +9:12 + let _8: &i32; // in scope 0 at $DIR/ref_mut.rs:+9:10: +9:12 + let _9: i32; // in scope 0 at $DIR/ref_mut.rs:+9:11: +9:12 + scope 1 { + debug a => _1; // in scope 1 at $DIR/ref_mut.rs:+1:9: +1:14 + let _2: &mut i32; // in scope 1 at $DIR/ref_mut.rs:+2:9: +2:10 + scope 2 { + debug b => _2; // in scope 2 at $DIR/ref_mut.rs:+2:9: +2:10 + let _3: i32; // in scope 2 at $DIR/ref_mut.rs:+4:9: +4:10 + scope 3 { + debug c => _3; // in scope 3 at $DIR/ref_mut.rs:+4:9: +4:10 + let _4: i32; // in scope 3 at $DIR/ref_mut.rs:+6:9: +6:10 + scope 4 { + debug d => _4; // in scope 4 at $DIR/ref_mut.rs:+6:9: +6:10 + let mut _5: &i32; // in scope 4 at $DIR/ref_mut.rs:+7:9: +7:14 + scope 5 { + debug e => _5; // in scope 5 at $DIR/ref_mut.rs:+7:9: +7:14 + let _6: &mut &i32; // in scope 5 at $DIR/ref_mut.rs:+8:9: +8:10 + scope 6 { + debug f => _6; // in scope 6 at $DIR/ref_mut.rs:+8:9: +8:10 + let _10: i32; // in scope 6 at $DIR/ref_mut.rs:+10:9: +10:10 + let mut _11: &i32; // in scope 6 at $DIR/ref_mut.rs:+9:10: +9:12 + scope 7 { + debug g => _10; // in scope 7 at $DIR/ref_mut.rs:+10:9: +10:10 + } + } + } + } + } + } + } + + bb0: { + StorageLive(_1); // scope 0 at $DIR/ref_mut.rs:+1:9: +1:14 + _1 = const 0_i32; // scope 0 at $DIR/ref_mut.rs:+1:17: +1:18 + StorageLive(_2); // scope 1 at $DIR/ref_mut.rs:+2:9: +2:10 + _2 = &mut _1; // scope 1 at $DIR/ref_mut.rs:+2:13: +2:19 + (*_2) = const 1_i32; // scope 2 at $DIR/ref_mut.rs:+3:5: +3:11 + StorageLive(_3); // scope 2 at $DIR/ref_mut.rs:+4:9: +4:10 + _3 = _1; // scope 2 at $DIR/ref_mut.rs:+4:13: +4:14 + StorageLive(_4); // scope 3 at $DIR/ref_mut.rs:+6:9: +6:10 + _4 = const 0_i32; // scope 3 at $DIR/ref_mut.rs:+6:13: +6:14 + StorageLive(_5); // scope 4 at $DIR/ref_mut.rs:+7:9: +7:14 + _5 = &_4; // scope 4 at $DIR/ref_mut.rs:+7:17: +7:19 + StorageLive(_6); // scope 5 at $DIR/ref_mut.rs:+8:9: +8:10 + _6 = &mut _5; // scope 5 at $DIR/ref_mut.rs:+8:13: +8:19 + StorageLive(_7); // scope 6 at $DIR/ref_mut.rs:+9:10: +9:12 + StorageLive(_8); // scope 6 at $DIR/ref_mut.rs:+9:10: +9:12 + _11 = const main::promoted[0]; // scope 6 at $DIR/ref_mut.rs:+9:10: +9:12 + // mir::Constant + // + span: $DIR/ref_mut.rs:13:10: 13:12 + // + literal: Const { ty: &i32, val: Unevaluated(main, [], Some(promoted[0])) } + _8 = &(*_11); // scope 6 at $DIR/ref_mut.rs:+9:10: +9:12 + _7 = &(*_8); // scope 6 at $DIR/ref_mut.rs:+9:10: +9:12 + (*_6) = move _7; // scope 6 at $DIR/ref_mut.rs:+9:5: +9:12 + StorageDead(_7); // scope 6 at $DIR/ref_mut.rs:+9:11: +9:12 + StorageDead(_8); // scope 6 at $DIR/ref_mut.rs:+9:12: +9:13 + StorageLive(_10); // scope 6 at $DIR/ref_mut.rs:+10:9: +10:10 + _10 = (*_5); // scope 6 at $DIR/ref_mut.rs:+10:13: +10:15 + _0 = const (); // scope 0 at $DIR/ref_mut.rs:+0:11: +11:2 + StorageDead(_10); // scope 6 at $DIR/ref_mut.rs:+11:1: +11:2 + StorageDead(_6); // scope 5 at $DIR/ref_mut.rs:+11:1: +11:2 + StorageDead(_5); // scope 4 at $DIR/ref_mut.rs:+11:1: +11:2 + StorageDead(_4); // scope 3 at $DIR/ref_mut.rs:+11:1: +11:2 + StorageDead(_3); // scope 2 at $DIR/ref_mut.rs:+11:1: +11:2 + StorageDead(_2); // scope 1 at $DIR/ref_mut.rs:+11:1: +11:2 + StorageDead(_1); // scope 0 at $DIR/ref_mut.rs:+11:1: +11:2 + return; // scope 0 at $DIR/ref_mut.rs:+11:2: +11:2 + } + } + diff --git a/src/test/mir-opt/dataflow-const-prop/ref_mut.rs b/src/test/mir-opt/dataflow-const-prop/ref_mut.rs new file mode 100644 index 00000000000..8b2baf7676d --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/ref_mut.rs @@ -0,0 +1,15 @@ +// unit-test: DataflowConstProp + +// EMIT_MIR ref_mut.main.DataflowConstProp.diff +fn main() { + let mut a = 0; + let b = &mut a; + *b = 1; + let c = a; + + let d = 0; + let mut e = &d; + let f = &mut e; + *f = &1; + let g = *e; +} diff --git a/src/test/mir-opt/dataflow-const-prop/terminator.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/terminator.main.DataflowConstProp.diff new file mode 100644 index 00000000000..1d01d4fd2e7 --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/terminator.main.DataflowConstProp.diff @@ -0,0 +1,40 @@ +- // MIR for `main` before DataflowConstProp ++ // MIR for `main` after DataflowConstProp + + fn main() -> () { + let mut _0: (); // return place in scope 0 at $DIR/terminator.rs:+0:11: +0:11 + let _1: i32; // in scope 0 at $DIR/terminator.rs:+1:9: +1:10 + let _2: (); // in scope 0 at $DIR/terminator.rs:+2:5: +2:15 + let mut _3: i32; // in scope 0 at $DIR/terminator.rs:+2:9: +2:14 + let mut _4: i32; // in scope 0 at $DIR/terminator.rs:+2:9: +2:10 + scope 1 { + debug a => _1; // in scope 1 at $DIR/terminator.rs:+1:9: +1:10 + } + + bb0: { + StorageLive(_1); // scope 0 at $DIR/terminator.rs:+1:9: +1:10 + _1 = const 1_i32; // scope 0 at $DIR/terminator.rs:+1:13: +1:14 + StorageLive(_2); // scope 1 at $DIR/terminator.rs:+2:5: +2:15 + StorageLive(_3); // scope 1 at $DIR/terminator.rs:+2:9: +2:14 + StorageLive(_4); // scope 1 at $DIR/terminator.rs:+2:9: +2:10 +- _4 = _1; // scope 1 at $DIR/terminator.rs:+2:9: +2:10 +- _3 = Add(move _4, const 1_i32); // scope 1 at $DIR/terminator.rs:+2:9: +2:14 ++ _4 = const 1_i32; // scope 1 at $DIR/terminator.rs:+2:9: +2:10 ++ _3 = const 2_i32; // scope 1 at $DIR/terminator.rs:+2:9: +2:14 + StorageDead(_4); // scope 1 at $DIR/terminator.rs:+2:13: +2:14 +- _2 = foo(move _3) -> bb1; // scope 1 at $DIR/terminator.rs:+2:5: +2:15 ++ _2 = foo(const 2_i32) -> bb1; // scope 1 at $DIR/terminator.rs:+2:5: +2:15 + // mir::Constant + // + span: $DIR/terminator.rs:8:5: 8:8 + // + literal: Const { ty: fn(i32) {foo}, val: Value() } + } + + bb1: { + StorageDead(_3); // scope 1 at $DIR/terminator.rs:+2:14: +2:15 + StorageDead(_2); // scope 1 at $DIR/terminator.rs:+2:15: +2:16 + _0 = const (); // scope 0 at $DIR/terminator.rs:+0:11: +3:2 + StorageDead(_1); // scope 0 at $DIR/terminator.rs:+3:1: +3:2 + return; // scope 0 at $DIR/terminator.rs:+3:2: +3:2 + } + } + diff --git a/src/test/mir-opt/dataflow-const-prop/terminator.rs b/src/test/mir-opt/dataflow-const-prop/terminator.rs new file mode 100644 index 00000000000..e96b25de63c --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/terminator.rs @@ -0,0 +1,9 @@ +// unit-test: DataflowConstProp + +fn foo(n: i32) {} + +// EMIT_MIR terminator.main.DataflowConstProp.diff +fn main() { + let a = 1; + foo(a + 1); +} diff --git a/src/test/mir-opt/dataflow-const-prop/unnamed.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/unnamed.main.DataflowConstProp.diff new file mode 100644 index 00000000000..684c661fc11 --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/unnamed.main.DataflowConstProp.diff @@ -0,0 +1,38 @@ +- // MIR for `main` before DataflowConstProp ++ // MIR for `main` after DataflowConstProp + + fn main() -> () { + let mut _0: (); // return place in scope 0 at $DIR/unnamed.rs:+0:11: +0:11 + let mut _1: i32; // in scope 0 at $DIR/unnamed.rs:+1:9: +1:14 + let mut _2: i32; // in scope 0 at $DIR/unnamed.rs:+3:10: +3:11 + let mut _3: &i32; // in scope 0 at $DIR/unnamed.rs:+3:10: +3:11 + scope 1 { + debug a => _1; // in scope 1 at $DIR/unnamed.rs:+1:9: +1:14 + } + + bb0: { + StorageLive(_1); // scope 0 at $DIR/unnamed.rs:+1:9: +1:14 + _1 = const 0_i32; // scope 0 at $DIR/unnamed.rs:+1:17: +1:18 +- _1 = Add(_1, const 1_i32); // scope 1 at $DIR/unnamed.rs:+2:5: +2:11 ++ _1 = const 1_i32; // scope 1 at $DIR/unnamed.rs:+2:5: +2:11 + StorageLive(_2); // scope 1 at $DIR/unnamed.rs:+3:10: +3:11 + StorageLive(_3); // scope 1 at $DIR/unnamed.rs:+3:10: +3:11 + _3 = const {alloc1: &i32}; // scope 1 at $DIR/unnamed.rs:+3:10: +3:11 + // mir::Constant + // + span: $DIR/unnamed.rs:9:10: 9:11 + // + literal: Const { ty: &i32, val: Value(Scalar(alloc1)) } + _2 = (*_3); // scope 1 at $DIR/unnamed.rs:+3:10: +3:11 +- _1 = Add(_1, move _2); // scope 1 at $DIR/unnamed.rs:+3:5: +3:11 ++ _1 = Add(const 1_i32, move _2); // scope 1 at $DIR/unnamed.rs:+3:5: +3:11 + StorageDead(_2); // scope 1 at $DIR/unnamed.rs:+3:10: +3:11 + StorageDead(_3); // scope 1 at $DIR/unnamed.rs:+3:11: +3:12 + _0 = const (); // scope 0 at $DIR/unnamed.rs:+0:11: +4:2 + StorageDead(_1); // scope 0 at $DIR/unnamed.rs:+4:1: +4:2 + return; // scope 0 at $DIR/unnamed.rs:+4:2: +4:2 + } + } + + alloc1 (static: g, size: 4, align: 4) { + 02 00 00 00 │ .... + } + diff --git a/src/test/mir-opt/dataflow-const-prop/unnamed.rs b/src/test/mir-opt/dataflow-const-prop/unnamed.rs new file mode 100644 index 00000000000..91f5a9e1c12 --- /dev/null +++ b/src/test/mir-opt/dataflow-const-prop/unnamed.rs @@ -0,0 +1,10 @@ +// unit-test: DataflowConstProp + +static g: i32 = 2; + +// EMIT_MIR unnamed.main.DataflowConstProp.diff +fn main() { + let mut a = 0; + a += 1; + a += g; +}