Skip to content

Commit 549ef8c

Browse files
committed
Auto merge of rust-lang#116531 - cjgillot:nrvo, r=<try>
Fortify and re-enable RNVO MIR opt Fixes rust-lang#111005 Fixes rust-lang#110902 This PR re-enables NRVO opt that had been disabled in rust-lang#111007 To look for RVO opportunities, we walk the MIR backwards from `return` terminators. In addition to the former implementation, we look at all the traversed statements and terminators for writes. If a local is written-to or moved-from, we discard it from the RVO candidates (`assigned_locals` bitset). If we see an indirect write or move, we discard all borrowed locals from candidates. cc `@JakobDegen`
2 parents 6d27169 + 75d82bd commit 549ef8c

13 files changed

+379
-71
lines changed

compiler/rustc_mir_transform/src/nrvo.rs

Lines changed: 94 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//! See the docs for [`RenameReturnPlace`].
22
33
use rustc_hir::Mutability;
4-
use rustc_index::bit_set::HybridBitSet;
5-
use rustc_middle::mir::visit::{MutVisitor, NonUseContext, PlaceContext, Visitor};
6-
use rustc_middle::mir::{self, BasicBlock, Local, Location};
4+
use rustc_index::bit_set::BitSet;
5+
use rustc_middle::mir::visit::{MutVisitor, NonMutatingUseContext, PlaceContext, Visitor};
6+
use rustc_middle::mir::*;
77
use rustc_middle::ty::TyCtxt;
8+
use rustc_mir_dataflow::impls::borrowed_locals;
89

910
use crate::MirPass;
1011

@@ -34,21 +35,20 @@ pub struct RenameReturnPlace;
3435

3536
impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
3637
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
37-
// #111005
38-
sess.mir_opt_level() > 0 && sess.opts.unstable_opts.unsound_mir_opts
38+
sess.mir_opt_level() > 0
3939
}
4040

