Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 50dff95

Browse files
committedMay 9, 2023
Auto merge of #106285 - cjgillot:refprop-ssa, r=JakobDegen
Implement SSA-based reference propagation Rust has a tendency to create a lot of short-lived borrows, in particular for method calls. This PR aims to remove those short-lived borrows with a const-propagation dedicated to pointers to local places. This pass aims to transform the following pattern: ``` _1 = &raw? mut? PLACE; _3 = *_1; _4 = &raw? mut? *_1; ``` Into ``` _1 = &raw? mut? PLACE; _3 = PLACE; _4 = &raw? mut? PLACE; ``` where `PLACE` is a direct or an indirect place expression. By removing indirection, this pass should help both dest-prop and const-prop to handle more cases. This optimization is distinct from const-prop and dataflow const-prop since the borrow-reborrow patterns needs to preserve borrowck invariants, especially the uniqueness property of mutable references. The pointed-to places are computed using a SSA analysis. We suppose that removable borrows are typically temporaries from autoref, so they are by construction assigned only once, and a SSA analysis is enough to catch them. For each local, we store both where and how it is used, in order to efficiently compute the all-or-nothing property. Thanks to `Derefer`, we only have to track locals, not places in general. --- There are 3 properties that need to be upheld for this transformation to be legal: - place constness: `PLACE` must refer to the same memory wherever it appears; - pointer liveness: we must not introduce dereferences of dangling pointers; - `&mut` borrow uniqueness. ## Constness If `PLACE` is an indirect projection, if its of the form `(*LOCAL).PROJECTIONS` where: - `LOCAL` is SSA; - all projections in `PROJECTIONS` are constant (no dereference and no indexing). If `PLACE` is a direct projection of a local, we consider it as constant if: - the local is always live, or it has a single `StorageLive` that dominates all uses; - all projections are constant. # Liveness When performing a substitution, we must take care not to introduce uses of dangling locals. Using a dangling borrow is UB. Therefore, we assume that for any use of `*x`, where `x` is a borrow, the pointed-to memory is live. Limitations: - occurrences of `*x` in an `&raw mut? *x` are accepted; - raw pointers are allowed to be dangling. In those 2 case, we do not substitute anything, to be on the safe side. **Open question:** we do not differentiate borrows of ZST and non-ZST. The UB rules may be different depending on the layout. Having a different treatment would effectively prevent this pass from running on polymorphic MIR, which defeats the purpose of MIR opts. ## Uniqueness For `&mut` borrows, we also need to preserve the uniqueness property: we must avoid creating a state where we interleave uses of `*_1` and `_2`. To do it, we only perform full substitution of mutable borrows: we replace either all or none of the occurrences of `*_1`. Some care has to be taken when `_1` is copied in other locals. ``` _1 = &raw? mut? _2; _3 = *_1; _4 = _1 _5 = *_4 ``` In such cases, fully substituting `_1` means fully substituting all of the copies. For immutable borrows, we do not need to preserve such uniqueness property, so we perform all the possible substitutions without removing the `_1 = &_2` statement.
2 parents 2f6bc5d + bde213c commit 50dff95

23 files changed

+3141
-115
lines changed
 

‎compiler/rustc_middle/src/mir/mod.rs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,6 +1524,19 @@ impl<V, T> ProjectionElem<V, T> {
15241524
}
15251525
}
15261526

1527+
/// Returns `true` if the target of this projection always refers to the same memory region
1528+
/// whatever the state of the program.
1529+
pub fn is_stable_offset(&self) -> bool {
1530+
match self {
1531+
Self::Deref | Self::Index(_) => false,
1532+
Self::Field(_, _)
1533+
| Self::OpaqueCast(_)
1534+
| Self::ConstantIndex { .. }
1535+
| Self::Subslice { .. }
1536+
| Self::Downcast(_, _) => true,
1537+
}
1538+
}
1539+
15271540
/// Returns `true` if this is a `Downcast` projection with the given `VariantIdx`.
15281541
pub fn is_downcast_to(&self, v: VariantIdx) -> bool {
15291542
matches!(*self, Self::Downcast(_, x) if x == v)

‎compiler/rustc_mir_dataflow/src/impls/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub use self::borrowed_locals::borrowed_locals;
2626
pub use self::borrowed_locals::MaybeBorrowedLocals;
2727
pub use self::liveness::MaybeLiveLocals;
2828
pub use self::liveness::MaybeTransitiveLiveLocals;
29-
pub use self::storage_liveness::{MaybeRequiresStorage, MaybeStorageLive};
29+
pub use self::storage_liveness::{MaybeRequiresStorage, MaybeStorageDead, MaybeStorageLive};
3030

3131
/// `MaybeInitializedPlaces` tracks all places that might be
3232
/// initialized upon reaching a particular point in the control flow

‎compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,73 @@ impl<'tcx, 'a> crate::GenKillAnalysis<'tcx> for MaybeStorageLive<'a> {
7474
}
7575
}
7676

77+
#[derive(Clone)]
78+
pub struct MaybeStorageDead {
79+
always_live_locals: BitSet<Local>,
80+
}
81+
82+
impl MaybeStorageDead {
83+
pub fn new(always_live_locals: BitSet<Local>) -> Self {
84+
MaybeStorageDead { always_live_locals }
85+
}
86+
}
87+
88+
impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeStorageDead {
89+
type Domain = BitSet<Local>;
90+
91+
const NAME: &'static str = "maybe_storage_dead";
92+
93+
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
94+
// bottom = live
95+
BitSet::new_empty(body.local_decls.len())
96+
}
97+
98+
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) {
99+
assert_eq!(body.local_decls.len(), self.always_live_locals.domain_size());
100+
// Do not iterate on return place and args, as they are trivially always live.
101+
for local in body.vars_and_temps_iter() {
102+
if !self.always_live_locals.contains(local) {
103+
on_entry.insert(local);
104+
}
105+
}
106+
}
107+
}
108+
109+
impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeStorageDead {
110+
type Idx = Local;
111+
112+
fn statement_effect(
113+
&self,
114+
trans: &mut impl GenKill<Self::Idx>,
115+
stmt: &mir::Statement<'tcx>,
116+
_: Location,
117+
) {
118+
match stmt.kind {
119+
StatementKind::StorageLive(l) => trans.kill(l),
120+
StatementKind::StorageDead(l) => trans.gen(l),
121+
_ => (),
122+
}
123+
}
124+
125+
fn terminator_effect(
126+
&self,
127+
_trans: &mut impl GenKill<Self::Idx>,
128+
_: &mir::Terminator<'tcx>,
129+
_: Location,
130+
) {
131+
// Terminators have no effect
132+
}
133+
134+
fn call_return_effect(
135+
&self,
136+
_trans: &mut impl GenKill<Self::Idx>,
137+
_block: BasicBlock,
138+
_return_places: CallReturnPlaces<'_, 'tcx>,
139+
) {
140+
// Nothing to do when a call returns successfully
141+
}
142+
}
143+
77144
type BorrowedLocalsResults<'a, 'tcx> = ResultsRefCursor<'a, 'a, 'tcx, MaybeBorrowedLocals>;
78145

79146
/// Dataflow analysis that determines whether each local requires storage at a

‎compiler/rustc_mir_transform/src/copy_prop.rs‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,8 @@ impl<'tcx> MirPass<'tcx> for CopyProp {
3333
}
3434

3535
fn propagate_ssa<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
36-
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
3736
let borrowed_locals = borrowed_locals(body);
38-
let ssa = SsaLocals::new(tcx, param_env, body, &borrowed_locals);
37+
let ssa = SsaLocals::new(body);
3938

