rustc_mir_transform/
ssa.rs

1//! We denote as "SSA" the set of locals that verify the following properties:
2//! 1/ They are only assigned-to once, either as a function parameter, or in an assign statement;
3//! 2/ This single assignment dominates all uses;
4//!
5//! As we do not track indirect assignments, a local that has its address taken (via a borrow or raw
6//! borrow operator) is considered non-SSA. However, it is UB to modify through an immutable borrow
7//! of a `Freeze` local. Those can still be considered to be SSA.
8
9use rustc_data_structures::graph::dominators::Dominators;
10use rustc_index::bit_set::DenseBitSet;
11use rustc_index::{IndexSlice, IndexVec};
12use rustc_middle::bug;
13use rustc_middle::middle::resolve_bound_vars::Set1;
14use rustc_middle::mir::visit::*;
15use rustc_middle::mir::*;
16use rustc_middle::ty::{self, TyCtxt};
17use tracing::{debug, instrument, trace};
18
19pub(super) struct SsaLocals {
20    /// Assignments to each local. This defines whether the local is SSA.
21    assignments: IndexVec<Local, Set1<DefLocation>>,
22    /// We visit the body in reverse postorder, to ensure each local is assigned before it is used.
23    /// We remember the order in which we saw the assignments to compute the SSA values in a single
24    /// pass.
25    assignment_order: Vec<Local>,
26    /// Copy equivalence classes between locals. See `copy_classes` for documentation.
27    copy_classes: IndexVec<Local, Local>,
28    /// Number of "direct" uses of each local, ie. uses that are not dereferences.
29    /// We ignore non-uses (Storage statements, debuginfo).
30    direct_uses: IndexVec<Local, u32>,
31    /// Set of SSA locals that are immutably borrowed.
32    borrowed_locals: DenseBitSet<Local>,
33}
34
35pub(super) enum AssignedValue<'a, 'tcx> {
36    Arg,
37    Rvalue(&'a mut Rvalue<'tcx>),
38    Terminator,
39}
40
41impl SsaLocals {
42    pub(super) fn new<'tcx>(
43        tcx: TyCtxt<'tcx>,
44        body: &Body<'tcx>,
45        typing_env: ty::TypingEnv<'tcx>,
46    ) -> SsaLocals {
47        let assignment_order = Vec::with_capacity(body.local_decls.len());
48
49        let assignments = IndexVec::from_elem(Set1::Empty, &body.local_decls);
50        let dominators = body.basic_blocks.dominators();
51
52        let direct_uses = IndexVec::from_elem(0, &body.local_decls);
53        let borrowed_locals = DenseBitSet::new_empty(body.local_decls.len());
54        let mut visitor = SsaVisitor {
55            body,
56            assignments,
57            assignment_order,
58            dominators,
59            direct_uses,
60            borrowed_locals,
61        };
62
63        for local in body.args_iter() {
64            visitor.assignments[local] = Set1::One(DefLocation::Argument);
65            visitor.assignment_order.push(local);
66        }
67
68        // For SSA assignments, a RPO visit will see the assignment before it sees any use.
69        // We only visit reachable nodes: computing `dominates` on an unreachable node ICEs.
70        for (bb, data) in traversal::reverse_postorder(body) {
71            visitor.visit_basic_block_data(bb, data);
72        }
73
74        for var_debug_info in &body.var_debug_info {
75            visitor.visit_var_debug_info(var_debug_info);
76        }
77
78        // The immutability of shared borrows only works on `Freeze` locals. If the visitor found
79        // borrows, we need to check the types. For raw pointers and mutable borrows, the locals
80        // have already been marked as non-SSA.
81        debug!(?visitor.borrowed_locals);
82        for local in visitor.borrowed_locals.iter() {
83            if !body.local_decls[local].ty.is_freeze(tcx, typing_env) {
84                visitor.assignments[local] = Set1::Many;
85            }
86        }
87
88        debug!(?visitor.assignments);
89        debug!(?visitor.direct_uses);
90
91        visitor
92            .assignment_order
93            .retain(|&local| matches!(visitor.assignments[local], Set1::One(_)));
94        debug!(?visitor.assignment_order);
95
96        let mut ssa = SsaLocals {
97            assignments: visitor.assignments,
98            assignment_order: visitor.assignment_order,
99            direct_uses: visitor.direct_uses,
100            borrowed_locals: visitor.borrowed_locals,
101            // This is filled by `compute_copy_classes`.
102            copy_classes: IndexVec::default(),
103        };
104        compute_copy_classes(&mut ssa, body);
105        ssa
106    }
107
108    pub(super) fn num_locals(&self) -> usize {
109        self.assignments.len()
110    }
111
112    pub(super) fn locals(&self) -> impl Iterator<Item = Local> {
113        self.assignments.indices()
114    }
115
116    pub(super) fn is_ssa(&self, local: Local) -> bool {
117        matches!(self.assignments[local], Set1::One(_))
118    }
119
120    /// Return the number of uses if a local that are not "Deref".
121    pub(super) fn num_direct_uses(&self, local: Local) -> u32 {
122        self.direct_uses[local]
123    }
124
125    #[inline]
126    pub(super) fn assignment_dominates(
127        &self,
128        dominators: &Dominators<BasicBlock>,
129        local: Local,
130        location: Location,
131    ) -> bool {
132        match self.assignments[local] {
133            Set1::One(def) => def.dominates(location, dominators),
134            _ => false,
135        }
136    }
137
138    pub(super) fn assignments<'a, 'tcx>(
139        &'a self,
140        body: &'a Body<'tcx>,
141    ) -> impl Iterator<Item = (Local, &'a Rvalue<'tcx>, Location)> + 'a {
142        self.assignment_order.iter().filter_map(|&local| {
143            if let Set1::One(DefLocation::Assignment(loc)) = self.assignments[local] {
144                let stmt = body.stmt_at(loc).left()?;
145                // `loc` must point to a direct assignment to `local`.
146                let Some((target, rvalue)) = stmt.kind.as_assign() else { bug!() };
147                assert_eq!(target.as_local(), Some(local));
148                Some((local, rvalue, loc))
149            } else {
150                None
151            }
152        })
153    }
154
155    pub(super) fn for_each_assignment_mut<'tcx>(
156        &self,
157        basic_blocks: &mut IndexSlice<BasicBlock, BasicBlockData<'tcx>>,
158        mut f: impl FnMut(Local, AssignedValue<'_, 'tcx>, Location),
159    ) {
160        for &local in &self.assignment_order {
161            match self.assignments[local] {
162                Set1::One(DefLocation::Argument) => f(
163                    local,
164                    AssignedValue::Arg,
165                    Location { block: START_BLOCK, statement_index: 0 },
166                ),
167                Set1::One(DefLocation::Assignment(loc)) => {
168                    let bb = &mut basic_blocks[loc.block];
169                    // `loc` must point to a direct assignment to `local`.
170                    let stmt = &mut bb.statements[loc.statement_index];
171                    let StatementKind::Assign(box (target, ref mut rvalue)) = stmt.kind else {
172                        bug!()
173                    };
174                    assert_eq!(target.as_local(), Some(local));
175                    f(local, AssignedValue::Rvalue(rvalue), loc)
176                }
177                Set1::One(DefLocation::CallReturn { call, .. }) => {
178                    let bb = &mut basic_blocks[call];
179                    let loc = Location { block: call, statement_index: bb.statements.len() };
180                    f(local, AssignedValue::Terminator, loc)
181                }
182                _ => {}
183            }
184        }
185    }
186
187    /// Compute the equivalence classes for locals, based on copy statements.
188    ///
189    /// The returned vector maps each local to the one it copies. In the following case:
190    ///   _a = &mut _0
191    ///   _b = move? _a
192    ///   _c = move? _a
193    ///   _d = move? _c
194    /// We return the mapping
195    ///   _a => _a // not a copy so, represented by itself
196    ///   _b => _a
197    ///   _c => _a
198    ///   _d => _a // transitively through _c
199    ///
200    /// Exception: we do not see through the return place, as it cannot be instantiated.
201    pub(super) fn copy_classes(&self) -> &IndexSlice<Local, Local> {
202        &self.copy_classes
203    }
204
205    /// Set of SSA locals that are immutably borrowed.
206    pub(super) fn borrowed_locals(&self) -> &DenseBitSet<Local> {
207        &self.borrowed_locals
208    }
209
210    /// Make a property uniform on a copy equivalence class by removing elements.
211    pub(super) fn meet_copy_equivalence(&self, property: &mut DenseBitSet<Local>) {
212        // Consolidate to have a local iff all its copies are.
213        //
214        // `copy_classes` defines equivalence classes between locals. The `local`s that recursively
215        // move/copy the same local all have the same `head`.
216        for (local, &head) in self.copy_classes.iter_enumerated() {
217            // If any copy does not have `property`, then the head is not.
218            if !property.contains(local) {
219                property.remove(head);
220            }
221        }
222        for (local, &head) in self.copy_classes.iter_enumerated() {
223            // If any copy does not have `property`, then the head doesn't either,
224            // then no copy has `property`.
225            if !property.contains(head) {
226                property.remove(local);
227            }
228        }
229
230        // Verify that we correctly computed equivalence classes.
231        #[cfg(debug_assertions)]
232        for (local, &head) in self.copy_classes.iter_enumerated() {
233            assert_eq!(property.contains(local), property.contains(head));
234        }
235    }
236}
237
238struct SsaVisitor<'a, 'tcx> {
239    body: &'a Body<'tcx>,
240    dominators: &'a Dominators<BasicBlock>,
241    assignments: IndexVec<Local, Set1<DefLocation>>,
242    assignment_order: Vec<Local>,
243    direct_uses: IndexVec<Local, u32>,
244    // Track locals that are immutably borrowed, so we can check their type is `Freeze` later.
245    borrowed_locals: DenseBitSet<Local>,
246}
247
248impl SsaVisitor<'_, '_> {
249    fn check_dominates(&mut self, local: Local, loc: Location) {
250        let set = &mut self.assignments[local];
251        let assign_dominates = match *set {
252            Set1::Empty | Set1::Many => false,
253            Set1::One(def) => def.dominates(loc, self.dominators),
254        };
255        // We are visiting a use that is not dominated by an assignment.
256        // Either there is a cycle involved, or we are reading for uninitialized local.
257        // Bail out.
258        if !assign_dominates {
259            *set = Set1::Many;
260        }
261    }
262}
263
264impl<'tcx> Visitor<'tcx> for SsaVisitor<'_, 'tcx> {
265    fn visit_local(&mut self, local: Local, ctxt: PlaceContext, loc: Location) {
266        match ctxt {
267            PlaceContext::MutatingUse(MutatingUseContext::Projection)
268            | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => bug!(),
269            // Anything can happen with raw pointers, so remove them.
270            PlaceContext::NonMutatingUse(NonMutatingUseContext::RawBorrow)
271            | PlaceContext::MutatingUse(_) => {
272                self.assignments[local] = Set1::Many;
273            }
274            // Immutable borrows are ok, but we need to delay a check that the type is `Freeze`.
275            PlaceContext::NonMutatingUse(
276                NonMutatingUseContext::SharedBorrow | NonMutatingUseContext::FakeBorrow,
277            ) => {
278                self.borrowed_locals.insert(local);
279                self.check_dominates(local, loc);
280                self.direct_uses[local] += 1;
281            }
282            PlaceContext::NonMutatingUse(_) => {
283                self.check_dominates(local, loc);
284                self.direct_uses[local] += 1;
285            }
286            PlaceContext::NonUse(_) => {}
287        }
288    }
289
290    fn visit_place(&mut self, place: &Place<'tcx>, ctxt: PlaceContext, loc: Location) {
291        let location = match ctxt {
292            PlaceContext::MutatingUse(MutatingUseContext::Store) => {
293                Some(DefLocation::Assignment(loc))
294            }
295            PlaceContext::MutatingUse(MutatingUseContext::Call) => {
296                let call = loc.block;
297                let TerminatorKind::Call { target, .. } =
298                    self.body.basic_blocks[call].terminator().kind
299                else {
300                    bug!()
301                };
302                Some(DefLocation::CallReturn { call, target })
303            }
304            _ => None,
305        };
306        if let Some(location) = location
307            && let Some(local) = place.as_local()
308        {
309            self.assignments[local].insert(location);
310            if let Set1::One(_) = self.assignments[local] {
311                // Only record if SSA-like, to avoid growing the vector needlessly.
312                self.assignment_order.push(local);
313            }
314        } else if place.projection.first() == Some(&PlaceElem::Deref) {
315            // Do not do anything for debuginfo.
316            if ctxt.is_use() {
317                // Only change the context if it is a real use, not a "use" in debuginfo.
318                let new_ctxt = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy);
319
320                self.visit_projection(place.as_ref(), new_ctxt, loc);
321                self.check_dominates(place.local, loc);
322            }
323        } else {
324            self.visit_projection(place.as_ref(), ctxt, loc);
325            self.visit_local(place.local, ctxt, loc);
326        }
327    }
328}
329
330#[instrument(level = "trace", skip(ssa, body))]
331fn compute_copy_classes(ssa: &mut SsaLocals, body: &Body<'_>) {
332    let mut direct_uses = std::mem::take(&mut ssa.direct_uses);
333    let mut copies = IndexVec::from_fn_n(|l| l, body.local_decls.len());
334
335    for (local, rvalue, _) in ssa.assignments(body) {
336        let (Rvalue::Use(Operand::Copy(place) | Operand::Move(place))
337        | Rvalue::CopyForDeref(place)) = rvalue
338        else {
339            continue;
340        };
341
342        let Some(rhs) = place.as_local() else { continue };
343        let local_ty = body.local_decls()[local].ty;
344        let rhs_ty = body.local_decls()[rhs].ty;
345        if local_ty != rhs_ty {
346            // FIXME(#112651): This can be removed afterwards.
347            trace!("skipped `{local:?} = {rhs:?}` due to subtyping: {local_ty} != {rhs_ty}");
348            continue;
349        }
350
351        if !ssa.is_ssa(rhs) {
352            continue;
353        }
354
355        // We visit in `assignment_order`, ie. reverse post-order, so `rhs` has been
356        // visited before `local`, and we just have to copy the representing local.
357        let head = copies[rhs];
358
359        if local == RETURN_PLACE {
360            // `_0` is special, we cannot rename it. Instead, rename the class of `rhs` to
361            // `RETURN_PLACE`. This is only possible if the class head is a temporary, not an
362            // argument.
363            if body.local_kind(head) != LocalKind::Temp {
364                continue;
365            }
366            for h in copies.iter_mut() {
367                if *h == head {
368                    *h = RETURN_PLACE;
369                }
370            }
371        } else {
372            copies[local] = head;
373        }
374        direct_uses[rhs] -= 1;
375    }
376
377    debug!(?copies);
378    debug!(?direct_uses);
379
380    // Invariant: `copies` must point to the head of an equivalence class.
381    #[cfg(debug_assertions)]
382    for &head in copies.iter() {
383        assert_eq!(copies[head], head);
384    }
385    debug_assert_eq!(copies[RETURN_PLACE], RETURN_PLACE);
386
387    ssa.direct_uses = direct_uses;
388    ssa.copy_classes = copies;
389}
390
391#[derive(Debug)]
392pub(crate) struct StorageLiveLocals {
393    /// Set of "StorageLive" statements for each local.
394    storage_live: IndexVec<Local, Set1<DefLocation>>,
395}
396
397impl StorageLiveLocals {
398    pub(crate) fn new(
399        body: &Body<'_>,
400        always_storage_live_locals: &DenseBitSet<Local>,
401    ) -> StorageLiveLocals {
402        let mut storage_live = IndexVec::from_elem(Set1::Empty, &body.local_decls);
403        for local in always_storage_live_locals.iter() {
404            storage_live[local] = Set1::One(DefLocation::Argument);
405        }
406        for (block, bbdata) in body.basic_blocks.iter_enumerated() {
407            for (statement_index, statement) in bbdata.statements.iter().enumerate() {
408                if let StatementKind::StorageLive(local) = statement.kind {
409                    storage_live[local]
410                        .insert(DefLocation::Assignment(Location { block, statement_index }));
411                }
412            }
413        }
414        debug!(?storage_live);
415        StorageLiveLocals { storage_live }
416    }
417
418    #[inline]
419    pub(crate) fn has_single_storage(&self, local: Local) -> bool {
420        matches!(self.storage_live[local], Set1::One(_))
421    }
422}