Skip to main content

rustc_mir_transform/
sroa.rs

1use rustc_abi::FieldIdx;
2use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
3use rustc_hir::attrs::lang_items::LangItem;
4use rustc_index::IndexVec;
5use rustc_index::bit_set::{DenseBitSet, GrowableBitSet};
6use rustc_middle::bug;
7use rustc_middle::mir::visit::*;
8use rustc_middle::mir::*;
9use rustc_middle::ty::{self, Ty, TyCtxt};
10use rustc_mir_dataflow::value_analysis::{excluded_locals, iter_fields};
11use tracing::{debug, instrument};
12
13use crate::PassPolicy;
14use crate::patch::MirPatch;
15
16pub(super) struct ScalarReplacementOfAggregates;
17
18impl<'tcx> crate::MirPass<'tcx> for ScalarReplacementOfAggregates {
19    fn policy(&self, sess: &rustc_session::Session) -> PassPolicy {
20        PassPolicy::optimization(sess.mir_opt_level() >= 2)
21    }
22
23    #[instrument(level = "debug", skip(self, tcx, body))]
24    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
25        debug!(def_id = ?body.source.def_id());
26
27        // Avoid query cycles (coroutines require optimized MIR for layout).
28        if tcx.type_of(body.source.def_id()).instantiate_identity().skip_norm_wip().is_coroutine() {
29            return;
30        }
31
32        let mut excluded = excluded_locals(body);
33        let typing_env = body.typing_env(tcx);
34        loop {
35            debug!(?excluded);
36            let escaping = escaping_locals(tcx, &excluded, body);
37            debug!(?escaping);
38            let replacements = compute_flattening(tcx, typing_env, body, escaping);
39            debug!(?replacements);
40            let all_dead_locals = replace_flattened_locals(tcx, body, replacements);
41            if !all_dead_locals.is_empty() {
42                excluded.union(&all_dead_locals);
43                excluded = {
44                    let mut growable = GrowableBitSet::from(excluded);
45                    growable.ensure(body.local_decls.len());
46                    growable.into()
47                };
48            } else {
49                break;
50            }
51        }
52    }
53}
54
55/// Identify all locals that are not eligible for SROA.
56///
57/// There are 3 cases:
58/// - the aggregated local is used or passed to other code (function parameters and arguments);
59/// - the locals is a union or an enum;
60/// - the local's address is taken, and thus the relative addresses of the fields are observable to
61///   client code.
62fn escaping_locals<'tcx>(
63    tcx: TyCtxt<'tcx>,
64    excluded: &DenseBitSet<Local>,
65    body: &Body<'tcx>,
66) -> DenseBitSet<Local> {
67    let is_excluded_ty = |ty: Ty<'tcx>| {
68        if ty.is_union() || ty.is_enum() {
69            return true;
70        }
71        if let ty::Adt(def, _args) = ty.kind()
72            && (def.repr().simd()
73                || def.repr().scalable()
74                || tcx.is_lang_item(def.did(), LangItem::DynMetadata))
75        {
76            // Exclude #[repr(simd)] types so that they are not de-optimized into an array
77            // (MCP#838 banned projections into SIMD types, but if the value is unused
78            // this pass sees "all the uses are of the fields" and expands it.)
79
80            // codegen wants to see the `DynMetadata<T>`,
81            // not the inner reference-to-opaque-type.
82            return true;
83        }
84        // Default for non-ADTs
85        false
86    };
87
88    let mut set = DenseBitSet::new_empty(body.local_decls.len());
89    set.insert_range(RETURN_PLACE..Local::arg(body.arg_count));
90    for (local, decl) in body.local_decls().iter_enumerated() {
91        if excluded.contains(local) || is_excluded_ty(decl.ty) {
92            set.insert(local);
93        }
94    }
95    let mut visitor = EscapeVisitor { set };
96    visitor.visit_body(body);
97    return visitor.set;
98
99    struct EscapeVisitor {
100        set: DenseBitSet<Local>,
101    }
102
103    impl<'tcx> Visitor<'tcx> for EscapeVisitor {
104        fn visit_local(&mut self, local: Local, _: PlaceContext, _: Location) {
105            self.set.insert(local);
106        }
107
108        fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
109            // Mirror the implementation in PreFlattenVisitor.
110            if let &[PlaceElem::Field(..), ..] = &place.projection[..] {
111                return;
112            }
113            self.super_place(place, context, location);
114        }
115
116        fn visit_assign(
117            &mut self,
118            lvalue: &Place<'tcx>,
119            rvalue: &Rvalue<'tcx>,
120            location: Location,
121        ) {
122            if lvalue.as_local().is_some() {
123                match rvalue {
124                    // Aggregate assignments are expanded in run_pass.
125                    Rvalue::Aggregate(..) | Rvalue::Use(..) => {
126                        self.visit_rvalue(rvalue, location);
127                        return;
128                    }
129                    _ => {}
130                }
131            }
132            self.super_assign(lvalue, rvalue, location)
133        }
134
135        fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
136            match statement.kind {
137                // Storage statements are expanded in run_pass.
138                StatementKind::StorageLive(..) | StatementKind::StorageDead(..) => return,
139                _ => self.super_statement(statement, location),
140            }
141        }
142
143        // We ignore anything that happens in debuginfo, since we expand it using
144        // `VarDebugInfoFragment`.
145        fn visit_var_debug_info(&mut self, _: &VarDebugInfo<'tcx>) {}
146    }
147}
148
149#[derive(Default, Debug)]
150struct ReplacementMap<'tcx> {
151    /// Pre-computed list of all "new" locals for each "old" local. This is used to expand storage
152    /// and deinit statement and debuginfo.
153    fragments: IndexVec<Local, Option<IndexVec<FieldIdx, Option<(Ty<'tcx>, Local)>>>>,
154}
155
156impl<'tcx> ReplacementMap<'tcx> {
157    fn replace_place(&self, tcx: TyCtxt<'tcx>, place: PlaceRef<'tcx>) -> Option<Place<'tcx>> {
158        let &[PlaceElem::Field(f, _), ref rest @ ..] = place.projection else {
159            return None;
160        };
161        let fields = self.fragments[place.local].as_ref()?;
162        let (_, new_local) = fields[f]?;
163        Some(Place { local: new_local, projection: tcx.mk_place_elems(rest) })
164    }
165
166    fn place_fragments(
167        &self,
168        place: Place<'tcx>,
169    ) -> Option<impl Iterator<Item = (FieldIdx, Ty<'tcx>, Local)>> {
170        let local = place.as_local()?;
171        let fields = self.fragments[local].as_ref()?;
172        Some(fields.iter_enumerated().filter_map(|(field, &opt_ty_local)| {
173            let (ty, local) = opt_ty_local?;
174            Some((field, ty, local))
175        }))
176    }
177}
178
179/// Compute the replacement of flattened places into locals.
180///
181/// For each eligible place, we assign a new local to each accessed field.
182/// The replacement will be done later in `ReplacementVisitor`.
183fn compute_flattening<'tcx>(
184    tcx: TyCtxt<'tcx>,
185    typing_env: ty::TypingEnv<'tcx>,
186    body: &mut Body<'tcx>,
187    escaping: DenseBitSet<Local>,
188) -> ReplacementMap<'tcx> {
189    let mut fragments = IndexVec::from_elem(None, &body.local_decls);
190
191    for local in body.local_decls.indices() {
192        if escaping.contains(local) {
193            continue;
194        }
195        let decl = body.local_decls[local].clone();
196        let ty = decl.ty;
197        iter_fields(ty, tcx, typing_env, |variant, field, field_ty| {
198            if variant.is_some() {
199                // Downcasts are currently not supported.
200                return;
201            };
202            let new_local =
203                body.local_decls.push(LocalDecl { ty: field_ty, user_ty: None, ..decl.clone() });
204            fragments.get_or_insert_with(local, IndexVec::new).insert(field, (field_ty, new_local));
205        });
206    }
207    ReplacementMap { fragments }
208}
209
210/// Perform the replacement computed by `compute_flattening`.
211fn replace_flattened_locals<'tcx>(
212    tcx: TyCtxt<'tcx>,
213    body: &mut Body<'tcx>,
214    replacements: ReplacementMap<'tcx>,
215) -> DenseBitSet<Local> {
216    let mut all_dead_locals = DenseBitSet::new_empty(replacements.fragments.len());
217    for (local, replacements) in replacements.fragments.iter_enumerated() {
218        if replacements.is_some() {
219            all_dead_locals.insert(local);
220        }
221    }
222    debug!(?all_dead_locals);
223    if all_dead_locals.is_empty() {
224        return all_dead_locals;
225    }
226
227    let mut visitor = ReplacementVisitor {
228        tcx,
229        local_decls: &body.local_decls,
230        replacements: &replacements,
231        all_dead_locals,
232        patch: MirPatch::new(body),
233    };
234    for (bb, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
235        visitor.visit_basic_block_data(bb, data);
236    }
237    for scope in &mut body.source_scopes {
238        visitor.visit_source_scope_data(scope);
239    }
240    for (index, annotation) in body.user_type_annotations.iter_enumerated_mut() {
241        visitor.visit_user_type_annotation(index, annotation);
242    }
243    visitor.expand_var_debug_info(&mut body.var_debug_info);
244    let ReplacementVisitor { patch, all_dead_locals, .. } = visitor;
245    patch.apply(body);
246    all_dead_locals
247}
248
249struct ReplacementVisitor<'tcx, 'll> {
250    tcx: TyCtxt<'tcx>,
251    /// This is only used to compute the type for `VarDebugInfoFragment`.
252    local_decls: &'ll LocalDecls<'tcx>,
253    /// Work to do.
254    replacements: &'ll ReplacementMap<'tcx>,
255    /// This is used to check that we are not leaving references to replaced locals behind.
256    all_dead_locals: DenseBitSet<Local>,
257    patch: MirPatch<'tcx>,
258}
259
260impl<'tcx> ReplacementVisitor<'tcx, '_> {
261    #[instrument(level = "trace", skip(self))]
262    fn expand_var_debug_info(&mut self, var_debug_info: &mut Vec<VarDebugInfo<'tcx>>) {
263        var_debug_info.flat_map_in_place(|mut var_debug_info| {
264            let place = match var_debug_info.value {
265                VarDebugInfoContents::Const(_) => return vec![var_debug_info],
266                VarDebugInfoContents::Place(ref mut place) => place,
267            };
268
269            if let Some(repl) = self.replacements.replace_place(self.tcx, place.as_ref()) {
270                *place = repl;
271                return vec![var_debug_info];
272            }
273
274            let Some(parts) = self.replacements.place_fragments(*place) else {
275                return vec![var_debug_info];
276            };
277
278            let ty = place.ty(self.local_decls, self.tcx).ty;
279
280            parts
281                .map(|(field, field_ty, replacement_local)| {
282                    let mut var_debug_info = var_debug_info.clone();
283                    let composite = var_debug_info.composite.get_or_insert_with(|| {
284                        Box::new(VarDebugInfoFragment { ty, projection: Vec::new() })
285                    });
286                    composite.projection.push(PlaceElem::Field(field, field_ty));
287
288                    var_debug_info.value = VarDebugInfoContents::Place(replacement_local.into());
289                    var_debug_info
290                })
291                .collect()
292        });
293    }
294}
295
296impl<'tcx, 'll> MutVisitor<'tcx> for ReplacementVisitor<'tcx, 'll> {
297    fn tcx(&self) -> TyCtxt<'tcx> {
298        self.tcx
299    }
300
301    fn visit_place(&mut self, place: &mut Place<'tcx>, context: PlaceContext, location: Location) {
302        if let Some(repl) = self.replacements.replace_place(self.tcx, place.as_ref()) {
303            *place = repl
304        } else {
305            self.super_place(place, context, location)
306        }
307    }
308
309    #[instrument(level = "trace", skip(self))]
310    fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
311        match statement.kind {
312            // Duplicate storage and deinit statements, as they pretty much apply to all fields.
313            StatementKind::StorageLive(l) => {
314                if let Some(final_locals) = self.replacements.place_fragments(l.into()) {
315                    for (_, _, fl) in final_locals {
316                        self.patch.add_statement(location, StatementKind::StorageLive(fl));
317                    }
318                    statement.make_nop(true);
319                }
320                return;
321            }
322            StatementKind::StorageDead(l) => {
323                if let Some(final_locals) = self.replacements.place_fragments(l.into()) {
324                    for (_, _, fl) in final_locals {
325                        self.patch.add_statement(location, StatementKind::StorageDead(fl));
326                    }
327                    statement.make_nop(true);
328                }
329                return;
330            }
331
332            // We have `a = Struct { 0: x, 1: y, .. }`.
333            // We replace it by
334            // ```
335            // a_0 = x
336            // a_1 = y
337            // ...
338            // ```
339            StatementKind::Assign((place, Rvalue::Aggregate(_, ref mut operands))) => {
340                if let Some(local) = place.as_local()
341                    && let Some(final_locals) = &self.replacements.fragments[local]
342                {
343                    // This is ok as we delete the statement later.
344                    let operands = std::mem::take(operands);
345                    for (&opt_ty_local, mut operand) in final_locals.iter().zip(operands) {
346                        if let Some((_, new_local)) = opt_ty_local {
347                            // Replace mentions of SROA'd locals that appear in the operand.
348                            self.visit_operand(&mut operand, location);
349
350                            let rvalue = Rvalue::Use(operand, WithRetag::Yes);
351                            self.patch.add_statement(
352                                location,
353                                StatementKind::Assign(Box::new((new_local.into(), rvalue))),
354                            );
355                        }
356                    }
357                    statement.make_nop(true);
358                    return;
359                }
360            }
361
362            // We have `a = some constant`
363            // We add the projections.
364            // ```
365            // a_0 = a.0
366            // a_1 = a.1
367            // ...
368            // ```
369            // ConstProp will pick up the pieces and replace them by actual constants.
370            StatementKind::Assign((place, Rvalue::Use(Operand::Constant(_), retag))) => {
371                if let Some(final_locals) = self.replacements.place_fragments(place) {
372                    // Put the deaggregated statements *after* the original one.
373                    let location = location.successor_within_block();
374                    for (field, ty, new_local) in final_locals {
375                        let rplace = self.tcx.mk_place_field(place, field, ty);
376                        let rvalue = Rvalue::Use(Operand::Move(rplace), retag);
377                        self.patch.add_statement(
378                            location,
379                            StatementKind::Assign(Box::new((new_local.into(), rvalue))),
380                        );
381                    }
382                    // We still need `place.local` to exist, so don't make it nop.
383                    return;
384                }
385            }
386
387            // We have `a = move? place`
388            // We replace it by
389            // ```
390            // a_0 = move? place.0
391            // a_1 = move? place.1
392            // ...
393            // ```
394            StatementKind::Assign((
395                lhs,
396                Rvalue::Use(ref op @ (Operand::Copy(rplace) | Operand::Move(rplace)), retag),
397            )) => {
398                let copy = match *op {
399                    Operand::Copy(_) => true,
400                    Operand::Move(_) => false,
401                    Operand::Constant(_) | Operand::RuntimeChecks(_) => bug!(),
402                };
403                if let Some(final_locals) = self.replacements.place_fragments(lhs) {
404                    for (field, ty, new_local) in final_locals {
405                        let rplace = self.tcx.mk_place_field(rplace, field, ty);
406                        debug!(?rplace);
407                        let rplace = self
408                            .replacements
409                            .replace_place(self.tcx, rplace.as_ref())
410                            .unwrap_or(rplace);
411                        debug!(?rplace);
412                        let rvalue = if copy {
413                            Rvalue::Use(Operand::Copy(rplace), retag)
414                        } else {
415                            Rvalue::Use(Operand::Move(rplace), retag)
416                        };
417                        self.patch.add_statement(
418                            location,
419                            StatementKind::Assign(Box::new((new_local.into(), rvalue))),
420                        );
421                    }
422                    statement.make_nop(true);
423                    return;
424                }
425            }
426
427            _ => {}
428        }
429        self.super_statement(statement, location)
430    }
431
432    fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _: Location) {
433        assert!(!self.all_dead_locals.contains(*local));
434    }
435}