Skip to content

Commit d627cf0

Browse files
committed
Auto merge of rust-lang#113915 - cjgillot:ssa-call, r=tmiasko
Also consider call and yield as MIR SSA. The SSA analysis on MIR only considered `Assign` statements as defining a SSA local. This PR adds assignments as part of a `Call` or `Yield` terminator in that category. This mainly allows to perform CopyProp on a call return place. The only subtlety is in the dominance property: the assignment is only complete at the beginning of the target block.
2 parents c30b28b + 37f080e commit d627cf0

13 files changed

+224
-124
lines changed

compiler/rustc_mir_transform/src/gvn.rs

+22-15
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ use rustc_middle::mir::*;
6363
use rustc_middle::ty::{self, Ty, TyCtxt};
6464
use rustc_target::abi::{VariantIdx, FIRST_VARIANT};
6565

66-
use crate::ssa::SsaLocals;
66+
use crate::ssa::{AssignedValue, SsaLocals};
6767
use crate::MirPass;
6868

6969
pub struct GVN;
@@ -87,21 +87,28 @@ fn propagate_ssa<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
8787
let dominators = body.basic_blocks.dominators().clone();
8888

8989
let mut state = VnState::new(tcx, param_env, &ssa, &dominators, &body.local_decls);
90-
for arg in body.args_iter() {
91-
if ssa.is_ssa(arg) {
92-
let value = state.new_opaque().unwrap();
93-
state.assign(arg, value);
94-
}
95-
}
96-
97-
ssa.for_each_assignment_mut(&mut body.basic_blocks, |local, rvalue, location| {
98-
let value = state.simplify_rvalue(rvalue, location).or_else(|| state.new_opaque()).unwrap();
99-
// FIXME(#112651) `rvalue` may have a subtype to `local`. We can only mark `local` as
100-
// reusable if we have an exact type match.
101-
if state.local_decls[local].ty == rvalue.ty(state.local_decls, tcx) {
90+
ssa.for_each_assignment_mut(
91+
body.basic_blocks.as_mut_preserves_cfg(),
92+
|local, value, location| {
93+
let value = match value {
94+
// We do not know anything of this assigned value.
95+
AssignedValue::Arg | AssignedValue::Terminator(_) => None,
96+
// Try to get some insight.
97+
AssignedValue::Rvalue(rvalue) => {
98+
let value = state.simplify_rvalue(rvalue, location);
99+
// FIXME(#112651) `rvalue` may have a subtype to `local`. We can only mark `local` as
100+
// reusable if we have an exact type match.
101+
if state.local_decls[local].ty != rvalue.ty(state.local_decls, tcx) {
102+
return;
103+
}
104+
value
105+
}
106+
};
107+
// `next_opaque` is `Some`, so `new_opaque` must return `Some`.
108+
let value = value.or_else(|| state.new_opaque()).unwrap();
102109
state.assign(local, value);
103-
}
104-
});
110+
},
111+
);
105112

106113
// Stop creating opaques during replacement as it is useless.
107114
state.next_opaque = None;

compiler/rustc_mir_transform/src/ssa.rs

+49-28
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
//! As a consequence of rule 2, we consider that borrowed locals are not SSA, even if they are
66
//! `Freeze`, as we do not track that the assignment dominates all uses of the borrow.
77
8-
use either::Either;
98
use rustc_data_structures::graph::dominators::Dominators;
109
use rustc_index::bit_set::BitSet;
1110
use rustc_index::{IndexSlice, IndexVec};
@@ -27,6 +26,12 @@ pub struct SsaLocals {
2726
direct_uses: IndexVec<Local, u32>,
2827
}
2928

