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 374814c

Browse files
committedMar 21, 2025
Added PropRvalue pass
1 parent be73c1f commit 374814c

File tree

3 files changed

+248
-1
lines changed

3 files changed

+248
-1
lines changed
 

‎compiler/rustc_mir_transform/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ declare_passes! {
171171
mod remove_zsts : RemoveZsts;
172172
mod required_consts : RequiredConstsVisitor;
173173
mod post_analysis_normalize : PostAnalysisNormalize;
174+
mod prop_rvalues : PropRvalues;
174175
mod sanity_check : SanityCheck;
175176
// This pass is public to allow external drivers to perform MIR cleanup
176177
pub mod simplify :
@@ -721,6 +722,7 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
721722
&copy_prop::CopyProp,
722723
&dead_store_elimination::DeadStoreElimination::Final,
723724
&nrvo::RenameReturnPlace,
725+
&prop_rvalues::PropRvalues,
724726
&simplify::SimplifyLocals::Final,
725727
&multiple_return_terminators::MultipleReturnTerminators,
726728
&large_enums::EnumSizeOpt { discrepancy: 128 },
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
use rustc_index::IndexVec;
2+
use rustc_index::bit_set::DenseBitSet;
3+
use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
4+
use rustc_middle::mir::*;
5+
use rustc_middle::ty::*;
6+
use rustc_session::Session;
7+
8+
use crate::MirPass;
9+
/// This pass simplifes MIR by replacing places based on past Rvalues.
10+
/// For example, this MIR:
11+
/// ```text
12+
/// _2 = Some(_1)
13+
/// _3 = (_2 as Some).0
14+
/// ```text
15+
/// Will get simplfied into this MIR:
16+
/// ```text
17+
/// _2 = Some(_1)
18+
/// _3 = _1
19+
/// ```
20+
/// This pass can also propagate uses of locals:
21+
/// ```text
22+
/// _2 = copy _1.0
23+
/// _3 = copy _2.1
24+
/// ```
25+
/// ```text
26+
/// _2 = copy _1.0
27+
/// _3 = copy _1.0.1
28+
/// ```
29+
/// To simplify the implementation, this pass has some limitations:
30+
/// 1. It will never propagate any rvalue across a write to memory.
31+
/// 2. It never propagates rvalues with moves.
32+
pub(super) struct PropRvalues;
33+
impl<'tcx> MirPass<'tcx> for PropRvalues {
34+
/// This pass is relatively cheap, and(by itself) does not affect the debug information whatsoever.
35+
/// FIXME: check if this pass would be benficial to enable for mir_opt_level = 1
36+
fn is_enabled(&self, sess: &Session) -> bool {
37+
sess.mir_opt_level() > 1
38+
}
39+
fn is_required(&self) -> bool {
40+
false
41+
}
42+
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
43+
// This pass has a O(n^2) memory usage, so I limit it to <= 256 locals.
44+
// FIXME: check if this limit can be raised.
45+
if body.local_decls.len() > 256 {
46+
return;
47+
}
48+
49+
let mut prop = PropagateLocals::new(body.local_decls.len(), tcx);
50+
// We don't care about the location here.
51+
let dummy = Location { block: BasicBlock::ZERO, statement_index: 0 };
52+
// We consider each block separately - this simplifes the implementation considerably.
53+
for block in body.basic_blocks.as_mut_preserves_cfg() {
54+
for statement in &mut block.statements {
55+
prop.visit_statement(statement, dummy);
56+
}
57+
// This allows us to *sometimes* elide needless copies.
58+
if let Some(ref mut terminator) = block.terminator {
59+
prop.visit_terminator(terminator, dummy);
60+
};
61+
prop.reset();
62+
}
63+
}
64+
}
65+
struct PropagateLocals<'tcx> {
66+
locals: IndexVec<Local, Option<Rvalue<'tcx>>>,
67+
/// Contains the list of rvalues which are invalidated if local `if_modifed` is modifed.
68+
/// \[if_modifed\]\[should_invalidate\]
69+
deps: IndexVec<Local, DenseBitSet<Local>>,
70+
tcx: TyCtxt<'tcx>,
71+
}
72+
impl<'tcx> PropagateLocals<'tcx> {
73+
/// Registers that `target_local` depends on `local`, and will be invalidated once `local` is written to.
74+
fn register_dep(&mut self, target_local: Local, local: Local) {
75+
self.deps[local].insert(target_local);
76+
}
77+
/// Marks `local` as potentially written to, invalidating all rvalues which reference it
78+
fn write_local(&mut self, local: Local) {
79+
for set in self.deps[local].iter() {
80+
self.locals[set] = None;
81+
}
82+
}
83+
fn new(locals: usize, tcx: TyCtxt<'tcx>) -> Self {
84+
Self {
85+
locals: IndexVec::from_elem_n(None, locals),
86+
deps: IndexVec::from_elem_n(DenseBitSet::new_empty(locals), locals),
87+
tcx,
88+
}
89+
}
90+
/// Resets the propagator, marking all rvalues as invalid, and clearing dependency info.
91+
fn reset(&mut self) {
92+
self.locals.iter_mut().for_each(|l| *l = None);
93+
self.deps.iter_mut().for_each(|si| si.clear());
94+
}
95+
/// Checks what rvalues are invalidated by `lvalue`, and removes them from the propagation process.
96+
fn invalidate_place(&mut self, lvalue: &Place<'_>) {
97+
// if this contains *deref* then the *lvalue* could be writing to anything.
98+
if lvalue.projection.contains(&ProjectionElem::Deref) {
99+
self.reset();
100+
}
101+
self.write_local(lvalue.local);
102+
}
103+
/// Adds rvalue to the propagation list, if eligible.
104+
/// Rvalue is eligible for propagation if:
105+
/// 1. It does not read from memory(no Deref projection)
106+
/// 2. It does not move from any locals(since that invalidates them).
107+
/// 3. lvalue it is assigned to is a local.
108+
/// This function also automaticaly invalidates all locals this rvalue is moving.
109+
fn add_rvalue(&mut self, rvalue: &Rvalue<'tcx>, lvalue: &Place<'tcx>) {
110+
struct HasDerfOrMoves<'b, 'tcx> {
111+
has_deref: bool,
112+
has_moves: bool,
113+
prop: &'b mut PropagateLocals<'tcx>,
114+
target_local: Local,
115+
}
116+
impl<'tcx, 'b> Visitor<'tcx> for HasDerfOrMoves<'b, 'tcx> {
117+
fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
118+
match operand {
119+
Operand::Move(place) => {
120+
self.has_moves = true;
121+
// Exact semantics of moves are not decided yet, so I *assume* they leave behind unintialized memory.
122+
self.prop.invalidate_place(place);
123+
}
124+
_ => (),
125+
}
126+
self.super_operand(operand, location);
127+
}
128+
fn visit_place(&mut self, place: &Place<'tcx>, ctx: PlaceContext, loc: Location) {
129+
// if this contains *deref* then the *rvalue* could be reading anything.
130+
if place.projection.contains(&ProjectionElem::Deref) {
131+
self.has_deref = true;
132+
}
133+
self.super_place(place, ctx, loc);
134+
}
135+
fn visit_local(&mut self, local: Local, _: PlaceContext, _: Location) {
136+
self.prop.register_dep(self.target_local, local);
137+
}
138+
}
139+
// Check if this rvalue has derefs or moves, and invalidate all moved locals.
140+
let HasDerfOrMoves { has_deref, has_moves, .. } = {
141+
let mut vis = HasDerfOrMoves {
142+
has_deref: false,
143+
has_moves: false,
144+
prop: self,
145+
target_local: lvalue.local,
146+
};
147+
// We don't care about the location here.
148+
let dummy = Location { block: BasicBlock::ZERO, statement_index: 0 };
149+
vis.visit_rvalue(rvalue, dummy);
150+
vis
151+
};
152+
// Reads from memory, so moving it around is not always sound.
153+
if has_deref {
154+
self.write_local(lvalue.local);
155+
return;
156+
}
157+
// Has moves, can't be soundly duplicated / moved around (semantics of moves are undecided, so this may leave unitialized memory behind).
158+
if has_moves {
159+
self.write_local(lvalue.local);
160+
return;
161+
}
162+
// Add to the propagation list, if this rvalue is directly assigned to a local.
163+
if let Some(local) = lvalue.as_local() {
164+
self.locals[local] = Some(rvalue.clone());
165+
} else {
166+
self.write_local(lvalue.local);
167+
}
168+
}
169+
}
170+
impl<'tcx> MutVisitor<'tcx> for PropagateLocals<'tcx> {
171+
fn tcx(&self) -> TyCtxt<'tcx> {
172+
self.tcx
173+
}
174+
/// Visit an operand, propagating rvalues along the way.
175+
fn visit_operand(&mut self, operand: &mut Operand<'tcx>, _: Location) {
176+
use rustc_middle::mir::Rvalue::*;
177+
// Constant - rvalues can't be propagated
178+
let Some(place) = operand.place() else {
179+
return;
180+
};
181+
// No rvalue registered for this local, so we can't propagate anything.
182+
let Some(ref rval) = self.locals[place.local] else {
183+
return;
184+
};
185+
// *For now*, this only handles aggregates and direct uses.
186+
// however, this can be easily extended in the future, to add support for more rvalues and places
187+
// (eg. for removing unneeded transmutes)
188+
match (&place.projection[..], rval) {
189+
([], Use(Operand::Copy(src_place))) => *operand = Operand::Copy(src_place.clone()),
190+
(
191+
[ProjectionElem::Downcast(_, variant), PlaceElem::Field(field_idx, _)],
192+
Aggregate(box AggregateKind::Adt(_, var, _, _, active), fields),
193+
) => {
194+
if variant == var && active.is_none() {
195+
let Some(fplace) = fields[*field_idx].place().clone() else {
196+
return;
197+
};
198+
*operand = Operand::Copy(fplace);
199+
}
200+
}
201+
_ => (),
202+
}
203+
}
204+
fn visit_assign(&mut self, lvalue: &mut Place<'tcx>, rvalue: &mut Rvalue<'tcx>, loc: Location) {
205+
// Propagating rvalues/places for Ref is not sound, since :
206+
// _2 = copy _1
207+
// _3 = &_2
208+
// is not equivalent to:
209+
// _2 = copy _1
210+
// _3 = &_1
211+
if matches!(rvalue, Rvalue::Ref(..) | Rvalue::RawPtr(..)) {
212+
return;
213+
}
214+
self.super_rvalue(rvalue, loc);
215+
self.invalidate_place(lvalue);
216+
self.add_rvalue(rvalue, lvalue);
217+
}
218+
fn visit_statement(&mut self, statement: &mut Statement<'tcx>, loc: Location) {
219+
use rustc_middle::mir::StatementKind::*;
220+
match &mut statement.kind {
221+
Assign(_) | FakeRead(_) | PlaceMention(_) | AscribeUserType(..) => {
222+
self.super_statement(statement, loc)
223+
}
224+
// StorageDead and Deinit invalidates `loc`, cause they may deinitialize that local.
225+
StorageDead(loc) => self.write_local(*loc),
226+
Deinit(place) => self.write_local(place.local),
227+
// SetDiscriminant invalidates `loc`, since it could turn, eg. Ok(u32) to Err(u32).
228+
SetDiscriminant { place, .. } => self.write_local(place.local),
229+
// FIXME: *do retags invalidate the local*? Per docs, this "reads and modifies the place in an opaque way".
230+
// So, I assume this invalidates the local to be sure.
231+
Retag(_, place) => self.write_local(place.local),
232+
// FIXME: should coverage invalidate all locals?
233+
// I conservatively assume I can't propagate *any* locals across a coverage statement,
234+
// because that *could* cause a computation to be ascribed to wrong coverage info.
235+
Coverage(..) => self.locals.iter_mut().for_each(|l| *l = None),
236+
// FIXME: intrinsics *almost certainly* don't read / write to any locals whose address has not been taken,
237+
// but I am still unsure if moving a computation across them is safe. So, I don't do that for now.
238+
Intrinsic(_) => self.locals.iter_mut().for_each(|l| *l = None),
239+
// StorageLive and ConstEvalCounter can't invalidate a local.
240+
StorageLive(_) => (),
241+
// Nop and Nop-like statements.
242+
ConstEvalCounter | Nop | BackwardIncompatibleDropHint { .. } => (),
243+
}
244+
}
245+
}

‎src/tools/cargo

Submodule cargo updated 44 files

0 commit comments

Comments
 (0)
Failed to load comments.