1use rustc_abi::FieldIdx;
2use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
3use rustc_hir::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() || tcx.is_lang_item(def.did(), LangItem::DynMetadata))
73 {
74 return true;
81 }
82 false
84 };
85
86 let mut set = DenseBitSet::new_empty(body.local_decls.len());
87 set.insert_range(RETURN_PLACE..Local::arg(body.arg_count));
88 for (local, decl) in body.local_decls().iter_enumerated() {
89 if excluded.contains(local) || is_excluded_ty(decl.ty) {
90 set.insert(local);
91 }
92 }
93 let mut visitor = EscapeVisitor { set };
94 visitor.visit_body(body);
95 return visitor.set;
96
97 struct EscapeVisitor {
98 set: DenseBitSet<Local>,
99 }
100
101 impl<'tcx> Visitor<'tcx> for EscapeVisitor {
102 fn visit_local(&mut self, local: Local, _: PlaceContext, _: Location) {
103 self.set.insert(local);
104 }
105
106 fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
107 if let &[PlaceElem::Field(..), ..] = &place.projection[..] {
109 return;
110 }
111 self.super_place(place, context, location);
112 }
113
114 fn visit_assign(
115 &mut self,
116 lvalue: &Place<'tcx>,
117 rvalue: &Rvalue<'tcx>,
118 location: Location,
119 ) {
120 if lvalue.as_local().is_some() {
121 match rvalue {
122 Rvalue::Aggregate(..) | Rvalue::Use(..) => {
124 self.visit_rvalue(rvalue, location);
125 return;
126 }
127 _ => {}
128 }
129 }
130 self.super_assign(lvalue, rvalue, location)
131 }
132
133 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
134 match statement.kind {
135 StatementKind::StorageLive(..) | StatementKind::StorageDead(..) => return,
137 _ => self.super_statement(statement, location),
138 }
139 }
140
141 fn visit_var_debug_info(&mut self, _: &VarDebugInfo<'tcx>) {}
144 }
145}
146
147#[derive(Default, Debug)]
148struct ReplacementMap<'tcx> {
149 fragments: IndexVec<Local, Option<IndexVec<FieldIdx, Option<(Ty<'tcx>, Local)>>>>,
152}
153
154impl<'tcx> ReplacementMap<'tcx> {
155 fn replace_place(&self, tcx: TyCtxt<'tcx>, place: PlaceRef<'tcx>) -> Option<Place<'tcx>> {
156 let &[PlaceElem::Field(f, _), ref rest @ ..] = place.projection else {
157 return None;
158 };
159 let fields = self.fragments[place.local].as_ref()?;
160 let (_, new_local) = fields[f]?;
161 Some(Place { local: new_local, projection: tcx.mk_place_elems(rest) })
162 }
163
164 fn place_fragments(
165 &self,
166 place: Place<'tcx>,
167 ) -> Option<impl Iterator<Item = (FieldIdx, Ty<'tcx>, Local)>> {
168 let local = place.as_local()?;
169 let fields = self.fragments[local].as_ref()?;
170 Some(fields.iter_enumerated().filter_map(|(field, &opt_ty_local)| {
171 let (ty, local) = opt_ty_local?;
172 Some((field, ty, local))
173 }))
174 }
175}
176
177fn compute_flattening<'tcx>(
182 tcx: TyCtxt<'tcx>,
183 typing_env: ty::TypingEnv<'tcx>,
184 body: &mut Body<'tcx>,
185 escaping: DenseBitSet<Local>,
186) -> ReplacementMap<'tcx> {
187 let mut fragments = IndexVec::from_elem(None, &body.local_decls);
188
189 for local in body.local_decls.indices() {
190 if escaping.contains(local) {
191 continue;
192 }
193 let decl = body.local_decls[local].clone();
194 let ty = decl.ty;
195 iter_fields(ty, tcx, typing_env, |variant, field, field_ty| {
196 if variant.is_some() {
197 return;
199 };
200 let new_local =
201 body.local_decls.push(LocalDecl { ty: field_ty, user_ty: None, ..decl.clone() });
202 fragments.get_or_insert_with(local, IndexVec::new).insert(field, (field_ty, new_local));
203 });
204 }
205 ReplacementMap { fragments }
206}
207
208fn replace_flattened_locals<'tcx>(
210 tcx: TyCtxt<'tcx>,
211 body: &mut Body<'tcx>,
212 replacements: ReplacementMap<'tcx>,
213) -> DenseBitSet<Local> {
214 let mut all_dead_locals = DenseBitSet::new_empty(replacements.fragments.len());
215 for (local, replacements) in replacements.fragments.iter_enumerated() {
216 if replacements.is_some() {
217 all_dead_locals.insert(local);
218 }
219 }
220 debug!(?all_dead_locals);
221 if all_dead_locals.is_empty() {
222 return all_dead_locals;
223 }
224
225 let mut visitor = ReplacementVisitor {
226 tcx,
227 local_decls: &body.local_decls,
228 replacements: &replacements,
229 all_dead_locals,
230 patch: MirPatch::new(body),
231 };
232 for (bb, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
233 visitor.visit_basic_block_data(bb, data);
234 }
235 for scope in &mut body.source_scopes {
236 visitor.visit_source_scope_data(scope);
237 }
238 for (index, annotation) in body.user_type_annotations.iter_enumerated_mut() {
239 visitor.visit_user_type_annotation(index, annotation);
240 }
241 visitor.expand_var_debug_info(&mut body.var_debug_info);
242 let ReplacementVisitor { patch, all_dead_locals, .. } = visitor;
243 patch.apply(body);
244 all_dead_locals
245}
246
247struct ReplacementVisitor<'tcx, 'll> {
248 tcx: TyCtxt<'tcx>,
249 local_decls: &'ll LocalDecls<'tcx>,
251 replacements: &'ll ReplacementMap<'tcx>,
253 all_dead_locals: DenseBitSet<Local>,
255 patch: MirPatch<'tcx>,
256}
257
258impl<'tcx> ReplacementVisitor<'tcx, '_> {
259 #[instrument(level = "trace", skip(self))]
260 fn expand_var_debug_info(&mut self, var_debug_info: &mut Vec<VarDebugInfo<'tcx>>) {
261 var_debug_info.flat_map_in_place(|mut var_debug_info| {
262 let place = match var_debug_info.value {
263 VarDebugInfoContents::Const(_) => return vec![var_debug_info],
264 VarDebugInfoContents::Place(ref mut place) => place,
265 };
266
267 if let Some(repl) = self.replacements.replace_place(self.tcx, place.as_ref()) {
268 *place = repl;
269 return vec![var_debug_info];
270 }
271
272 let Some(parts) = self.replacements.place_fragments(*place) else {
273 return vec![var_debug_info];
274 };
275
276 let ty = place.ty(self.local_decls, self.tcx).ty;
277
278 parts
279 .map(|(field, field_ty, replacement_local)| {
280 let mut var_debug_info = var_debug_info.clone();
281 let composite = var_debug_info.composite.get_or_insert_with(|| {
282 Box::new(VarDebugInfoFragment { ty, projection: Vec::new() })
283 });
284 composite.projection.push(PlaceElem::Field(field, field_ty));
285
286 var_debug_info.value = VarDebugInfoContents::Place(replacement_local.into());
287 var_debug_info
288 })
289 .collect()
290 });
291 }
292}
293
294impl<'tcx, 'll> MutVisitor<'tcx> for ReplacementVisitor<'tcx, 'll> {
295 fn tcx(&self) -> TyCtxt<'tcx> {
296 self.tcx
297 }
298
299 fn visit_place(&mut self, place: &mut Place<'tcx>, context: PlaceContext, location: Location) {
300 if let Some(repl) = self.replacements.replace_place(self.tcx, place.as_ref()) {
301 *place = repl
302 } else {
303 self.super_place(place, context, location)
304 }
305 }
306
307 #[instrument(level = "trace", skip(self))]
308 fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
309 match statement.kind {
310 StatementKind::StorageLive(l) => {
312 if let Some(final_locals) = self.replacements.place_fragments(l.into()) {
313 for (_, _, fl) in final_locals {
314 self.patch.add_statement(location, StatementKind::StorageLive(fl));
315 }
316 statement.make_nop(true);
317 }
318 return;
319 }
320 StatementKind::StorageDead(l) => {
321 if let Some(final_locals) = self.replacements.place_fragments(l.into()) {
322 for (_, _, fl) in final_locals {
323 self.patch.add_statement(location, StatementKind::StorageDead(fl));
324 }
325 statement.make_nop(true);
326 }
327 return;
328 }
329
330 StatementKind::Assign((place, Rvalue::Aggregate(_, ref mut operands))) => {
338 if let Some(local) = place.as_local()
339 && let Some(final_locals) = &self.replacements.fragments[local]
340 {
341 let operands = std::mem::take(operands);
343 for (&opt_ty_local, mut operand) in final_locals.iter().zip(operands) {
344 if let Some((_, new_local)) = opt_ty_local {
345 self.visit_operand(&mut operand, location);
347
348 let rvalue = Rvalue::Use(operand, WithRetag::Yes);
349 self.patch.add_statement(
350 location,
351 StatementKind::Assign(Box::new((new_local.into(), rvalue))),
352 );
353 }
354 }
355 statement.make_nop(true);
356 return;
357 }
358 }
359
360 StatementKind::Assign((place, Rvalue::Use(Operand::Constant(_), retag))) => {
369 if let Some(final_locals) = self.replacements.place_fragments(place) {
370 let location = location.successor_within_block();
372 for (field, ty, new_local) in final_locals {
373 let rplace = self.tcx.mk_place_field(place, field, ty);
374 let rvalue = Rvalue::Use(Operand::Move(rplace), retag);
375 self.patch.add_statement(
376 location,
377 StatementKind::Assign(Box::new((new_local.into(), rvalue))),
378 );
379 }
380 return;
382 }
383 }
384
385 StatementKind::Assign((
393 lhs,
394 Rvalue::Use(ref op @ (Operand::Copy(rplace) | Operand::Move(rplace)), retag),
395 )) => {
396 let copy = match *op {
397 Operand::Copy(_) => true,
398 Operand::Move(_) => false,
399 Operand::Constant(_) | Operand::RuntimeChecks(_) => bug!(),
400 };
401 if let Some(final_locals) = self.replacements.place_fragments(lhs) {
402 for (field, ty, new_local) in final_locals {
403 let rplace = self.tcx.mk_place_field(rplace, field, ty);
404 debug!(?rplace);
405 let rplace = self
406 .replacements
407 .replace_place(self.tcx, rplace.as_ref())
408 .unwrap_or(rplace);
409 debug!(?rplace);
410 let rvalue = if copy {
411 Rvalue::Use(Operand::Copy(rplace), retag)
412 } else {
413 Rvalue::Use(Operand::Move(rplace), retag)
414 };
415 self.patch.add_statement(
416 location,
417 StatementKind::Assign(Box::new((new_local.into(), rvalue))),
418 );
419 }
420 statement.make_nop(true);
421 return;
422 }
423 }
424
425 _ => {}
426 }
427 self.super_statement(statement, location)
428 }
429
430 fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _: Location) {
431 assert!(!self.all_dead_locals.contains(*local));
432 }
433}