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 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
55fn 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 return true;
83 }
84 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 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 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 StatementKind::StorageLive(..) | StatementKind::StorageDead(..) => return,
139 _ => self.super_statement(statement, location),
140 }
141 }
142
143 fn visit_var_debug_info(&mut self, _: &VarDebugInfo<'tcx>) {}
146 }
147}
148
149#[derive(Default, Debug)]
150struct ReplacementMap<'tcx> {
151 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
179fn 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 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
210fn 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 local_decls: &'ll LocalDecls<'tcx>,
253 replacements: &'ll ReplacementMap<'tcx>,
255 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 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 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 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 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 StatementKind::Assign((place, Rvalue::Use(Operand::Constant(_), retag))) => {
371 if let Some(final_locals) = self.replacements.place_fragments(place) {
372 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 return;
384 }
385 }
386
387 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}