1use std::borrow::Cow;
2
3use rustc_data_structures::fx::FxHashSet;
4use rustc_index::IndexVec;
5use rustc_index::bit_set::DenseBitSet;
6use rustc_middle::bug;
7use rustc_middle::mir::visit::*;
8use rustc_middle::mir::*;
9use rustc_middle::ty::TyCtxt;
10use rustc_mir_dataflow::Analysis;
11use rustc_mir_dataflow::impls::{MaybeStorageDead, always_storage_live_locals};
12use tracing::{debug, instrument};
13
14use crate::PassPolicy;
15use crate::ssa::{SsaLocals, StorageLiveLocals};
16
17pub(super) struct ReferencePropagation;
74
75impl<'tcx> crate::MirPass<'tcx> for ReferencePropagation {
76 fn policy(&self, sess: &rustc_session::Session) -> PassPolicy {
77 PassPolicy::optimization(sess.mir_opt_level() >= 2)
78 }
79
80 #[instrument(level = "trace", skip(self, tcx, body))]
81 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
82 debug!(def_id = ?body.source.def_id());
83 move_to_copy_pointers(tcx, body);
84 while propagate_ssa(tcx, body) {}
85 }
86}
87
88fn move_to_copy_pointers<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
94 let mut visitor = MoveToCopyVisitor { tcx, local_decls: &body.local_decls };
95 for (bb, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
96 visitor.visit_basic_block_data(bb, data);
97 }
98
99 struct MoveToCopyVisitor<'a, 'tcx> {
100 tcx: TyCtxt<'tcx>,
101 local_decls: &'a IndexVec<Local, LocalDecl<'tcx>>,
102 }
103
104 impl<'a, 'tcx> MutVisitor<'tcx> for MoveToCopyVisitor<'a, 'tcx> {
105 fn tcx(&self) -> TyCtxt<'tcx> {
106 self.tcx
107 }
108
109 fn visit_operand(&mut self, operand: &mut Operand<'tcx>, loc: Location) {
110 if let Operand::Move(place) = *operand {
111 if place.ty(self.local_decls, self.tcx).ty.is_any_ptr() {
112 *operand = Operand::Copy(place);
113 }
114 }
115 self.super_operand(operand, loc);
116 }
117 }
118}
119
120fn propagate_ssa<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool {
121 let typing_env = body.typing_env(tcx);
122 let ssa = SsaLocals::new(tcx, body, typing_env);
123
124 let mut replacer = compute_replacement(tcx, body, ssa);
125 debug!(?replacer.targets);
126 debug!(?replacer.allowed_replacements);
127 debug!(?replacer.storage_to_remove);
128
129 replacer.visit_body_preserves_cfg(body);
130
131 if replacer.any_replacement {
132 crate::simplify::remove_unused_definitions(body);
133 }
134
135 replacer.any_replacement
136}
137
138#[derive(Copy, Clone, Debug, PartialEq, Eq)]
139enum Value<'tcx> {
140 Unknown,
142 Pointer(Place<'tcx>, bool),
145}
146
147#[instrument(level = "trace", skip(tcx, body, ssa))]
149fn compute_replacement<'tcx>(
150 tcx: TyCtxt<'tcx>,
151 body: &Body<'tcx>,
152 ssa: SsaLocals,
153) -> Replacer<'tcx> {
154 let always_live_locals = always_storage_live_locals(body);
155
156 let storage_live = StorageLiveLocals::new(body, &always_live_locals);
158
159 let mut maybe_dead = MaybeStorageDead::new(Cow::Owned(always_live_locals))
162 .iterate_to_fixpoint(tcx, body, None)
163 .into_results_cursor(body);
164
165 let mut targets = IndexVec::from_elem(Value::Unknown, &body.local_decls);
167 let mut storage_to_remove = DenseBitSet::new_empty(body.local_decls.len());
170
171 let fully_replaceable_locals = fully_replaceable_locals(&ssa);
172
173 let is_constant_place = |place: Place<'_>| {
194 if let Some((&PlaceElem::Deref, rest)) = place.projection.split_first() {
196 ssa.is_ssa(place.local) && rest.iter().all(PlaceElem::is_stable_offset)
199 } else {
200 storage_live.has_single_storage(place.local)
201 && place.projection[..].iter().all(PlaceElem::is_stable_offset)
202 }
203 };
204
205 let mut can_perform_opt = |target: Place<'tcx>, loc: Location| {
206 if target.is_indirect_first_projection() {
207 storage_to_remove.insert(target.local);
210 true
211 } else {
212 maybe_dead.seek_after_primary_effect(loc);
214 let maybe_dead = maybe_dead.get().contains(target.local);
215 !maybe_dead
216 }
217 };
218
219 for (local, rvalue, location) in ssa.assignments(body) {
220 debug!(?local);
221
222 let Value::Unknown = targets[local] else { bug!() };
224
225 let ty = body.local_decls[local].ty;
226
227 if !ty.is_any_ptr() {
229 debug!("not a reference or pointer");
230 continue;
231 }
232
233 let needs_unique = ty.is_mutable_ptr();
235
236 if needs_unique && !fully_replaceable_locals.contains(local) {
238 debug!("not fully replaceable");
239 continue;
240 }
241
242 debug!(?rvalue);
243 match rvalue {
244 Rvalue::Use(Operand::Copy(place) | Operand::Move(place), _) => {
248 if let Some(rhs) = place.as_local()
249 && ssa.is_ssa(rhs)
250 {
251 let target = targets[rhs];
252 if !needs_unique && matches!(target, Value::Pointer(..)) {
255 targets[local] = target;
256 } else {
257 targets[local] =
258 Value::Pointer(tcx.mk_place_deref(rhs.into()), needs_unique);
259 }
260 }
261 }
262 Rvalue::Ref(_, _, place) | Rvalue::RawPtr(_, place) => {
263 let mut place = *place;
264 if let Some((&PlaceElem::Deref, rest)) = place.projection.split_first()
266 && let Value::Pointer(target, inner_needs_unique) = targets[place.local]
267 && !inner_needs_unique
270 && can_perform_opt(target, location)
272 {
273 place = target.project_deeper(rest, tcx);
274 }
275 assert_ne!(place.local, local);
276 if is_constant_place(place) {
277 targets[local] = Value::Pointer(place, needs_unique);
278 }
279 }
280 _ => {}
282 }
283 }
284
285 debug!(?targets);
286
287 let mut finder =
288 ReplacementFinder { targets, can_perform_opt, allowed_replacements: FxHashSet::default() };
289 let reachable_blocks = traversal::reachable_as_bitset(body);
290 for (bb, bbdata) in body.basic_blocks.iter_enumerated() {
291 if reachable_blocks.contains(bb) {
293 finder.visit_basic_block_data(bb, bbdata);
294 }
295 }
296
297 let allowed_replacements = finder.allowed_replacements;
298 return Replacer {
299 tcx,
300 targets: finder.targets,
301 remap_var_debug_infos: IndexVec::from_elem(None, body.local_decls()),
302 storage_to_remove,
303 allowed_replacements,
304 any_replacement: false,
305 };
306
307 struct ReplacementFinder<'tcx, F> {
308 targets: IndexVec<Local, Value<'tcx>>,
309 can_perform_opt: F,
310 allowed_replacements: FxHashSet<(Local, Location)>,
311 }
312
313 impl<'tcx, F> Visitor<'tcx> for ReplacementFinder<'tcx, F>
314 where
315 F: FnMut(Place<'tcx>, Location) -> bool,
316 {
317 fn visit_place(&mut self, place: &Place<'tcx>, ctxt: PlaceContext, loc: Location) {
318 if matches!(ctxt, PlaceContext::NonUse(_)) {
319 return;
321 }
322
323 if !place.is_indirect_first_projection() {
324 return;
326 }
327
328 let mut place = place.as_ref();
329 loop {
330 if let Value::Pointer(target, needs_unique) = self.targets[place.local] {
331 let perform_opt = (self.can_perform_opt)(target, loc);
332 debug!(?place, ?target, ?needs_unique, ?perform_opt);
333
334 if let &[PlaceElem::Deref] = &target.projection[..] {
339 assert!(perform_opt);
340 self.allowed_replacements.insert((target.local, loc));
341 place.local = target.local;
342 continue;
343 } else if perform_opt {
344 self.allowed_replacements.insert((target.local, loc));
345 } else if needs_unique {
346 self.targets[place.local] = Value::Unknown;
348 }
349 }
350
351 break;
352 }
353 }
354 }
355}
356
357fn fully_replaceable_locals(ssa: &SsaLocals) -> DenseBitSet<Local> {
362 let mut replaceable = DenseBitSet::new_empty(ssa.num_locals());
363
364 for local in ssa.locals() {
366 if ssa.num_direct_uses(local) == 0 {
367 replaceable.insert(local);
368 }
369 }
370
371 ssa.meet_copy_equivalence(&mut replaceable);
373
374 replaceable
375}
376
377struct Replacer<'tcx> {
379 tcx: TyCtxt<'tcx>,
380 targets: IndexVec<Local, Value<'tcx>>,
381 remap_var_debug_infos: IndexVec<Local, Option<Local>>,
382 storage_to_remove: DenseBitSet<Local>,
383 allowed_replacements: FxHashSet<(Local, Location)>,
384 any_replacement: bool,
385}
386
387impl<'tcx> MutVisitor<'tcx> for Replacer<'tcx> {
388 fn tcx(&self) -> TyCtxt<'tcx> {
389 self.tcx
390 }
391
392 fn visit_var_debug_info(&mut self, debuginfo: &mut VarDebugInfo<'tcx>) {
393 if let VarDebugInfoContents::Place(ref mut place) = debuginfo.value
394 && place.projection.is_empty()
395 {
396 let mut new_local = place.local;
397
398 while let Value::Pointer(target, _) = self.targets[new_local]
401 && let &[PlaceElem::Deref] = &target.projection[..]
402 {
403 new_local = target.local;
404 }
405 if place.local != new_local {
406 self.remap_var_debug_infos[place.local] = Some(new_local);
407 place.local = new_local;
408
409 self.any_replacement = true;
410 }
411 }
412
413 self.super_var_debug_info(debuginfo);
415 }
416
417 fn visit_statement_debuginfo(
418 &mut self,
419 stmt_debuginfo: &mut StmtDebugInfo<'tcx>,
420 location: Location,
421 ) {
422 let local = match stmt_debuginfo {
423 StmtDebugInfo::AssignRef(local, _) | StmtDebugInfo::InvalidAssign(local) => local,
424 };
425 if let Some(target) = self.remap_var_debug_infos[*local] {
426 *local = target;
427 self.any_replacement = true;
428 }
429 self.super_statement_debuginfo(stmt_debuginfo, location);
430 }
431
432 fn visit_place(&mut self, place: &mut Place<'tcx>, ctxt: PlaceContext, loc: Location) {
433 loop {
434 let Some((&PlaceElem::Deref, rest)) = place.projection.split_first() else { return };
435
436 let Value::Pointer(target, _) = self.targets[place.local] else { return };
437
438 let perform_opt = match ctxt {
439 PlaceContext::NonUse(NonUseContext::VarDebugInfo) => {
440 target.projection.iter().all(|p| p.can_use_in_debuginfo())
441 }
442 PlaceContext::NonUse(_) => true,
443 _ => self.allowed_replacements.contains(&(target.local, loc)),
444 };
445
446 if !perform_opt {
447 return;
448 }
449
450 *place = target.project_deeper(rest, self.tcx);
451 self.any_replacement = true;
452 }
453 }
454
455 fn visit_statement(&mut self, stmt: &mut Statement<'tcx>, loc: Location) {
456 match stmt.kind {
457 StatementKind::StorageLive(l) | StatementKind::StorageDead(l)
458 if self.storage_to_remove.contains(l) =>
459 {
460 stmt.make_nop(true);
461 }
462 _ => {}
463 }
464 self.super_statement(stmt, loc);
466 }
467}