Skip to main content

rustc_mir_transform/
ref_prop.rs

1use std::borrow::Cow;
2
3use rustc_data_structures::fx::FxHashSet;
4use rustc_index::IndexVec;
5use rustc_index::bit_set::DenseBitSet;
6use rustc_middle::bug;
7use rustc_middle::mir::visit::*;
8use rustc_middle::mir::*;
9use rustc_middle::ty::TyCtxt;
10use rustc_mir_dataflow::Analysis;
11use rustc_mir_dataflow::impls::{MaybeStorageDead, always_storage_live_locals};
12use tracing::{debug, instrument};
13
14use crate::PassPolicy;
15use crate::ssa::{SsaLocals, StorageLiveLocals};
16
17/// Propagate references using SSA analysis.
18///
19/// MIR building may produce a lot of borrow-dereference patterns.
20///
21/// This pass aims to transform the following pattern:
22///   _1 = &raw? mut? PLACE;
23///   _3 = *_1;
24///   _4 = &raw? mut? *_1;
25///
26/// Into
27///   _1 = &raw? mut? PLACE;
28///   _3 = PLACE;
29///   _4 = &raw? mut? PLACE;
30///
31/// where `PLACE` is a direct or an indirect place expression.
32///
33/// There are 3 properties that need to be upheld for this transformation to be legal:
34/// - place stability: `PLACE` must refer to the same memory wherever it appears;
35/// - pointer liveness: we must not introduce dereferences of dangling pointers;
36/// - `&mut` borrow uniqueness.
37///
38/// # Stability
39///
40/// If `PLACE` is an indirect projection, if its of the form `(*LOCAL).PROJECTIONS` where:
41/// - `LOCAL` is SSA;
42/// - all projections in `PROJECTIONS` have a stable offset (no dereference and no indexing).
43///
44/// If `PLACE` is a direct projection of a local, we consider it as constant if:
45/// - the local is always live, or it has a single `StorageLive`;
46/// - all projections have a stable offset.
47///
48/// # Liveness
49///
50/// When performing an instantiation, we must take care not to introduce uses of dangling locals.
51/// To ensure this, we walk the body with the `MaybeStorageDead` dataflow analysis:
52/// - if we want to replace `*x` by reborrow `*y` and `y` may be dead, we allow replacement and
53///   mark storage statements on `y` for removal;
54/// - if we want to replace `*x` by non-reborrow `y` and `y` must be live, we allow replacement;
55/// - if we want to replace `*x` by non-reborrow `y` and `y` may be dead, we do not replace.
56///
57/// # Uniqueness
58///
59/// For `&mut` borrows, we also need to preserve the uniqueness property:
60/// we must avoid creating a state where we interleave uses of `*_1` and `_2`.
61/// To do it, we only perform full instantiation of mutable borrows:
62/// we replace either all or none of the occurrences of `*_1`.
63///
64/// Some care has to be taken when `_1` is copied in other locals.
65///   _1 = &raw? mut? _2;
66///   _3 = *_1;
67///   _4 = _1
68///   _5 = *_4
69/// In such cases, fully instantiating `_1` means fully instantiating all of the copies.
70///
71/// For immutable borrows, we do not need to preserve such uniqueness property,
72/// so we perform all the possible instantiations without removing the `_1 = &_2` statement.
73pub(super) struct ReferencePropagation;
74
75impl<'tcx> crate::MirPass<'tcx> for ReferencePropagation {
76    fn policy(&self, sess: &rustc_session::Session) -> PassPolicy {
77        PassPolicy::optimization(sess.mir_opt_level() >= 2)
78    }
79
80    #[instrument(level = "trace", skip(self, tcx, body))]
81    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
82        debug!(def_id = ?body.source.def_id());
83        move_to_copy_pointers(tcx, body);
84        while propagate_ssa(tcx, body) {}
85    }
86}
87
88/// The SSA analysis done by [`SsaLocals`] treats [`Operand::Move`] as a read, even though in
89/// general [`Operand::Move`] represents pass-by-pointer where the callee can overwrite the
90/// pointee (Miri always considers the place deinitialized). CopyProp has a similar trick to
91/// turn [`Operand::Move`] into [`Operand::Copy`] when required for an optimization, but in this
92/// pass we just turn all moves of pointers into copies because pointers should be by-value anyway.
93fn move_to_copy_pointers<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
94    let mut visitor = MoveToCopyVisitor { tcx, local_decls: &body.local_decls };
95    for (bb, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
96        visitor.visit_basic_block_data(bb, data);
97    }
98
99    struct MoveToCopyVisitor<'a, 'tcx> {
100        tcx: TyCtxt<'tcx>,
101        local_decls: &'a IndexVec<Local, LocalDecl<'tcx>>,
102    }
103
104    impl<'a, 'tcx> MutVisitor<'tcx> for MoveToCopyVisitor<'a, 'tcx> {
105        fn tcx(&self) -> TyCtxt<'tcx> {
106            self.tcx
107        }
108
109        fn visit_operand(&mut self, operand: &mut Operand<'tcx>, loc: Location) {
110            if let Operand::Move(place) = *operand {
111                if place.ty(self.local_decls, self.tcx).ty.is_any_ptr() {
112                    *operand = Operand::Copy(place);
113                }
114            }
115            self.super_operand(operand, loc);
116        }
117    }
118}
119
120fn propagate_ssa<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool {
121    let typing_env = body.typing_env(tcx);
122    let ssa = SsaLocals::new(tcx, body, typing_env);
123
124    let mut replacer = compute_replacement(tcx, body, ssa);
125    debug!(?replacer.targets);
126    debug!(?replacer.allowed_replacements);
127    debug!(?replacer.storage_to_remove);
128
129    replacer.visit_body_preserves_cfg(body);
130
131    if replacer.any_replacement {
132        crate::simplify::remove_unused_definitions(body);
133    }
134
135    replacer.any_replacement
136}
137
138#[derive(Copy, Clone, Debug, PartialEq, Eq)]
139enum Value<'tcx> {
140    /// Not a pointer, or we can't know.
141    Unknown,
142    /// We know the value to be a pointer to this place.
143    /// The boolean indicates whether the reference is mutable, subject the uniqueness rule.
144    Pointer(Place<'tcx>, bool),
145}
146
147/// For each local, save the place corresponding to `*local`.
148#[instrument(level = "trace", skip(tcx, body, ssa))]
149fn compute_replacement<'tcx>(
150    tcx: TyCtxt<'tcx>,
151    body: &Body<'tcx>,
152    ssa: SsaLocals,
153) -> Replacer<'tcx> {
154    let always_live_locals = always_storage_live_locals(body);
155
156    // Compute which locals have a single `StorageLive` statement ever.
157    let storage_live = StorageLiveLocals::new(body, &always_live_locals);
158
159    // Compute `MaybeStorageDead` dataflow to check that we only replace when the pointee is
160    // definitely live.
161    let mut maybe_dead = MaybeStorageDead::new(Cow::Owned(always_live_locals))
162        .iterate_to_fixpoint(tcx, body, None)
163        .into_results_cursor(body);
164
165    // Map for each local to the pointee.
166    let mut targets = IndexVec::from_elem(Value::Unknown, &body.local_decls);
167    // Set of locals for which we will remove their storage statement. This is useful for
168    // reborrowed references.
169    let mut storage_to_remove = DenseBitSet::new_empty(body.local_decls.len());
170
171    let fully_replaceable_locals = fully_replaceable_locals(&ssa);
172
173    // Returns true iff we can use `place` as a pointee.
174    //
175    // Note that we only need to verify that there is a single `StorageLive` statement, and we do
176    // not need to verify that it dominates all uses of that local.
177    //
178    // Consider the three statements:
179    //   SL : StorageLive(a)
180    //   DEF: b = &raw? mut? a
181    //   USE: stuff that uses *b
182    //
183    // First, we recall that DEF is checked to dominate USE. Now imagine for the sake of
184    // contradiction there is a DEF -> SL -> USE path. Consider two cases:
185    //
186    // - DEF dominates SL. We always have UB the first time control flow reaches DEF,
187    //   because the storage of `a` is dead. Since DEF dominates USE, that means we cannot
188    //   reach USE and so our optimization is ok.
189    //
190    // - DEF does not dominate SL. Then there is a `START_BLOCK -> SL` path not including DEF.
191    //   But we can extend this path to USE, meaning there is also a `START_BLOCK -> USE` path not
192    //   including DEF. This violates the DEF dominates USE condition, and so is impossible.
193    let is_constant_place = |place: Place<'_>| {
194        // We only allow `Deref` as the first projection, to avoid surprises.
195        if let Some((&PlaceElem::Deref, rest)) = place.projection.split_first() {
196            // `place == (*some_local).xxx`, it is constant only if `some_local` is constant.
197            // We approximate constness using SSAness.
198            ssa.is_ssa(place.local) && rest.iter().all(PlaceElem::is_stable_offset)
199        } else {
200            storage_live.has_single_storage(place.local)
201                && place.projection[..].iter().all(PlaceElem::is_stable_offset)
202        }
203    };
204
205    let mut can_perform_opt = |target: Place<'tcx>, loc: Location| {
206        if target.is_indirect_first_projection() {
207            // We are creating a reborrow. As `place.local` is a reference, removing the storage
208            // statements should not make it much harder for LLVM to optimize.
209            storage_to_remove.insert(target.local);
210            true
211        } else {
212            // This is a proper dereference. We can only allow it if `target` is live.
213            maybe_dead.seek_after_primary_effect(loc);
214            let maybe_dead = maybe_dead.get().contains(target.local);
215            !maybe_dead
216        }
217    };
218
219    for (local, rvalue, location) in ssa.assignments(body) {
220        debug!(?local);
221
222        // Only visit if we have something to do.
223        let Value::Unknown = targets[local] else { bug!() };
224
225        let ty = body.local_decls[local].ty;
226
227        // If this is not a reference or pointer, do nothing.
228        if !ty.is_any_ptr() {
229            debug!("not a reference or pointer");
230            continue;
231        }
232
233        // Whether the current local is subject to the uniqueness rule.
234        let needs_unique = ty.is_mutable_ptr();
235
236        // If this a mutable reference that we cannot fully replace, mark it as unknown.
237        if needs_unique && !fully_replaceable_locals.contains(local) {
238            debug!("not fully replaceable");
239            continue;
240        }
241
242        debug!(?rvalue);
243        match rvalue {
244            // This is a copy, just use the value we have in store for the previous one.
245            // As we are visiting in `assignment_order`, i.e. reverse postorder, `rhs` should
246            // have been visited before.
247            Rvalue::Use(Operand::Copy(place) | Operand::Move(place), _) => {
248                if let Some(rhs) = place.as_local()
249                    && ssa.is_ssa(rhs)
250                {
251                    let target = targets[rhs];
252                    // Only see through immutable reference and pointers, as we do not know yet if
253                    // mutable references are fully replaced.
254                    if !needs_unique && matches!(target, Value::Pointer(..)) {
255                        targets[local] = target;
256                    } else {
257                        targets[local] =
258                            Value::Pointer(tcx.mk_place_deref(rhs.into()), needs_unique);
259                    }
260                }
261            }
262            Rvalue::Ref(_, _, place) | Rvalue::RawPtr(_, place) => {
263                let mut place = *place;
264                // Try to see through `place` in order to collapse reborrow chains.
265                if let Some((&PlaceElem::Deref, rest)) = place.projection.split_first()
266                    && let Value::Pointer(target, inner_needs_unique) = targets[place.local]
267                    // Only see through immutable reference and pointers, as we do not know yet if
268                    // mutable references are fully replaced.
269                    && !inner_needs_unique
270                    // Only collapse chain if the pointee is definitely live.
271                    && can_perform_opt(target, location)
272                {
273                    place = target.project_deeper(rest, tcx);
274                }
275                assert_ne!(place.local, local);
276                if is_constant_place(place) {
277                    targets[local] = Value::Pointer(place, needs_unique);
278                }
279            }
280            // We do not know what to do, so keep as not-a-pointer.
281            _ => {}
282        }
283    }
284
285    debug!(?targets);
286
287    let mut finder =
288        ReplacementFinder { targets, can_perform_opt, allowed_replacements: FxHashSet::default() };
289    let reachable_blocks = traversal::reachable_as_bitset(body);
290    for (bb, bbdata) in body.basic_blocks.iter_enumerated() {
291        // Only visit reachable blocks as we rely on dataflow.
292        if reachable_blocks.contains(bb) {
293            finder.visit_basic_block_data(bb, bbdata);
294        }
295    }
296
297    let allowed_replacements = finder.allowed_replacements;
298    return Replacer {
299        tcx,
300        targets: finder.targets,
301        remap_var_debug_infos: IndexVec::from_elem(None, body.local_decls()),
302        storage_to_remove,
303        allowed_replacements,
304        any_replacement: false,
305    };
306
307    struct ReplacementFinder<'tcx, F> {
308        targets: IndexVec<Local, Value<'tcx>>,
309        can_perform_opt: F,
310        allowed_replacements: FxHashSet<(Local, Location)>,
311    }
312
313    impl<'tcx, F> Visitor<'tcx> for ReplacementFinder<'tcx, F>
314    where
315        F: FnMut(Place<'tcx>, Location) -> bool,
316    {
317        fn visit_place(&mut self, place: &Place<'tcx>, ctxt: PlaceContext, loc: Location) {
318            if matches!(ctxt, PlaceContext::NonUse(_)) {
319                // There is no need to check liveness for non-uses.
320                return;
321            }
322
323            if !place.is_indirect_first_projection() {
324                // This is not a dereference, nothing to do.
325                return;
326            }
327
328            let mut place = place.as_ref();
329            loop {
330                if let Value::Pointer(target, needs_unique) = self.targets[place.local] {
331                    let perform_opt = (self.can_perform_opt)(target, loc);
332                    debug!(?place, ?target, ?needs_unique, ?perform_opt);
333
334                    // This a reborrow chain, recursively allow the replacement.
335                    //
336                    // This also allows to detect cases where `target.local` is not replaceable,
337                    // and mark it as such.
338                    if let &[PlaceElem::Deref] = &target.projection[..] {
339                        assert!(perform_opt);
340                        self.allowed_replacements.insert((target.local, loc));
341                        place.local = target.local;
342                        continue;
343                    } else if perform_opt {
344                        self.allowed_replacements.insert((target.local, loc));
345                    } else if needs_unique {
346                        // This mutable reference is not fully replaceable, so drop it.
347                        self.targets[place.local] = Value::Unknown;
348                    }
349                }
350
351                break;
352            }
353        }
354    }
355}
356
357/// Compute the set of locals that can be fully replaced.
358///
359/// We consider a local to be replaceable iff it's only used in a `Deref` projection `*_local` or
360/// non-use position (like storage statements and debuginfo).
361fn fully_replaceable_locals(ssa: &SsaLocals) -> DenseBitSet<Local> {
362    let mut replaceable = DenseBitSet::new_empty(ssa.num_locals());
363
364    // First pass: for each local, whether its uses can be fully replaced.
365    for local in ssa.locals() {
366        if ssa.num_direct_uses(local) == 0 {
367            replaceable.insert(local);
368        }
369    }
370
371    // Second pass: a local can only be fully replaced if all its copies can.
372    ssa.meet_copy_equivalence(&mut replaceable);
373
374    replaceable
375}
376
377/// Utility to help performing substitution of `*pattern` by `target`.
378struct Replacer<'tcx> {
379    tcx: TyCtxt<'tcx>,
380    targets: IndexVec<Local, Value<'tcx>>,
381    remap_var_debug_infos: IndexVec<Local, Option<Local>>,
382    storage_to_remove: DenseBitSet<Local>,
383    allowed_replacements: FxHashSet<(Local, Location)>,
384    any_replacement: bool,
385}
386
387impl<'tcx> MutVisitor<'tcx> for Replacer<'tcx> {
388    fn tcx(&self) -> TyCtxt<'tcx> {
389        self.tcx
390    }
391
392    fn visit_var_debug_info(&mut self, debuginfo: &mut VarDebugInfo<'tcx>) {
393        if let VarDebugInfoContents::Place(ref mut place) = debuginfo.value
394            && place.projection.is_empty()
395        {
396            let mut new_local = place.local;
397
398            // If the debuginfo is a pointer to another place
399            // and it's a reborrow: see through it
400            while let Value::Pointer(target, _) = self.targets[new_local]
401                && let &[PlaceElem::Deref] = &target.projection[..]
402            {
403                new_local = target.local;
404            }
405            if place.local != new_local {
406                self.remap_var_debug_infos[place.local] = Some(new_local);
407                place.local = new_local;
408
409                self.any_replacement = true;
410            }
411        }
412
413        // Simplify eventual projections left inside `debuginfo`.
414        self.super_var_debug_info(debuginfo);
415    }
416
417    fn visit_statement_debuginfo(
418        &mut self,
419        stmt_debuginfo: &mut StmtDebugInfo<'tcx>,
420        location: Location,
421    ) {
422        let local = match stmt_debuginfo {
423            StmtDebugInfo::AssignRef(local, _) | StmtDebugInfo::InvalidAssign(local) => local,
424        };
425        if let Some(target) = self.remap_var_debug_infos[*local] {
426            *local = target;
427            self.any_replacement = true;
428        }
429        self.super_statement_debuginfo(stmt_debuginfo, location);
430    }
431
432    fn visit_place(&mut self, place: &mut Place<'tcx>, ctxt: PlaceContext, loc: Location) {
433        loop {
434            let Some((&PlaceElem::Deref, rest)) = place.projection.split_first() else { return };
435
436            let Value::Pointer(target, _) = self.targets[place.local] else { return };
437
438            let perform_opt = match ctxt {
439                PlaceContext::NonUse(NonUseContext::VarDebugInfo) => {
440                    target.projection.iter().all(|p| p.can_use_in_debuginfo())
441                }
442                PlaceContext::NonUse(_) => true,
443                _ => self.allowed_replacements.contains(&(target.local, loc)),
444            };
445
446            if !perform_opt {
447                return;
448            }
449
450            *place = target.project_deeper(rest, self.tcx);
451            self.any_replacement = true;
452        }
453    }
454
455    fn visit_statement(&mut self, stmt: &mut Statement<'tcx>, loc: Location) {
456        match stmt.kind {
457            StatementKind::StorageLive(l) | StatementKind::StorageDead(l)
458                if self.storage_to_remove.contains(l) =>
459            {
460                stmt.make_nop(true);
461            }
462            _ => {}
463        }
464        // Do not remove assignments as they may still be useful for debuginfo.
465        self.super_statement(stmt, loc);
466    }
467}