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}