1use rustc_abi::FieldIdx;
2use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
3use rustc_index::IndexVec;
4use rustc_index::bit_set::{DenseBitSet, GrowableBitSet};
5use rustc_middle::mir::visit::*;
6use rustc_middle::mir::*;
7use rustc_middle::ty::{self, Ty, TyCtxt};
8use rustc_mir_dataflow::value_analysis::{excluded_locals, iter_fields};
9use rustc_span::bug;
10use tracing::{debug, instrument};
11
12use crate::PassPolicy;
13use crate::patch::MirPatch;
14
15pub(super) struct ScalarReplacementOfAggregates;
16
17impl<'tcx> crate::MirPass<'tcx> for ScalarReplacementOfAggregates {
18 fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy {
19 PassPolicy::optional(ctx.mir_opt_level() >= 2)
20 }
21
22 #[instrument(level = "debug", skip(self, tcx, body))]
23 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
24 debug!(def_id = ?body.source.def_id());
25
26 if tcx.type_of(body.source.def_id()).instantiate_identity().skip_norm_wip().is_coroutine() {
28 return;
29 }
30
31 let mut excluded = excluded_locals(body);
32 let typing_env = body.typing_env(tcx);
33 loop {
34 debug!(?excluded);
35 let escaping = escaping_locals(tcx, &excluded, body);
36 debug!(?escaping);
37 let replacements = compute_flattening(tcx, typing_env, body, escaping);
38 debug!(?replacements);
39 let all_dead_locals = replace_flattened_locals(tcx, body, replacements);
40 if !all_dead_locals.is_empty() {
41 excluded.union(&all_dead_locals);
42 } else {
43 break;
44 }
45 }
46 }
47}
48
49fn escaping_locals<'tcx>(
57 tcx: TyCtxt<'tcx>,
58 excluded: &GrowableBitSet<Local>,
59 body: &Body<'tcx>,
60) -> DenseBitSet<Local> {
61 let is_excluded_ty = |ty: Ty<'tcx>| {
62 if ty.is_union() || ty.is_enum() {
63 return true;
64 }
65 if ty.is_like_maybe_dangling() {
67 return true;
68 }
69 if let ty::Adt(def, _args) = ty.kind()
70 && !def.can_project_fields(tcx)
71 {
72 return true;
73 }
74 false
76 };
77
78 let mut set = DenseBitSet::new_empty(body.local_decls.len());
79 set.insert_range(RETURN_PLACE..Local::arg(body.arg_count));
80 for (local, decl) in body.local_decls().iter_enumerated() {
81 if excluded.contains(local) || is_excluded_ty(decl.ty) {
82 set.insert(local);
83 }
84 }
85 let mut visitor = EscapeVisitor { set };
86 visitor.visit_body(body);
87 return visitor.set;
88
89 struct EscapeVisitor {
90 set: DenseBitSet<Local>,
91 }
92
93 impl<'tcx> Visitor<'tcx> for EscapeVisitor {
94 fn visit_local(&mut self, local: Local, _: PlaceContext, _: Location) {
95 self.set.insert(local);
96 }
97
98 fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
99 if let &[PlaceElem::Field(..), ..] = &place.projection[..] {
101 return;
102 }
103 self.super_place(place, context, location);
104 }
105
106 fn visit_assign(
107 &mut self,
108 lvalue: &Place<'tcx>,
109 rvalue: &Rvalue<'tcx>,
110 location: Location,
111 ) {
112 if lvalue.as_local().is_some() {
113 match rvalue {
114 Rvalue::Aggregate(..) | Rvalue::Use(..) => {
116 self.visit_rvalue(rvalue, location);
117 return;
118 }
119 _ => {}
120 }
121 }
122 self.super_assign(lvalue, rvalue, location)
123 }
124
125 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
126 match statement.kind {
127 StatementKind::StorageLive(..) | StatementKind::StorageDead(..) => return,
129 _ => self.super_statement(statement, location),
130 }
131 }
132
133 fn visit_var_debug_info(&mut self, _: &VarDebugInfo<'tcx>) {}
136 }
137}
138
139#[derive(Default, Debug)]
140struct ReplacementMap<'tcx> {
141 fragments: IndexVec<Local, Option<IndexVec<FieldIdx, Option<(Ty<'tcx>, Local)>>>>,
144}
145
146impl<'tcx> ReplacementMap<'tcx> {
147 fn replace_place(&self, tcx: TyCtxt<'tcx>, place: PlaceRef<'tcx>) -> Option<Place<'tcx>> {
148 let &[PlaceElem::Field(f, _), ref rest @ ..] = place.projection else {
149 return None;
150 };
151 let fields = self.fragments[place.local].as_ref()?;
152 let (_, new_local) = fields[f]?;
153 Some(Place { local: new_local, projection: tcx.mk_place_elems(rest) })
154 }
155
156 fn place_fragments(
157 &self,
158 place: Place<'tcx>,
159 ) -> Option<impl Iterator<Item = (FieldIdx, Ty<'tcx>, Local)>> {
160 let local = place.as_local()?;
161 let fields = self.fragments[local].as_ref()?;
162 Some(fields.iter_enumerated().filter_map(|(field, &opt_ty_local)| {
163 let (ty, local) = opt_ty_local?;
164 Some((field, ty, local))
165 }))
166 }
167}
168
169fn compute_flattening<'tcx>(
174 tcx: TyCtxt<'tcx>,
175 typing_env: ty::TypingEnv<'tcx>,
176 body: &mut Body<'tcx>,
177 escaping: DenseBitSet<Local>,
178) -> ReplacementMap<'tcx> {
179 let mut fragments = IndexVec::from_elem(None, &body.local_decls);
180
181 for local in body.local_decls.indices() {
182 if escaping.contains(local) {
183 continue;
184 }
185 let decl = body.local_decls[local].clone();
186 let ty = decl.ty;
187 iter_fields(ty, tcx, typing_env, |variant, field, field_ty| {
188 if variant.is_some() {
189 return;
191 };
192 let new_local =
193 body.local_decls.push(LocalDecl { ty: field_ty, user_ty: None, ..decl.clone() });
194 fragments.get_or_insert_with(local, IndexVec::new).insert(field, (field_ty, new_local));
195 });
196 }
197 ReplacementMap { fragments }
198}
199
200fn replace_flattened_locals<'tcx>(
202 tcx: TyCtxt<'tcx>,
203 body: &mut Body<'tcx>,
204 replacements: ReplacementMap<'tcx>,
205) -> GrowableBitSet<Local> {
206 let mut all_dead_locals = GrowableBitSet::new_empty();
209 for (local, replacements) in replacements.fragments.iter_enumerated().rev() {
210 if replacements.is_some() {
211 all_dead_locals.insert(local);
212 }
213 }
214 debug!(?all_dead_locals);
215 if all_dead_locals.is_empty() {
216 return all_dead_locals;
217 }
218
219 let mut visitor = ReplacementVisitor {
220 tcx,
221 local_decls: &body.local_decls,
222 replacements: &replacements,
223 all_dead_locals,
224 patch: MirPatch::new(body),
225 };
226 for (bb, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
227 visitor.visit_basic_block_data(bb, data);
228 }
229 for scope in &mut body.source_scopes {
230 visitor.visit_source_scope_data(scope);
231 }
232 for (index, annotation) in body.user_type_annotations.iter_enumerated_mut() {
233 visitor.visit_user_type_annotation(index, annotation);
234 }
235 visitor.expand_var_debug_info(&mut body.var_debug_info);
236 let ReplacementVisitor { patch, all_dead_locals, .. } = visitor;
237 patch.apply(body);
238 all_dead_locals
239}
240
241struct ReplacementVisitor<'tcx, 'll> {
242 tcx: TyCtxt<'tcx>,
243 local_decls: &'ll LocalDecls<'tcx>,
245 replacements: &'ll ReplacementMap<'tcx>,
247 all_dead_locals: GrowableBitSet<Local>,
249 patch: MirPatch<'tcx>,
250}
251
252impl<'tcx> ReplacementVisitor<'tcx, '_> {
253 #[instrument(level = "trace", skip(self))]
254 fn expand_var_debug_info(&mut self, var_debug_info: &mut Vec<VarDebugInfo<'tcx>>) {
255 var_debug_info.flat_map_in_place(|mut var_debug_info| {
256 let place = match var_debug_info.value {
257 VarDebugInfoContents::Const(_) => return vec![var_debug_info],
258 VarDebugInfoContents::Place(ref mut place) => place,
259 };
260
261 if let Some(repl) = self.replacements.replace_place(self.tcx, place.as_ref()) {
262 *place = repl;
263 return vec![var_debug_info];
264 }
265
266 let Some(parts) = self.replacements.place_fragments(*place) else {
267 return vec![var_debug_info];
268 };
269
270 let ty = place.ty(self.local_decls, self.tcx).ty;
271
272 parts
273 .map(|(field, field_ty, replacement_local)| {
274 let mut var_debug_info = var_debug_info.clone();
275 let composite = var_debug_info.composite.get_or_insert_with(|| {
276 Box::new(VarDebugInfoFragment { ty, projection: Vec::new() })
277 });
278 composite.projection.push(PlaceElem::Field(field, field_ty));
279
280 var_debug_info.value = VarDebugInfoContents::Place(replacement_local.into());
281 var_debug_info
282 })
283 .collect()
284 });
285 }
286}
287
288impl<'tcx, 'll> MutVisitor<'tcx> for ReplacementVisitor<'tcx, 'll> {
289 fn tcx(&self) -> TyCtxt<'tcx> {
290 self.tcx
291 }
292
293 fn visit_place(&mut self, place: &mut Place<'tcx>, context: PlaceContext, location: Location) {
294 if let Some(repl) = self.replacements.replace_place(self.tcx, place.as_ref()) {
295 *place = repl
296 } else {
297 self.super_place(place, context, location)
298 }
299 }
300
301 #[instrument(level = "trace", skip(self))]
302 fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
303 match statement.kind {
304 StatementKind::StorageLive(l) => {
306 if let Some(final_locals) = self.replacements.place_fragments(l.into()) {
307 for (_, _, fl) in final_locals {
308 self.patch.add_statement(location, StatementKind::StorageLive(fl));
309 }
310 statement.make_nop(true);
311 }
312 return;
313 }
314 StatementKind::StorageDead(l) => {
315 if let Some(final_locals) = self.replacements.place_fragments(l.into()) {
316 for (_, _, fl) in final_locals {
317 self.patch.add_statement(location, StatementKind::StorageDead(fl));
318 }
319 statement.make_nop(true);
320 }
321 return;
322 }
323
324 StatementKind::Assign((place, Rvalue::Aggregate(_, ref mut operands))) => {
332 if let Some(local) = place.as_local()
333 && let Some(final_locals) = &self.replacements.fragments[local]
334 {
335 let operands = std::mem::take(operands);
337 for (&opt_ty_local, mut operand) in final_locals.iter().zip(operands) {
338 if let Some((_, new_local)) = opt_ty_local {
339 self.visit_operand(&mut operand, location);
341
342 let rvalue = Rvalue::Use(operand, WithRetag::Yes);
343 self.patch.add_statement(
344 location,
345 StatementKind::Assign(Box::new((new_local.into(), rvalue))),
346 );
347 }
348 }
349 statement.make_nop(true);
350 return;
351 }
352 }
353
354 StatementKind::Assign((place, Rvalue::Use(Operand::Constant(_), retag))) => {
363 if let Some(final_locals) = self.replacements.place_fragments(place) {
364 let location = location.successor_within_block();
366 for (field, ty, new_local) in final_locals {
367 let rplace = self.tcx.mk_place_field(place, field, ty);
368 let rvalue = Rvalue::Use(Operand::Move(rplace), retag);
369 self.patch.add_statement(
370 location,
371 StatementKind::Assign(Box::new((new_local.into(), rvalue))),
372 );
373 }
374 return;
376 }
377 }
378
379 StatementKind::Assign((
387 lhs,
388 Rvalue::Use(ref op @ (Operand::Copy(rplace) | Operand::Move(rplace)), retag),
389 )) => {
390 let copy = match *op {
391 Operand::Copy(_) => true,
392 Operand::Move(_) => false,
393 Operand::Constant(_) | Operand::RuntimeChecks(_) => bug!(),
394 };
395 if let Some(final_locals) = self.replacements.place_fragments(lhs) {
396 for (field, ty, new_local) in final_locals {
397 let rplace = self.tcx.mk_place_field(rplace, field, ty);
398 debug!(?rplace);
399 let rplace = self
400 .replacements
401 .replace_place(self.tcx, rplace.as_ref())
402 .unwrap_or(rplace);
403 debug!(?rplace);
404 let rvalue = if copy {
405 Rvalue::Use(Operand::Copy(rplace), retag)
406 } else {
407 Rvalue::Use(Operand::Move(rplace), retag)
408 };
409 self.patch.add_statement(
410 location,
411 StatementKind::Assign(Box::new((new_local.into(), rvalue))),
412 );
413 }
414 statement.make_nop(true);
415 return;
416 }
417 }
418
419 _ => {}
420 }
421 self.super_statement(statement, location)
422 }
423
424 fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _: Location) {
425 assert!(!self.all_dead_locals.contains(*local));
426 }
427}