4039
let fully_moved = fully_moved_locals(&ssa, body);
4140
debug!(?fully_moved);
@@ -76,7 +75,7 @@ fn propagate_ssa<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
7675
fn fully_moved_locals(ssa: &SsaLocals, body: &Body<'_>) -> BitSet<Local> {
7776
let mut fully_moved = BitSet::new_filled(body.local_decls.len());
7877

79-
for (_, rvalue) in ssa.assignments(body) {
78+
for (_, rvalue, _) in ssa.assignments(body) {
8079
let (Rvalue::Use(Operand::Copy(place) | Operand::Move(place)) | Rvalue::CopyForDeref(place))
8180
= rvalue
8281
else { continue };

‎compiler/rustc_mir_transform/src/lib.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ mod match_branches;
8484
mod multiple_return_terminators;
8585
mod normalize_array_len;
8686
mod nrvo;
87+
mod ref_prop;
8788
mod remove_noop_landing_pads;
8889
mod remove_storage_markers;
8990
mod remove_uninit_drops;
@@ -559,6 +560,7 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
559560
&separate_const_switch::SeparateConstSwitch,
560561
&simplify::SimplifyLocals::BeforeConstProp,
561562
&copy_prop::CopyProp,
563+
&ref_prop::ReferencePropagation,
562564
&const_prop::ConstProp,
563565
&dataflow_const_prop::DataflowConstProp,
564566
//

‎compiler/rustc_mir_transform/src/normalize_array_len.rs‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use rustc_index::IndexVec;
77
use rustc_middle::mir::visit::*;
88
use rustc_middle::mir::*;
99
use rustc_middle::ty::{self, TyCtxt};
10-
use rustc_mir_dataflow::impls::borrowed_locals;
1110

1211
pub struct NormalizeArrayLen;
1312

@@ -24,9 +23,7 @@ impl<'tcx> MirPass<'tcx> for NormalizeArrayLen {
2423
}
2524

2625
fn normalize_array_len_calls<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
27-
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
28-
let borrowed_locals = borrowed_locals(body);
29-
let ssa = SsaLocals::new(tcx, param_env, body, &borrowed_locals);
26+
let ssa = SsaLocals::new(body);
3027

3128
let slice_lengths = compute_slice_length(tcx, &ssa, body);
3229
debug!(?slice_lengths);
@@ -41,7 +38,7 @@ fn compute_slice_length<'tcx>(
4138
) -> IndexVec<Local, Option<ty::Const<'tcx>>> {
4239
let mut slice_lengths = IndexVec::from_elem(None, &body.local_decls);
4340

44-
for (local, rvalue) in ssa.assignments(body) {
41+
for (local, rvalue, _) in ssa.assignments(body) {
4542
match rvalue {
4643
Rvalue::Cast(
4744
CastKind::Pointer(ty::adjustment::PointerCast::Unsize),
Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
use rustc_data_structures::fx::FxHashSet;
2+
use rustc_index::bit_set::BitSet;
3+
use rustc_index::IndexVec;
4+
use rustc_middle::mir::visit::*;
5+
use rustc_middle::mir::*;
6+
use rustc_middle::ty::TyCtxt;
7+
use rustc_mir_dataflow::impls::MaybeStorageDead;
8+
use rustc_mir_dataflow::storage::always_storage_live_locals;
9+
use rustc_mir_dataflow::Analysis;
10+
11+
use crate::ssa::{SsaLocals, StorageLiveLocals};
12+
use crate::MirPass;
13+
14+
/// Propagate references using SSA analysis.
15+
///
16+
/// MIR building may produce a lot of borrow-dereference patterns.
17+
///
18+
/// This pass aims to transform the following pattern:
19+
/// _1 = &raw? mut? PLACE;
20+
/// _3 = *_1;
21+
/// _4 = &raw? mut? *_1;
22+
///
23+
/// Into
24+
/// _1 = &raw? mut? PLACE;
25+
/// _3 = PLACE;
26+
/// _4 = &raw? mut? PLACE;
27+
///
28+
/// where `PLACE` is a direct or an indirect place expression.
29+
///
30+
/// There are 3 properties that need to be upheld for this transformation to be legal:
31+
/// - place stability: `PLACE` must refer to the same memory wherever it appears;
32+
/// - pointer liveness: we must not introduce dereferences of dangling pointers;
33+
/// - `&mut` borrow uniqueness.
34+
///
35+
/// # Stability
36+
///
37+
/// If `PLACE` is an indirect projection, if its of the form `(*LOCAL).PROJECTIONS` where:
38+
/// - `LOCAL` is SSA;
39+
/// - all projections in `PROJECTIONS` have a stable offset (no dereference and no indexing).
40+
///
41+
/// If `PLACE` is a direct projection of a local, we consider it as constant if:
42+
/// - the local is always live, or it has a single `StorageLive`;
43+
/// - all projections have a stable offset.
44+
///
45+
/// # Liveness
46+
///
47+
/// When performing a substitution, we must take care not to introduce uses of dangling locals.
48+
/// To ensure this, we walk the body with the `MaybeStorageDead` dataflow analysis:
49+
/// - if we want to replace `*x` by reborrow `*y` and `y` may be dead, we allow replacement and
50+
/// mark storage statements on `y` for removal;
51+
/// - if we want to replace `*x` by non-reborrow `y` and `y` must be live, we allow replacement;
52+
/// - if we want to replace `*x` by non-reborrow `y` and `y` may be dead, we do not replace.
53+
///
54+
/// # Uniqueness
55+
///
56+
/// For `&mut` borrows, we also need to preserve the uniqueness property:
57+
/// we must avoid creating a state where we interleave uses of `*_1` and `_2`.
58+
/// To do it, we only perform full substitution of mutable borrows:
59+
/// we replace either all or none of the occurrences of `*_1`.
60+
///
61+
/// Some care has to be taken when `_1` is copied in other locals.
62+
/// _1 = &raw? mut? _2;
63+
/// _3 = *_1;
64+
/// _4 = _1
65+
/// _5 = *_4
66+
/// In such cases, fully substituting `_1` means fully substituting all of the copies.
67+
///
68+
/// For immutable borrows, we do not need to preserve such uniqueness property,
69+
/// so we perform all the possible substitutions without removing the `_1 = &_2` statement.
70+
pub struct ReferencePropagation;
71+
72+
impl<'tcx> MirPass<'tcx> for ReferencePropagation {
73+
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
74+
sess.mir_opt_level() >= 4
75+
}
76+
77+
#[instrument(level = "trace", skip(self, tcx, body))]
78+
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
79+
debug!(def_id = ?body.source.def_id());
80+
propagate_ssa(tcx, body);
81+
}
82+
}
83+
84+
fn propagate_ssa<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
85+
let ssa = SsaLocals::new(body);
86+
87+
let mut replacer = compute_replacement(tcx, body, &ssa);
88+
debug!(?replacer.targets, ?replacer.allowed_replacements, ?replacer.storage_to_remove);
89+
90+
replacer.visit_body_preserves_cfg(body);
91+
92+
if replacer.any_replacement {
93+
crate::simplify::remove_unused_definitions(body);
94+
}
95+
}
96+
97+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
98+
enum Value<'tcx> {
99+
/// Not a pointer, or we can't know.
100+
Unknown,
101+
/// We know the value to be a pointer to this place.
102+
/// The boolean indicates whether the reference is mutable, subject the uniqueness rule.
103+
Pointer(Place<'tcx>, bool),
104+
}
105+
106+
/// For each local, save the place corresponding to `*local`.
107+
#[instrument(level = "trace", skip(tcx, body))]
108+
fn compute_replacement<'tcx>(
109+
tcx: TyCtxt<'tcx>,
110+
body: &Body<'tcx>,
111+
ssa: &SsaLocals,
112+
) -> Replacer<'tcx> {
113+
let always_live_locals = always_storage_live_locals(body);
114+
115+
// Compute which locals have a single `StorageLive` statement ever.
116+
let storage_live = StorageLiveLocals::new(body, &always_live_locals);
117+
118+
// Compute `MaybeStorageDead` dataflow to check that we only replace when the pointee is
119+
// definitely live.
120+
let mut maybe_dead = MaybeStorageDead::new(always_live_locals)
121+
.into_engine(tcx, body)
122+
.iterate_to_fixpoint()
123+
.into_results_cursor(body);
124+
125+
// Map for each local to the pointee.
126+
let mut targets = IndexVec::from_elem(Value::Unknown, &body.local_decls);
127+
// Set of locals for which we will remove their storage statement. This is useful for
128+
// reborrowed references.
129+
let mut storage_to_remove = BitSet::new_empty(body.local_decls.len());
130+
131+
let fully_replacable_locals = fully_replacable_locals(ssa);
132+
133+
// Returns true iff we can use `place` as a pointee.
134+
//
135+
// Note that we only need to verify that there is a single `StorageLive` statement, and we do
136+
// not need to verify that it dominates all uses of that local.
137+
//
138+
// Consider the three statements:
139+
// SL : StorageLive(a)
140+
// DEF: b = &raw? mut? a
141+
// USE: stuff that uses *b
142+
//
143+
// First, we recall that DEF is checked to dominate USE. Now imagine for the sake of
144+
// contradiction there is a DEF -> SL -> USE path. Consider two cases:
145+
//
146+
// - DEF dominates SL. We always have UB the first time control flow reaches DEF,
147+
// because the storage of `a` is dead. Since DEF dominates USE, that means we cannot
148+
// reach USE and so our optimization is ok.
149+
//
150+
// - DEF does not dominate SL. Then there is a `START_BLOCK -> SL` path not including DEF.
151+
// But we can extend this path to USE, meaning there is also a `START_BLOCK -> USE` path not
152+
// including DEF. This violates the DEF dominates USE condition, and so is impossible.
153+
let is_constant_place = |place: Place<'_>| {
154+
// We only allow `Deref` as the first projection, to avoid surprises.
155+
if place.projection.first() == Some(&PlaceElem::Deref) {
156+
// `place == (*some_local).xxx`, it is constant only if `some_local` is constant.
157+
// We approximate constness using SSAness.
158+
ssa.is_ssa(place.local) && place.projection[1..].iter().all(PlaceElem::is_stable_offset)
159+
} else {
160+
storage_live.has_single_storage(place.local)
161+
&& place.projection[..].iter().all(PlaceElem::is_stable_offset)
162+
}
163+
};
164+
165+
let mut can_perform_opt = |target: Place<'tcx>, loc: Location| {
166+
if target.projection.first() == Some(&PlaceElem::Deref) {
167+
// We are creating a reborrow. As `place.local` is a reference, removing the storage
168+
// statements should not make it much harder for LLVM to optimize.
169+
storage_to_remove.insert(target.local);
170+
true
171+
} else {
172+
// This is a proper dereference. We can only allow it if `target` is live.
173+
maybe_dead.seek_after_primary_effect(loc);
174+
let maybe_dead = maybe_dead.contains(target.local);
175+
!maybe_dead
176+
}
177+
};
178+
179+
for (local, rvalue, location) in ssa.assignments(body) {
180+
debug!(?local);
181+
182+
// Only visit if we have something to do.
183+
let Value::Unknown = targets[local] else { bug!() };
184+
185+
let ty = body.local_decls[local].ty;
186+
187+
// If this is not a reference or pointer, do nothing.
188+
if !ty.is_any_ptr() {
189+
debug!("not a reference or pointer");
190+
continue;
191+
}
192+
193+
// If this a mutable reference that we cannot fully replace, mark it as unknown.
194+
if ty.is_mutable_ptr() && !fully_replacable_locals.contains(local) {
195+
debug!("not fully replaceable");
196+
continue;
197+
}
198+
199+
debug!(?rvalue);
200+
match rvalue {
201+
// This is a copy, just use the value we have in store for the previous one.
202+
// As we are visiting in `assignment_order`, ie. reverse postorder, `rhs` should
203+
// have been visited before.
204+
Rvalue::Use(Operand::Copy(place) | Operand::Move(place))
205+
| Rvalue::CopyForDeref(place) => {
206+
if let Some(rhs) = place.as_local() {
207+
let target = targets[rhs];
208+
if matches!(target, Value::Pointer(..)) {
209+
targets[local] = target;
210+
} else if ssa.is_ssa(rhs) {
211+
let refmut = body.local_decls[rhs].ty.is_mutable_ptr();
212+
targets[local] = Value::Pointer(tcx.mk_place_deref(rhs.into()), refmut);
213+
}
214+
}
215+
}
216+
Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
217+
let mut place = *place;
218+
// Try to see through `place` in order to collapse reborrow chains.
219+
if place.projection.first() == Some(&PlaceElem::Deref)
220+
&& let Value::Pointer(target, refmut) = targets[place.local]
221+
// Only see through immutable reference and pointers, as we do not know yet if
222+
// mutable references are fully replaced.
223+
&& !refmut
224+
// Only collapse chain if the pointee is definitely live.
225+
&& can_perform_opt(target, location)
226+
{
227+
place = target.project_deeper(&place.projection[1..], tcx);
228+
}
229+
assert_ne!(place.local, local);
230+
if is_constant_place(place) {
231+
targets[local] = Value::Pointer(place, ty.is_mutable_ptr());
232+
}
233+
}
234+
// We do not know what to do, so keep as not-a-pointer.
235+
_ => {}
236+
}
237+
}
238+
239+
debug!(?targets);
240+
241+
let mut finder = ReplacementFinder {
242+
targets: &mut targets,
243+
can_perform_opt,
244+
allowed_replacements: FxHashSet::default(),
245+
};
246+
let reachable_blocks = traversal::reachable_as_bitset(body);
247+
for (bb, bbdata) in body.basic_blocks.iter_enumerated() {
248+
// Only visit reachable blocks as we rely on dataflow.
249+
if reachable_blocks.contains(bb) {
250+
finder.visit_basic_block_data(bb, bbdata);
251+
}
252+
}
253+
254+
let allowed_replacements = finder.allowed_replacements;
255+
return Replacer {
256+
tcx,
257+
targets,
258+
storage_to_remove,
259+
allowed_replacements,
260+
any_replacement: false,
261+
};
262+
263+
struct ReplacementFinder<'a, 'tcx, F> {
264+
targets: &'a mut IndexVec<Local, Value<'tcx>>,
265+
can_perform_opt: F,
266+
allowed_replacements: FxHashSet<(Local, Location)>,
267+
}
268+
269+
impl<'tcx, F> Visitor<'tcx> for ReplacementFinder<'_, 'tcx, F>
270+
where
271+
F: FnMut(Place<'tcx>, Location) -> bool,
272+
{
273+
fn visit_place(&mut self, place: &Place<'tcx>, ctxt: PlaceContext, loc: Location) {
274+
if matches!(ctxt, PlaceContext::NonUse(_)) {
275+
// There is no need to check liveness for non-uses.
276+
return;
277+
}
278+
279+
if let Value::Pointer(target, refmut) = self.targets[place.local]
280+
&& place.projection.first() == Some(&PlaceElem::Deref)
281+
{
282+
let perform_opt = (self.can_perform_opt)(target, loc);
283+
if perform_opt {
284+
self.allowed_replacements.insert((target.local, loc));
285+
} else if refmut {
286+
// This mutable reference is not fully replacable, so drop it.
287+
self.targets[place.local] = Value::Unknown;
288+
}
289+
}
290+
}
291+
}
292+
}
293+
294+
/// Compute the set of locals that can be fully replaced.
295+
///
296+
/// We consider a local to be replacable iff it's only used in a `Deref` projection `*_local` or
297+
/// non-use position (like storage statements and debuginfo).
298+
fn fully_replacable_locals(ssa: &SsaLocals) -> BitSet<Local> {
299+
let mut replacable = BitSet::new_empty(ssa.num_locals());
300+
301+
// First pass: for each local, whether its uses can be fully replaced.
302+
for local in ssa.locals() {
303+
if ssa.num_direct_uses(local) == 0 {
304+
replacable.insert(local);
305+
}
306+
}
307+
308+
// Second pass: a local can only be fully replaced if all its copies can.
309+
ssa.meet_copy_equivalence(&mut replacable);
310+
311+
replacable
312+
}
313+
314+
/// Utility to help performing subtitution of `*pattern` by `target`.
315+
struct Replacer<'tcx> {
316+
tcx: TyCtxt<'tcx>,
317+
targets: IndexVec<Local, Value<'tcx>>,
318+
storage_to_remove: BitSet<Local>,
319+
allowed_replacements: FxHashSet<(Local, Location)>,
320+
any_replacement: bool,
321+
}
322+
323+
impl<'tcx> MutVisitor<'tcx> for Replacer<'tcx> {
324+
fn tcx(&self) -> TyCtxt<'tcx> {
325+
self.tcx
326+
}
327+
328+
fn visit_place(&mut self, place: &mut Place<'tcx>, ctxt: PlaceContext, loc: Location) {
329+
if let Value::Pointer(target, _) = self.targets[place.local]
330+
&& place.projection.first() == Some(&PlaceElem::Deref)
331+
{
332+
let perform_opt = matches!(ctxt, PlaceContext::NonUse(_))
333+
|| self.allowed_replacements.contains(&(target.local, loc));
334+
335+
if perform_opt {
336+
*place = target.project_deeper(&place.projection[1..], self.tcx);
337+
self.any_replacement = true;
338+
}
339+
} else {
340+
self.super_place(place, ctxt, loc);
341+
}
342+
}
343+
344+
fn visit_statement(&mut self, stmt: &mut Statement<'tcx>, loc: Location) {
345+
match stmt.kind {
346+
StatementKind::StorageLive(l) | StatementKind::StorageDead(l)
347+
if self.storage_to_remove.contains(l) =>
348+
{
349+
stmt.make_nop();
350+
}
351+
// Do not remove assignments as they may still be useful for debuginfo.
352+
_ => self.super_statement(stmt, loc),
353+
}
354+
}
355+
}

