Skip to content

Commit 24f5ef4

Browse files
committed
Fix NRVO pass.
1 parent cdddcd3 commit 24f5ef4

11 files changed

+376
-59
lines changed

compiler/rustc_mir_transform/src/nrvo.rs

+93-54
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

@@ -38,17 +39,17 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
3839
sess.mir_opt_level() > 0 && sess.opts.unstable_opts.unsound_mir_opts
3940
}
4041

41-
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>) {
42+
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
4243
let def_id = body.source.def_id();
44+
if !tcx.consider_optimizing(|| format!("RenameReturnPlace {def_id:?}")) {
45+
return;
46+
}
47+
4348
let Some(returned_local) = local_eligible_for_nrvo(body) else {
4449
debug!("`{:?}` was ineligible for NRVO", def_id);
4550
return;
4651
};
4752

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

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

6465
// 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);
66+
let (renamed_decl, ret_decl) = body.local_decls.pick2_mut(returned_local, RETURN_PLACE);
6767

6868
// Sometimes, the return place is assigned a local of a different but coercible type, for
6969
// example `&mut T` instead of `&T`. Overwriting the `LocalInfo` for the return place means
@@ -84,26 +84,26 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
8484
///
8585
/// If the MIR fulfills both these conditions, this function returns the `Local` that is assigned
8686
/// 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> {
87+
fn local_eligible_for_nrvo(body: &mut Body<'_>) -> Option<Local> {
8888
if IsReturnPlaceRead::run(body) {
8989
return None;
9090
}
9191

9292
let mut copied_to_return_place = None;
9393
for block in body.basic_blocks.indices() {
9494
// Look for blocks with a `Return` terminator.
95-
if !matches!(body[block].terminator().kind, mir::TerminatorKind::Return) {
95+
if !matches!(body[block].terminator().kind, TerminatorKind::Return) {
9696
continue;
9797
}
9898

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

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

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

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

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

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

137171
match body.basic_blocks.predecessors()[block].as_slice() {
@@ -145,10 +179,10 @@ fn find_local_assigned_to_return_place(
145179

146180
// If this statement is an assignment of an unprojected local to the return place,
147181
// 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 {
182+
fn as_local_assigned_to_return_place(stmt: &Statement<'_>) -> Option<Local> {
183+
if let StatementKind::Assign(box (lhs, rhs)) = &stmt.kind {
184+
if lhs.as_local() == Some(RETURN_PLACE) {
185+
if let Rvalue::Use(Operand::Copy(rhs) | Operand::Move(rhs)) = rhs {
152186
return rhs.as_local();
153187
}
154188
}
@@ -168,51 +202,38 @@ impl<'tcx> MutVisitor<'tcx> for RenameToReturnPlace<'tcx> {
168202
self.tcx
169203
}
170204

171-
fn visit_statement(&mut self, stmt: &mut mir::Statement<'tcx>, loc: Location) {
205+
fn visit_statement(&mut self, stmt: &mut Statement<'tcx>, loc: Location) {
172206
// Remove assignments of the local being replaced to the return place, since it is now the
173207
// return place:
174208
// _0 = _1
175209
if as_local_assigned_to_return_place(stmt) == Some(self.to_rename) {
176-
stmt.kind = mir::StatementKind::Nop;
210+
stmt.kind = StatementKind::Nop;
177211
return;
178212
}
179213

180214
// Remove storage annotations for the local being replaced:
181215
// StorageLive(_1)
182-
if let mir::StatementKind::StorageLive(local) | mir::StatementKind::StorageDead(local) =
183-
stmt.kind
184-
{
216+
if let StatementKind::StorageLive(local) | StatementKind::StorageDead(local) = stmt.kind {
185217
if local == self.to_rename {
186-
stmt.kind = mir::StatementKind::Nop;
218+
stmt.kind = StatementKind::Nop;
187219
return;
188220
}
189221
}
190222

191223
self.super_statement(stmt, loc)
192224
}
193225

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;
226+
fn visit_local(&mut self, l: &mut Local, _: PlaceContext, _: Location) {
227+
if *l == self.to_rename {
228+
*l = RETURN_PLACE;
208229
}
209230
}
210231
}
211232

212233
struct IsReturnPlaceRead(bool);
213234

214235
impl IsReturnPlaceRead {
215-
fn run(body: &mir::Body<'_>) -> bool {
236+
fn run(body: &Body<'_>) -> bool {
216237
let mut vis = IsReturnPlaceRead(false);
217238
vis.visit_body(body);
218239
vis.0
@@ -221,17 +242,35 @@ impl IsReturnPlaceRead {
221242

222243
impl<'tcx> Visitor<'tcx> for IsReturnPlaceRead {
223244
fn visit_local(&mut self, l: Local, ctxt: PlaceContext, _: Location) {
224-
if l == mir::RETURN_PLACE && ctxt.is_use() && !ctxt.is_place_assignment() {
245+
if l == RETURN_PLACE && ctxt.is_use() && !ctxt.is_place_assignment() {
225246
self.0 = true;
226247
}
227248
}
228249

229-
fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, loc: Location) {
250+
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, loc: Location) {
230251
// Ignore the implicit "use" of the return place in a `Return` statement.
231-
if let mir::TerminatorKind::Return = terminator.kind {
252+
if let TerminatorKind::Return = terminator.kind {
232253
return;
233254
}
234255

235256
self.super_terminator(terminator, loc);
236257
}
237258
}
259+
260+
struct DiscardWrites<'a> {
261+
assigned_locals: &'a mut BitSet<Local>,
262+
seen_indirect: bool,
263+
}
264+
265+
impl<'tcx> Visitor<'tcx> for DiscardWrites<'_> {
266+
fn visit_place(&mut self, place: &Place<'tcx>, ctxt: PlaceContext, _: Location) {
267+
match ctxt {
268+
PlaceContext::MutatingUse(_)
269+
| PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) => {
270+
self.seen_indirect |= place.is_indirect_first_projection();
271+
self.assigned_locals.insert(place.local);
272+
}
273+
PlaceContext::NonMutatingUse(_) | PlaceContext::NonUse(_) => {}
274+
}
275+
}
276+
}
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+
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+
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+
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+
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+
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)