rust-lang/rust · #163338
MIR move elimination [4/6]: MoveElimination
compiler/rustc_index/src/interval.rs16 + / 0 −
@@ -374,6 +374,12 @@ impl<R: Idx, C: Step + Idx> SparseIntervalMatrix<R, C> { self.rows.iter_enumerated() } + pub fn clear_row(&mut self, row: R) {+ if let Some(row) = self.rows.get_mut(row) {+ row.clear();+ }+ }+ fn ensure_row(&mut self, row: R) -> &mut IntervalSet<C> { self.rows.ensure_contains_elem(row, || IntervalSet::new(self.column_size)) }@@ -397,6 +403,16 @@ impl<R: Idx, C: Step + Idx> SparseIntervalMatrix<R, C> { write_row.union(read_row) } + pub fn disjoint_rows(&self, a: R, b: R) -> bool+ where+ C: Step,+ {+ let (Some(a), Some(b)) = (self.rows.get(a), self.rows.get(b)) else {+ return true;+ };+ a.disjoint(b)+ }+ pub fn insert_all_into_row(&mut self, row: R) { self.ensure_row(row).insert_all(); }compiler/rustc_middle/src/mir/syntax.rs0 + / 4 −
@@ -127,7 +127,6 @@ pub enum RuntimePhase { /// disallowed: /// * [`TerminatorKind::Yield`] /// * [`TerminatorKind::CoroutineDrop`]- /// * [`Rvalue::Aggregate`] for any `AggregateKind` except `Array` /// * [`Rvalue::CopyForDeref`] /// * [`PlaceElem::OpaqueCast`] /// * [`LocalInfo::DerefTemp`](super::LocalInfo::DerefTemp)@@ -1449,9 +1448,6 @@ pub enum Rvalue<'tcx> { /// This is needed because dataflow analysis needs to distinguish /// `dest = Foo { x: ..., y: ... }` from `dest.x = ...; dest.y = ...;` in the case that `Foo` /// has a destructor.- ///- /// Disallowed after deaggregation for all aggregate kinds except `Array` and `Coroutine`. After- /// coroutine lowering, `Coroutine` aggregate kinds are disallowed too. Aggregate(Box<AggregateKind<'tcx>>, IndexVec<FieldIdx, Operand<'tcx>>), /// A CopyForDeref is equivalent to a read from a place at thecompiler/rustc_middle/src/ty/adt.rs13 + / 0 −
@@ -212,6 +212,19 @@ impl<'tcx> AdtDef<'tcx> { self.0.0.repr } + /// Whether MIR may project into the fields of this ADT.+ ///+ /// MCP#838 bans field projections into SIMD types, including scalable+ /// vectors.+ ///+ /// Codegen requires `DynMetadata` to be manipulated as a whole, with its+ /// insides being treated as opaque..+ pub fn can_project_fields(self, tcx: TyCtxt<'tcx>) -> bool {+ !self.repr().simd()+ && !self.repr().scalable()+ && !tcx.is_lang_item(self.did(), LangItem::DynMetadata)+ }+ pub fn field_representing_type_info( self, tcx: TyCtxt<'tcx>,compiler/rustc_mir_transform/src/dest_prop.rs3 + / 1 −
@@ -155,7 +155,9 @@ pub(super) struct DestinationPropagation; impl<'tcx> crate::MirPass<'tcx> for DestinationPropagation { fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy {- PassPolicy::optional(ctx.mir_opt_level() >= 2)+ PassPolicy::optional(+ ctx.mir_opt_level() >= 2 && !ctx.opts.unstable_opts.mir_move_elimination,+ ) } #[tracing::instrument(level = "trace", skip(self, tcx, body))]compiler/rustc_mir_transform/src/lib.rs2 + / 0 −
@@ -165,6 +165,7 @@ declare_passes! { mod lower_slice_len : LowerSliceLenCalls; mod match_branches : MatchBranchSimplification; mod mentioned_items : MentionedItems;+ mod move_elimination : MoveElimination; mod multiple_return_terminators : MultipleReturnTerminators; mod post_drop_elaboration : CheckLiveDrops; mod prettify : ReorderBasicBlocks, ReorderLocals;@@ -770,6 +771,7 @@ pub(crate) fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<' &dead_store_elimination::DeadStoreElimination::Final, &dest_prop::DestinationPropagation, &tail_copy_to_move::TailCopyToMove,+ &move_elimination::MoveElimination, &simplify::SimplifyLocals::Final, &multiple_return_terminators::MultipleReturnTerminators, // Some cleanup necessary at least for LLVM and potentially other codegen backends.compiler/rustc_mir_transform/src/move_elimination.rsadded911 + / 0 −
@@ -0,0 +1,911 @@+//! Eliminates copies and moves by unifying MIR places whose allocation ranges+//! are disjoint.+//!+//! See RFC 3943 for the local-lifetime semantics that make this optimization+//! possible.+//!+//! # Motivation+//!+//! MIR building can insert a lot of redundant copies, and Rust code in general+//! often tends to move values around a lot. The result is a lot of assignments+//! of the form `dest = {move} src;` in MIR. MIR building for constants in+//! particular tends to create additional locals that are only used inside a+//! single block to shuffle a value around unnecessarily.+//!+//! Additionally, Rust constructs nested aggregates by repeatedly moving values+//! into fields. For example, a function may build an inner value in a local,+//! move it into an outer aggregate, then move that aggregate into the caller's+//! destination. If these intermediate source and destination places have+//! different addresses, each layer needs an actual copy or move of the bytes.+//!+//! LLVM cannot remove these copies when both the source and destination+//! addresses are observed because merging the allocations would be an+//! observable change: the program could see that two addresses which were+//! previously distinct have become the same. This pass removes the copies+//! earlier, while MIR still has the information needed to prove that the two+//! allocation ranges do not overlap.+//!+//! # Optimization+//!+//! The basis of this optimization is place unification. If the source and+//! destination of an assignment have the same address, then the assignment is a+//! no-op. The same idea applies to aggregate construction: if a field operand+//! is already located at the corresponding field of the destination, then the+//! aggregate assignment does not need to copy those fields.+//!+//! The pass represents each unification as a mapping from a local to the place+//! that should replace it. Mappings are transitive, so `_3` can be resolved+//! through `_2.1` to `_1.0.1` if earlier mappings established those+//! relationships.+//!+//! The mapping is built by scanning the MIR for assignment statements. For+//! simple `Use` assignments, it tries to unify the source and destination+//! places. For `Aggregate` assignments, it tries to map each field operand to+//! the corresponding field in the assignment destination. Once all mappings+//! have been chosen, they are applied with one rewrite pass over the body.+//!+//! # Constraints+//!+//! Adding a mapping must preserve these conditions:+//!+//! * At least one side of the candidate pair must be a bare local. The pass can+//! map a local to a place with projections, but it cannot map between two+//! places that both already have projections.+//!+//! * Any projections in the mapped place must be stable everywhere the local is+//! used. `Deref` and `Index` projections are rejected because they may refer+//! to different memory at different points in the function.+//!+//! * The allocation ranges of the source and destination places must not+//! overlap. This is checked using `PreciseLiveness`, which computes the+//! points where each local must have a distinct allocation. The non-overlap+//! proof is required so that the operational semantics can allow both places+//! to have the same address.+//!+//! * Special-use locals such as arguments and the return place must keep their+//! roles. Temps may be mapped into an argument or return place, but two+//! special-use locals are not mapped into each other.+//!+//! * Some locals are used in contexts where projections cannot be added, such+//! as `Index` projections. These locals may only be replaced by another bare+//! local.+//!+//! # Storage lifetimes+//!+//! The original storage markers no longer describe the merged locals, so they+//! are removed. Locals without storage markers have storage for the full function.+//!+//! # Aliasing fixup+//!+//! MIR assignments currently require source and destination places not to+//! overlap for types that are not treated as scalars in codegen. After local+//! unification, some assignments may violate that invariant, so a final phase+//! rewrites them into a form codegen can handle. For each assignment:+//!+//! * Self-assignments, such as `_1 = _1`, are deleted.+//!+//! * Simple `Use` assignments whose source and destination overlap but are not+//! identical are routed through a temporary: the source is read into the+//! temporary first, and the temporary is then moved into the destination.+//!+//! * Aggregate assignments with any operand that aliases the destination are+//! decomposed into per-field assignments. Field self-assignments are dropped.+//! Other aliasing fields are read into temporaries first, then all+//! destination fields are written. For enum aggregates the discriminant is+//! set after fields are written.+//!+//! * Other rvalues, such as `Repeat` and `Cast`, are hoisted into a temporary+//! if any place they access aliases the destination.+//!+//! * Rvalues that operate only on scalar types, such as binary and unary ops,+//! `Discriminant`, `Ref`, and `RawPtr`, are left untouched because their+//! codegen does not rely on the no-aliasing assumption.++use rustc_abi::{ExternAbi, FieldIdx, VariantIdx};+use rustc_const_eval::util::most_packed_projection;+use rustc_index::IndexVec;+use rustc_index::bit_set::DenseBitSet;+use rustc_index::interval::SparseIntervalMatrix;+use rustc_middle::mir::visit::{MutVisitor, NonUseContext, PlaceContext, VisitPlacesWith, Visitor};+use rustc_middle::mir::*;+use rustc_middle::ty::{Ty, TyCtxt};+use rustc_mir_dataflow::impls::{SplitPointIndex, dump_liveness_matrix, liveness_matrix};+use rustc_mir_dataflow::points::DenseLocationMap;+use tracing::{debug, trace};++use crate::PassPolicy;+use crate::patch::MirPatch;++pub(super) struct MoveElimination;++impl<'tcx> crate::MirPass<'tcx> for MoveElimination {+ fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy {+ PassPolicy::optional(+ ctx.mir_opt_level() >= 2 && ctx.opts.unstable_opts.mir_move_elimination,+ )+ }++ #[tracing::instrument(level = "trace", skip(self, tcx, body))]+ fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {+ let def_id = body.source.def_id();+ trace!(?def_id);++ let points = DenseLocationMap::new(body);+ let mut liveness_matrix =+ liveness_matrix(tcx, body, &points, Some("MoveElimination.liveness"));++ dump_liveness_matrix(tcx, body, "MoveElimination.pre-liveness", &points, &liveness_matrix);++ let unprojectable_locals = UnprojectableLocals::find(body);+ trace!(?unprojectable_locals);++ let rust_call_tuples = find_rust_call_tuples(tcx, body);+ trace!(?rust_call_tuples);++ let remapped_locals = PlaceUnification::run(+ tcx,+ body,+ &mut liveness_matrix,+ unprojectable_locals,+ rust_call_tuples,+ );++ apply_mappings(tcx, body, &remapped_locals);++ dump_liveness_matrix(tcx, body, "MoveElimination.post-liveness", &points, &liveness_matrix);++ apply_alias_fixup(tcx, body);+ }+}++////////////////////////////////////////////////////////////////////////////////+// Unprojectable locals++/// Set of locals which can only be replaced with another local, instead of+/// an arbitrary place. This is usually because it is used directly as a+/// `Local` outside of a place (e.g. `Index` projections).+#[derive(Debug)]+struct UnprojectableLocals {+ locals: DenseBitSet<Local>,+}++impl UnprojectableLocals {+ fn find(body: &Body<'_>) -> DenseBitSet<Local> {+ let mut out = Self { locals: DenseBitSet::new_empty(body.local_decls.len()) };++ // Arguments and return places have fixed roles and cannot be replaced+ // with projected locals.+ out.locals.insert(RETURN_PLACE);+ for arg in body.args_iter() {+ out.locals.insert(arg);+ }++ out.visit_body(body);+ out.locals+ }+}++impl<'tcx> Visitor<'tcx> for UnprojectableLocals {+ fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {+ // We can't add more projections before a first position Deref projection.+ if place.is_indirect() {+ trace!(+ "unprojectable local {:?} due to use as deref base at {location:?}",+ place.local+ );+ self.locals.insert(place.local);+ }++ // Only call visit_local for projections, not the base local.+ self.visit_projection(place.as_ref(), context, location);+ }++ fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) {+ // Storage markers are removed.+ if let PlaceContext::NonUse(NonUseContext::StorageLive | NonUseContext::StorageDead) =+ context+ {+ return;+ }++ // If this is reached, it means that this is a bare local used outside+ // of a place, which means it cannot be replaced with a projection of+ // another local.+ trace!("unprojectable local {local:?} at {location:?} ({context:?})");+ self.locals.insert(local);+ }+}++////////////////////////////////////////////////////////////////////////////////+// "rust-call" tuple handling++/// Search for tuple locals passed to calls using the "rust-call" ABI.+///+/// For rust-call ABI calls, caller-side MIR passes the logical arguments as a+/// tuple operand. We want to avoid remapping other locals into fields of that+/// tuple, especially if one of those locals is borrowed.+///+/// Since the tuple itself is never borrowed, it is trivial for LLVM alias+/// analysis to see that accesses to one argument do not affect the others, but+/// merging the arguments into tuple fields from the start can hide that+/// independence.+fn find_rust_call_tuples<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> DenseBitSet<Local> {+ let mut rust_call_tuples = DenseBitSet::new_empty(body.local_decls.len());++ for block in body.basic_blocks.iter() {+ let terminator = block.terminator();+ let (func, args) = match &terminator.kind {+ TerminatorKind::Call { func, args, .. }+ | TerminatorKind::TailCall { func, args, .. } => (func, args),+ _ => continue,+ };++ let sig = func.ty(&body.local_decls, tcx).fn_sig(tcx);+ if sig.abi() != ExternAbi::RustCall {+ continue;+ }++ let arg_tuple = args.last().expect("rust-call ABI requires a tuple argument");+ let (Operand::Copy(place) | Operand::Move(place)) = arg_tuple.node else {+ continue;+ };+ if let Some(local) = place.as_local() {+ rust_call_tuples.insert(local);+ }+ }++ rust_call_tuples+}++////////////////////////////////////////////////////////////////////////////////+// Local unification++struct PlaceUnification<'a, 'tcx> {+ tcx: TyCtxt<'tcx>,+ body: &'a Body<'tcx>,+ liveness_matrix: &'a mut SparseIntervalMatrix<Local, SplitPointIndex>,+ unprojectable_locals: DenseBitSet<Local>,+ rust_call_tuples: DenseBitSet<Local>,+ remapped_locals: IndexVec<Local, Option<Place<'tcx>>>,+}++impl<'tcx> PlaceUnification<'_, 'tcx> {+ fn run(+ tcx: TyCtxt<'tcx>,+ body: &Body<'tcx>,+ liveness_matrix: &mut SparseIntervalMatrix<Local, SplitPointIndex>,+ unprojectable_locals: DenseBitSet<Local>,+ rust_call_tuples: DenseBitSet<Local>,+ ) -> IndexVec<Local, Option<Place<'tcx>>> {+ let mut visitor = PlaceUnification {+ tcx,+ body,+ liveness_matrix,+ unprojectable_locals,+ rust_call_tuples,+ remapped_locals: IndexVec::from_elem_n(None, body.local_decls.len()),+ };+ visitor.visit_body(body);++ // Finalize the mappings by transitively resolving all locals to their+ // new final place.+ for local in visitor.remapped_locals.indices() {+ if let Some(place) = visitor.remapped_locals[local] {+ let place = visitor.resolve_place(place);+ visitor.remapped_locals[local] = Some(place);+ trace!("Remapped {local:?} to {place:?}");+ }+ }++ visitor.remapped_locals+ }++ #[tracing::instrument(ret, level = "trace", skip(self))]+ fn resolve_place(&self, mut place: Place<'tcx>) -> Place<'tcx> {+ while let Some(new_place) = self.remapped_locals[place.local] {+ place = new_place.project_deeper(place.projection, self.tcx);+ }+ place+ }++ #[tracing::instrument(ret, level = "trace", skip(self))]+ fn can_unify_places(&self, a: Place<'tcx>, b: Place<'tcx>) -> Option<(Local, Place<'tcx>)> {+ let a = self.resolve_place(a);+ let b = self.resolve_place(b);++ if a.local == b.local {+ if a.projection != b.projection {+ trace!("cannot unify same local with different projections");+ }+ return None;+ }++ if self.rust_call_tuples.contains(a.local) || self.rust_call_tuples.contains(b.local) {+ trace!("cannot unify {a:?} and {b:?} involving a rust-call tuple argument");+ return None;+ }++ let (local, place) = match (a.as_local(), b.as_local()) {+ (None, None) => {+ trace!("cannot unify 2 places that both have projections");+ return None;+ }+ (None, Some(b)) => {+ if self.unprojectable_locals.contains(b) {+ trace!("cannot unify {b:?} which cannot be projected");+ return None;+ }+ (b, a)+ }+ (Some(a), None) => {+ if self.unprojectable_locals.contains(a) {+ trace!("cannot unify {a:?} which cannot be projected");+ return None;+ }+ (a, b)+ }+ (Some(a), Some(b)) => match (self.body.local_kind(a), self.body.local_kind(b)) {+ (+ LocalKind::Arg | LocalKind::ReturnPointer,+ LocalKind::Arg | LocalKind::ReturnPointer,+ ) => {+ trace!("cannot unify {a:?} and {b:?} which are both arguments or return place");+ return None;+ }+ (LocalKind::Arg | LocalKind::ReturnPointer, LocalKind::Temp) => (b, a.into()),+ (LocalKind::Temp, _) => (a, b.into()),+ },+ };++ if most_packed_projection(self.tcx, &self.body.local_decls, place).is_some() {+ trace!("cannot unify {place:?} which has packed field projections");+ return None;+ }++ if !self.liveness_matrix.disjoint_rows(local, place.local) {+ trace!("cannot unify {a:?} and {b:?} which have overlapping live ranges");+ return None;+ }++ // FIXME(#112651): This can be removed afterwards.+ let local_ty = self.body.local_decls[local].ty;+ let place_ty = place.ty(&self.body.local_decls, self.tcx).ty;+ if local_ty != place_ty {+ trace!(+ "cannot unify {a:?} and {b:?} which have different types due to subtyping ({local_ty:?} vs {place_ty:?})"+ );+ return None;+ }++ Some((local, place))+ }++ #[tracing::instrument(level = "trace", skip(self))]+ fn remap_local(&mut self, local: Local, place: Place<'tcx>) {+ debug_assert!(self.remapped_locals[local].is_none());+ self.remapped_locals[local] = Some(place);++ self.liveness_matrix.union_rows(local, place.local);+ self.liveness_matrix.clear_row(local);++ // If the original local was unprojectable then this now also applies to+ // the mapped local.+ if self.unprojectable_locals.contains(local) {+ debug_assert!(place.projection.is_empty());+ self.unprojectable_locals.insert(place.local);+ }+ }++ fn visit_aggregate_assign(+ &mut self,+ dest: Place<'tcx>,+ project_field: impl Fn(TyCtxt<'tcx>, Place<'tcx>, FieldIdx, Ty<'tcx>) -> Place<'tcx>,+ operands: &IndexVec<FieldIdx, Operand<'tcx>>,+ location: Location,+ ) {+ // Attempt to unify each field operand with the corresponding field in+ // the destination place.+ let mut candidates = vec![];+ for (idx, operand) in operands.iter_enumerated() {+ let (Operand::Copy(src) | Operand::Move(src)) = *operand else {+ continue;+ };+ let Some(src) = src.as_local() else {+ continue;+ };+ let dest = project_field(self.tcx, dest, idx, self.body.local_decls[src].ty);+ trace!("Attempting to unify {dest:?} and {src:?} at {location:?}");+ if let Some((local, place)) = self.can_unify_places(dest, src.into()) {+ candidates.push((local, place));+ }+ }++ // Do the actual remapping *after* checking for live range overlaps.+ //+ // If we tried to do this immediately after `can_unify_places` for each+ // operand then this would only allow unifying with one operand: once+ // the first operand's live range is unified with the destination, all+ // remaining inputs conflict with the combined live range.+ //+ // By deferring remapping until all inputs are checked, we can unify+ // with all eligible inputs instead of just one. This is sound because+ // they will each be mapped to a disjoint field of the destination+ // place.+ for (local, place) in candidates {+ // Repeated operands can produce multiple candidates for the same+ // local. Only keep the first mapping.+ if self.remapped_locals[local].is_some() {+ continue;+ }+ self.remap_local(local, place);+ }+ }+}++/// Since we are replacing all uses of a local with another place, we need to+/// ensure that the projections on that place are stable no matter where it is+/// used in the body. Additional this local may be used in debuginfo, so ensure+/// that the projections are compatible with usage in debuginfo.+fn check_projections(place: Place<'_>) -> bool {+ place.projection.iter().all(|elem| elem.is_stable_offset() && elem.can_use_in_debuginfo())+}++impl<'tcx> Visitor<'tcx> for PlaceUnification<'_, 'tcx> {+ fn visit_assign(&mut self, dest: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {+ if !check_projections(*dest) {+ return;+ }+ match rvalue {+ Rvalue::Use(Operand::Copy(src) | Operand::Move(src), _) => {+ if !check_projections(*src) {+ return;+ }++ trace!("Attempting to unify {dest:?} and {src:?} at {location:?}");+ if let Some((local, place)) = self.can_unify_places(*src, *dest) {+ self.remap_local(local, place);+ }+ }+ Rvalue::Aggregate(aggregate_kind, operands) => match *aggregate_kind {+ AggregateKind::Array(_) => self.visit_aggregate_assign(+ *dest,+ |tcx, place, field_idx, _field_ty| {+ place.project_deeper(+ &[PlaceElem::ConstantIndex {+ offset: field_idx.as_u32().into(),+ min_length: field_idx.as_u32() as u64 + 1,+ from_end: false,+ }],+ tcx,+ )+ },+ operands,+ location,+ ),+ AggregateKind::Tuple => self.visit_aggregate_assign(+ *dest,+ |tcx, place, field_idx, field_ty| {+ place.project_deeper(&[PlaceElem::Field(field_idx, field_ty)], tcx)+ },+ operands,+ location,+ ),+ AggregateKind::Adt(_, _, _, _, Some(union_field_idx)) => {+ debug_assert_eq!(operands.len(), 1);+ self.visit_aggregate_assign(+ *dest,+ |tcx, place, _, field_ty| {+ place+ .project_deeper(&[PlaceElem::Field(union_field_idx, field_ty)], tcx)+ },+ operands,+ location,+ )+ }+ AggregateKind::Adt(adt_did, var_idx, _, _, None) => {+ let def = self.tcx.adt_def(adt_did);+ if !def.can_project_fields(self.tcx) {+ return;+ }+ self.visit_aggregate_assign(+ *dest,+ |tcx, place, field_idx, field_ty| {+ if def.is_enum() {+ place.project_deeper(+ &[+ PlaceElem::Downcast(None, var_idx),+ PlaceElem::Field(field_idx, field_ty),+ ],+ tcx,+ )+ } else {+ place.project_deeper(&[PlaceElem::Field(field_idx, field_ty)], tcx)+ }+ },+ operands,+ location,+ )+ }+ _ => {}+ },+ _ => {}+ };+ }+}++////////////////////////////////////////////////////////////////////////////////+// Apply place mappings to the MIR body.++fn apply_mappings<'tcx>(+ tcx: TyCtxt<'tcx>,+ body: &mut Body<'tcx>,+ remapped_locals: &IndexVec<Local, Option<Place<'tcx>>>,+) {+ let mut rewriter = PlaceUpdater { tcx, remapped_locals };+ rewriter.visit_body_preserves_cfg(body);+}++struct PlaceUpdater<'a, 'tcx> {+ tcx: TyCtxt<'tcx>,+ remapped_locals: &'a IndexVec<Local, Option<Place<'tcx>>>,+}++impl<'tcx> MutVisitor<'tcx> for PlaceUpdater<'_, 'tcx> {+ fn tcx(&self) -> TyCtxt<'tcx> {+ self.tcx+ }++ fn visit_local(&mut self, local: &mut Local, context: PlaceContext, location: Location) {+ if let Some(new_place) = self.remapped_locals[*local] {+ trace!("replacing {local:?} with {new_place:?} at {location:?} ({context:?})");+ *local = new_place.as_local().expect("mapped place shouldn't have projections");+ }+ }++ fn visit_place(&mut self, place: &mut Place<'tcx>, context: PlaceContext, location: Location) {+ if let Some(new_place) = self.remapped_locals[place.local] {+ trace!("replacing {place:?} with {new_place:?} at {location:?} ({context:?})");+ *place = new_place.project_deeper(place.projection, self.tcx)+ }++ // Only call visit_local for projections, not the base local.+ if let Some(new_projection) = self.process_projection(&place.projection, location) {+ place.projection = self.tcx().mk_place_elems(&new_projection);+ }+ }++ fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {+ match statement.kind {+ // Remove storage lifetime markers because they no longer describe+ // the merged locals. The later storage reconstruction pass rebuilds+ // these markers for all locals, which can also shorten the storage+ // lifetimes of locals that were not merged.+ StatementKind::StorageDead(_) | StatementKind::StorageLive(_) => {+ statement.make_nop(true);+ return;+ }+ _ => {}+ }++ self.super_statement(statement, location);+ }+}++////////////////////////////////////////////////////////////////////////////////+// Aliasing assignment fixup+//+// MIR assignments currently do not allow source and destination to alias, so+// fix this in post-processing.++fn apply_alias_fixup<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {+ let mut patcher = MirPatch::new(body);+ let mut fixup = AliasFixup { tcx, local_decls: &body.local_decls, patcher: &mut patcher };+ for (block, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {+ fixup.visit_basic_block_data(block, data);+ }+ patcher.apply(body);+}++/// Returns whether 2 places alias, ignoring indirect places.+fn places_directly_alias<'tcx>(+ tcx: TyCtxt<'tcx>,+ local_decls: &IndexVec<Local, LocalDecl<'tcx>>,+ a: Place<'tcx>,+ b: Place<'tcx>,+) -> bool {+ // This function doesn't handle indirect aliasing.+ if a.local != b.local || a.is_indirect_first_projection() || b.is_indirect_first_projection() {+ return false;+ }++ for ((prefix, elem_a), (_, elem_b)) in a.iter_projections().zip(b.iter_projections()) {+ // Continue until we find the first mismatching projection.+ if elem_a == elem_b {+ continue;+ }++ match (elem_a, elem_b) {+ // Disjoint fields don't alias except if they are union fields.+ (PlaceElem::Field(_, _), PlaceElem::Field(_, _)) => {+ let ty = prefix.ty(local_decls, tcx).ty;+ return ty.is_union();+ }++ // Disjoint slice elements don't alias.+ (+ PlaceElem::ConstantIndex { offset: offset_a, min_length: _, from_end: from_end_a },+ PlaceElem::ConstantIndex { offset: offset_b, min_length: _, from_end: from_end_b },+ ) if from_end_a == from_end_b && offset_a != offset_b => {+ return false;+ }++ // Conservatively assume the places may alias.+ _ => return true,+ }+ }++ // If the projections are identical *or* one is a prefix of the other then+ // the places alias.+ true+}++struct AliasFixup<'a, 'tcx> {+ tcx: TyCtxt<'tcx>,+ local_decls: &'a IndexVec<Local, LocalDecl<'tcx>>,+ patcher: &'a mut MirPatch<'tcx>,+}++impl<'tcx> AliasFixup<'_, 'tcx> {+ fn isolate_rvalue_to_local(+ &mut self,+ rvalue: Rvalue<'tcx>,+ source_info: SourceInfo,+ location: Location,+ ) -> Place<'tcx> {+ let ty = rvalue.ty(self.local_decls, self.tcx);+ let temp = Place::from(self.patcher.new_temp(ty, source_info.span));+ trace!("isolating {rvalue:?} to {temp:?} due to conflict");+ self.patcher.add_statement(location, StatementKind::StorageLive(temp.local));+ self.patcher.add_assign(location, Place::from(temp), rvalue);+ self.patcher.add_statement(+ location.successor_within_block(),+ StatementKind::StorageDead(temp.local),+ );+ temp+ }++ fn visit_aggregate_assign(+ &mut self,+ dest: Place<'tcx>,+ enum_variant: Option<VariantIdx>,+ project_field: impl Fn(TyCtxt<'tcx>, Place<'tcx>, FieldIdx, Ty<'tcx>) -> Place<'tcx>,+ operands: &IndexVec<FieldIdx, Operand<'tcx>>,+ source_info: SourceInfo,+ location: Location,+ ) {+ // Fast path: if no direct operand aliases the destination, we're done.+ //+ // We only look for direct aliases here, which is sufficient because we+ // know the input MIR did not have any aliasing and we didn't introduce+ // any indirect aliasing in this pass.+ //+ // If the destination place is indirect then it cannot be the start of+ // a lifetime as per the RFC 3943 MIR semantics. This means that the+ // lifetime of the underlying allocation must have started earlier,+ // which overlaps the early point of the assignment statement. Therefore+ // we couldn't have unified any source operand with this destination+ // place.+ //+ // If the source place is indirect then a similar reasoning applies. The+ // only exception is if there are multiple source places (e.g.+ // aggregates). In that situation it's possible for an indirect source+ // to overlap the destination if and only if there is also a direct+ // source that overlaps it:+ //+ // _2 = &_1+ // _3 = (copy *_2, move _1) // _1 becomes _3.1 after unification+ //+ // We handle this here in 2 ways: if there is no direct alias, then+ // we're fine. Otherwise, treat all indirect sources as potentially+ // aliasing with the destination operand.+ let has_direct_alias = operands.iter().any(|op| match op {+ Operand::Copy(src) | Operand::Move(src) => {+ places_directly_alias(self.tcx, self.local_decls, dest, *src)+ }+ Operand::Constant(_) | Operand::RuntimeChecks(_) => false,+ });+ if !has_direct_alias {+ return;+ }++ debug!("splitting aggregate assignment at {location:?}");++ // Split into per-field assignments.+ let mut assignments = vec![];+ for (idx, op) in operands.iter_enumerated() {+ let field_ty = op.ty(self.local_decls, self.tcx);+ let dest_field = project_field(self.tcx, dest, idx, field_ty);++ let emit_op = match op {+ Operand::Copy(src) | Operand::Move(src) => {+ if *src == dest_field {+ // Skip identity assignments.+ continue;+ } else if src.is_indirect_first_projection()+ || places_directly_alias(self.tcx, self.local_decls, dest, *src)+ {+ // Partial alias: hoist the source to a temp first so the+ // per-field write no longer overlaps the dest. Indirect+ // sources also need hoisting here because they may point+ // at one of the direct aliasing operands.+ Operand::Move(self.isolate_rvalue_to_local(+ Rvalue::Use(op.clone(), WithRetag::No),+ source_info,+ location,+ ))+ } else {+ op.clone()+ }+ }+ Operand::Constant(_) | Operand::RuntimeChecks(_) => op.clone(),+ };+ assignments.push((dest_field, emit_op));+ }++ // Perform assignments *after* all aliasing fields have been read into+ // temporary locals.+ for (dest_field, emit_op) in assignments {+ self.patcher.add_assign(location, dest_field, Rvalue::Use(emit_op, WithRetag::No));+ }++ // Delete the original aggregate assignment.+ self.patcher.nop_statement(location);++ // For enum variants, set the discriminant after all field writes.+ if let Some(variant_index) = enum_variant {+ self.patcher.add_statement(+ location,+ StatementKind::SetDiscriminant { place: Box::new(dest), variant_index },+ );+ }+ }+}++impl<'tcx> MutVisitor<'tcx> for AliasFixup<'_, 'tcx> {+ fn tcx(&self) -> TyCtxt<'tcx> {+ self.tcx+ }++ fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {+ // Fixup the MIR to remove aliasing assignments.+ if let StatementKind::Assign((dest, rvalue)) = &mut statement.kind {+ match *rvalue {+ Rvalue::Use(Operand::Copy(src) | Operand::Move(src), with_retag) => {+ if places_directly_alias(self.tcx, self.local_decls, *dest, src) {+ if src == *dest {+ debug!("{:?} turned into self-assignment, deleting", location);+ statement.make_nop(true);+ } else {+ let temp = self.isolate_rvalue_to_local(+ rvalue.clone(),+ statement.source_info,+ location,+ );+ *rvalue = Rvalue::Use(Operand::Move(temp), with_retag);+ }+ }+ }+ Rvalue::Aggregate(AggregateKind::Array(_), ref mut operands) => self+ .visit_aggregate_assign(+ *dest,+ None,+ |tcx, place, field_idx, _field_ty| {+ place.project_deeper(+ &[PlaceElem::ConstantIndex {+ offset: field_idx.as_u32().into(),+ min_length: field_idx.as_u32() as u64 + 1,+ from_end: false,+ }],+ tcx,+ )+ },+ operands,+ statement.source_info,+ location,+ ),+ Rvalue::Aggregate(AggregateKind::Tuple, ref mut operands) => self+ .visit_aggregate_assign(+ *dest,+ None,+ |tcx, place, field_idx, field_ty| {+ place.project_deeper(&[PlaceElem::Field(field_idx, field_ty)], tcx)+ },+ operands,+ statement.source_info,+ location,+ ),+ Rvalue::Aggregate(+ AggregateKind::Adt(_, _, _, _, Some(union_field_idx)),+ ref mut operands,+ ) => {+ debug_assert_eq!(operands.len(), 1);+ self.visit_aggregate_assign(+ *dest,+ None,+ |tcx, place, _, field_ty| {+ place+ .project_deeper(&[PlaceElem::Field(union_field_idx, field_ty)], tcx)+ },+ operands,+ statement.source_info,+ location,+ )+ }+ // Types that forbid field projections must use the general+ // aliasing fallback below.+ Rvalue::Aggregate(+ AggregateKind::Adt(adt_did, var_idx, _, _, None),+ ref mut operands,+ ) if self.tcx.adt_def(adt_did).can_project_fields(self.tcx) => {+ let def = self.tcx.adt_def(adt_did);+ self.visit_aggregate_assign(+ *dest,+ def.is_enum().then_some(var_idx),+ |tcx, place, field_idx, field_ty| {+ if def.is_enum() {+ place.project_deeper(+ &[+ PlaceElem::Downcast(None, var_idx),+ PlaceElem::Field(field_idx, field_ty),+ ],+ tcx,+ )+ } else {+ place.project_deeper(&[PlaceElem::Field(field_idx, field_ty)], tcx)+ }+ },+ operands,+ statement.source_info,+ location,+ )+ }++ // For other rvalues, don't try to split them into components+ // and instead just introduce a temporary if there is any+ // aliasing+ Rvalue::Aggregate(..)+ | Rvalue::Repeat(..)+ | Rvalue::Cast(..)+ | Rvalue::CopyForDeref(..)+ | Rvalue::WrapUnsafeBinder(..) => {+ let mut overlaps_dest = false;+ VisitPlacesWith(|place, _ctxt| {+ if places_directly_alias(self.tcx, self.local_decls, *dest, place) {+ overlaps_dest = true;+ }+ })+ .visit_rvalue(rvalue, location);+ if overlaps_dest {+ let temp = self.isolate_rvalue_to_local(+ rvalue.clone(),+ statement.source_info,+ location,+ );+ *rvalue = Rvalue::Use(Operand::Move(temp), WithRetag::No);+ }+ }++ // These either cannot have aliasing, or allow it because they+ // only operate on scalar backend types.+ Rvalue::Use(Operand::Constant(..) | Operand::RuntimeChecks(..), _)+ | Rvalue::Ref(..)+ | Rvalue::ThreadLocalRef(..)+ | Rvalue::BinaryOp(..)+ | Rvalue::UnaryOp(..)+ | Rvalue::Discriminant(..)+ | Rvalue::RawPtr(..)+ | Rvalue::Reborrow(..) => {}+ }+ }+ }+}compiler/rustc_mir_transform/src/sroa.rs1 + / 10 −
@@ -1,5 +1,4 @@ use rustc_abi::FieldIdx;-use rustc_attr_ir::lang_items::LangItem; use rustc_data_structures::flat_map_in_place::FlatMapInPlace; use rustc_index::IndexVec; use rustc_index::bit_set::{DenseBitSet, GrowableBitSet};@@ -64,16 +63,8 @@ fn escaping_locals<'tcx>( return true; } if let ty::Adt(def, _args) = ty.kind()- && (def.repr().simd()- || def.repr().scalable()- || tcx.is_lang_item(def.did(), LangItem::DynMetadata))+ && !def.can_project_fields(tcx) {- // Exclude #[repr(simd)] types so that they are not de-optimized into an array- // (MCP#838 banned projections into SIMD types, but if the value is unused- // this pass sees "all the uses are of the fields" and expands it.)-- // codegen wants to see the `DynMetadata<T>`,- // not the inner reference-to-opaque-type. return true; } // Default for non-ADTscompiler/rustc_mir_transform/src/validate.rs12 + / 17 −
@@ -716,24 +716,19 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { // Debug info is allowed to project into pattern types ty::Pat(base, _) => check_equal(self, location, *base), ty::Adt(adt_def, args) => {- // see <https://github.com/rust-lang/rust/blob/7601adcc764d42c9f2984082b49948af652df986/compiler/rustc_middle/src/ty/layout.rs#L861-L864>- if self.tcx.is_lang_item(adt_def.did(), LangItem::DynMetadata) {- self.fail(- location,- format!(- "You can't project to field {f:?} of `DynMetadata` because \+ if !adt_def.can_project_fields(self.tcx) {+ let message =+ if self.tcx.is_lang_item(adt_def.did(), LangItem::DynMetadata) {+ format!(+ "You can't project to field {f:?} of `DynMetadata` because \ layout is weird and thinks it doesn't have fields."- ),- );- }-- if adt_def.repr().simd() || adt_def.repr().scalable() {- self.fail(- location,- format!(- "Projecting into SIMD type {adt_def:?} is banned by MCP#838"- ),- );+ )+ } else {+ format!(+ "Projecting into SIMD type {adt_def:?} is banned by MCP#838"+ )+ };+ self.fail(location, message); } let var = parent_ty.variant_index.unwrap_or(FIRST_VARIANT);compiler/rustc_public/src/mir/body.rs0 + / 3 −
@@ -551,9 +551,6 @@ pub enum Rvalue { /// This is needed because dataflow analysis needs to distinguish /// `dest = Foo { x: ..., y: ... }` from `dest.x = ...; dest.y = ...;` in the case that `Foo` /// has a destructor.- ///- /// Disallowed after deaggregation for all aggregate kinds except `Array` and `Coroutine`. After- /// coroutine lowering, `Coroutine` aggregate kinds are disallowed too. Aggregate(AggregateKind, Vec<Operand>), /// * `Offset` has the same semantics as `<*const T>::offset`, except that the secondcompiler/rustc_session/src/options.rs1 + / 1 −
@@ -2690,7 +2690,7 @@ options! { "include extra comments in mir pretty printing, like line numbers and statement indices, \ details about types, etc. (boolean for all passes, 'nll' to enable in NLL MIR only, default: 'nll')"), mir_move_elimination: bool = (false, parse_bool, [TRACKED],- "enable the experimental MIR move elimination pass (default: no)"),+ "enable the experimental MIR semantics for the move elimination optimization (default: no)"), mir_opt_bisect_limit: Option<usize> = (None, parse_opt_number, [TRACKED], "limit the number of MIR optimization pass executions (global across all bodies). \ Pass executions after this limit are skipped and reported. (default: no limit)"),tests/mir-opt/move-elimination/alias_fixup.aggregate_indirect_source_alias.MoveElimination.diffadded27 + / 0 −
@@ -0,0 +1,27 @@+- // MIR for `aggregate_indirect_source_alias` before MoveElimination++ // MIR for `aggregate_indirect_source_alias` after MoveElimination+ + fn aggregate_indirect_source_alias() -> (u8, u8) {+ let mut _0: (u8, u8);+ let mut _1: u8;+ let mut _2: *const u8;+ let mut _3: (u8, u8);++ let mut _4: u8;+ + bb0: {+- _1 = const 1_u8;+- _2 = &raw const _1;+- _3 = (copy (*_2), move _1);+- _0 = copy _3;++ (_0.1: u8) = const 1_u8;++ _2 = &raw const (_0.1: u8);++ StorageLive(_4);++ _4 = no_retag copy (*_2);++ (_0.0: u8) = no_retag move _4;++ nop;++ StorageDead(_4);++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/alias_fixup.aggregate_swap.MoveElimination.diffadded84 + / 0 −
@@ -0,0 +1,84 @@+- // MIR for `aggregate_swap` before MoveElimination++ // MIR for `aggregate_swap` after MoveElimination+ + fn aggregate_swap(_1: u8, _2: u8) -> (u8, u8) {+ debug x => _1;+ debug y => _2;+ let mut _0: (u8, u8);+ let mut _3: (u8, u8);+ let mut _4: u8;+ let mut _5: u8;+ let mut _8: u8;+ let mut _9: u8;++ let mut _10: u8;+ scope 1 {+- debug pair => _3;++ debug pair => _0;+ let _6: u8;+ scope 2 {+- debug a => _6;++ debug a => _9;+ let _7: u8;+ scope 3 {+- debug b => _7;++ debug b => (_0.1: u8);+ }+ }+ }+ + bb0: {+- StorageLive(_3);+- StorageLive(_4);+- _4 = copy _1;+- StorageLive(_5);+- _5 = copy _2;+- _3 = (move _4, move _5);+- StorageDead(_5);+- StorageDead(_4);+- StorageLive(_6);+- _6 = copy (_3.0: u8);+- StorageLive(_7);+- _7 = copy (_3.1: u8);+- StorageLive(_8);+- _8 = copy _7;+- StorageLive(_9);+- _9 = copy _6;+- _3 = (move _8, move _9);+- StorageDead(_9);+- StorageDead(_8);+- _0 = copy _3;+- StorageDead(_7);+- StorageDead(_6);+- StorageDead(_3);++ nop;++ nop;++ nop;++ nop;++ nop;++ _0 = (move _1, move _2);++ nop;++ nop;++ nop;++ _9 = copy (_0.0: u8);++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ StorageLive(_10);++ _10 = no_retag move (_0.1: u8);++ (_0.0: u8) = no_retag move _10;++ (_0.1: u8) = no_retag move _9;++ nop;++ StorageDead(_10);++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/alias_fixup.array_swap.MoveElimination.diffadded55 + / 0 −
@@ -0,0 +1,55 @@+- // MIR for `array_swap` before MoveElimination++ // MIR for `array_swap` after MoveElimination+ + fn array_swap(_1: [u8; 2]) -> [u8; 2] {+ debug array => _1;+ let mut _0: [u8; 2];+ let _2: u8;+ let _3: u8;+ let mut _4: u8;+ let mut _5: u8;++ let mut _6: u8;+ scope 1 {+- debug a => _2;+- debug b => _3;++ debug a => _5;++ debug b => _1[1 of 2];+ }+ + bb0: {+- StorageLive(_2);+- _2 = copy _1[0 of 2];+- StorageLive(_3);+- _3 = copy _1[1 of 2];+- StorageLive(_4);+- _4 = copy _3;+- StorageLive(_5);+- _5 = copy _2;+- _1 = [move _4, move _5];+- StorageDead(_5);+- StorageDead(_4);++ nop;++ _5 = copy _1[0 of 2];++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ StorageLive(_6);++ _6 = no_retag move _1[1 of 2];++ _1[0 of 1] = no_retag move _6;++ _1[1 of 2] = no_retag move _5;++ nop;++ StorageDead(_6);++ nop;++ nop;+ _0 = copy _1;+- StorageDead(_3);+- StorageDead(_2);++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/alias_fixup.enum_alias.MoveElimination.diffadded120 + / 0 −
@@ -0,0 +1,120 @@+- // MIR for `enum_alias` before MoveElimination++ // MIR for `enum_alias` after MoveElimination+ + fn enum_alias(_1: bool, _2: u8, _3: u8) -> PairEnum {+ debug flag => _1;+ debug x => _2;+ debug y => _3;+ let mut _0: PairEnum;+ let _4: PairEnum;+ let mut _5: u8;+ let mut _6: u8;+ let mut _7: bool;+ let mut _8: isize;+ let mut _11: u8;+ let mut _12: u8;++ let mut _13: u8;+ scope 1 {+- debug input => _4;++ debug input => _0;+ let _9: u8;+ let _10: u8;+ scope 2 {+- debug a => _9;+- debug b => _10;++ debug a => _12;++ debug b => ((_0 as Pair).1: u8);+ }+ }+ + bb0: {+- StorageLive(_4);+- StorageLive(_5);+- _5 = copy _2;+- StorageLive(_6);+- _6 = copy _3;+- _4 = PairEnum::Pair(move _5, move _6);+- StorageDead(_6);+- StorageDead(_5);+- StorageLive(_7);+- _7 = copy _1;+- switchInt(move _7) -> [0: bb2, otherwise: bb1];++ nop;++ nop;++ nop;++ nop;++ nop;++ _0 = PairEnum::Pair(move _2, move _3);++ nop;++ nop;++ nop;++ nop;++ switchInt(move _1) -> [0: bb2, otherwise: bb1];+ }+ + bb1: {+- _0 = move _4;++ nop;+ goto -> bb6;+ }+ + bb2: {+- _8 = discriminant(_4);++ _8 = discriminant(_0);+ switchInt(move _8) -> [0: bb4, 1: bb5, otherwise: bb3];+ }+ + bb3: {+ unreachable;+ }+ + bb4: {+ _0 = PairEnum::Empty;+ goto -> bb6;+ }+ + bb5: {+- StorageLive(_9);+- _9 = copy ((_4 as Pair).0: u8);+- StorageLive(_10);+- _10 = copy ((_4 as Pair).1: u8);+- StorageLive(_11);+- _11 = copy _10;+- StorageLive(_12);+- _12 = copy _9;+- _0 = PairEnum::Pair(move _11, move _12);+- StorageDead(_12);+- StorageDead(_11);+- StorageDead(_10);+- StorageDead(_9);++ nop;++ _12 = copy ((_0 as Pair).0: u8);++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ StorageLive(_13);++ _13 = no_retag move ((_0 as Pair).1: u8);++ ((_0 as variant#1).0: u8) = no_retag move _13;++ ((_0 as variant#1).1: u8) = no_retag move _12;++ discriminant(_0) = 1;++ nop;++ StorageDead(_13);++ nop;++ nop;++ nop;++ nop;+ goto -> bb6;+ }+ + bb6: {+- StorageDead(_7);+- StorageDead(_4);++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/alias_fixup.mixed_aggregate_aliasing.MoveElimination.diffadded90 + / 0 −
@@ -0,0 +1,90 @@+- // MIR for `mixed_aggregate_aliasing` before MoveElimination++ // MIR for `mixed_aggregate_aliasing` after MoveElimination+ + fn mixed_aggregate_aliasing(_1: bool, _2: u8) -> Triple {+ debug flag => _1;+ debug z => _2;+ let mut _0: Triple;+ let _3: Triple;+ let mut _5: bool;+ let mut _6: u8;+ let mut _7: u8;+ let mut _8: u8;++ let mut _9: u8;+ scope 1 {+- debug input => _3;++ debug input => _0;+ let _4: Triple;+ scope 2 {+- debug out => _4;++ debug out => _0;+ }+ }+ + bb0: {+- StorageLive(_3);+- _3 = opaque_triple() -> [return: bb1, unwind unreachable];++ nop;++ _0 = opaque_triple() -> [return: bb1, unwind unreachable];+ }+ + bb1: {+- StorageLive(_4);+- StorageLive(_5);+- _5 = copy _1;+- switchInt(move _5) -> [0: bb3, otherwise: bb2];++ nop;++ nop;++ nop;++ switchInt(move _1) -> [0: bb3, otherwise: bb2];+ }+ + bb2: {+- _4 = copy _3;++ nop;+ goto -> bb4;+ }+ + bb3: {+- StorageLive(_6);+- _6 = copy (_3.1: u8);+- StorageLive(_7);+- _7 = copy (_3.0: u8);+- StorageLive(_8);+- _8 = copy _2;+- _4 = Triple(move _6, move _7, move _8);+- StorageDead(_8);+- StorageDead(_7);+- StorageDead(_6);++ nop;++ _6 = copy (_0.1: u8);++ nop;++ nop;++ nop;++ nop;++ StorageLive(_9);++ _9 = no_retag move (_0.0: u8);++ (_0.0: u8) = no_retag move _6;++ (_0.1: u8) = no_retag move _9;++ (_0.2: u8) = no_retag move _2;++ nop;++ StorageDead(_9);++ nop;++ nop;++ nop;+ goto -> bb4;+ }+ + bb4: {+- StorageDead(_5);+- _0 = copy _4;+- StorageDead(_4);+- StorageDead(_3);++ nop;++ nop;++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/alias_fixup.rsadded156 + / 0 −
@@ -0,0 +1,156 @@+//@ test-mir-pass: MoveElimination+//@ compile-flags: -Cpanic=abort++#![feature(core_intrinsics, custom_mir, repr_simd)]+#![allow(dead_code)]+#![allow(internal_features)]++use std::intrinsics::mir::*;++#[derive(Copy, Clone)]+pub struct Triple(u8, u8, u8);++pub union U {+ a: [u8; 4],+ b: [u8; 4],+}++unsafe extern "C" {+ safe fn opaque_triple() -> Triple;+}++// EMIT_MIR alias_fixup.mixed_aggregate_aliasing.MoveElimination.diff+pub fn mixed_aggregate_aliasing(flag: bool, z: u8) -> Triple {+ // This checks an aggregate assignment on one branch after the other branch+ // remaps the input into the return place: overlapping field reads are+ // hoisted through temporaries before writing back into the return place.+ // CHECK-LABEL: fn mixed_aggregate_aliasing(+ // CHECK: debug z => _2;+ // CHECK: debug input => _0;+ // CHECK: debug out => _0;+ // CHECK: [[field1:_.*]] = copy (_0.1: u8);+ // CHECK: [[field0:_.*]] = no_retag move (_0.0: u8);+ // CHECK: (_0.0: u8) = no_retag move [[field1]];+ // CHECK: (_0.1: u8) = no_retag move [[field0]];+ // CHECK: (_0.2: u8) = no_retag move _2;+ let input = opaque_triple();+ let out = if flag { input } else { Triple(input.1, input.0, z) };+ out+}++// EMIT_MIR alias_fixup.aggregate_swap.MoveElimination.diff+pub fn aggregate_swap(x: u8, y: u8) -> (u8, u8) {+ // This checks that an aggregate swap-like assignment is safe after any+ // remapping that makes source fields share storage with destination fields.+ // CHECK-LABEL: fn aggregate_swap(+ // CHECK: debug pair => _0;+ // CHECK: [[saved:_.*]] = copy (_0.0: u8);+ // CHECK: [[tmp:_.*]] = no_retag move (_0.1: u8);+ // CHECK: (_0.0: u8) = no_retag move [[tmp]];+ // CHECK: (_0.1: u8) = no_retag move [[saved]];+ let mut pair = (x, y);+ let a = pair.0;+ let b = pair.1;+ pair = (b, a);+ pair+}++// EMIT_MIR alias_fixup.simple_partial_alias.MoveElimination.diff+pub fn simple_partial_alias(x: [u8; 4]) -> U {+ // This checks a non-aggregate assignment involving two same-typed union+ // fields, which are conservatively treated as aliasing.+ // CHECK-LABEL: fn simple_partial_alias(+ // CHECK: debug u => _0;+ // CHECK: _0 = U { a: move _1 };+ // CHECK: [[tmp:_.*]] = copy (_0.0: [u8; 4]);+ // CHECK: (_0.1: [u8; 4]) = move [[tmp]];+ let mut u = U { a: x };+ let tmp = unsafe { u.a };+ u.b = tmp;+ u+}++// EMIT_MIR alias_fixup.aggregate_indirect_source_alias.MoveElimination.diff+#[custom_mir(dialect = "runtime", phase = "post-cleanup")]+pub fn aggregate_indirect_source_alias() -> (u8, u8) {+ // This checks that, when an aggregate has a direct operand aliasing the+ // destination, indirect operands are also hoisted before field writes.+ // CHECK-LABEL: fn aggregate_indirect_source_alias(+ // CHECK: [[p:_.*]] = &raw const (_0.1: u8);+ // CHECK: [[tmp:_.*]] = no_retag copy (*[[p]]);+ // CHECK: (_0.0: u8) = no_retag move [[tmp]];+ mir! {+ let a: u8;+ let p: *const u8;+ let out: (u8, u8);++ {+ a = 1_u8;+ p = &raw const a;+ out = (*p, Move(a));+ RET = out;+ Return()+ }+ }+}++// Aliasing array elements must all be read before any element is overwritten.+// EMIT_MIR alias_fixup.array_swap.MoveElimination.diff+pub fn array_swap(mut array: [u8; 2]) -> [u8; 2] {+ // CHECK-LABEL: fn array_swap(+ // CHECK: debug array => _1;+ // CHECK: [[FIRST:_[0-9]+]] = copy _1[0 of 2];+ // CHECK: [[SECOND:_[0-9]+]] = no_retag move _1[1 of 2];+ // CHECK: _1[0 of 1] = no_retag move [[SECOND]];+ // CHECK: _1[1 of 2] = no_retag move [[FIRST]];+ let [a, b] = array;+ array = [b, a];+ array+}++pub enum PairEnum {+ Empty,+ Pair(u8, u8),+}++// Enum alias fixup must read both fields before writing the swapped payload,+// then set the discriminant after the field writes.+// EMIT_MIR alias_fixup.enum_alias.MoveElimination.diff+pub fn enum_alias(flag: bool, x: u8, y: u8) -> PairEnum {+ // CHECK-LABEL: fn enum_alias(+ // CHECK: debug input => _0;+ // CHECK: [[FIRST:_[0-9]+]] = copy ((_0 as Pair).0: u8);+ // CHECK: [[SECOND:_[0-9]+]] = no_retag move ((_0 as Pair).1: u8);+ // CHECK: ((_0 as variant#1).0: u8) = no_retag move [[SECOND]];+ // CHECK: ((_0 as variant#1).1: u8) = no_retag move [[FIRST]];+ // CHECK-NEXT: discriminant(_0) = 1;+ let input = PairEnum::Pair(x, y);+ if flag {+ input+ } else {+ match input {+ PairEnum::Pair(a, b) => PairEnum::Pair(b, a), // swap+ PairEnum::Empty => PairEnum::Empty,+ }+ }+}++#[repr(simd)]+#[derive(Copy, Clone)]+pub struct Vector([u32; 4]);++pub union VectorUnion {+ vector: Vector,+ array: [u32; 4],+}++// SIMD aggregates cannot be replaced with direct access to the inner field.+// EMIT_MIR alias_fixup.simd_alias.MoveElimination.diff+pub unsafe fn simd_alias(flag: bool) -> VectorUnion {+ // CHECK-LABEL: fn simd_alias(+ // CHECK: debug input => _0;+ // CHECK: [[TEMP:_[0-9]+]] = Vector(move (_0.1: [u32; 4]));+ // CHECK-NEXT: (_0.0: Vector) = no_retag move [[TEMP]];+ let input = VectorUnion { vector: Vector([0; 4]) };+ if flag { VectorUnion { vector: Vector(input.array) } } else { input }+}tests/mir-opt/move-elimination/alias_fixup.simd_alias.MoveElimination.diffadded78 + / 0 −
@@ -0,0 +1,78 @@+- // MIR for `simd_alias` before MoveElimination++ // MIR for `simd_alias` after MoveElimination+ + fn simd_alias(_1: bool) -> VectorUnion {+ debug flag => _1;+ let mut _0: VectorUnion;+ let _2: VectorUnion;+ let mut _3: Vector;+ let mut _4: [u32; 4];+ let mut _5: bool;+ let mut _6: Vector;+ let mut _7: [u32; 4];++ let mut _8: Vector;+ scope 1 {+- debug input => _2;++ debug input => _0;+ }+ + bb0: {+- StorageLive(_2);+- StorageLive(_3);+- StorageLive(_4);++ nop;++ nop;++ nop;+ _4 = [const 0_u32; 4];+- _3 = Vector(move _4);+- StorageDead(_4);+- _2 = VectorUnion { vector: move _3 };+- StorageDead(_3);+- StorageLive(_5);+- _5 = copy _1;+- switchInt(move _5) -> [0: bb2, otherwise: bb1];++ (_0.0: Vector) = Vector(move _4);++ nop;++ nop;++ nop;++ nop;++ nop;++ switchInt(move _1) -> [0: bb2, otherwise: bb1];+ }+ + bb1: {+- StorageLive(_6);+- StorageLive(_7);+- _7 = copy (_2.1: [u32; 4]);+- _6 = Vector(move _7);+- StorageDead(_7);+- _0 = VectorUnion { vector: move _6 };+- StorageDead(_6);++ nop;++ nop;++ nop;++ StorageLive(_8);++ _8 = Vector(move (_0.1: [u32; 4]));++ (_0.0: Vector) = no_retag move _8;++ StorageDead(_8);++ nop;++ nop;++ nop;+ goto -> bb3;+ }+ + bb2: {+- _0 = move _2;++ nop;+ goto -> bb3;+ }+ + bb3: {+- StorageDead(_5);+- StorageDead(_2);++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/alias_fixup.simple_partial_alias.MoveElimination.diffadded52 + / 0 −
@@ -0,0 +1,52 @@+- // MIR for `simple_partial_alias` before MoveElimination++ // MIR for `simple_partial_alias` after MoveElimination+ + fn simple_partial_alias(_1: [u8; 4]) -> U {+ debug x => _1;+ let mut _0: U;+ let mut _2: U;+ let mut _3: [u8; 4];+ let mut _5: [u8; 4];+ scope 1 {+- debug u => _2;++ debug u => _0;+ let _4: [u8; 4];+ scope 2 {+- debug tmp => _4;++ debug tmp => _5;+ }+ }+ + bb0: {+- StorageLive(_2);+- StorageLive(_3);+- _3 = copy _1;+- _2 = U { a: move _3 };+- StorageDead(_3);+- StorageLive(_4);+- _4 = copy (_2.0: [u8; 4]);+- StorageLive(_5);+- _5 = copy _4;+- (_2.1: [u8; 4]) = move _5;+- StorageDead(_5);+- _0 = move _2;+- StorageDead(_4);+- StorageDead(_2);++ nop;++ nop;++ nop;++ _0 = U { a: move _1 };++ nop;++ nop;++ _5 = copy (_0.0: [u8; 4]);++ nop;++ nop;++ (_0.1: [u8; 4]) = move _5;++ nop;++ nop;++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/basic.array_aggregate.MoveElimination.diffadded67 + / 0 −
@@ -0,0 +1,67 @@+- // MIR for `array_aggregate` before MoveElimination++ // MIR for `array_aggregate` after MoveElimination+ + fn array_aggregate() -> [[u8; 8]; 3] {+ let mut _0: [[u8; 8]; 3];+ let _1: [u8; 8];+ let mut _4: [u8; 8];+ let mut _5: [u8; 8];+ let mut _6: [u8; 8];+ scope 1 {+- debug a => _1;++ debug a => _0[0 of 1];+ let _2: [u8; 8];+ scope 2 {+- debug b => _2;++ debug b => _0[1 of 2];+ let _3: [u8; 8];+ scope 3 {+- debug c => _3;++ debug c => _0[2 of 3];+ }+ }+ }+ + bb0: {+- StorageLive(_1);+- _1 = [const 1_u8; 8];+- StorageLive(_2);+- _2 = [const 2_u8; 8];+- StorageLive(_3);+- _3 = [const 3_u8; 8];+- StorageLive(_4);+- _4 = move _1;+- StorageLive(_5);+- _5 = move _2;+- StorageLive(_6);+- _6 = move _3;+- _0 = [move _4, move _5, move _6];+- StorageDead(_6);+- StorageDead(_5);+- StorageDead(_4);+- StorageDead(_3);+- StorageDead(_2);+- StorageDead(_1);++ nop;++ _0[0 of 1] = [const 1_u8; 8];++ nop;++ _0[1 of 2] = [const 2_u8; 8];++ nop;++ _0[2 of 3] = [const 3_u8; 8];++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/basic.enum_aggregate.MoveElimination.diffadded57 + / 0 −
@@ -0,0 +1,57 @@+- // MIR for `enum_aggregate` before MoveElimination++ // MIR for `enum_aggregate` after MoveElimination+ + fn enum_aggregate() -> Result<([u8; 8], [u8; 8]), ()> {+ let mut _0: std::result::Result<([u8; 8], [u8; 8]), ()>;+ let _1: [u8; 8];+ let mut _3: ([u8; 8], [u8; 8]);+ let mut _4: [u8; 8];+ let mut _5: [u8; 8];+ scope 1 {+- debug a => _1;++ debug a => (((_0 as variant#0).0: ([u8; 8], [u8; 8])).0: [u8; 8]);+ let _2: [u8; 8];+ scope 2 {+- debug b => _2;++ debug b => (((_0 as variant#0).0: ([u8; 8], [u8; 8])).1: [u8; 8]);+ }+ }+ + bb0: {+- StorageLive(_1);+- _1 = [const 1_u8; 8];+- StorageLive(_2);+- _2 = [const 2_u8; 8];+- StorageLive(_3);+- StorageLive(_4);+- _4 = move _1;+- StorageLive(_5);+- _5 = move _2;+- _3 = (move _4, move _5);+- StorageDead(_5);+- StorageDead(_4);+- _0 = Result::<([u8; 8], [u8; 8]), ()>::Ok(move _3);+- StorageDead(_3);+- StorageDead(_2);+- StorageDead(_1);++ nop;++ (((_0 as variant#0).0: ([u8; 8], [u8; 8])).0: [u8; 8]) = [const 1_u8; 8];++ nop;++ (((_0 as variant#0).0: ([u8; 8], [u8; 8])).1: [u8; 8]) = [const 2_u8; 8];++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ discriminant(_0) = 0;++ nop;++ nop;++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/basic.nrvo_borrowed.MoveElimination.diffadded46 + / 0 −
@@ -0,0 +1,46 @@+- // MIR for `nrvo_borrowed` before MoveElimination++ // MIR for `nrvo_borrowed` after MoveElimination+ + fn nrvo_borrowed() -> [u8; 8] {+ let mut _0: [u8; 8];+ let mut _1: [u8; 8];+ let _2: ();+ let mut _3: &mut [u8; 8];+ let mut _4: &mut [u8; 8];+ scope 1 {+- debug buf => _1;++ debug buf => _0;+ }+ + bb0: {+- StorageLive(_1);+- _1 = [const 1_u8; 8];+- StorageLive(_2);+- StorageLive(_3);+- StorageLive(_4);+- _4 = &mut _1;++ nop;++ _0 = [const 1_u8; 8];++ nop;++ nop;++ nop;++ _4 = &mut _0;+ _3 = &mut (*_4);+ _2 = init(move _3) -> [return: bb1, unwind unreachable];+ }+ + bb1: {+- StorageDead(_3);+- StorageDead(_4);+- StorageDead(_2);+- _0 = move _1;+- StorageDead(_1);++ nop;++ nop;++ nop;++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/basic.nrvo_unborrowed.MoveElimination.diffadded24 + / 0 −
@@ -0,0 +1,24 @@+- // MIR for `nrvo_unborrowed` before MoveElimination++ // MIR for `nrvo_unborrowed` after MoveElimination+ + fn nrvo_unborrowed() -> [u8; 8] {+ let mut _0: [u8; 8];+ let _1: [u8; 8];+ scope 1 {+- debug buf => _1;++ debug buf => _0;+ }+ + bb0: {+- StorageLive(_1);+- _1 = [const 1_u8; 8];+- _0 = move _1;+- StorageDead(_1);++ nop;++ _0 = [const 1_u8; 8];++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/basic.rsadded80 + / 0 −
@@ -0,0 +1,80 @@+//@ test-mir-pass: MoveElimination+//@ compile-flags: -Cpanic=abort -Zmir-enable-passes=+TailCopyToMove++struct Pair {+ a: [u8; 8],+ b: [u8; 8],+}++fn init(_: &mut [u8; 8]) {}++// EMIT_MIR basic.nrvo_unborrowed.MoveElimination.diff+pub fn nrvo_unborrowed() -> [u8; 8] {+ // This checks the simplest NRVO-style case: the local should be merged with+ // the return place even though it has `Copy` type.+ // CHECK-LABEL: fn nrvo_unborrowed(+ // CHECK: debug buf => _0;+ // CHECK: _0 = [const 1_u8; 8]+ let buf = [1; 8];+ buf+}++// EMIT_MIR basic.nrvo_borrowed.MoveElimination.diff+pub fn nrvo_borrowed() -> [u8; 8] {+ // This checks that taking a temporary mutable borrow does not prevent+ // merging a `Copy` local once the borrow has ended.+ // CHECK-LABEL: fn nrvo_borrowed(+ // CHECK: debug buf => _0;+ // CHECK: _0 = [const 1_u8; 8]+ // CHECK: init(move {{_.*}})+ // CHECK-NOT: _0 = move+ let mut buf = [1; 8];+ init(&mut buf);+ buf+}++// EMIT_MIR basic.struct_aggregate.MoveElimination.diff+pub fn struct_aggregate() -> Pair {+ // This checks aggregate field remapping: the field locals can live directly+ // in the return place's fields.+ // CHECK-LABEL: fn struct_aggregate(+ // CHECK: debug a => (_0.0: [u8; 8]);+ // CHECK: debug b => (_0.1: [u8; 8]);+ // CHECK: (_0.0: [u8; 8]) = [const 1_u8; 8];+ // CHECK: (_0.1: [u8; 8]) = [const 2_u8; 8];+ let a = [1; 8];+ let b = [2; 8];+ Pair { a, b }+}++// EMIT_MIR basic.enum_aggregate.MoveElimination.diff+pub fn enum_aggregate() -> Result<([u8; 8], [u8; 8]), ()> {+ // This checks aggregate field remapping for enums: the payload fields can+ // be written directly and then the discriminant is set for the variant.+ // CHECK-LABEL: fn enum_aggregate(+ // CHECK: debug a => (((_0 as variant#0).0: ([u8; 8], [u8; 8])).0: [u8; 8]);+ // CHECK: debug b => (((_0 as variant#0).0: ([u8; 8], [u8; 8])).1: [u8; 8]);+ // CHECK: (((_0 as variant#0).0: ([u8; 8], [u8; 8])).0: [u8; 8]) = [const 1_u8; 8];+ // CHECK: (((_0 as variant#0).0: ([u8; 8], [u8; 8])).1: [u8; 8]) = [const 2_u8; 8];+ // CHECK: discriminant(_0) = 0;+ let a = [1; 8];+ let b = [2; 8];+ Result::Ok((a, b))+}++// EMIT_MIR basic.array_aggregate.MoveElimination.diff+pub fn array_aggregate() -> [[u8; 8]; 3] {+ // This checks aggregate remapping for arrays, which uses ConstantIndex+ // projections rather than field projections.+ // CHECK-LABEL: fn array_aggregate(+ // CHECK: debug a => _0[0 of 1];+ // CHECK: debug b => _0[1 of 2];+ // CHECK: debug c => _0[2 of 3];+ // CHECK: _0[0 of 1] = [const 1_u8; 8];+ // CHECK: _0[1 of 2] = [const 2_u8; 8];+ // CHECK: _0[2 of 3] = [const 3_u8; 8];+ let a = [1; 8];+ let b = [2; 8];+ let c = [3; 8];+ [a, b, c]+}tests/mir-opt/move-elimination/basic.struct_aggregate.MoveElimination.diffadded49 + / 0 −
@@ -0,0 +1,49 @@+- // MIR for `struct_aggregate` before MoveElimination++ // MIR for `struct_aggregate` after MoveElimination+ + fn struct_aggregate() -> Pair {+ let mut _0: Pair;+ let _1: [u8; 8];+ let mut _3: [u8; 8];+ let mut _4: [u8; 8];+ scope 1 {+- debug a => _1;++ debug a => (_0.0: [u8; 8]);+ let _2: [u8; 8];+ scope 2 {+- debug b => _2;++ debug b => (_0.1: [u8; 8]);+ }+ }+ + bb0: {+- StorageLive(_1);+- _1 = [const 1_u8; 8];+- StorageLive(_2);+- _2 = [const 2_u8; 8];+- StorageLive(_3);+- _3 = move _1;+- StorageLive(_4);+- _4 = move _2;+- _0 = Pair { a: move _3, b: move _4 };+- StorageDead(_4);+- StorageDead(_3);+- StorageDead(_2);+- StorageDead(_1);++ nop;++ (_0.0: [u8; 8]) = [const 1_u8; 8];++ nop;++ (_0.1: [u8; 8]) = [const 2_u8; 8];++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/dse.dse_guard.MoveElimination.diffadded92 + / 0 −
@@ -0,0 +1,92 @@+- // MIR for `dse_guard` before MoveElimination++ // MIR for `dse_guard` after MoveElimination+ + fn dse_guard() -> () {+ let mut _0: ();+ let mut _1: Fields;+ let mut _3: Fields;+ let mut _4: Fields;+ let _5: ();+ let mut _6: *const Fields;+ let mut _7: Fields;+ let _8: ();+ let mut _9: *const Fields;+ scope 1 {+- debug a => _1;++ debug a => _7;+ let mut _2: Fields;+ scope 2 {+ debug b => _2;+ }+ }+ + bb0: {+- StorageLive(_1);+- StorageLive(_2);+- StorageLive(_3);+- _3 = make_fields(const 0_u8) -> [return: bb1, unwind unreachable];++ nop;++ nop;++ nop;++ _2 = make_fields(const 0_u8) -> [return: bb1, unwind unreachable];+ }+ + bb1: {+- _2 = move _3;+- StorageDead(_3);+- StorageLive(_4);+- _4 = make_fields(const 1_u8) -> [return: bb2, unwind unreachable];++ nop;++ nop;++ nop;++ _7 = make_fields(const 1_u8) -> [return: bb2, unwind unreachable];+ }+ + bb2: {+- _1 = move _4;+- StorageDead(_4);+- StorageLive(_5);+- StorageLive(_6);+- _6 = &raw const _1;++ nop;++ nop;++ nop;++ nop;++ _6 = &raw const _7;+ _5 = observe(move _6) -> [return: bb3, unwind unreachable];+ }+ + bb3: {+- StorageDead(_6);+- StorageDead(_5);+- StorageLive(_7);+- _7 = move _1;++ nop;++ nop;++ nop;++ nop;+ _2 = move _7;+- StorageDead(_7);+- StorageLive(_8);+- StorageLive(_9);++ nop;++ nop;++ nop;+ _9 = &raw const _2;+ _8 = observe(move _9) -> [return: bb4, unwind unreachable];+ }+ + bb4: {+- StorageDead(_9);+- StorageDead(_8);++ nop;++ nop;+ _0 = const ();+- StorageDead(_2);+- StorageDead(_1);++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/dse.rsadded36 + / 0 −
@@ -0,0 +1,36 @@+//@ test-mir-pass: MoveElimination+//@ compile-flags: -Cpanic=abort -Zmir-enable-passes=+DeadStoreElimination-initial++pub struct Fields {+ data: [u8; 8],+ tag: u8,+}++unsafe extern "C" {+ safe fn observe(_: *const Fields);+ safe fn make_fields(_: u8) -> Fields;+}++// EMIT_MIR dse.dse_guard.MoveElimination.diff+pub fn dse_guard() {+ // This guards the RFC soundness hazard: DSE must not remove the first write+ // to `b`, because that write keeps `b`'s address-observed lifetime+ // overlapping with `a` and prevents the later move from being eliminated.+ // CHECK-LABEL: fn dse_guard(+ // CHECK: debug a => [[a:_.*]];+ // CHECK: debug b => [[b:_.*]];+ // CHECK: [[b]] = make_fields(const 0_u8)+ // CHECK: [[a]] = make_fields(const 1_u8)+ // CHECK: observe(move+ // CHECK: [[b]] = move [[a]]+ // CHECK: observe(move+ let mut a;+ let mut b;++ b = make_fields(0);++ a = make_fields(1);+ observe(&raw const a);+ b = a;+ observe(&raw const b);+}tests/mir-opt/move-elimination/exclusions.constant_index_projected.MoveElimination.diffadded41 + / 0 −
@@ -0,0 +1,41 @@+- // MIR for `constant_index_projected` before MoveElimination++ // MIR for `constant_index_projected` after MoveElimination+ + fn constant_index_projected(_1: [[u8; 4]; 2]) -> () {+ debug array => _1;+ let mut _0: ();+ let _2: [u8; 4];+ let _3: [u8; 4];+ let mut _4: [u8; 4];+ scope 1 {+- debug first => _2;++ debug first => _1[0 of 2];+ }+ + bb0: {+- StorageLive(_2);+- _2 = copy _1[0 of 2];+- StorageLive(_3);+- StorageLive(_4);+- _4 = copy _2;+- _3 = std::hint::black_box::<[u8; 4]>(move _4) -> [return: bb1, unwind unreachable];++ nop;++ nop;++ nop;++ nop;++ nop;++ _3 = std::hint::black_box::<[u8; 4]>(move _1[0 of 2]) -> [return: bb1, unwind unreachable];+ }+ + bb1: {+- StorageDead(_4);+- StorageDead(_3);++ nop;++ nop;+ _0 = const ();+- StorageDead(_2);++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/exclusions.index_local_not_projected.MoveElimination.diffadded16 + / 0 −
@@ -0,0 +1,16 @@+- // MIR for `index_local_not_projected` before MoveElimination++ // MIR for `index_local_not_projected` after MoveElimination+ + fn index_local_not_projected(_1: [usize; 4]) -> [usize; 1] {+ let mut _0: [usize; 1];+ let mut _2: usize;+ let mut _3: usize;+ + bb0: {+ _2 = const 2_usize;+ _3 = copy _1[_2];+ _0 = [copy _2];+ return;+ }+ }+ tests/mir-opt/move-elimination/exclusions.overlapping_lifetimes.MoveElimination.diffadded126 + / 0 −
@@ -0,0 +1,126 @@+- // MIR for `overlapping_lifetimes` before MoveElimination++ // MIR for `overlapping_lifetimes` after MoveElimination+ + fn overlapping_lifetimes(_1: bool) -> Fields {+ debug flag => _1;+ let mut _0: Fields;+ let _2: Fields;+ let _4: ();+ let mut _5: *const Fields;+ let _6: ();+ let mut _7: bool;+ let mut _8: Fields;+ let mut _9: Fields;+ let mut _10: Fields;+ let _11: ();+ let mut _12: *const Fields;+ scope 1 {+- debug src => _2;++ debug src => _10;+ let mut _3: Fields;+ scope 2 {+- debug dst => _3;++ debug dst => _0;+ }+ }+ + bb0: {+- StorageLive(_2);+- _2 = make_fields(const 0_u8) -> [return: bb1, unwind unreachable];++ nop;++ _10 = make_fields(const 0_u8) -> [return: bb1, unwind unreachable];+ }+ + bb1: {+- StorageLive(_3);+- StorageLive(_4);+- StorageLive(_5);+- _5 = &raw const _2;++ nop;++ nop;++ nop;++ _5 = &raw const _10;+ _4 = observe(move _5) -> [return: bb2, unwind unreachable];+ }+ + bb2: {+- StorageDead(_5);+- StorageDead(_4);+- StorageLive(_6);+- StorageLive(_7);+- _7 = copy _1;+- switchInt(move _7) -> [0: bb5, otherwise: bb3];++ nop;++ nop;++ nop;++ nop;++ nop;++ switchInt(move _1) -> [0: bb5, otherwise: bb3];+ }+ + bb3: {+- StorageLive(_8);+- _8 = make_fields(const 1_u8) -> [return: bb4, unwind unreachable];++ nop;++ _0 = make_fields(const 1_u8) -> [return: bb4, unwind unreachable];+ }+ + bb4: {+- _3 = move _8;+- StorageDead(_8);+- StorageLive(_9);+- _9 = move _2;+- _3 = move _9;+- StorageDead(_9);++ nop;++ nop;++ nop;++ nop;++ _0 = move _10;++ nop;+ _6 = const ();+ goto -> bb6;+ }+ + bb5: {+- StorageLive(_10);+- _10 = move _2;+- _3 = move _10;+- StorageDead(_10);++ nop;++ nop;++ _0 = move _10;++ nop;+ _6 = const ();+ goto -> bb6;+ }+ + bb6: {+- StorageDead(_7);+- StorageDead(_6);+- StorageLive(_11);+- StorageLive(_12);+- _12 = &raw const _3;++ nop;++ nop;++ nop;++ nop;++ _12 = &raw const _0;+ _11 = observe(move _12) -> [return: bb7, unwind unreachable];+ }+ + bb7: {+- StorageDead(_12);+- StorageDead(_11);+- _0 = move _3;+- StorageDead(_3);+- StorageDead(_2);++ nop;++ nop;++ nop;++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/exclusions.packed_fields_not_projected.MoveElimination.diffadded48 + / 0 −
@@ -0,0 +1,48 @@+- // MIR for `packed_fields_not_projected` before MoveElimination++ // MIR for `packed_fields_not_projected` after MoveElimination+ + fn packed_fields_not_projected() -> Packed {+ let mut _0: Packed;+ let _1: [u8; 8];+ let mut _3: [u8; 8];+ let mut _4: [u8; 8];+ scope 1 {+- debug a => _1;++ debug a => _3;+ let _2: [u8; 8];+ scope 2 {+- debug b => _2;++ debug b => _4;+ }+ }+ + bb0: {+- StorageLive(_1);+- _1 = [const 1_u8; 8];+- StorageLive(_2);+- _2 = [const 2_u8; 8];+- StorageLive(_3);+- _3 = copy _1;+- StorageLive(_4);+- _4 = copy _2;++ nop;++ _3 = [const 1_u8; 8];++ nop;++ _4 = [const 2_u8; 8];++ nop;++ nop;++ nop;++ nop;+ _0 = Packed { a: move _3, b: move _4 };+- StorageDead(_4);+- StorageDead(_3);+- StorageDead(_2);+- StorageDead(_1);++ nop;++ nop;++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/exclusions.rsadded139 + / 0 −
@@ -0,0 +1,139 @@+//@ test-mir-pass: MoveElimination+//@ compile-flags: -Cpanic=abort++#![feature(core_intrinsics, custom_mir, repr_simd)]+#![allow(internal_features)]++use std::intrinsics::mir::*;++pub struct Fields {+ data: [u8; 8],+ tag: u8,+}++#[repr(packed)]+struct Packed {+ a: [u8; 8],+ b: [u8; 8],+}++#[repr(simd)]+struct U32x4([u32; 4]);++unsafe extern "C" {+ safe fn observe(_: *const Fields);+ safe fn make_fields(_: u8) -> Fields;+}++// EMIT_MIR exclusions.index_local_not_projected.MoveElimination.diff+#[custom_mir(dialect = "runtime", phase = "post-cleanup")]+pub fn index_local_not_projected(a: [usize; 4]) -> [usize; 1] {+ // This checks that a local used as an array index is kept as a bare local,+ // because it cannot later be rewritten to a projection like `_0[0]`.+ // CHECK-LABEL: fn index_local_not_projected(+ // CHECK: [[idx:_.*]] = const 2_usize;+ // CHECK: {{.*}} = copy _1[{{.*}}[[idx]]{{.*}}];+ // CHECK: _0 = [copy [[idx]]];+ mir! {+ let idx: usize;+ let b: usize;++ {+ idx = 2usize;+ b = a[idx];+ RET = [idx];+ Return()+ }+ }+}++// EMIT_MIR exclusions.packed_fields_not_projected.MoveElimination.diff+pub fn packed_fields_not_projected() -> Packed {+ // This checks that aggregate fields are not remapped into packed struct+ // fields, which could create unaligned projected places.+ // CHECK-LABEL: fn packed_fields_not_projected(+ // CHECK: debug a => [[a:_.*]];+ // CHECK: debug b => [[b:_.*]];+ // CHECK: _0 = Packed { a: move [[a]], b: move [[b]] };+ let a = [1; 8];+ let b = [2; 8];+ Packed { a, b }+}++// EMIT_MIR exclusions.simd_field_not_projected.MoveElimination.diff+pub fn simd_field_not_projected() -> U32x4 {+ // This checks that aggregate fields are not remapped into repr(simd) ADTs,+ // since optimized MIR must not project into SIMD vectors.+ // CHECK-LABEL: fn simd_field_not_projected(+ // CHECK: debug lanes => [[lanes:_.*]];+ // CHECK: _0 = U32x4(move [[lanes]]);+ let lanes = [1, 2, 3, 4];+ U32x4(lanes)+}++// EMIT_MIR exclusions.overlapping_lifetimes.MoveElimination.diff+pub fn overlapping_lifetimes(flag: bool) -> Fields {+ // This checks the liveness-matrix overlap test for an address-observed+ // move-only local: `src` and `dst` only overlap on one branch, but that is+ // enough to reject merging them for the whole function.+ // CHECK-LABEL: fn overlapping_lifetimes(+ // CHECK: debug flag => _1;+ // CHECK: debug src => [[src:_[1-9][0-9]*]];+ // CHECK: debug dst => _0;+ // CHECK: &raw const [[src]];+ // CHECK: observe+ // CHECK: switchInt(move _1)+ // CHECK: _0 = make_fields(const 1_u8)+ // CHECK: _0 = move [[src]];+ // CHECK: &raw const _0;+ // CHECK: observe+ let src = make_fields(0);+ let mut dst;+ observe(&raw const src);+ if flag {+ dst = make_fields(1);+ dst = src;+ } else {+ dst = src;+ }+ observe(&raw const dst);+ dst+}++// EMIT_MIR exclusions.rust_call_tuple_not_projected.MoveElimination.diff+pub fn rust_call_tuple_not_projected<F: FnOnce([u8; 8], [u8; 8])>(f: F) {+ // This checks that locals are not remapped into the tuple argument passed+ // to a rust-call ABI function. If the tuple itself is never borrowed, alias+ // analysis can trivially see that accesses to one argument don't affect the+ // others. Merging the arguments into tuple fields from the start can hide+ // that independence.+ // CHECK-LABEL: fn rust_call_tuple_not_projected(+ // CHECK: debug a => [[a:_.*]];+ // CHECK: debug b => [[b:_.*]];+ // CHECK: [[tuple:_.*]] = (move [[a]], move [[b]]);+ // CHECK: <F as FnOnce<([u8; 8], [u8; 8])>>::call_once(move _1, move [[tuple]])+ let a = [1; 8];+ let b = [2; 8];+ f(a, b);+}++// Subslice projections cannot appear in debuginfo, so refuse to merge `array`+// and `local`.+// EMIT_MIR exclusions.subslice_not_projected.MoveElimination.diff+pub fn subslice_not_projected(array: [u8; 4]) {+ // CHECK-LABEL: fn subslice_not_projected(+ // CHECK: debug middle => [[MIDDLE:_[0-9]+]];+ // CHECK: [[MIDDLE]] = copy _1[1..3];+ let [_, middle @ .., _] = array;+ std::hint::black_box(middle);+}++// Forward constant indices are supported in debuginfo and allow remapping.+// EMIT_MIR exclusions.constant_index_projected.MoveElimination.diff+pub fn constant_index_projected(array: [[u8; 4]; 2]) {+ // CHECK-LABEL: fn constant_index_projected(+ // CHECK: debug first => _1[0 of 2];+ // CHECK: std::hint::black_box::<[u8; 4]>(move _1[0 of 2])+ let [first, ..] = array;+ std::hint::black_box(first);+}tests/mir-opt/move-elimination/exclusions.rust_call_tuple_not_projected.MoveElimination.diffadded72 + / 0 −
@@ -0,0 +1,72 @@+- // MIR for `rust_call_tuple_not_projected` before MoveElimination++ // MIR for `rust_call_tuple_not_projected` after MoveElimination+ + fn rust_call_tuple_not_projected(_1: F) -> () {+ debug f => _1;+ let mut _0: ();+ let _2: [u8; 8];+ let _4: ();+ let mut _5: F;+ let mut _6: ([u8; 8], [u8; 8]);+ let mut _7: [u8; 8];+ let mut _8: [u8; 8];+ scope 1 {+- debug a => _2;++ debug a => _7;+ let _3: [u8; 8];+ scope 2 {+- debug b => _3;++ debug b => _8;+ }+ }+ + bb0: {+- StorageLive(_2);+- _2 = [const 1_u8; 8];+- StorageLive(_3);+- _3 = [const 2_u8; 8];+- StorageLive(_4);+- StorageLive(_5);+- _5 = move _1;+- StorageLive(_6);+- StorageLive(_7);+- _7 = copy _2;+- StorageLive(_8);+- _8 = copy _3;++ nop;++ _7 = [const 1_u8; 8];++ nop;++ _8 = [const 2_u8; 8];++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;++ nop;+ _6 = (move _7, move _8);+- _4 = <F as FnOnce<([u8; 8], [u8; 8])>>::call_once(move _5, move _6) -> [return: bb1, unwind unreachable];++ _4 = <F as FnOnce<([u8; 8], [u8; 8])>>::call_once(move _1, move _6) -> [return: bb1, unwind unreachable];+ }+ + bb1: {+- StorageDead(_8);+- StorageDead(_7);+- StorageDead(_6);+- StorageDead(_5);+- StorageDead(_4);++ nop;++ nop;++ nop;++ nop;++ nop;+ _0 = const ();+- StorageDead(_3);+- StorageDead(_2);++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/exclusions.simd_field_not_projected.MoveElimination.diffadded30 + / 0 −
@@ -0,0 +1,30 @@+- // MIR for `simd_field_not_projected` before MoveElimination++ // MIR for `simd_field_not_projected` after MoveElimination+ + fn simd_field_not_projected() -> U32x4 {+ let mut _0: U32x4;+ let _1: [u32; 4];+ let mut _2: [u32; 4];+ scope 1 {+- debug lanes => _1;++ debug lanes => _2;+ }+ + bb0: {+- StorageLive(_1);+- _1 = [const 1_u32, const 2_u32, const 3_u32, const 4_u32];+- StorageLive(_2);+- _2 = copy _1;++ nop;++ _2 = [const 1_u32, const 2_u32, const 3_u32, const 4_u32];++ nop;++ nop;+ _0 = U32x4(move _2);+- StorageDead(_2);+- StorageDead(_1);++ nop;++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/exclusions.subslice_not_projected.MoveElimination.diffadded40 + / 0 −
@@ -0,0 +1,40 @@+- // MIR for `subslice_not_projected` before MoveElimination++ // MIR for `subslice_not_projected` after MoveElimination+ + fn subslice_not_projected(_1: [u8; 4]) -> () {+ debug array => _1;+ let mut _0: ();+ let _2: [u8; 2];+ let _3: [u8; 2];+ let mut _4: [u8; 2];+ scope 1 {+- debug middle => _2;++ debug middle => _4;+ }+ + bb0: {+- StorageLive(_2);+- _2 = copy _1[1..3];+- StorageLive(_3);+- StorageLive(_4);+- _4 = copy _2;++ nop;++ _4 = copy _1[1..3];++ nop;++ nop;++ nop;+ _3 = std::hint::black_box::<[u8; 2]>(move _4) -> [return: bb1, unwind unreachable];+ }+ + bb1: {+- StorageDead(_4);+- StorageDead(_3);++ nop;++ nop;+ _0 = const ();+- StorageDead(_2);++ nop;+ return;+ }+ }+ tests/mir-opt/move-elimination/unwind.call_destination.MoveElimination.diffadded27 + / 0 −
@@ -0,0 +1,27 @@+- // MIR for `call_destination` before MoveElimination++ // MIR for `call_destination` after MoveElimination+ + fn call_destination() -> () {+ let mut _0: ();+ let mut _1: u32;+ let mut _2: u32;+ let mut _3: *const u32;+ + bb0: {+ _1 = const 0_u32;+ _3 = &raw const _1;+ _1 = opaque() -> [return: bb1, unwind: bb2];+ }+ + bb1: {+ _0 = ();+ return;+ }+ + bb2 (cleanup): {+ _2 = const 2_u32;+ _1 = move _2;+ resume;+ }+ }+ tests/mir-opt/move-elimination/unwind.rsadded41 + / 0 −
@@ -0,0 +1,41 @@+//@ test-mir-pass: MoveElimination+//@ compile-flags: -Cpanic=unwind++#![crate_type = "lib"]+#![feature(custom_mir, core_intrinsics)]+use std::intrinsics::mir::*;++#[inline(never)]+fn opaque() -> u32 {+ 1+}++// EMIT_MIR unwind.call_destination.MoveElimination.diff+#[custom_mir(dialect = "runtime", phase = "post-cleanup")]+pub fn call_destination() {+ // CHECK-LABEL: fn call_destination(+ // CHECK: [[DEST:_[0-9]+]] = opaque()+ // CHECK: [[SRC:_[0-9]+]] = const 2_u32;+ // CHECK-NEXT: [[DEST]] = move [[SRC]];+ // Unwinding doesn't deallocate the call destination, it only leaves it+ // uninitialized. This checks that dest is not merged with src.+ mir! {+ let dest: u32;+ let src: u32;+ let p: *const u32;+ {+ dest = 0;+ p = &raw const dest;+ Call(dest = opaque(), ReturnTo(done), UnwindCleanup(cleanup))+ }+ done = {+ RET = ();+ Return()+ }+ cleanup (cleanup) = {+ src = 2;+ dest = Move(src);+ UnwindResume()+ }+ }+}