29+
pub enum AssignedValue<'a, 'tcx> {
30+
Arg,
31+
Rvalue(&'a mut Rvalue<'tcx>),
32+
Terminator(&'a mut TerminatorKind<'tcx>),
33+
}
34+
3035
impl SsaLocals {
3136
pub fn new<'tcx>(body: &Body<'tcx>) -> SsaLocals {
3237
let assignment_order = Vec::with_capacity(body.local_decls.len());
@@ -39,6 +44,7 @@ impl SsaLocals {
3944

4045
for local in body.args_iter() {
4146
visitor.assignments[local] = Set1::One(DefLocation::Argument);
47+
visitor.assignment_order.push(local);
4248
}
4349

4450
// For SSA assignments, a RPO visit will see the assignment before it sees any use.
@@ -105,8 +111,8 @@ impl SsaLocals {
105111
) -> impl Iterator<Item = (Local, &'a Rvalue<'tcx>, Location)> + 'a {
106112
self.assignment_order.iter().filter_map(|&local| {
107113
if let Set1::One(DefLocation::Body(loc)) = self.assignments[local] {
114+
let stmt = body.stmt_at(loc).left()?;
108115
// `loc` must point to a direct assignment to `local`.
109-
let Either::Left(stmt) = body.stmt_at(loc) else { bug!() };
110116
let Some((target, rvalue)) = stmt.kind.as_assign() else { bug!() };
111117
assert_eq!(target.as_local(), Some(local));
112118
Some((local, rvalue, loc))
@@ -118,18 +124,33 @@ impl SsaLocals {
118124

119125
pub fn for_each_assignment_mut<'tcx>(
120126
&self,
121-
basic_blocks: &mut BasicBlocks<'tcx>,
122-
mut f: impl FnMut(Local, &mut Rvalue<'tcx>, Location),
127+
basic_blocks: &mut IndexSlice<BasicBlock, BasicBlockData<'tcx>>,
128+
mut f: impl FnMut(Local, AssignedValue<'_, 'tcx>, Location),
123129
) {
124130
for &local in &self.assignment_order {
125-
if let Set1::One(DefLocation::Body(loc)) = self.assignments[local] {
126-
// `loc` must point to a direct assignment to `local`.
127-
let bbs = basic_blocks.as_mut_preserves_cfg();
128-
let bb = &mut bbs[loc.block];
129-
let stmt = &mut bb.statements[loc.statement_index];
130-
let StatementKind::Assign(box (target, ref mut rvalue)) = stmt.kind else { bug!() };
131-
assert_eq!(target.as_local(), Some(local));
132-
f(local, rvalue, loc)
131+
match self.assignments[local] {
132+
Set1::One(DefLocation::Argument) => f(
133+
local,
134+
AssignedValue::Arg,
135+
Location { block: START_BLOCK, statement_index: 0 },
136+
),
137+
Set1::One(DefLocation::Body(loc)) => {
138+
let bb = &mut basic_blocks[loc.block];
139+
let value = if loc.statement_index < bb.statements.len() {
140+
// `loc` must point to a direct assignment to `local`.
141+
let stmt = &mut bb.statements[loc.statement_index];
142+
let StatementKind::Assign(box (target, ref mut rvalue)) = stmt.kind else {
143+
bug!()
144+
};
145+
assert_eq!(target.as_local(), Some(local));
146+
AssignedValue::Rvalue(rvalue)
147+
} else {
148+
let term = bb.terminator_mut();
149+
AssignedValue::Terminator(&mut term.kind)
150+
};
151+
f(local, value, loc)
152+
}
153+
_ => {}
133154
}
134155
}
135156
}
@@ -228,34 +249,34 @@ impl<'tcx> Visitor<'tcx> for SsaVisitor<'_> {
228249
}
229250

230251
fn visit_place(&mut self, place: &Place<'tcx>, ctxt: PlaceContext, loc: Location) {
231-
if place.projection.first() == Some(&PlaceElem::Deref) {
232-
// Do not do anything for storage statements and debuginfo.
252+
let location = match ctxt {
253+
PlaceContext::MutatingUse(
254+
MutatingUseContext::Store | MutatingUseContext::Call | MutatingUseContext::Yield,
255+
) => Some(DefLocation::Body(loc)),
256+
_ => None,
257+
};
258+
if let Some(location) = location
259+
&& let Some(local) = place.as_local()
260+
{
261+
self.assignments[local].insert(location);
262+
if let Set1::One(_) = self.assignments[local] {
263+
// Only record if SSA-like, to avoid growing the vector needlessly.
264+
self.assignment_order.push(local);
265+
}
266+
} else if place.projection.first() == Some(&PlaceElem::Deref) {
267+
// Do not do anything for debuginfo.
233268
if ctxt.is_use() {
234269
// Only change the context if it is a real use, not a "use" in debuginfo.
235270
let new_ctxt = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy);
236271

237272
self.visit_projection(place.as_ref(), new_ctxt, loc);
238273
self.check_dominates(place.local, loc);
239274
}
240-
return;
241275
} else {
242276
self.visit_projection(place.as_ref(), ctxt, loc);
243277
self.visit_local(place.local, ctxt, loc);
244278
}
245279
}
246-
247-
fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, loc: Location) {
248-
if let Some(local) = place.as_local() {
249-
self.assignments[local].insert(DefLocation::Body(loc));
250-
if let Set1::One(_) = self.assignments[local] {
251-
// Only record if SSA-like, to avoid growing the vector needlessly.
252-
self.assignment_order.push(local);
253-
}
254-
} else {
255-
self.visit_place(place, PlaceContext::MutatingUse(MutatingUseContext::Store), loc);
256-
}
257-
self.visit_rvalue(rvalue, loc);
258-
}
259280
}
260281

261282
#[instrument(level = "trace", skip(ssa, body))]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
- // MIR for `multiple_edges` before CopyProp
2+
+ // MIR for `multiple_edges` after CopyProp
3+
4+
fn multiple_edges(_1: bool) -> u8 {
5+
let mut _0: u8;
6+
let mut _2: u8;
7+
8+
bb0: {
9+
switchInt(_1) -> [1: bb1, otherwise: bb2];
10+
}
11+
12+
bb1: {
13+
_2 = dummy(const 13_u8) -> [return: bb2, unwind continue];
14+
}
15+
16+
bb2: {
17+
_0 = _2;
18+
return;
19+
}
20+
}
21+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
- // MIR for `nrvo` before CopyProp
2+
+ // MIR for `nrvo` after CopyProp
3+
4+
fn nrvo() -> u8 {
5+
let mut _0: u8;
6+
let _1: u8;
7+
scope 1 {
8+
- debug y => _1;
9+
+ debug y => _0;
10+
}
11+
12+
bb0: {
13+
- StorageLive(_1);
14+
- _1 = dummy(const 5_u8) -> [return: bb1, unwind continue];
15+
+ _0 = dummy(const 5_u8) -> [return: bb1, unwind continue];
16+
}
17+
18+
bb1: {
19+
- _0 = _1;
20+
- StorageDead(_1);
21+
return;
22+
}
23+
}
24+

tests/mir-opt/copy-prop/calls.rs

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Check that CopyProp does propagate return values of call terminators.
2+
// unit-test: CopyProp
3+
// needs-unwind
4+
5+
#![feature(custom_mir, core_intrinsics)]
6+
use std::intrinsics::mir::*;
7+
8+
#[inline(never)]
9+
fn dummy(x: u8) -> u8 {
10+
x
11+
}
12+
13+
// EMIT_MIR calls.nrvo.CopyProp.diff
14+
fn nrvo() -> u8 {
15+
let y = dummy(5); // this should get NRVO
16+
y
17+
}
18+
19+
// EMIT_MIR calls.multiple_edges.CopyProp.diff
20+
#[custom_mir(dialect = "runtime", phase = "initial")]
21+
fn multiple_edges(t: bool) -> u8 {
22+
mir! {
23+
let x: u8;
24+
{
25+
match t { true => bbt, _ => ret }
26+
}
27+
bbt = {
28+
Call(x = dummy(13), ret)
29+
}
30+
ret = {
31+
// `x` is not assigned on the `bb0 -> ret` edge,
32+
// so should not be marked as SSA for merging with `_0`.
33+
RET = x;
34+
Return()
35+
}
36+
}
37+
}
38+
39+
fn main() {
40+
// Make sure the function actually gets instantiated.
41+
nrvo();
42+
multiple_edges(false);
43+
}

tests/mir-opt/inline/inline_diverging.h.Inline.panic-abort.diff

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
+ scope 1 (inlined call_twice::<!, fn() -> ! {sleep}>) {
99
+ debug f => _2;
1010
+ let mut _3: &fn() -> ! {sleep};
11-
+ let mut _4: !;
11+
+ let _4: !;
1212
+ let mut _5: &fn() -> ! {sleep};
13-
+ let mut _6: !;
1413
+ scope 2 {
1514
+ debug a => _4;
15+
+ let _6: !;
1616
+ scope 3 {
1717
+ debug b => _6;
1818
+ }

tests/mir-opt/inline/inline_diverging.h.Inline.panic-unwind.diff

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
+ let mut _3: &fn() -> ! {sleep};
1111
+ let _4: !;
1212
+ let mut _5: &fn() -> ! {sleep};
13-
+ let mut _6: !;
1413
+ let mut _7: !;
1514
+ scope 2 {
1615
+ debug a => _4;
16+
+ let _6: !;
1717
+ scope 3 {
1818
+ debug b => _6;
1919
+ }

0 commit comments

Comments
 (0)