‎compiler/rustc_mir_transform/src/ssa.rs‎

Lines changed: 113 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
//! We denote as "SSA" the set of locals that verify the following properties:
2+
//! 1/ They are only assigned-to once, either as a function parameter, or in an assign statement;
3+
//! 2/ This single assignment dominates all uses;
4+
//!
5+
//! As a consequence of rule 2, we consider that borrowed locals are not SSA, even if they are
6+
//! `Freeze`, as we do not track that the assignment dominates all uses of the borrow.
7+
18
use either::Either;
29
use rustc_data_structures::graph::dominators::Dominators;
310
use rustc_index::bit_set::BitSet;
411
use rustc_index::{IndexSlice, IndexVec};
512
use rustc_middle::middle::resolve_bound_vars::Set1;
613
use rustc_middle::mir::visit::*;
714
use rustc_middle::mir::*;
8-
use rustc_middle::ty::{ParamEnv, TyCtxt};
915

1016
#[derive(Debug)]
1117
pub struct SsaLocals {
@@ -17,6 +23,9 @@ pub struct SsaLocals {
1723
assignment_order: Vec<Local>,
1824
/// Copy equivalence classes between locals. See `copy_classes` for documentation.
1925
copy_classes: IndexVec<Local, Local>,
26+
/// Number of "direct" uses of each local, ie. uses that are not dereferences.
27+
/// We ignore non-uses (Storage statements, debuginfo).
28+
direct_uses: IndexVec<Local, u32>,
2029
}
2130

2231
/// We often encounter MIR bodies with 1 or 2 basic blocks. In those cases, it's unnecessary to
@@ -26,48 +35,48 @@ struct SmallDominators {
2635
inner: Option<Dominators<BasicBlock>>,
2736
}
2837

29-
trait DomExt {
30-
fn dominates(self, _other: Self, dominators: &SmallDominators) -> bool;
31-
}
32-
33-
impl DomExt for Location {
34-
fn dominates(self, other: Location, dominators: &SmallDominators) -> bool {
35-
if self.block == other.block {
36-
self.statement_index <= other.statement_index
38+
impl SmallDominators {
39+
fn dominates(&self, first: Location, second: Location) -> bool {
40+
if first.block == second.block {
41+
first.statement_index <= second.statement_index
42+
} else if let Some(inner) = &self.inner {
43+
inner.dominates(first.block, second.block)
3744
} else {
38-
dominators.dominates(self.block, other.block)
45+
first.block < second.block
3946
}
4047
}
41-
}
4248

43-
impl SmallDominators {
44-
fn dominates(&self, dom: BasicBlock, node: BasicBlock) -> bool {
45-
if let Some(inner) = &self.inner { inner.dominates(dom, node) } else { dom < node }
49+
fn check_dominates(&mut self, set: &mut Set1<LocationExtended>, loc: Location) {
50+
let assign_dominates = match *set {
51+
Set1::Empty | Set1::Many => false,
52+
Set1::One(LocationExtended::Arg) => true,
53+
Set1::One(LocationExtended::Plain(assign)) => {
54+
self.dominates(assign.successor_within_block(), loc)
55+
}
56+
};
57+
// We are visiting a use that is not dominated by an assignment.
58+
// Either there is a cycle involved, or we are reading for uninitialized local.
59+
// Bail out.
60+
if !assign_dominates {
61+
*set = Set1::Many;
62+
}
4663
}
4764
}
4865

4966
impl SsaLocals {
50-
pub fn new<'tcx>(
51-
tcx: TyCtxt<'tcx>,
52-
param_env: ParamEnv<'tcx>,
53-
body: &Body<'tcx>,
54-
borrowed_locals: &BitSet<Local>,
55-
) -> SsaLocals {
67+
pub fn new<'tcx>(body: &Body<'tcx>) -> SsaLocals {
5668
let assignment_order = Vec::with_capacity(body.local_decls.len());
5769

5870
let assignments = IndexVec::from_elem(Set1::Empty, &body.local_decls);
5971
let dominators =
6072
if body.basic_blocks.len() > 2 { Some(body.basic_blocks.dominators()) } else { None };
6173
let dominators = SmallDominators { inner: dominators };
62-
let mut visitor = SsaVisitor { assignments, assignment_order, dominators };
6374

64-
for (local, decl) in body.local_decls.iter_enumerated() {
65-
if matches!(body.local_kind(local), LocalKind::Arg) {
66-
visitor.assignments[local] = Set1::One(LocationExtended::Arg);
67-
}
68-
if borrowed_locals.contains(local) && !decl.ty.is_freeze(tcx, param_env) {
69-
visitor.assignments[local] = Set1::Many;
70-
}
75+
let direct_uses = IndexVec::from_elem(0, &body.local_decls);
76+
let mut visitor = SsaVisitor { assignments, assignment_order, dominators, direct_uses };
77+
78+
for local in body.args_iter() {
79+
visitor.assignments[local] = Set1::One(LocationExtended::Arg);
7180
}
7281

7382
if body.basic_blocks.len() > 2 {
@@ -85,36 +94,51 @@ impl SsaLocals {
8594
}
8695

8796
debug!(?visitor.assignments);
97+
debug!(?visitor.direct_uses);
8898

8999
visitor
90100
.assignment_order
91101
.retain(|&local| matches!(visitor.assignments[local], Set1::One(_)));
92102
debug!(?visitor.assignment_order);
93103

94-
let copy_classes = compute_copy_classes(&visitor, body);
104+
let copy_classes = compute_copy_classes(&mut visitor, body);
95105

96106
SsaLocals {
97107
assignments: visitor.assignments,
98108
assignment_order: visitor.assignment_order,
109+
direct_uses: visitor.direct_uses,
99110
copy_classes,
100111
}
101112
}
102113

114+
pub fn num_locals(&self) -> usize {
115+
self.assignments.len()
116+
}
117+
118+
pub fn locals(&self) -> impl Iterator<Item = Local> {
119+
self.assignments.indices()
120+
}
121+
103122
pub fn is_ssa(&self, local: Local) -> bool {
104123
matches!(self.assignments[local], Set1::One(_))
105124
}
106125

126+
/// Return the number of uses if a local that are not "Deref".
127+
pub fn num_direct_uses(&self, local: Local) -> u32 {
128+
self.direct_uses[local]
129+
}
130+
107131
pub fn assignments<'a, 'tcx>(
108132
&'a self,
109133
body: &'a Body<'tcx>,
110-
) -> impl Iterator<Item = (Local, &'a Rvalue<'tcx>)> + 'a {
134+
) -> impl Iterator<Item = (Local, &'a Rvalue<'tcx>, Location)> + 'a {
111135
self.assignment_order.iter().filter_map(|&local| {
112136
if let Set1::One(LocationExtended::Plain(loc)) = self.assignments[local] {
113137
// `loc` must point to a direct assignment to `local`.
114138
let Either::Left(stmt) = body.stmt_at(loc) else { bug!() };
115139
let Some((target, rvalue)) = stmt.kind.as_assign() else { bug!() };
116140
assert_eq!(target.as_local(), Some(local));
117-
Some((local, rvalue))
141+
Some((local, rvalue, loc))
118142
} else {
119143
None
120144
}
@@ -177,30 +201,14 @@ struct SsaVisitor {
177201
dominators: SmallDominators,
178202
assignments: IndexVec<Local, Set1<LocationExtended>>,
179203
assignment_order: Vec<Local>,
180-
}
181-
182-
impl SsaVisitor {
183-
fn check_assignment_dominates(&mut self, local: Local, loc: Location) {
184-
let set = &mut self.assignments[local];
185-
let assign_dominates = match *set {
186-
Set1::Empty | Set1::Many => false,
187-
Set1::One(LocationExtended::Arg) => true,
188-
Set1::One(LocationExtended::Plain(assign)) => {
189-
assign.successor_within_block().dominates(loc, &self.dominators)
190-
}
191-
};
192-
// We are visiting a use that is not dominated by an assignment.
193-
// Either there is a cycle involved, or we are reading for uninitialized local.
194-
// Bail out.
195-
if !assign_dominates {
196-
*set = Set1::Many;
197-
}
198-
}
204+
direct_uses: IndexVec<Local, u32>,
199205
}
200206

201207
impl<'tcx> Visitor<'tcx> for SsaVisitor {
202208
fn visit_local(&mut self, local: Local, ctxt: PlaceContext, loc: Location) {
203209
match ctxt {
210+
PlaceContext::MutatingUse(MutatingUseContext::Projection)
211+
| PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => bug!(),
204212
PlaceContext::MutatingUse(MutatingUseContext::Store) => {
205213
self.assignments[local].insert(LocationExtended::Plain(loc));
206214
if let Set1::One(_) = self.assignments[local] {
@@ -209,12 +217,20 @@ impl<'tcx> Visitor<'tcx> for SsaVisitor {
209217
}
210218
}
211219
// Anything can happen with raw pointers, so remove them.
212-
PlaceContext::NonMutatingUse(NonMutatingUseContext::AddressOf)
213-
| PlaceContext::MutatingUse(_) => self.assignments[local] = Set1::Many,
214-
// Immutable borrows are taken into account in `SsaLocals::new` by
215-
// removing non-freeze locals.
220+
// We do not verify that all uses of the borrow dominate the assignment to `local`,
221+
// so we have to remove them too.
222+
PlaceContext::NonMutatingUse(
223+
NonMutatingUseContext::SharedBorrow
224+
| NonMutatingUseContext::ShallowBorrow
225+
| NonMutatingUseContext::UniqueBorrow
226+
| NonMutatingUseContext::AddressOf,
227+
)
228+
| PlaceContext::MutatingUse(_) => {
229+
self.assignments[local] = Set1::Many;
230+
}
216231
PlaceContext::NonMutatingUse(_) => {
217-
self.check_assignment_dominates(local, loc);
232+
self.dominators.check_dominates(&mut self.assignments[local], loc);
233+
self.direct_uses[local] += 1;
218234
}
219235
PlaceContext::NonUse(_) => {}
220236
}
@@ -224,20 +240,22 @@ impl<'tcx> Visitor<'tcx> for SsaVisitor {
224240
if place.projection.first() == Some(&PlaceElem::Deref) {
225241
// Do not do anything for storage statements and debuginfo.
226242
if ctxt.is_use() {
227-
// A use through a `deref` only reads from the local, and cannot write to it.
228-
let new_ctxt = PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection);
243+
// Only change the context if it is a real use, not a "use" in debuginfo.
244+
let new_ctxt = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy);
229245

230246
self.visit_projection(place.as_ref(), new_ctxt, loc);
231-
self.check_assignment_dominates(place.local, loc);
247+
self.dominators.check_dominates(&mut self.assignments[place.local], loc);
232248
}
233249
return;
250+
} else {
251+
self.visit_projection(place.as_ref(), ctxt, loc);
252+
self.visit_local(place.local, ctxt, loc);
234253
}
235-
self.super_place(place, ctxt, loc);
236254
}
237255
}
238256

239257
#[instrument(level = "trace", skip(ssa, body))]
240-
fn compute_copy_classes(ssa: &SsaVisitor, body: &Body<'_>) -> IndexVec<Local, Local> {
258+
fn compute_copy_classes(ssa: &mut SsaVisitor, body: &Body<'_>) -> IndexVec<Local, Local> {
241259
let mut copies = IndexVec::from_fn_n(|l| l, body.local_decls.len());
242260

243261
for &local in &ssa.assignment_order {
@@ -267,9 +285,11 @@ fn compute_copy_classes(ssa: &SsaVisitor, body: &Body<'_>) -> IndexVec<Local, Lo
267285
// We visit in `assignment_order`, ie. reverse post-order, so `rhs` has been
268286
// visited before `local`, and we just have to copy the representing local.
269287
copies[local] = copies[rhs];
288+
ssa.direct_uses[rhs] -= 1;
270289
}
271290

272291
debug!(?copies);
292+
debug!(?ssa.direct_uses);
273293

274294
// Invariant: `copies` must point to the head of an equivalence class.
275295
#[cfg(debug_assertions)]
@@ -279,3 +299,36 @@ fn compute_copy_classes(ssa: &SsaVisitor, body: &Body<'_>) -> IndexVec<Local, Lo
279299

280300
copies
281301
}
302+
303+
#[derive(Debug)]
304+
pub(crate) struct StorageLiveLocals {
305+
/// Set of "StorageLive" statements for each local.
306+
storage_live: IndexVec<Local, Set1<LocationExtended>>,
307+
}
308+
309+
impl StorageLiveLocals {
310+
pub(crate) fn new(
311+
body: &Body<'_>,
312+
always_storage_live_locals: &BitSet<Local>,
313+
) -> StorageLiveLocals {
314+
let mut storage_live = IndexVec::from_elem(Set1::Empty, &body.local_decls);
315+
for local in always_storage_live_locals.iter() {
316+
storage_live[local] = Set1::One(LocationExtended::Arg);
317+
}
318+
for (block, bbdata) in body.basic_blocks.iter_enumerated() {
319+
for (statement_index, statement) in bbdata.statements.iter().enumerate() {
320+
if let StatementKind::StorageLive(local) = statement.kind {
321+
storage_live[local]
322+
.insert(LocationExtended::Plain(Location { block, statement_index }));
323+
}
324+
}
325+
}
326+
debug!(?storage_live);
327+
StorageLiveLocals { storage_live }
328+
}
329+
330+
#[inline]
331+
pub(crate) fn has_single_storage(&self, local: Local) -> bool {
332+
matches!(self.storage_live[local], Set1::One(_))
333+
}
334+
}

‎tests/mir-opt/copy-prop/borrowed_local.f.CopyProp.diff‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
}
2121

2222
bb1: {
23-
- _0 = opaque::<u8>(_3) -> bb2; // scope 0 at $DIR/borrowed_local.rs:+12:13: +12:38
24-
+ _0 = opaque::<u8>(_1) -> bb2; // scope 0 at $DIR/borrowed_local.rs:+12:13: +12:38
23+
_0 = opaque::<u8>(_3) -> bb2; // scope 0 at $DIR/borrowed_local.rs:+12:13: +12:38
2524
// mir::Constant
2625
// + span: $DIR/borrowed_local.rs:28:28: 28:34
2726
// + literal: Const { ty: fn(u8) -> bool {opaque::<u8>}, val: Value(<ZST>) }
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
- // MIR for `dominate_storage` before ReferencePropagation
2+
+ // MIR for `dominate_storage` after ReferencePropagation
3+
4+
fn dominate_storage() -> () {
5+
let mut _0: (); // return place in scope 0 at $DIR/reference_prop.rs:+0:23: +0:23
6+
let mut _1: i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
7+
let mut _2: &i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
8+
let mut _3: i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
9+
let mut _4: bool; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
10+
let mut _5: i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
11+
let mut _6: bool; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
12+
13+
bb0: {
14+
goto -> bb1; // scope 0 at $DIR/reference_prop.rs:+8:11: +8:20
15+
}
16+
17+
bb1: {
18+
_1 = const 5_i32; // scope 0 at $DIR/reference_prop.rs:+10:13: +10:18
19+
_2 = &_1; // scope 0 at $DIR/reference_prop.rs:+11:13: +11:19
20+
goto -> bb2; // scope 0 at $DIR/reference_prop.rs:+12:13: +12:22
21+
}
22+
23+
bb2: {
24+
_5 = (*_2); // scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
25+
_0 = opaque::<i32>(_5) -> bb3; // scope 0 at $DIR/reference_prop.rs:+16:13: +16:38
26+
// mir::Constant
27+
// + span: $DIR/reference_prop.rs:383:28: 383:34
28+
// + literal: Const { ty: fn(i32) {opaque::<i32>}, val: Value(<ZST>) }
29+
}
30+
31+
bb3: {
32+
StorageDead(_1); // scope 0 at $DIR/reference_prop.rs:+19:13: +19:27
33+
StorageLive(_1); // scope 0 at $DIR/reference_prop.rs:+20:13: +20:27
34+
_6 = const true; // scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
35+
switchInt(_6) -> [0: bb3, otherwise: bb1]; // scope 0 at $DIR/reference_prop.rs:+22:13: +22:47
36+
}
37+
}
38+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
- // MIR for `maybe_dead` before ReferencePropagation
2+
+ // MIR for `maybe_dead` after ReferencePropagation
3+
4+
fn maybe_dead(_1: bool) -> () {
5+
let mut _0: (); // return place in scope 0 at $DIR/reference_prop.rs:+0:24: +0:24
6+
let mut _2: i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
7+
let mut _3: i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
8+
let mut _4: &i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
9+
let mut _5: &mut i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
10+
let mut _6: i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
11+
let mut _7: i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
12+
let mut _8: i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
13+
14+
bb0: {
15+
StorageLive(_2); // scope 0 at $DIR/reference_prop.rs:+7:13: +7:27
16+
StorageLive(_3); // scope 0 at $DIR/reference_prop.rs:+8:13: +8:27
17+
_2 = const 5_i32; // scope 0 at $DIR/reference_prop.rs:+9:13: +9:18
18+
_3 = const 5_i32; // scope 0 at $DIR/reference_prop.rs:+10:13: +10:18
19+
_4 = &_2; // scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
20+
_5 = &mut _3; // scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
21+
(*_5) = const 7_i32; // scope 0 at $DIR/reference_prop.rs:+14:13: +14:19
22+
- _6 = (*_4); // scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
23+
+ _6 = _2; // scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
24+
switchInt(_1) -> [1: bb1, otherwise: bb2]; // scope 0 at $DIR/reference_prop.rs:+17:13: +17:46
25+
}
26+
27+
bb1: {
28+
StorageDead(_2); // scope 0 at $DIR/reference_prop.rs:+20:13: +20:27
29+
StorageDead(_3); // scope 0 at $DIR/reference_prop.rs:+21:13: +21:27
30+
_0 = opaque::<i32>(_6) -> bb2; // scope 0 at $DIR/reference_prop.rs:+22:13: +22:38
31+
// mir::Constant
32+
// + span: $DIR/reference_prop.rs:417:28: 417:34
33+
// + literal: Const { ty: fn(i32) {opaque::<i32>}, val: Value(<ZST>) }
34+
}
35+
36+
bb2: {
37+
_7 = (*_4); // scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
38+
_0 = opaque::<i32>(_7) -> bb3; // scope 0 at $DIR/reference_prop.rs:+27:13: +27:38
39+
// mir::Constant
40+
// + span: $DIR/reference_prop.rs:422:28: 422:34
41+
// + literal: Const { ty: fn(i32) {opaque::<i32>}, val: Value(<ZST>) }
42+
}
43+
44+
bb3: {
45+
_8 = (*_5); // scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
46+
_0 = opaque::<i32>(_8) -> bb4; // scope 0 at $DIR/reference_prop.rs:+33:13: +33:43
47+
// mir::Constant
48+
// + span: $DIR/reference_prop.rs:428:33: 428:39
49+
// + literal: Const { ty: fn(i32) {opaque::<i32>}, val: Value(<ZST>) }
50+
}
51+
52+
bb4: {
53+
return; // scope 0 at $DIR/reference_prop.rs:+36:13: +36:21
54+
}
55+
}
56+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
- // MIR for `multiple_storage` before ReferencePropagation
2+
+ // MIR for `multiple_storage` after ReferencePropagation
3+
4+
fn multiple_storage() -> () {
5+
let mut _0: (); // return place in scope 0 at $DIR/reference_prop.rs:+0:23: +0:23
6+
let mut _1: i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
7+
let mut _2: &i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
8+
let mut _3: i32; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
9+
10+
bb0: {
11+
StorageLive(_1); // scope 0 at $DIR/reference_prop.rs:+6:13: +6:27
12+
_1 = const 5_i32; // scope 0 at $DIR/reference_prop.rs:+7:13: +7:18
13+
_2 = &_1; // scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
14+
StorageDead(_1); // scope 0 at $DIR/reference_prop.rs:+9:13: +9:27
15+
StorageLive(_1); // scope 0 at $DIR/reference_prop.rs:+10:13: +10:27
16+
_3 = (*_2); // scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
17+
_0 = opaque::<i32>(_3) -> bb1; // scope 0 at $DIR/reference_prop.rs:+14:13: +14:43
18+
// mir::Constant
19+
// + span: $DIR/reference_prop.rs:357:33: 357:39
20+
// + literal: Const { ty: fn(i32) {opaque::<i32>}, val: Value(<ZST>) }
21+
}
22+
23+
bb1: {
24+
return; // scope 0 at $DIR/reference_prop.rs:+18:13: +18:21
25+
}
26+
}
27+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
- // MIR for `read_through_raw` before ReferencePropagation
2+
+ // MIR for `read_through_raw` after ReferencePropagation
3+
4+
fn read_through_raw(_1: &mut usize) -> usize {
5+
let mut _0: usize; // return place in scope 0 at $DIR/reference_prop.rs:+0:39: +0:44
6+
let mut _2: &mut usize; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
7+
let mut _3: &mut usize; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
8+
let mut _4: *mut usize; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
9+
let mut _5: *mut usize; // in scope 0 at $SRC_DIR/core/src/intrinsics/mir.rs:LL:COL
10+
11+
bb0: {
12+
_2 = &mut (*_1); // scope 0 at $DIR/reference_prop.rs:+10:13: +10:25
13+
- _3 = &mut (*_2); // scope 0 at $DIR/reference_prop.rs:+11:13: +11:26
14+
- _4 = &raw mut (*_2); // scope 0 at $DIR/reference_prop.rs:+12:13: +12:30
15+
- _5 = &raw mut (*_3); // scope 0 at $DIR/reference_prop.rs:+13:13: +13:30
16+
- _0 = (*_4); // scope 0 at $DIR/reference_prop.rs:+15:13: +15:22
17+
- _0 = (*_5); // scope 0 at $DIR/reference_prop.rs:+16:13: +16:22
18+
+ _3 = &mut (*_1); // scope 0 at $DIR/reference_prop.rs:+11:13: +11:26
19+
+ _0 = (*_2); // scope 0 at $DIR/reference_prop.rs:+15:13: +15:22
20+
+ _0 = (*_3); // scope 0 at $DIR/reference_prop.rs:+16:13: +16:22
21+
return; // scope 0 at $DIR/reference_prop.rs:+17:13: +17:21
22+
}
23+
}
24+

‎tests/mir-opt/reference_prop.reference_propagation.ReferencePropagation.diff‎

Lines changed: 375 additions & 0 deletions
Large diffs are not rendered by default.

‎tests/mir-opt/reference_prop.reference_propagation_const_ptr.ReferencePropagation.diff‎

Lines changed: 431 additions & 0 deletions
Large diffs are not rendered by default.

‎tests/mir-opt/reference_prop.reference_propagation_mut.ReferencePropagation.diff‎

Lines changed: 372 additions & 0 deletions
Large diffs are not rendered by default.

‎tests/mir-opt/reference_prop.reference_propagation_mut_ptr.ReferencePropagation.diff‎

Lines changed: 378 additions & 0 deletions
Large diffs are not rendered by default.

‎tests/mir-opt/reference_prop.rs‎

Lines changed: 456 additions & 0 deletions
Large diffs are not rendered by default.

‎tests/mir-opt/slice_filter.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ pub fn variant_b(input: &[(usize, usize, usize, usize)]) -> usize {
1212
input.iter().filter(|&&(a, b, c, d)| a <= c && d <= b || c <= a && b <= d).count()
1313
}
1414

15+
// EMIT_MIR slice_filter.variant_a-{closure#0}.ReferencePropagation.diff
1516
// EMIT_MIR slice_filter.variant_a-{closure#0}.CopyProp.diff
1617
// EMIT_MIR slice_filter.variant_a-{closure#0}.DestinationPropagation.diff
1718
// EMIT_MIR slice_filter.variant_b-{closure#0}.CopyProp.diff
19+
// EMIT_MIR slice_filter.variant_b-{closure#0}.ReferencePropagation.diff
1820
// EMIT_MIR slice_filter.variant_b-{closure#0}.DestinationPropagation.diff

‎tests/mir-opt/slice_filter.variant_a-{closure#0}.CopyProp.diff‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,16 @@
101101
}
102102

103103
bb0: {
104-
- StorageLive(_3); // scope 0 at $DIR/slice_filter.rs:+0:27: +0:28
104+
StorageLive(_3); // scope 0 at $DIR/slice_filter.rs:+0:27: +0:28
105105
_25 = deref_copy (*_2); // scope 0 at $DIR/slice_filter.rs:+0:27: +0:28
106106
_3 = &((*_25).0: usize); // scope 0 at $DIR/slice_filter.rs:+0:27: +0:28
107-
- StorageLive(_4); // scope 0 at $DIR/slice_filter.rs:+0:30: +0:31
107+
StorageLive(_4); // scope 0 at $DIR/slice_filter.rs:+0:30: +0:31
108108
_26 = deref_copy (*_2); // scope 0 at $DIR/slice_filter.rs:+0:30: +0:31
109109
_4 = &((*_26).1: usize); // scope 0 at $DIR/slice_filter.rs:+0:30: +0:31
110-
- StorageLive(_5); // scope 0 at $DIR/slice_filter.rs:+0:33: +0:34
110+
StorageLive(_5); // scope 0 at $DIR/slice_filter.rs:+0:33: +0:34
111111
_27 = deref_copy (*_2); // scope 0 at $DIR/slice_filter.rs:+0:33: +0:34
112112
_5 = &((*_27).2: usize); // scope 0 at $DIR/slice_filter.rs:+0:33: +0:34
113-
- StorageLive(_6); // scope 0 at $DIR/slice_filter.rs:+0:36: +0:37
113+
StorageLive(_6); // scope 0 at $DIR/slice_filter.rs:+0:36: +0:37
114114
_28 = deref_copy (*_2); // scope 0 at $DIR/slice_filter.rs:+0:36: +0:37
115115
_6 = &((*_28).3: usize); // scope 0 at $DIR/slice_filter.rs:+0:36: +0:37
116116
StorageLive(_7); // scope 1 at $DIR/slice_filter.rs:+0:40: +0:56
@@ -184,10 +184,10 @@
184184
bb3: {
185185
StorageDead(_16); // scope 1 at $DIR/slice_filter.rs:+0:75: +0:76
186186
StorageDead(_7); // scope 1 at $DIR/slice_filter.rs:+0:75: +0:76
187-
- StorageDead(_6); // scope 0 at $DIR/slice_filter.rs:+0:75: +0:76
188-
- StorageDead(_5); // scope 0 at $DIR/slice_filter.rs:+0:75: +0:76
189-
- StorageDead(_4); // scope 0 at $DIR/slice_filter.rs:+0:75: +0:76
190-
- StorageDead(_3); // scope 0 at $DIR/slice_filter.rs:+0:75: +0:76
187+
StorageDead(_6); // scope 0 at $DIR/slice_filter.rs:+0:75: +0:76
188+
StorageDead(_5); // scope 0 at $DIR/slice_filter.rs:+0:75: +0:76
189+
StorageDead(_4); // scope 0 at $DIR/slice_filter.rs:+0:75: +0:76
190+
StorageDead(_3); // scope 0 at $DIR/slice_filter.rs:+0:75: +0:76
191191
return; // scope 0 at $DIR/slice_filter.rs:+0:76: +0:76
192192
}
193193

‎tests/mir-opt/slice_filter.variant_a-{closure#0}.DestinationPropagation.diff‎

Lines changed: 68 additions & 36 deletions
Large diffs are not rendered by default.

‎tests/mir-opt/slice_filter.variant_a-{closure#0}.ReferencePropagation.diff‎

Lines changed: 247 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
- // MIR for `variant_b::{closure#0}` before ReferencePropagation
2+
+ // MIR for `variant_b::{closure#0}` after ReferencePropagation
3+
4+
fn variant_b::{closure#0}(_1: &mut [closure@$DIR/slice_filter.rs:12:25: 12:41], _2: &&(usize, usize, usize, usize)) -> bool {
5+
let mut _0: bool; // return place in scope 0 at $DIR/slice_filter.rs:+0:42: +0:42
6+
let _3: usize; // in scope 0 at $DIR/slice_filter.rs:+0:29: +0:30
7+
let _4: usize; // in scope 0 at $DIR/slice_filter.rs:+0:32: +0:33
8+
let _5: usize; // in scope 0 at $DIR/slice_filter.rs:+0:35: +0:36
9+
let _6: usize; // in scope 0 at $DIR/slice_filter.rs:+0:38: +0:39
10+
let mut _7: bool; // in scope 0 at $DIR/slice_filter.rs:+0:42: +0:58
11+
let mut _8: bool; // in scope 0 at $DIR/slice_filter.rs:+0:42: +0:48
12+
let mut _9: usize; // in scope 0 at $DIR/slice_filter.rs:+0:42: +0:43
13+
let mut _10: usize; // in scope 0 at $DIR/slice_filter.rs:+0:47: +0:48
14+
let mut _11: bool; // in scope 0 at $DIR/slice_filter.rs:+0:52: +0:58
15+
let mut _12: usize; // in scope 0 at $DIR/slice_filter.rs:+0:52: +0:53
16+
let mut _13: usize; // in scope 0 at $DIR/slice_filter.rs:+0:57: +0:58
17+
let mut _14: bool; // in scope 0 at $DIR/slice_filter.rs:+0:62: +0:78
18+
let mut _15: bool; // in scope 0 at $DIR/slice_filter.rs:+0:62: +0:68
19+
let mut _16: usize; // in scope 0 at $DIR/slice_filter.rs:+0:62: +0:63
20+
let mut _17: usize; // in scope 0 at $DIR/slice_filter.rs:+0:67: +0:68
21+
let mut _18: bool; // in scope 0 at $DIR/slice_filter.rs:+0:72: +0:78
22+
let mut _19: usize; // in scope 0 at $DIR/slice_filter.rs:+0:72: +0:73
23+
let mut _20: usize; // in scope 0 at $DIR/slice_filter.rs:+0:77: +0:78
24+
let mut _21: &(usize, usize, usize, usize); // in scope 0 at $DIR/slice_filter.rs:+0:26: +0:40
25+
let mut _22: &(usize, usize, usize, usize); // in scope 0 at $DIR/slice_filter.rs:+0:26: +0:40
26+
let mut _23: &(usize, usize, usize, usize); // in scope 0 at $DIR/slice_filter.rs:+0:26: +0:40
27+
let mut _24: &(usize, usize, usize, usize); // in scope 0 at $DIR/slice_filter.rs:+0:26: +0:40
28+
scope 1 {
29+
debug a => _3; // in scope 1 at $DIR/slice_filter.rs:+0:29: +0:30
30+
debug b => _4; // in scope 1 at $DIR/slice_filter.rs:+0:32: +0:33
31+
debug c => _5; // in scope 1 at $DIR/slice_filter.rs:+0:35: +0:36
32+
debug d => _6; // in scope 1 at $DIR/slice_filter.rs:+0:38: +0:39
33+
}
34+
35+
bb0: {
36+
_21 = deref_copy (*_2); // scope 0 at $DIR/slice_filter.rs:+0:29: +0:30
37+
_3 = ((*_21).0: usize); // scope 0 at $DIR/slice_filter.rs:+0:29: +0:30
38+
_22 = deref_copy (*_2); // scope 0 at $DIR/slice_filter.rs:+0:32: +0:33
39+
_4 = ((*_22).1: usize); // scope 0 at $DIR/slice_filter.rs:+0:32: +0:33
40+
_23 = deref_copy (*_2); // scope 0 at $DIR/slice_filter.rs:+0:35: +0:36
41+
_5 = ((*_23).2: usize); // scope 0 at $DIR/slice_filter.rs:+0:35: +0:36
42+
_24 = deref_copy (*_2); // scope 0 at $DIR/slice_filter.rs:+0:38: +0:39
43+
_6 = ((*_24).3: usize); // scope 0 at $DIR/slice_filter.rs:+0:38: +0:39
44+
StorageLive(_7); // scope 1 at $DIR/slice_filter.rs:+0:42: +0:58
45+
StorageLive(_8); // scope 1 at $DIR/slice_filter.rs:+0:42: +0:48
46+
_8 = Le(_3, _5); // scope 1 at $DIR/slice_filter.rs:+0:42: +0:48
47+
switchInt(move _8) -> [0: bb4, otherwise: bb5]; // scope 1 at $DIR/slice_filter.rs:+0:42: +0:58
48+
}
49+
50+
bb1: {
51+
_0 = const true; // scope 1 at $DIR/slice_filter.rs:+0:42: +0:78
52+
goto -> bb3; // scope 1 at $DIR/slice_filter.rs:+0:42: +0:78
53+
}
54+
55+
bb2: {
56+
StorageLive(_14); // scope 1 at $DIR/slice_filter.rs:+0:62: +0:78
57+
StorageLive(_15); // scope 1 at $DIR/slice_filter.rs:+0:62: +0:68
58+
_15 = Le(_5, _3); // scope 1 at $DIR/slice_filter.rs:+0:62: +0:68
59+
switchInt(move _15) -> [0: bb6, otherwise: bb7]; // scope 1 at $DIR/slice_filter.rs:+0:62: +0:78
60+
}
61+
62+
bb3: {
63+
StorageDead(_14); // scope 1 at $DIR/slice_filter.rs:+0:77: +0:78
64+
StorageDead(_7); // scope 1 at $DIR/slice_filter.rs:+0:77: +0:78
65+
return; // scope 0 at $DIR/slice_filter.rs:+0:78: +0:78
66+
}
67+
68+
bb4: {
69+
_7 = const false; // scope 1 at $DIR/slice_filter.rs:+0:42: +0:58
70+
StorageDead(_11); // scope 1 at $DIR/slice_filter.rs:+0:57: +0:58
71+
StorageDead(_8); // scope 1 at $DIR/slice_filter.rs:+0:57: +0:58
72+
goto -> bb2; // scope 1 at $DIR/slice_filter.rs:+0:42: +0:58
73+
}
74+
75+
bb5: {
76+
StorageLive(_11); // scope 1 at $DIR/slice_filter.rs:+0:52: +0:58
77+
_11 = Le(_6, _4); // scope 1 at $DIR/slice_filter.rs:+0:52: +0:58
78+
_7 = move _11; // scope 1 at $DIR/slice_filter.rs:+0:42: +0:58
79+
StorageDead(_11); // scope 1 at $DIR/slice_filter.rs:+0:57: +0:58
80+
StorageDead(_8); // scope 1 at $DIR/slice_filter.rs:+0:57: +0:58
81+
switchInt(move _7) -> [0: bb2, otherwise: bb1]; // scope 1 at $DIR/slice_filter.rs:+0:42: +0:78
82+
}
83+
84+
bb6: {
85+
_14 = const false; // scope 1 at $DIR/slice_filter.rs:+0:62: +0:78
86+
goto -> bb8; // scope 1 at $DIR/slice_filter.rs:+0:62: +0:78
87+
}
88+
89+
bb7: {
90+
StorageLive(_18); // scope 1 at $DIR/slice_filter.rs:+0:72: +0:78
91+
_18 = Le(_4, _6); // scope 1 at $DIR/slice_filter.rs:+0:72: +0:78
92+
_14 = move _18; // scope 1 at $DIR/slice_filter.rs:+0:62: +0:78
93+
goto -> bb8; // scope 1 at $DIR/slice_filter.rs:+0:62: +0:78
94+
}
95+
96+
bb8: {
97+
StorageDead(_18); // scope 1 at $DIR/slice_filter.rs:+0:77: +0:78
98+
StorageDead(_15); // scope 1 at $DIR/slice_filter.rs:+0:77: +0:78
99+
_0 = move _14; // scope 1 at $DIR/slice_filter.rs:+0:42: +0:78
100+
goto -> bb3; // scope 1 at $DIR/slice_filter.rs:+0:42: +0:78
101+
}
102+
}
103+

0 commit comments

Comments
 (0)
Please sign in to comment.