Skip to main content

rustc_mir_transform/
move_elimination.rs

1//! Eliminates copies and moves by unifying MIR places whose allocation ranges
2//! are disjoint.
3//!
4//! See RFC 3943 for the local-lifetime semantics that make this optimization
5//! possible.
6//!
7//! # Motivation
8//!
9//! MIR building can insert a lot of redundant copies, and Rust code in general
10//! often tends to move values around a lot. The result is a lot of assignments
11//! of the form `dest = {move} src;` in MIR. MIR building for constants in
12//! particular tends to create additional locals that are only used inside a
13//! single block to shuffle a value around unnecessarily.
14//!
15//! Additionally, Rust constructs nested aggregates by repeatedly moving values
16//! into fields. For example, a function may build an inner value in a local,
17//! move it into an outer aggregate, then move that aggregate into the caller's
18//! destination. If these intermediate source and destination places have
19//! different addresses, each layer needs an actual copy or move of the bytes.
20//!
21//! LLVM cannot remove these copies when both the source and destination
22//! addresses are observed because merging the allocations would be an
23//! observable change: the program could see that two addresses which were
24//! previously distinct have become the same. This pass removes the copies
25//! earlier, while MIR still has the information needed to prove that the two
26//! allocation ranges do not overlap.
27//!
28//! # Optimization
29//!
30//! The basis of this optimization is place unification. If the source and
31//! destination of an assignment have the same address, then the assignment is a
32//! no-op. The same idea applies to aggregate construction: if a field operand
33//! is already located at the corresponding field of the destination, then the
34//! aggregate assignment does not need to copy those fields.
35//!
36//! The pass represents each unification as a mapping from a local to the place
37//! that should replace it. Mappings are transitive, so `_3` can be resolved
38//! through `_2.1` to `_1.0.1` if earlier mappings established those
39//! relationships.
40//!
41//! The mapping is built by scanning the MIR for assignment statements. For
42//! simple `Use` assignments, it tries to unify the source and destination
43//! places. For `Aggregate` assignments, it tries to map each field operand to
44//! the corresponding field in the assignment destination. Once all mappings
45//! have been chosen, they are applied with one rewrite pass over the body.
46//!
47//! # Constraints
48//!
49//! Adding a mapping must preserve these conditions:
50//!
51//! * At least one side of the candidate pair must be a bare local. The pass can
52//!   map a local to a place with projections, but it cannot map between two
53//!   places that both already have projections.
54//!
55//! * Any projections in the mapped place must be stable everywhere the local is
56//!   used. `Deref` and `Index` projections are rejected because they may refer
57//!   to different memory at different points in the function.
58//!
59//! * The allocation ranges of the source and destination places must not
60//!   overlap. This is checked using `PreciseLiveness`, which computes the
61//!   points where each local must have a distinct allocation. The non-overlap
62//!   proof is required so that the operational semantics can allow both places
63//!   to have the same address.
64//!
65//! * Special-use locals such as arguments and the return place must keep their
66//!   roles. Temps may be mapped into an argument or return place, but two
67//!   special-use locals are not mapped into each other.
68//!
69//! * Some locals are used in contexts where projections cannot be added, such
70//!   as `Index` projections. These locals may only be replaced by another bare
71//!   local.
72//!
73//! # Storage reconstruction
74//!
75//! The original `StorageLive` and `StorageDead` statements no longer describe
76//! the merged liveness produced by unification, so they are removed and rebuilt
77//! from the liveness matrix when lifetime markers are emitted. This is done for
78//! all locals, even ones that have not been merged, which has the additional
79//! benefit of tightening the storage lifetime passed to LLVM.
80//!
81//! # Aliasing fixup
82//!
83//! MIR assignments currently require source and destination places not to
84//! overlap for types that are not treated as scalars in codegen. After local
85//! unification, some assignments may violate that invariant, so a final phase
86//! rewrites them into a form codegen can handle. For each assignment:
87//!
88//! * Self-assignments, such as `_1 = _1`, are deleted.
89//!
90//! * Simple `Use` assignments whose source and destination overlap but are not
91//!   identical are routed through a temporary: the source is read into the
92//!   temporary first, and the temporary is then moved into the destination.
93//!
94//! * Aggregate assignments with any operand that aliases the destination are
95//!   decomposed into per-field assignments. Field self-assignments are dropped.
96//!   Other aliasing fields are read into temporaries first, then all
97//!   destination fields are written. For enum aggregates the discriminant is
98//!   set after fields are written.
99//!
100//! * Other rvalues, such as `Repeat` and `Cast`, are hoisted into a temporary
101//!   if any place they access aliases the destination.
102//!
103//! * Rvalues that operate only on scalar types, such as binary and unary ops,
104//!   `Discriminant`, `Ref`, and `RawPtr`, are left untouched because their
105//!   codegen does not rely on the no-aliasing assumption.
106
107use std::range::RangeInclusive;
108
109use rustc_abi::{ExternAbi, FieldIdx, VariantIdx};
110use rustc_const_eval::util::most_packed_projection;
111use rustc_data_structures::fx::FxHashMap;
112use rustc_index::IndexVec;
113use rustc_index::bit_set::DenseBitSet;
114use rustc_index::interval::SparseIntervalMatrix;
115use rustc_middle::mir::visit::{MutVisitor, NonUseContext, PlaceContext, VisitPlacesWith, Visitor};
116use rustc_middle::mir::*;
117use rustc_middle::ty::{Ty, TyCtxt};
118use rustc_mir_dataflow::impls::{
119    DefUse, SplitPointEffect, SplitPointIndex, VisitAllocatedLocalsWith, dump_liveness_matrix,
120    liveness_matrix,
121};
122use rustc_mir_dataflow::points::DenseLocationMap;
123use rustc_mir_dataflow::{Analysis, Backward, GenKill, ResultsVisitor, visit_results};
124use tracing::{debug, trace};
125
126use crate::PassPolicy;
127use crate::patch::MirPatch;
128
129pub(super) struct MoveElimination;
130
131impl<'tcx> crate::MirPass<'tcx> for MoveElimination {
132    fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy {
133        PassPolicy::optional(
134            ctx.mir_opt_level() >= 2 && ctx.opts.unstable_opts.mir_move_elimination,
135        )
136    }
137
138    #[tracing::instrument(level = "trace", skip(self, tcx, body))]
139    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
140        let def_id = body.source.def_id();
141        trace!(?def_id);
142
143        let points = DenseLocationMap::new(body);
144        let mut liveness_matrix =
145            liveness_matrix(tcx, body, &points, Some("MoveElimination.liveness"));
146
147        dump_liveness_matrix(tcx, body, "MoveElimination.pre-liveness", &points, &liveness_matrix);
148
149        let unprojectable_locals = UnprojectableLocals::find(body);
150        trace!(?unprojectable_locals);
151
152        let rust_call_tuples = find_rust_call_tuples(tcx, body);
153        trace!(?rust_call_tuples);
154
155        let remapped_locals = PlaceUnification::run(
156            tcx,
157            body,
158            &mut liveness_matrix,
159            unprojectable_locals,
160            rust_call_tuples,
161        );
162
163        apply_mappings(tcx, body, &remapped_locals);
164
165        dump_liveness_matrix(tcx, body, "MoveElimination.post-liveness", &points, &liveness_matrix);
166
167        if tcx.sess.emit_lifetime_markers() {
168            reconstruct_storage(tcx, body, &points, &liveness_matrix);
169        }
170
171        apply_alias_fixup(tcx, body);
172    }
173}
174
175////////////////////////////////////////////////////////////////////////////////
176// Unprojectable locals
177
178/// Set of locals which can only be replaced with another local, instead of
179/// an arbitrary place. This is usually because it is used directly as a
180/// `Local` outside of a place (e.g. `Index` projections).
181#[derive(Debug)]
182struct UnprojectableLocals {
183    locals: DenseBitSet<Local>,
184}
185
186impl UnprojectableLocals {
187    fn find(body: &Body<'_>) -> DenseBitSet<Local> {
188        let mut out = Self { locals: DenseBitSet::new_empty(body.local_decls.len()) };
189
190        // Arguments and return places have fixed roles and cannot be replaced
191        // with projected locals.
192        out.locals.insert(RETURN_PLACE);
193        for arg in body.args_iter() {
194            out.locals.insert(arg);
195        }
196
197        out.visit_body(body);
198        out.locals
199    }
200}
201
202impl<'tcx> Visitor<'tcx> for UnprojectableLocals {
203    fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
204        // We can't add more projections before a first position Deref projection.
205        if place.is_indirect() {
206            trace!(
207                "unprojectable local {:?} due to use as deref base at {location:?}",
208                place.local
209            );
210            self.locals.insert(place.local);
211        }
212
213        // Only call visit_local for projections, not the base local.
214        self.visit_projection(place.as_ref(), context, location);
215    }
216
217    fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) {
218        // Storage markers are removed.
219        if let PlaceContext::NonUse(NonUseContext::StorageLive | NonUseContext::StorageDead) =
220            context
221        {
222            return;
223        }
224
225        // If this is reached, it means that this is a bare local used outside
226        // of a place, which means it cannot be replaced with a projection of
227        // another local.
228        trace!("unprojectable local {local:?} at {location:?} ({context:?})");
229        self.locals.insert(local);
230    }
231}
232
233////////////////////////////////////////////////////////////////////////////////
234// "rust-call" tuple handling
235
236/// Search for tuple locals passed to calls using the "rust-call" ABI.
237///
238/// For rust-call ABI calls, caller-side MIR passes the logical arguments as a
239/// tuple operand. We want to avoid remapping other locals into fields of that
240/// tuple, especially if one of those locals is borrowed.
241///
242/// Since the tuple itself is never borrowed, it is trivial for LLVM alias
243/// analysis to see that accesses to one argument do not affect the others, but
244/// merging the arguments into tuple fields from the start can hide that
245/// independence.
246fn find_rust_call_tuples<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> DenseBitSet<Local> {
247    let mut rust_call_tuples = DenseBitSet::new_empty(body.local_decls.len());
248
249    for block in body.basic_blocks.iter() {
250        let terminator = block.terminator();
251        let (func, args) = match &terminator.kind {
252            TerminatorKind::Call { func, args, .. }
253            | TerminatorKind::TailCall { func, args, .. } => (func, args),
254            _ => continue,
255        };
256
257        let sig = func.ty(&body.local_decls, tcx).fn_sig(tcx);
258        if sig.abi() != ExternAbi::RustCall {
259            continue;
260        }
261
262        let arg_tuple = args.last().expect("rust-call ABI requires a tuple argument");
263        let (Operand::Copy(place) | Operand::Move(place)) = arg_tuple.node else {
264            continue;
265        };
266        if let Some(local) = place.as_local() {
267            rust_call_tuples.insert(local);
268        }
269    }
270
271    rust_call_tuples
272}
273
274////////////////////////////////////////////////////////////////////////////////
275// Local unification
276
277struct PlaceUnification<'a, 'tcx> {
278    tcx: TyCtxt<'tcx>,
279    body: &'a Body<'tcx>,
280    liveness_matrix: &'a mut SparseIntervalMatrix<Local, SplitPointIndex>,
281    unprojectable_locals: DenseBitSet<Local>,
282    rust_call_tuples: DenseBitSet<Local>,
283    remapped_locals: IndexVec<Local, Option<Place<'tcx>>>,
284}
285
286impl<'tcx> PlaceUnification<'_, 'tcx> {
287    fn run(
288        tcx: TyCtxt<'tcx>,
289        body: &Body<'tcx>,
290        liveness_matrix: &mut SparseIntervalMatrix<Local, SplitPointIndex>,
291        unprojectable_locals: DenseBitSet<Local>,
292        rust_call_tuples: DenseBitSet<Local>,
293    ) -> IndexVec<Local, Option<Place<'tcx>>> {
294        let mut visitor = PlaceUnification {
295            tcx,
296            body,
297            liveness_matrix,
298            unprojectable_locals,
299            rust_call_tuples,
300            remapped_locals: IndexVec::from_elem_n(None, body.local_decls.len()),
301        };
302        visitor.visit_body(body);
303
304        // Finalize the mappings by transitively resolving all locals to their
305        // new final place.
306        for local in visitor.remapped_locals.indices() {
307            if let Some(place) = visitor.remapped_locals[local] {
308                let place = visitor.resolve_place(place);
309                visitor.remapped_locals[local] = Some(place);
310                trace!("Remapped {local:?} to {place:?}");
311            }
312        }
313
314        visitor.remapped_locals
315    }
316
317    #[tracing::instrument(ret, level = "trace", skip(self))]
318    fn resolve_place(&self, mut place: Place<'tcx>) -> Place<'tcx> {
319        while let Some(new_place) = self.remapped_locals[place.local] {
320            place = new_place.project_deeper(place.projection, self.tcx);
321        }
322        place
323    }
324
325    #[tracing::instrument(ret, level = "trace", skip(self))]
326    fn can_unify_places(&self, a: Place<'tcx>, b: Place<'tcx>) -> Option<(Local, Place<'tcx>)> {
327        let a = self.resolve_place(a);
328        let b = self.resolve_place(b);
329
330        if a.local == b.local {
331            if a.projection != b.projection {
332                trace!("cannot unify same local with different projections");
333            }
334            return None;
335        }
336
337        if self.rust_call_tuples.contains(a.local) || self.rust_call_tuples.contains(b.local) {
338            trace!("cannot unify {a:?} and {b:?} involving a rust-call tuple argument");
339            return None;
340        }
341
342        let (local, place) = match (a.as_local(), b.as_local()) {
343            (None, None) => {
344                trace!("cannot unify 2 places that both have projections");
345                return None;
346            }
347            (None, Some(b)) => {
348                if self.unprojectable_locals.contains(b) {
349                    trace!("cannot unify {b:?} which cannot be projected");
350                    return None;
351                }
352                (b, a)
353            }
354            (Some(a), None) => {
355                if self.unprojectable_locals.contains(a) {
356                    trace!("cannot unify {a:?} which cannot be projected");
357                    return None;
358                }
359                (a, b)
360            }
361            (Some(a), Some(b)) => match (self.body.local_kind(a), self.body.local_kind(b)) {
362                (
363                    LocalKind::Arg | LocalKind::ReturnPointer,
364                    LocalKind::Arg | LocalKind::ReturnPointer,
365                ) => {
366                    trace!("cannot unify {a:?} and {b:?} which are both arguments or return place");
367                    return None;
368                }
369                (LocalKind::Arg | LocalKind::ReturnPointer, LocalKind::Temp) => (b, a.into()),
370                (LocalKind::Temp, _) => (a, b.into()),
371            },
372        };
373
374        if most_packed_projection(self.tcx, &self.body.local_decls, place).is_some() {
375            trace!("cannot unify {place:?} which has packed field projections");
376            return None;
377        }
378
379        if !self.liveness_matrix.disjoint_rows(local, place.local) {
380            trace!("cannot unify {a:?} and {b:?} which have overlapping live ranges");
381            return None;
382        }
383
384        // FIXME(#112651): This can be removed afterwards.
385        let local_ty = self.body.local_decls[local].ty;
386        let place_ty = place.ty(&self.body.local_decls, self.tcx).ty;
387        if local_ty != place_ty {
388            trace!(
389                "cannot unify {a:?} and {b:?} which have different types due to subtyping ({local_ty:?} vs {place_ty:?})"
390            );
391            return None;
392        }
393
394        Some((local, place))
395    }
396
397    #[tracing::instrument(level = "trace", skip(self))]
398    fn remap_local(&mut self, local: Local, place: Place<'tcx>) {
399        debug_assert!(self.remapped_locals[local].is_none());
400        self.remapped_locals[local] = Some(place);
401
402        self.liveness_matrix.union_rows(local, place.local);
403        self.liveness_matrix.clear_row(local);
404
405        // If the original local was unprojectable then this now also applies to
406        // the mapped local.
407        if self.unprojectable_locals.contains(local) {
408            debug_assert!(place.projection.is_empty());
409            self.unprojectable_locals.insert(place.local);
410        }
411    }
412
413    fn visit_aggregate_assign(
414        &mut self,
415        dest: Place<'tcx>,
416        project_field: impl Fn(TyCtxt<'tcx>, Place<'tcx>, FieldIdx, Ty<'tcx>) -> Place<'tcx>,
417        operands: &IndexVec<FieldIdx, Operand<'tcx>>,
418        location: Location,
419    ) {
420        // Attempt to unify each field operand with the corresponding field in
421        // the destination place.
422        let mut candidates = vec![];
423        for (idx, operand) in operands.iter_enumerated() {
424            let (Operand::Copy(src) | Operand::Move(src)) = *operand else {
425                continue;
426            };
427            let Some(src) = src.as_local() else {
428                continue;
429            };
430            let dest = project_field(self.tcx, dest, idx, self.body.local_decls[src].ty);
431            trace!("Attempting to unify {dest:?} and {src:?} at {location:?}");
432            if let Some((local, place)) = self.can_unify_places(dest, src.into()) {
433                candidates.push((local, place));
434            }
435        }
436
437        // Do the actual remapping *after* checking for live range overlaps.
438        //
439        // If we tried to do this immediately after `can_unify_places` for each
440        // operand then this would only allow unifying with one operand: once
441        // the first operand's live range is unified with the destination, all
442        // remaining inputs conflict with the combined live range.
443        //
444        // By deferring remapping until all inputs are checked, we can unify
445        // with all eligible inputs instead of just one. This is sound because
446        // they will each be mapped to a disjoint field of the destination
447        // place.
448        for (local, place) in candidates {
449            // Repeated operands can produce multiple candidates for the same
450            // local. Only keep the first mapping.
451            if self.remapped_locals[local].is_some() {
452                continue;
453            }
454            self.remap_local(local, place);
455        }
456    }
457}
458
459/// Since we are replacing all uses of a local with another place, we need to
460/// ensure that the projections on that place are stable no matter where it is
461/// used in the body. Additional this local may be used in debuginfo, so ensure
462/// that the projections are compatible with usage in debuginfo.
463fn check_projections(place: Place<'_>) -> bool {
464    place.projection.iter().all(|elem| elem.is_stable_offset() && elem.can_use_in_debuginfo())
465}
466
467impl<'tcx> Visitor<'tcx> for PlaceUnification<'_, 'tcx> {
468    fn visit_assign(&mut self, dest: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {
469        if !check_projections(*dest) {
470            return;
471        }
472        match rvalue {
473            Rvalue::Use(Operand::Copy(src) | Operand::Move(src), _) => {
474                if !check_projections(*src) {
475                    return;
476                }
477
478                trace!("Attempting to unify {dest:?} and {src:?} at {location:?}");
479                if let Some((local, place)) = self.can_unify_places(*src, *dest) {
480                    self.remap_local(local, place);
481                }
482            }
483            Rvalue::Aggregate(aggregate_kind, operands) => match *aggregate_kind {
484                AggregateKind::Array(_) => self.visit_aggregate_assign(
485                    *dest,
486                    |tcx, place, field_idx, _field_ty| {
487                        place.project_deeper(
488                            &[PlaceElem::ConstantIndex {
489                                offset: field_idx.as_u32().into(),
490                                min_length: field_idx.as_u32() as u64 + 1,
491                                from_end: false,
492                            }],
493                            tcx,
494                        )
495                    },
496                    operands,
497                    location,
498                ),
499                AggregateKind::Tuple => self.visit_aggregate_assign(
500                    *dest,
501                    |tcx, place, field_idx, field_ty| {
502                        place.project_deeper(&[PlaceElem::Field(field_idx, field_ty)], tcx)
503                    },
504                    operands,
505                    location,
506                ),
507                AggregateKind::Adt(_, _, _, _, Some(union_field_idx)) => {
508                    debug_assert_eq!(operands.len(), 1);
509                    self.visit_aggregate_assign(
510                        *dest,
511                        |tcx, place, _, field_ty| {
512                            place
513                                .project_deeper(&[PlaceElem::Field(union_field_idx, field_ty)], tcx)
514                        },
515                        operands,
516                        location,
517                    )
518                }
519                AggregateKind::Adt(adt_did, var_idx, _, _, None) => {
520                    let def = self.tcx.adt_def(adt_did);
521                    if !def.can_project_fields(self.tcx) {
522                        return;
523                    }
524                    self.visit_aggregate_assign(
525                        *dest,
526                        |tcx, place, field_idx, field_ty| {
527                            if def.is_enum() {
528                                place.project_deeper(
529                                    &[
530                                        PlaceElem::Downcast(None, var_idx),
531                                        PlaceElem::Field(field_idx, field_ty),
532                                    ],
533                                    tcx,
534                                )
535                            } else {
536                                place.project_deeper(&[PlaceElem::Field(field_idx, field_ty)], tcx)
537                            }
538                        },
539                        operands,
540                        location,
541                    )
542                }
543                _ => {}
544            },
545            _ => {}
546        };
547    }
548}
549
550////////////////////////////////////////////////////////////////////////////////
551// Apply place mappings to the MIR body.
552
553fn apply_mappings<'tcx>(
554    tcx: TyCtxt<'tcx>,
555    body: &mut Body<'tcx>,
556    remapped_locals: &IndexVec<Local, Option<Place<'tcx>>>,
557) {
558    let mut rewriter = PlaceUpdater { tcx, remapped_locals };
559    rewriter.visit_body_preserves_cfg(body);
560}
561
562struct PlaceUpdater<'a, 'tcx> {
563    tcx: TyCtxt<'tcx>,
564    remapped_locals: &'a IndexVec<Local, Option<Place<'tcx>>>,
565}
566
567impl<'tcx> MutVisitor<'tcx> for PlaceUpdater<'_, 'tcx> {
568    fn tcx(&self) -> TyCtxt<'tcx> {
569        self.tcx
570    }
571
572    fn visit_local(&mut self, local: &mut Local, context: PlaceContext, location: Location) {
573        if let Some(new_place) = self.remapped_locals[*local] {
574            trace!("replacing {local:?} with {new_place:?} at {location:?} ({context:?})");
575            *local = new_place.as_local().expect("mapped place shouldn't have projections");
576        }
577    }
578
579    fn visit_place(&mut self, place: &mut Place<'tcx>, context: PlaceContext, location: Location) {
580        if let Some(new_place) = self.remapped_locals[place.local] {
581            trace!("replacing {place:?} with {new_place:?} at {location:?} ({context:?})");
582            *place = new_place.project_deeper(place.projection, self.tcx)
583        }
584
585        // Only call visit_local for projections, not the base local.
586        if let Some(new_projection) = self.process_projection(&place.projection, location) {
587            place.projection = self.tcx().mk_place_elems(&new_projection);
588        }
589    }
590
591    fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
592        match statement.kind {
593            // Remove storage lifetime markers because they no longer describe
594            // the merged locals. The later storage reconstruction pass rebuilds
595            // these markers for all locals, which can also shorten the storage
596            // lifetimes of locals that were not merged.
597            StatementKind::StorageDead(_) | StatementKind::StorageLive(_) => {
598                statement.make_nop(true);
599                return;
600            }
601            _ => {}
602        }
603
604        self.super_statement(statement, location);
605    }
606}
607
608////////////////////////////////////////////////////////////////////////////////
609// Storage reconstruction
610
611/// Backward dataflow analysis which answers the question: from this point, is
612/// there a path whose first access to a local is an initialization?
613///
614/// This is used when a local is dead in a predecessor but maybe-live in its
615/// successor. A `StorageLive` is inserted on that edge only if some continuation
616/// initializes the local before reading it. If every continuation instead
617/// reads the local first or never accesses it, `StorageLive` would be
618/// unnecessary: it only allocates uninitialized storage and cannot make the
619/// read valid.
620///
621/// For example:
622///
623/// ```text
624///              bb1                              bb2
625///     StorageLive(_1); _1 = ...             // _1 is dead
626///              _flag = true                  _flag = false
627///                       \                     /
628///                        \                   /
629///                               bb3
630///                         switchInt(_flag)
631///                           /          \
632///                  bb4: use(_1)     bb5: no use
633/// ```
634///
635/// Liveness is path-insensitive, so `_1` is maybe-live in `bb3`: it is live in
636/// `bb1` and `bb4`, but dead in `bb2` and `bb5`. Nevertheless, no `StorageLive`
637/// is needed on `bb2 -> bb3`. The continuation to `bb5` never accesses `_1`,
638/// while the continuation to `bb4` reads `_1` without initializing it first and
639/// is therefore already UB. (The `_flag` assignments make that latter
640/// continuation dynamically impossible, but this analysis does not need to
641/// prove the correlation.)
642///
643/// Live ranges which start in the middle of a block do not need this analysis:
644/// such ranges always start at an initialization, so a `StorageLive` is
645/// unconditionally required there.
646///
647/// This analysis deliberately ignores the reconstructed `StorageDead`
648/// boundaries. This can cause an initialization from a later allocation range
649/// to propagate into an earlier range and result in an unnecessary
650/// `StorageLive`, but cannot cause a required `StorageLive` to be omitted.
651struct InitializedBeforeUse;
652
653impl<'tcx> Analysis<'tcx> for InitializedBeforeUse {
654    type Domain = DenseBitSet<Local>;
655    type Direction = Backward;
656
657    const NAME: &'static str = "initialized-before-use";
658
659    fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
660        DenseBitSet::new_empty(body.local_decls.len())
661    }
662
663    fn initialize_start_block(&self, _body: &Body<'tcx>, _state: &mut Self::Domain) {}
664
665    fn apply_primary_statement_effect(
666        &self,
667        state: &mut Self::Domain,
668        statement: &Statement<'tcx>,
669        location: Location,
670    ) {
671        // In backward order, process writes before reads.
672        VisitAllocatedLocalsWith(|local, _| state.gen_(local)).visit_statement(statement, location);
673        VisitPlacesWith(|place: Place<'tcx>, context| {
674            if matches!(DefUse::for_place(place, context), DefUse::Use) {
675                state.kill(place.local);
676            }
677        })
678        .visit_statement(statement, location);
679    }
680
681    fn apply_primary_terminator_effect(
682        &self,
683        state: &mut Self::Domain,
684        terminator: &Terminator<'tcx>,
685        location: Location,
686    ) {
687        // In backward order, process writes before reads.
688        VisitAllocatedLocalsWith(|local, _| state.gen_(local))
689            .visit_terminator(terminator, location);
690        VisitPlacesWith(|place: Place<'tcx>, context| {
691            if matches!(DefUse::for_place(place, context), DefUse::Use) {
692                state.kill(place.local);
693            }
694        })
695        .visit_terminator(terminator, location);
696    }
697}
698
699impl InitializedBeforeUse {
700    /// Computes the analysis state at the start of each block.
701    fn compute<'tcx>(
702        tcx: TyCtxt<'tcx>,
703        body: &Body<'tcx>,
704    ) -> IndexVec<BasicBlock, DenseBitSet<Local>> {
705        let results =
706            Self.iterate_to_fixpoint(tcx, body, Some("MoveElimination.initialized-before-use"));
707        let mut block_start = IndexVec::from_elem_n(
708            DenseBitSet::new_empty(body.local_decls.len()),
709            body.basic_blocks.len(),
710        );
711
712        struct BlockStartVisitor<'a> {
713            block_start: &'a mut IndexVec<BasicBlock, DenseBitSet<Local>>,
714        }
715
716        impl<'tcx> ResultsVisitor<'tcx, InitializedBeforeUse> for BlockStartVisitor<'_> {
717            fn visit_block_exit(&mut self, state: &DenseBitSet<Local>, block: BasicBlock) {
718                self.block_start[block].clone_from(state);
719            }
720        }
721
722        visit_results(
723            body,
724            rustc_middle::mir::traversal::reachable(body).map(|(block, _)| block),
725            &results,
726            &mut BlockStartVisitor { block_start: &mut block_start },
727        );
728        block_start
729    }
730}
731
732/// Helper function to split a critical edge if necessary.
733fn get_or_split_edge<'tcx>(
734    patcher: &mut MirPatch<'tcx>,
735    body: &Body<'tcx>,
736    split_edges: &mut FxHashMap<(BasicBlock, BasicBlock), BasicBlock>,
737    pred: BasicBlock,
738    succ: BasicBlock,
739) -> BasicBlock {
740    if let Some(&split_bb) = split_edges.get(&(pred, succ)) {
741        return split_bb;
742    }
743    let source_info = body.basic_blocks[pred].terminator().source_info;
744    let split_bb = patcher.new_block(BasicBlockData::new(
745        Some(Terminator { source_info, kind: TerminatorKind::goto(succ) }),
746        body.basic_blocks[succ].is_cleanup,
747    ));
748    patcher.mutate_terminator(body, pred, |kind| {
749        kind.successors_mut(|t| {
750            if *t == succ {
751                *t = split_bb;
752            }
753        });
754    });
755    split_edges.insert((pred, succ), split_bb);
756    split_bb
757}
758
759/// Live range boundary within a block which indicates where a storage statement
760/// should be inserted.
761enum RangeBoundary {
762    /// The range boundary is at the given location, and storage statements
763    /// should be inserted before/after it.
764    Loc(Location),
765
766    /// The range is live across a block edge, and storage statements should be
767    /// inserted in predecessor/successor blocks.
768    Edge(BasicBlock),
769}
770
771/// Splits a live interval at block boundaries, returning the location at which
772/// storage statements need to be inserted at the start and end of the range.
773///
774/// This is needed because `SparseIntervalMatrix` coalesces numerically adjacent
775/// ranges, which can span multiple basic blocks.
776fn split_range_by_block(
777    points: &DenseLocationMap,
778    range: RangeInclusive<SplitPointIndex>,
779) -> impl Iterator<Item = (RangeBoundary, RangeBoundary)> {
780    let start = points.to_location(range.start.point());
781    let end = points.to_location(range.last.point());
782    debug_assert!(start.block <= end.block);
783
784    (start.block.index()..=end.block.index()).map(move |block| {
785        let block = BasicBlock::from_usize(block);
786
787        // If the live range starts at the first statement's `Early` point,
788        // the value came from a predecessor block. A write from the first
789        // statement would happen at the `Late` point instead.
790        let start = if block != start.block
791            || (start.statement_index == 0 && range.start.effect() == SplitPointEffect::Early)
792        {
793            RangeBoundary::Edge(block)
794        } else {
795            RangeBoundary::Loc(start)
796        };
797
798        // We need to insert `StorageDead` after the last statement that
799        // uses a local. If this is a terminator then we need to instead
800        // insert it at the start of every successor block where the local
801        // is dead on entry.
802        let end = if block == end.block && range.last.point() != points.terminator(block) {
803            RangeBoundary::Loc(end)
804        } else {
805            RangeBoundary::Edge(block)
806        };
807        (start, end)
808    })
809}
810
811/// Re-constructs storage statements for all locals.
812fn reconstruct_storage<'tcx>(
813    tcx: TyCtxt<'tcx>,
814    body: &mut Body<'tcx>,
815    points: &DenseLocationMap,
816    liveness_matrix: &SparseIntervalMatrix<Local, SplitPointIndex>,
817) {
818    let initialized_before_use = InitializedBeforeUse::compute(tcx, body);
819    let mut patcher = MirPatch::new(body);
820    let mut split_edges: FxHashMap<(BasicBlock, BasicBlock), BasicBlock> = Default::default();
821    let mut storage_lives = Vec::new();
822
823    for local in body.local_decls.indices() {
824        // Arguments and return values don't use storage statements.
825        match body.local_kind(local) {
826            LocalKind::Arg | LocalKind::ReturnPointer => continue,
827            LocalKind::Temp => {}
828        }
829
830        // Ignore dead locals.
831        let Some(row) = liveness_matrix.row(local) else { continue };
832        if row.is_empty() {
833            continue;
834        }
835
836        let mut emit_storage_live_in_preds =
837            |body: &mut Body<'tcx>,
838             patcher: &mut MirPatch<'tcx>,
839             storage_lives: &mut Vec<(Location, Local)>,
840             local: Local,
841             block: BasicBlock| {
842                if !initialized_before_use[block].contains(local) {
843                    // No continuation initializes the local before reading it,
844                    // so allocating storage cannot make any such read valid.
845                    return;
846                }
847
848                for &pred in &body.basic_blocks.predecessors()[block].clone() {
849                    // If the local is live at any point in the predecessor's
850                    // terminator then no StorageLive is needed.
851                    let term = points.terminator(pred);
852                    let term_early = SplitPointIndex::new(term, SplitPointEffect::Early);
853                    let term_late = SplitPointIndex::new(term, SplitPointEffect::Late);
854                    if !row.intersects_range(term_early..=term_late) {
855                        // The local must be live on at least one predecessor,
856                        // so if this is the only one then there is nothing to
857                        // do.
858                        debug_assert!(body.basic_blocks.predecessors()[block].len() > 1);
859
860                        // If the predecessor block has multiple successors then
861                        // we need to split the critical edge before inserting
862                        // StorageLive, otherwise the local would end up live on
863                        // paths where it is supposed to be dead.
864                        let loc = if body.basic_blocks[pred].terminator().successors().count() > 1 {
865                            get_or_split_edge(patcher, body, &mut split_edges, pred, block)
866                                .start_location()
867                        } else {
868                            body.terminator_loc(pred)
869                        };
870                        storage_lives.push((loc, local));
871                    }
872                }
873            };
874        let emit_storage_dead_in_succs =
875            |body: &mut Body<'tcx>,
876             patcher: &mut MirPatch<'tcx>,
877             local: Local,
878             block: BasicBlock| {
879                for succ in body.basic_blocks[block].terminator().successors() {
880                    if !row.contains(SplitPointIndex::new(
881                        points.entry_point(succ),
882                        SplitPointEffect::Early,
883                    )) {
884                        // We don't care about critical edges here: if the local
885                        // is already dead in the successor then it doesn't
886                        // matter if we emit a redundant StorageDead.
887
888                        patcher.add_statement(
889                            succ.start_location(),
890                            StatementKind::StorageDead(local),
891                        );
892                    }
893                }
894            };
895
896        // Iterate through the live range of the local and insert `StorageLive`
897        // and `StorageDead` at the points where it transitions from dead to
898        // live and vice versa.
899        for (start, end) in
900            row.iter_intervals().flat_map(|range| split_range_by_block(points, range))
901        {
902            match start {
903                // Live-in from predecessor blocks: emit StorageLive in
904                // predecessors in which it wasn't previously live.
905                RangeBoundary::Edge(block) => {
906                    emit_storage_live_in_preds(
907                        body,
908                        &mut patcher,
909                        &mut storage_lives,
910                        local,
911                        block,
912                    );
913                }
914                // Otherwise just add `StorageLive` before the statement that
915                // starts the live range.
916                RangeBoundary::Loc(location) => storage_lives.push((location, local)),
917            }
918
919            match end {
920                // If the range includes the terminator then emit StorageDead in
921                // successors which the local is dead on entry.
922                RangeBoundary::Edge(block) => {
923                    emit_storage_dead_in_succs(body, &mut patcher, local, block);
924                }
925                // Otherwise just add `StorageDead` after the statement at which
926                // the live range ends.
927                RangeBoundary::Loc(location) => {
928                    patcher.add_statement(
929                        location.successor_within_block(),
930                        StatementKind::StorageDead(local),
931                    );
932                }
933            }
934        }
935    }
936
937    // Queue all `StorageLive` statements after `StorageDead` so that, when
938    // both are inserted at the same location, `StorageDead` always precedes
939    // `StorageLive` to avoid false overlaps.
940    for (loc, local) in storage_lives {
941        patcher.add_statement(loc, StatementKind::StorageLive(local));
942    }
943
944    patcher.apply(body);
945}
946
947////////////////////////////////////////////////////////////////////////////////
948// Aliasing assignment fixup
949//
950// MIR assignments currently do not allow source and destination to alias, so
951// fix this in post-processing.
952
953fn apply_alias_fixup<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
954    let mut patcher = MirPatch::new(body);
955    let mut fixup = AliasFixup { tcx, local_decls: &body.local_decls, patcher: &mut patcher };
956    for (block, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
957        fixup.visit_basic_block_data(block, data);
958    }
959    patcher.apply(body);
960}
961
962/// Returns whether 2 places alias, ignoring indirect places.
963fn places_directly_alias<'tcx>(
964    tcx: TyCtxt<'tcx>,
965    local_decls: &IndexVec<Local, LocalDecl<'tcx>>,
966    a: Place<'tcx>,
967    b: Place<'tcx>,
968) -> bool {
969    // This function doesn't handle indirect aliasing.
970    if a.local != b.local || a.is_indirect_first_projection() || b.is_indirect_first_projection() {
971        return false;
972    }
973
974    for ((prefix, elem_a), (_, elem_b)) in a.iter_projections().zip(b.iter_projections()) {
975        // Continue until we find the first mismatching projection.
976        if elem_a == elem_b {
977            continue;
978        }
979
980        match (elem_a, elem_b) {
981            // Disjoint fields don't alias except if they are union fields.
982            (PlaceElem::Field(_, _), PlaceElem::Field(_, _)) => {
983                let ty = prefix.ty(local_decls, tcx).ty;
984                return ty.is_union();
985            }
986
987            // Disjoint slice elements don't alias.
988            (
989                PlaceElem::ConstantIndex { offset: offset_a, min_length: _, from_end: from_end_a },
990                PlaceElem::ConstantIndex { offset: offset_b, min_length: _, from_end: from_end_b },
991            ) if from_end_a == from_end_b && offset_a != offset_b => {
992                return false;
993            }
994
995            // Conservatively assume the places may alias.
996            _ => return true,
997        }
998    }
999
1000    // If the projections are identical *or* one is a prefix of the other then
1001    // the places alias.
1002    true
1003}
1004
1005struct AliasFixup<'a, 'tcx> {
1006    tcx: TyCtxt<'tcx>,
1007    local_decls: &'a IndexVec<Local, LocalDecl<'tcx>>,
1008    patcher: &'a mut MirPatch<'tcx>,
1009}
1010
1011impl<'tcx> AliasFixup<'_, 'tcx> {
1012    fn isolate_rvalue_to_local(
1013        &mut self,
1014        rvalue: Rvalue<'tcx>,
1015        source_info: SourceInfo,
1016        location: Location,
1017    ) -> Place<'tcx> {
1018        let ty = rvalue.ty(self.local_decls, self.tcx);
1019        let temp = Place::from(self.patcher.new_temp(ty, source_info.span));
1020        trace!("isolating {rvalue:?} to {temp:?} due to conflict");
1021        self.patcher.add_statement(location, StatementKind::StorageLive(temp.local));
1022        self.patcher.add_assign(location, Place::from(temp), rvalue);
1023        self.patcher.add_statement(
1024            location.successor_within_block(),
1025            StatementKind::StorageDead(temp.local),
1026        );
1027        temp
1028    }
1029
1030    fn visit_aggregate_assign(
1031        &mut self,
1032        dest: Place<'tcx>,
1033        enum_variant: Option<VariantIdx>,
1034        project_field: impl Fn(TyCtxt<'tcx>, Place<'tcx>, FieldIdx, Ty<'tcx>) -> Place<'tcx>,
1035        operands: &IndexVec<FieldIdx, Operand<'tcx>>,
1036        source_info: SourceInfo,
1037        location: Location,
1038    ) {
1039        // Fast path: if no direct operand aliases the destination, we're done.
1040        //
1041        // We only look for direct aliases here, which is sufficient because we
1042        // know the input MIR did not have any aliasing and we didn't introduce
1043        // any indirect aliasing in this pass.
1044        //
1045        // If the destination place is indirect then it cannot be the start of
1046        // a lifetime as per the RFC 3943 MIR semantics. This means that the
1047        // lifetime of the underlying allocation must have started earlier,
1048        // which overlaps the early point of the assignment statement. Therefore
1049        // we couldn't have unified any source operand with this destination
1050        // place.
1051        //
1052        // If the source place is indirect then a similar reasoning applies. The
1053        // only exception is if there are multiple source places (e.g.
1054        // aggregates). In that situation it's possible for an indirect source
1055        // to overlap the destination if and only if there is also a direct
1056        // source that overlaps it:
1057        //
1058        // _2 = &_1
1059        // _3 = (copy *_2, move _1) // _1 becomes _3.1 after unification
1060        //
1061        // We handle this here in 2 ways: if there is no direct alias, then
1062        // we're fine. Otherwise, treat all indirect sources as potentially
1063        // aliasing with the destination operand.
1064        let has_direct_alias = operands.iter().any(|op| match op {
1065            Operand::Copy(src) | Operand::Move(src) => {
1066                places_directly_alias(self.tcx, self.local_decls, dest, *src)
1067            }
1068            Operand::Constant(_) | Operand::RuntimeChecks(_) => false,
1069        });
1070        if !has_direct_alias {
1071            return;
1072        }
1073
1074        debug!("splitting aggregate assignment at {location:?}");
1075
1076        // Split into per-field assignments.
1077        let mut assignments = vec![];
1078        for (idx, op) in operands.iter_enumerated() {
1079            let field_ty = op.ty(self.local_decls, self.tcx);
1080            let dest_field = project_field(self.tcx, dest, idx, field_ty);
1081
1082            let emit_op = match op {
1083                Operand::Copy(src) | Operand::Move(src) => {
1084                    if *src == dest_field {
1085                        // Skip identity assignments.
1086                        continue;
1087                    } else if src.is_indirect_first_projection()
1088                        || places_directly_alias(self.tcx, self.local_decls, dest, *src)
1089                    {
1090                        // Partial alias: hoist the source to a temp first so the
1091                        // per-field write no longer overlaps the dest. Indirect
1092                        // sources also need hoisting here because they may point
1093                        // at one of the direct aliasing operands.
1094                        Operand::Move(self.isolate_rvalue_to_local(
1095                            Rvalue::Use(op.clone(), WithRetag::No),
1096                            source_info,
1097                            location,
1098                        ))
1099                    } else {
1100                        op.clone()
1101                    }
1102                }
1103                Operand::Constant(_) | Operand::RuntimeChecks(_) => op.clone(),
1104            };
1105            assignments.push((dest_field, emit_op));
1106        }
1107
1108        // Perform assignments *after* all aliasing fields have been read into
1109        // temporary locals.
1110        for (dest_field, emit_op) in assignments {
1111            self.patcher.add_assign(location, dest_field, Rvalue::Use(emit_op, WithRetag::No));
1112        }
1113
1114        // Delete the original aggregate assignment.
1115        self.patcher.nop_statement(location);
1116
1117        // For enum variants, set the discriminant after all field writes.
1118        if let Some(variant_index) = enum_variant {
1119            self.patcher.add_statement(
1120                location,
1121                StatementKind::SetDiscriminant { place: Box::new(dest), variant_index },
1122            );
1123        }
1124    }
1125}
1126
1127impl<'tcx> MutVisitor<'tcx> for AliasFixup<'_, 'tcx> {
1128    fn tcx(&self) -> TyCtxt<'tcx> {
1129        self.tcx
1130    }
1131
1132    fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
1133        // Fixup the MIR to remove aliasing assignments.
1134        if let StatementKind::Assign((dest, rvalue)) = &mut statement.kind {
1135            match *rvalue {
1136                Rvalue::Use(Operand::Copy(src) | Operand::Move(src), with_retag) => {
1137                    if places_directly_alias(self.tcx, self.local_decls, *dest, src) {
1138                        if src == *dest {
1139                            debug!("{:?} turned into self-assignment, deleting", location);
1140                            statement.make_nop(true);
1141                        } else {
1142                            let temp = self.isolate_rvalue_to_local(
1143                                rvalue.clone(),
1144                                statement.source_info,
1145                                location,
1146                            );
1147                            *rvalue = Rvalue::Use(Operand::Move(temp), with_retag);
1148                        }
1149                    }
1150                }
1151                Rvalue::Aggregate(AggregateKind::Array(_), ref mut operands) => self
1152                    .visit_aggregate_assign(
1153                        *dest,
1154                        None,
1155                        |tcx, place, field_idx, _field_ty| {
1156                            place.project_deeper(
1157                                &[PlaceElem::ConstantIndex {
1158                                    offset: field_idx.as_u32().into(),
1159                                    min_length: field_idx.as_u32() as u64 + 1,
1160                                    from_end: false,
1161                                }],
1162                                tcx,
1163                            )
1164                        },
1165                        operands,
1166                        statement.source_info,
1167                        location,
1168                    ),
1169                Rvalue::Aggregate(AggregateKind::Tuple, ref mut operands) => self
1170                    .visit_aggregate_assign(
1171                        *dest,
1172                        None,
1173                        |tcx, place, field_idx, field_ty| {
1174                            place.project_deeper(&[PlaceElem::Field(field_idx, field_ty)], tcx)
1175                        },
1176                        operands,
1177                        statement.source_info,
1178                        location,
1179                    ),
1180                Rvalue::Aggregate(
1181                    AggregateKind::Adt(_, _, _, _, Some(union_field_idx)),
1182                    ref mut operands,
1183                ) => {
1184                    debug_assert_eq!(operands.len(), 1);
1185                    self.visit_aggregate_assign(
1186                        *dest,
1187                        None,
1188                        |tcx, place, _, field_ty| {
1189                            place
1190                                .project_deeper(&[PlaceElem::Field(union_field_idx, field_ty)], tcx)
1191                        },
1192                        operands,
1193                        statement.source_info,
1194                        location,
1195                    )
1196                }
1197                // Types that forbid field projections must use the general
1198                // aliasing fallback below.
1199                Rvalue::Aggregate(
1200                    AggregateKind::Adt(adt_did, var_idx, _, _, None),
1201                    ref mut operands,
1202                ) if self.tcx.adt_def(adt_did).can_project_fields(self.tcx) => {
1203                    let def = self.tcx.adt_def(adt_did);
1204                    self.visit_aggregate_assign(
1205                        *dest,
1206                        def.is_enum().then_some(var_idx),
1207                        |tcx, place, field_idx, field_ty| {
1208                            if def.is_enum() {
1209                                place.project_deeper(
1210                                    &[
1211                                        PlaceElem::Downcast(None, var_idx),
1212                                        PlaceElem::Field(field_idx, field_ty),
1213                                    ],
1214                                    tcx,
1215                                )
1216                            } else {
1217                                place.project_deeper(&[PlaceElem::Field(field_idx, field_ty)], tcx)
1218                            }
1219                        },
1220                        operands,
1221                        statement.source_info,
1222                        location,
1223                    )
1224                }
1225
1226                // For other rvalues, don't try to split them into components
1227                // and instead just introduce a temporary if there is any
1228                // aliasing
1229                Rvalue::Aggregate(..)
1230                | Rvalue::Repeat(..)
1231                | Rvalue::Cast(..)
1232                | Rvalue::CopyForDeref(..)
1233                | Rvalue::WrapUnsafeBinder(..) => {
1234                    let mut overlaps_dest = false;
1235                    VisitPlacesWith(|place, _ctxt| {
1236                        if places_directly_alias(self.tcx, self.local_decls, *dest, place) {
1237                            overlaps_dest = true;
1238                        }
1239                    })
1240                    .visit_rvalue(rvalue, location);
1241                    if overlaps_dest {
1242                        let temp = self.isolate_rvalue_to_local(
1243                            rvalue.clone(),
1244                            statement.source_info,
1245                            location,
1246                        );
1247                        *rvalue = Rvalue::Use(Operand::Move(temp), WithRetag::No);
1248                    }
1249                }
1250
1251                // These either cannot have aliasing, or allow it because they
1252                // only operate on scalar backend types.
1253                Rvalue::Use(Operand::Constant(..) | Operand::RuntimeChecks(..), _)
1254                | Rvalue::Ref(..)
1255                | Rvalue::ThreadLocalRef(..)
1256                | Rvalue::BinaryOp(..)
1257                | Rvalue::UnaryOp(..)
1258                | Rvalue::Discriminant(..)
1259                | Rvalue::RawPtr(..)
1260                | Rvalue::Reborrow(..) => {}
1261            }
1262        }
1263    }
1264}