41-
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>) {
41+
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
4242
let def_id = body.source.def_id();
43+
if !tcx.consider_optimizing(|| format!("RenameReturnPlace {def_id:?}")) {
44+
return;
45+
}
46+
4347
let Some(returned_local) = local_eligible_for_nrvo(body) else {
4448
debug!("`{:?}` was ineligible for NRVO", def_id);
4549
return;
4650
};
4751

48-
if !tcx.consider_optimizing(|| format!("RenameReturnPlace {def_id:?}")) {
49-
return;
50-
}
51-
5252
debug!(
5353
"`{:?}` was eligible for NRVO, making {:?} the return place",
5454
def_id, returned_local
@@ -58,12 +58,11 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
5858

5959
// Clean up the `NOP`s we inserted for statements made useless by our renaming.
6060
for block_data in body.basic_blocks.as_mut_preserves_cfg() {
61-
block_data.statements.retain(|stmt| stmt.kind != mir::StatementKind::Nop);
61+
block_data.statements.retain(|stmt| stmt.kind != StatementKind::Nop);
6262
}
6363

6464
// Overwrite the debuginfo of `_0` with that of the renamed local.
65-
let (renamed_decl, ret_decl) =
66-
body.local_decls.pick2_mut(returned_local, mir::RETURN_PLACE);
65+
let (renamed_decl, ret_decl) = body.local_decls.pick2_mut(returned_local, RETURN_PLACE);
6766

6867
// Sometimes, the return place is assigned a local of a different but coercible type, for
6968
// example `&mut T` instead of `&T`. Overwriting the `LocalInfo` for the return place means
@@ -84,26 +83,26 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
8483
///
8584
/// If the MIR fulfills both these conditions, this function returns the `Local` that is assigned
8685
/// to the return place along all possible paths through the control-flow graph.
87-
fn local_eligible_for_nrvo(body: &mut mir::Body<'_>) -> Option<Local> {
86+
fn local_eligible_for_nrvo(body: &mut Body<'_>) -> Option<Local> {
8887
if IsReturnPlaceRead::run(body) {
8988
return None;
9089
}
9190

9291
let mut copied_to_return_place = None;
9392
for block in body.basic_blocks.indices() {
9493
// Look for blocks with a `Return` terminator.
95-
if !matches!(body[block].terminator().kind, mir::TerminatorKind::Return) {
94+
if !matches!(body[block].terminator().kind, TerminatorKind::Return) {
9695
continue;
9796
}
9897

9998
// Look for an assignment of a single local to the return place prior to the `Return`.
10099
let returned_local = find_local_assigned_to_return_place(block, body)?;
101100
match body.local_kind(returned_local) {
102101
// FIXME: Can we do this for arguments as well?
103-
mir::LocalKind::Arg => return None,
102+
LocalKind::Arg => return None,
104103

105-
mir::LocalKind::ReturnPointer => bug!("Return place was assigned to itself?"),
106-
mir::LocalKind::Temp => {}
104+
LocalKind::ReturnPointer => bug!("Return place was assigned to itself?"),
105+
LocalKind::Temp => {}
107106
}
108107

109108
// If multiple different locals are copied to the return place. We can't pick a
@@ -118,20 +117,54 @@ fn local_eligible_for_nrvo(body: &mut mir::Body<'_>) -> Option<Local> {
118117
copied_to_return_place
119118
}
120119

121-
fn find_local_assigned_to_return_place(
122-
start: BasicBlock,
123-
body: &mut mir::Body<'_>,
124-
) -> Option<Local> {
125-
let mut block = start;
126-
let mut seen = HybridBitSet::new_empty(body.basic_blocks.len());
120+
#[instrument(level = "trace", skip(body), ret)]
121+
fn find_local_assigned_to_return_place(start: BasicBlock, body: &mut Body<'_>) -> Option<Local> {
122+
// The locals that are assigned-to between `return` and `_0 = _rvo_local`.
123+
let mut assigned_locals = BitSet::new_empty(body.local_decls.len());
124+
// Whether we have seen an indirect write.
125+
let mut seen_indirect = false;
126+
127+
let mut discard_borrowed_locals = |assigned_locals: &mut BitSet<Local>| {
128+
// We have an indirect assignment to a local between the assignment to `_0 = _rvo`
129+
// and `return`. This means we may be modifying the RVO local after the assignment.
130+
// Discard all borrowed locals to be safe.
131+
if !seen_indirect {
132+
assigned_locals.union(&borrowed_locals(body));
133+
// Mark that we have seen an indirect write to avoid recomputing `borrowed_locals`.
134+
seen_indirect = true;
135+
}
136+
};
127137

128138
// Iterate as long as `block` has exactly one predecessor that we have not yet visited.
129-
while seen.insert(block) {
139+
let mut block = start;
140+
let mut seen_blocks = BitSet::new_empty(body.basic_blocks.len());
141+
while seen_blocks.insert(block) {
130142
trace!("Looking for assignments to `_0` in {:?}", block);
143+
let bbdata = &body.basic_blocks[block];
144+
145+
let mut vis = DiscardWrites { assigned_locals: &mut assigned_locals, seen_indirect: false };
146+
vis.visit_terminator(&bbdata.terminator(), body.terminator_loc(block));
147+
if vis.seen_indirect {
148+
discard_borrowed_locals(&mut assigned_locals);
149+
}
150+
151+
for (statement_index, stmt) in bbdata.statements.iter().enumerate().rev() {
152+
if let StatementKind::Assign(box (lhs, ref rhs)) = stmt.kind
153+
&& lhs.as_local() == Some(RETURN_PLACE)
154+
&& let Rvalue::Use(rhs) = rhs
155+
&& let Some(rhs) = rhs.place()
156+
&& let Some(rhs) = rhs.as_local()
157+
&& !assigned_locals.contains(rhs)
158+
{
159+
return Some(rhs);
160+
}
131161

132-
let local = body[block].statements.iter().rev().find_map(as_local_assigned_to_return_place);
133-
if local.is_some() {
134-
return local;
162+
let mut vis =
163+
DiscardWrites { assigned_locals: &mut assigned_locals, seen_indirect: false };
164+
vis.visit_statement(stmt, Location { block, statement_index });
165+
if vis.seen_indirect {
166+
discard_borrowed_locals(&mut assigned_locals);
167+
}
135168
}
136169

137170
match body.basic_blocks.predecessors()[block].as_slice() {
@@ -145,10 +178,10 @@ fn find_local_assigned_to_return_place(
145178

146179
// If this statement is an assignment of an unprojected local to the return place,
147180
// return that local.
148-
fn as_local_assigned_to_return_place(stmt: &mir::Statement<'_>) -> Option<Local> {
149-
if let mir::StatementKind::Assign(box (lhs, rhs)) = &stmt.kind {
150-
if lhs.as_local() == Some(mir::RETURN_PLACE) {
151-
if let mir::Rvalue::Use(mir::Operand::Copy(rhs) | mir::Operand::Move(rhs)) = rhs {
181+
fn as_local_assigned_to_return_place(stmt: &Statement<'_>) -> Option<Local> {
182+
if let StatementKind::Assign(box (lhs, rhs)) = &stmt.kind {
183+
if lhs.as_local() == Some(RETURN_PLACE) {
184+
if let Rvalue::Use(Operand::Copy(rhs) | Operand::Move(rhs)) = rhs {
152185
return rhs.as_local();
153186
}
154187
}
@@ -168,51 +201,38 @@ impl<'tcx> MutVisitor<'tcx> for RenameToReturnPlace<'tcx> {
168201
self.tcx
169202
}
170203

171-
fn visit_statement(&mut self, stmt: &mut mir::Statement<'tcx>, loc: Location) {
204+
fn visit_statement(&mut self, stmt: &mut Statement<'tcx>, loc: Location) {
172205
// Remove assignments of the local being replaced to the return place, since it is now the
173206
// return place:
174207
// _0 = _1
175208
if as_local_assigned_to_return_place(stmt) == Some(self.to_rename) {
176-
stmt.kind = mir::StatementKind::Nop;
209+
stmt.kind = StatementKind::Nop;
177210
return;
178211
}
179212

180213
// Remove storage annotations for the local being replaced:
181214
// StorageLive(_1)
182-
if let mir::StatementKind::StorageLive(local) | mir::StatementKind::StorageDead(local) =
183-
stmt.kind
184-
{
215+
if let StatementKind::StorageLive(local) | StatementKind::StorageDead(local) = stmt.kind {
185216
if local == self.to_rename {
186-
stmt.kind = mir::StatementKind::Nop;
217+
stmt.kind = StatementKind::Nop;
187218
return;
188219
}
189220
}
190221

191222
self.super_statement(stmt, loc)
192223
}
193224

194-
fn visit_terminator(&mut self, terminator: &mut mir::Terminator<'tcx>, loc: Location) {
195-
// Ignore the implicit "use" of the return place in a `Return` statement.
196-
if let mir::TerminatorKind::Return = terminator.kind {
197-
return;
198-
}
199-
200-
self.super_terminator(terminator, loc);
201-
}
202-
203-
fn visit_local(&mut self, l: &mut Local, ctxt: PlaceContext, _: Location) {
204-
if *l == mir::RETURN_PLACE {
205-
assert_eq!(ctxt, PlaceContext::NonUse(NonUseContext::VarDebugInfo));
206-
} else if *l == self.to_rename {
207-
*l = mir::RETURN_PLACE;
225+
fn visit_local(&mut self, l: &mut Local, _: PlaceContext, _: Location) {
226+
if *l == self.to_rename {
227+
*l = RETURN_PLACE;
208228
}
209229
}
210230
}
211231

212232
struct IsReturnPlaceRead(bool);
213233

214234
impl IsReturnPlaceRead {
215-
fn run(body: &mir::Body<'_>) -> bool {
235+
fn run(body: &Body<'_>) -> bool {
216236
let mut vis = IsReturnPlaceRead(false);
217237
vis.visit_body(body);
218238
vis.0
@@ -221,17 +241,35 @@ impl IsReturnPlaceRead {
221241

222242
impl<'tcx> Visitor<'tcx> for IsReturnPlaceRead {
223243
fn visit_local(&mut self, l: Local, ctxt: PlaceContext, _: Location) {
224-
if l == mir::RETURN_PLACE && ctxt.is_use() && !ctxt.is_place_assignment() {
244+
if l == RETURN_PLACE && ctxt.is_use() && !ctxt.is_place_assignment() {
225245
self.0 = true;
226246
}
227247
}
228248

229-
fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, loc: Location) {
249+
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, loc: Location) {
230250
// Ignore the implicit "use" of the return place in a `Return` statement.
231-
if let mir::TerminatorKind::Return = terminator.kind {
251+
if let TerminatorKind::Return = terminator.kind {
232252
return;
233253
}
234254

235255
self.super_terminator(terminator, loc);
236256
}
237257
}
258+
259+
struct DiscardWrites<'a> {
260+
assigned_locals: &'a mut BitSet<Local>,
261+
seen_indirect: bool,
262+
}
263+
264+
impl<'tcx> Visitor<'tcx> for DiscardWrites<'_> {
265+
fn visit_place(&mut self, place: &Place<'tcx>, ctxt: PlaceContext, _: Location) {
266+
match ctxt {
267+
PlaceContext::MutatingUse(_)
268+
| PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) => {
269+
self.seen_indirect |= place.is_indirect_first_projection();
270+
self.assigned_locals.insert(place.local);
271+
}
272+
PlaceContext::NonMutatingUse(_) | PlaceContext::NonUse(_) => {}
273+
}
274+
}
275+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
- // MIR for `call` before RenameReturnPlace
2+
+ // MIR for `call` after RenameReturnPlace
3+
4+
fn call(_1: char) -> char {
5+
let mut _0: char;
6+
let mut _2: char;
7+
8+
bb0: {
9+
_0 = wrong(_1) -> [return: bb1, unwind continue];
10+
}
11+
12+
bb1: {
13+
_2 = _1;
14+
_0 = _2;
15+
_2 = wrong(_1) -> [return: bb2, unwind continue];
16+
}
17+
18+
bb2: {
19+
return;
20+
}
21+
}
22+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
- // MIR for `call_ok` before RenameReturnPlace
2+
+ // MIR for `call_ok` after RenameReturnPlace
3+
4+
fn call_ok(_1: char) -> char {
5+
let mut _0: char;
6+
let mut _2: char;
7+
8+
bb0: {
9+
_0 = wrong(_1) -> [return: bb1, unwind continue];
10+
}
11+
12+
bb1: {
13+
- _2 = wrong(_1) -> [return: bb2, unwind continue];
14+
+ _0 = wrong(_1) -> [return: bb2, unwind continue];
15+
}
16+
17+
bb2: {
18+
- _0 = _2;
19+
return;
20+
}
21+
}
22+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
- // MIR for `indirect` before RenameReturnPlace
2+
+ // MIR for `indirect` after RenameReturnPlace
3+
4+
fn indirect(_1: char) -> char {
5+
let mut _0: char;
6+
let mut _2: char;
7+
let mut _3: &mut char;
8+
9+
bb0: {
10+
_2 = _1;
11+
_3 = &mut _2;
12+
_0 = _2;
13+
(*_3) = const 'b';
14+
return;
15+
}
16+
}
17+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
- // MIR for `moved` before RenameReturnPlace
2+
+ // MIR for `moved` after RenameReturnPlace
3+
4+
fn moved(_1: char) -> char {
5+
let mut _0: char;
6+
let mut _2: char;
7+
let mut _3: char;
8+
9+
bb0: {
10+
_2 = _1;
11+
_0 = _2;
12+
_3 = move _2;
13+
return;
14+
}
15+
}
16+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
- // MIR for `multiple` before RenameReturnPlace
2+
+ // MIR for `multiple` after RenameReturnPlace
3+
4+
fn multiple(_1: char) -> char {
5+
let mut _0: char;
6+
let mut _2: char;
7+
let mut _3: char;
8+
9+
bb0: {
10+
_2 = _1;
11+
- _3 = _1;
12+
- _0 = _3;
13+
+ _0 = _1;
14+
_0 = _2;
15+
_2 = const 'b';
16+
return;
17+
}
18+
}
19+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
- // MIR for `multiple_return` before RenameReturnPlace
2+
+ // MIR for `multiple_return` after RenameReturnPlace
3+
4+
fn multiple_return(_1: char, _2: bool) -> char {
5+
let mut _0: char;
6+
let mut _3: char;
7+
let mut _4: char;
8+
9+
bb0: {
10+
switchInt(_2) -> [0: bb1, otherwise: bb2];
11+
}
12+
13+
bb1: {
14+
_3 = _1;
15+
_0 = _3;
16+
return;
17+
}
18+
19+
bb2: {
20+
_4 = const 'z';
21+
_0 = _4;
22+
return;
23+
}
24+
}
25+

0 commit comments

Comments
 (0)