Skip to content

Commit 69b5139

Browse files
committedNov 28, 2017
rustc_mir: implement an "lvalue reuse" optimization (aka destination propagation aka NRVO).
1 parent 919ed40 commit 69b5139

File tree

7 files changed

+301
-13
lines changed

7 files changed

+301
-13
lines changed
 

Diff for: ‎src/librustc_mir/transform/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pub mod generator;
4444
pub mod inline;
4545
pub mod nll;
4646
pub mod lower_128bit;
47+
pub mod reuse_lvalues;
4748

4849
pub(crate) fn provide(providers: &mut Providers) {
4950
self::qualify_consts::provide(providers);
@@ -254,6 +255,7 @@ fn optimized_mir<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> &'tcx
254255
inline::Inline,
255256
instcombine::InstCombine,
256257
deaggregator::Deaggregator,
258+
reuse_lvalues::ReuseLvalues,
257259
copy_prop::CopyPropagation,
258260
simplify::SimplifyLocals,
259261

Diff for: ‎src/librustc_mir/transform/reuse_lvalues.rs

+288
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! A pass that reuses "final destinations" of values,
12+
//! propagating the lvalue back through a chain of moves.
13+
14+
use rustc::hir;
15+
use rustc::mir::*;
16+
use rustc::mir::visit::{LvalueContext, MutVisitor, Visitor};
17+
use rustc::session::config::FullDebugInfo;
18+
use rustc::ty::TyCtxt;
19+
use transform::{MirPass, MirSource};
20+
21+
use rustc_data_structures::indexed_vec::IndexVec;
22+
23+
pub struct ReuseLvalues;
24+
25+
impl MirPass for ReuseLvalues {
26+
fn run_pass<'a, 'tcx>(&self,
27+
tcx: TyCtxt<'a, 'tcx, 'tcx>,
28+
source: MirSource,
29+
mir: &mut Mir<'tcx>) {
30+
// Don't run on constant MIR, because trans might not be able to
31+
// evaluate the modified MIR.
32+
// FIXME(eddyb) Remove check after miri is merged.
33+
let id = tcx.hir.as_local_node_id(source.def_id).unwrap();
34+
match (tcx.hir.body_owner_kind(id), source.promoted) {
35+
(_, Some(_)) |
36+
(hir::BodyOwnerKind::Const, _) |
37+
(hir::BodyOwnerKind::Static(_), _) => return,
38+
39+
(hir::BodyOwnerKind::Fn, _) => {
40+
if tcx.is_const_fn(source.def_id) {
41+
// Don't run on const functions, as, again, trans might not be able to evaluate
42+
// the optimized IR.
43+
return
44+
}
45+
}
46+
}
47+
48+
// FIXME(eddyb) We should allow multiple user variables
49+
// per local for debuginfo instead of not optimizing.
50+
if tcx.sess.opts.debuginfo == FullDebugInfo {
51+
return;
52+
}
53+
54+
// Collect the def and move information for all locals.
55+
let mut collector = DefMoveCollector {
56+
defs_moves: IndexVec::from_elem((Def::Never, Move::Never), &mir.local_decls),
57+
};
58+
for arg in mir.args_iter() {
59+
// Arguments are special because they're initialized
60+
// in callers, and the collector doesn't know this.
61+
collector.defs_moves[arg].0 = Def::Other;
62+
}
63+
collector.visit_mir(mir);
64+
let mut defs_moves = collector.defs_moves;
65+
66+
// Keep only locals with a clear initialization and move,
67+
// as they are guaranteed to have all accesses in between.
68+
// Also, the destination local of the move has to also have
69+
// a single initialization (the move itself), otherwise
70+
// there could be accesses that overlap the move chain.
71+
for local in mir.local_decls.indices() {
72+
let (def, mov) = defs_moves[local];
73+
if let Move::Into(dest) = mov {
74+
if let Def::Once { .. } = def {
75+
if let (Def::Once { ref mut reused }, _) = defs_moves[dest] {
76+
*reused = true;
77+
continue;
78+
}
79+
}
80+
81+
// Failed to confirm the destination.
82+
defs_moves[local].1 = Move::Other;
83+
}
84+
}
85+
86+
// Apply the appropriate replacements.
87+
let mut replacer = Replacer {
88+
defs_moves
89+
};
90+
replacer.visit_mir(mir);
91+
}
92+
}
93+
94+
/// How (many times) was a local written?
95+
/// Note that borrows are completely ignored,
96+
/// as they get invalidated by moves regardless.
97+
#[derive(Copy, Clone, Debug)]
98+
enum Def {
99+
/// No writes to this local.
100+
Never,
101+
102+
/// Only one direct initialization, from `Assign` or `Call`.
103+
///
104+
/// If `reused` is `true`, this local is a destination
105+
/// in a chain of moves and should have all of its
106+
/// `StorageLive` statements removed.
107+
Once {
108+
reused: bool
109+
},
110+
111+
/// Any other number or kind of mutating accesses.
112+
Other,
113+
}
114+
115+
/// How (many times) was a local moved?
116+
#[derive(Copy, Clone, Debug)]
117+
enum Move {
118+
/// No moves of this local.
119+
Never,
120+
121+
/// Only one move, assigning another local.
122+
///
123+
/// Ends up replaced by its destination and should
124+
/// have all of its `StorageDead` statements removed.
125+
Into(Local),
126+
127+
/// Any other number or kind of moves.
128+
Other,
129+
}
130+
131+
132+
struct DefMoveCollector {
133+
defs_moves: IndexVec<Local, (Def, Move)>,
134+
}
135+
136+
impl<'tcx> Visitor<'tcx> for DefMoveCollector {
137+
fn visit_local(&mut self,
138+
&local: &Local,
139+
context: LvalueContext<'tcx>,
140+
_location: Location) {
141+
let (ref mut def, ref mut mov) = self.defs_moves[local];
142+
match context {
143+
// We specifically want the first direct initialization.
144+
LvalueContext::Store |
145+
LvalueContext::Call => {
146+
if let Def::Never = *def {
147+
*def = Def::Once { reused: false };
148+
} else {
149+
*def = Def::Other;
150+
}
151+
}
152+
153+
// Initialization of a field or similar.
154+
LvalueContext::Projection(Mutability::Mut) => {
155+
*def = Def::Other;
156+
}
157+
158+
// Both of these count as moved, and not the kind
159+
// we want for `Move::Into` (see `visit_assign`).
160+
LvalueContext::Drop |
161+
LvalueContext::Move => *mov = Move::Other,
162+
163+
// We can ignore everything else.
164+
LvalueContext::Inspect |
165+
LvalueContext::Copy |
166+
LvalueContext::Projection(Mutability::Not) |
167+
LvalueContext::Borrow { .. } |
168+
LvalueContext::StorageLive |
169+
LvalueContext::StorageDead |
170+
LvalueContext::Validate => {}
171+
}
172+
}
173+
174+
fn visit_projection(&mut self,
175+
proj: &LvalueProjection<'tcx>,
176+
context: LvalueContext<'tcx>,
177+
location: Location) {
178+
// Pretend derefs copy the underlying pointer, as we don't
179+
// need to treat the base local as being mutated or moved.
180+
let context = if let ProjectionElem::Deref = proj.elem {
181+
LvalueContext::Copy
182+
} else {
183+
match context {
184+
// Pass down the kinds of contexts for which we don't
185+
// need to disambigutate between direct and projected.
186+
LvalueContext::Borrow { .. } |
187+
LvalueContext::Copy |
188+
LvalueContext::Move |
189+
LvalueContext::Drop => context,
190+
191+
_ if context.is_mutating_use() => {
192+
LvalueContext::Projection(Mutability::Mut)
193+
}
194+
_ => {
195+
LvalueContext::Projection(Mutability::Not)
196+
}
197+
}
198+
};
199+
self.visit_lvalue(&proj.base, context, location);
200+
self.visit_projection_elem(&proj.elem, context, location);
201+
}
202+
203+
fn visit_assign(&mut self,
204+
_: BasicBlock,
205+
lvalue: &Lvalue<'tcx>,
206+
rvalue: &Rvalue<'tcx>,
207+
location: Location) {
208+
self.visit_lvalue(lvalue, LvalueContext::Store, location);
209+
210+
// Handle `dest = move src`, and skip the `visit_local`
211+
// for `src`, which would always set it to `Move::Other`.
212+
match (lvalue, rvalue) {
213+
(&Lvalue::Local(dest), &Rvalue::Use(Operand::Move(Lvalue::Local(src)))) => {
214+
let (_, ref mut mov) = self.defs_moves[src];
215+
// We specifically want the first whole move into another local.
216+
if let Move::Never = *mov {
217+
*mov = Move::Into(dest);
218+
} else {
219+
*mov = Move::Other;
220+
}
221+
}
222+
_ => {
223+
self.visit_rvalue(rvalue, location);
224+
}
225+
}
226+
}
227+
}
228+
229+
struct Replacer {
230+
defs_moves: IndexVec<Local, (Def, Move)>,
231+
}
232+
233+
impl<'a, 'tcx> MutVisitor<'tcx> for Replacer {
234+
fn visit_local(&mut self,
235+
local: &mut Local,
236+
context: LvalueContext<'tcx>,
237+
location: Location) {
238+
let original = *local;
239+
if let (_, Move::Into(dest)) = self.defs_moves[original] {
240+
*local = dest;
241+
242+
// Keep going, in case the move chain doesn't stop here.
243+
self.visit_local(local, context, location);
244+
245+
// Cache the final result, in a similar way to union-find.
246+
let final_dest = *local;
247+
if final_dest != dest {
248+
self.defs_moves[original].1 = Move::Into(final_dest);
249+
}
250+
}
251+
}
252+
253+
fn visit_statement(&mut self,
254+
block: BasicBlock,
255+
statement: &mut Statement<'tcx>,
256+
location: Location) {
257+
// Fuse storage liveness ranges of move chains, by removing
258+
// `StorageLive` of destinations and `StorageDead` of sources.
259+
match statement.kind {
260+
StatementKind::StorageLive(local) |
261+
StatementKind::StorageDead(local) => {
262+
// FIXME(eddyb) We also have to remove `StorageLive` of
263+
// sources and `StorageDead` of destinations to avoid
264+
// creating invalid storage liveness (half-)ranges.
265+
// The proper solution might involve recomputing them.
266+
match self.defs_moves[local] {
267+
(Def::Once { reused: true }, _) |
268+
(_, Move::Into(_)) => {
269+
statement.kind = StatementKind::Nop;
270+
}
271+
_ => {}
272+
}
273+
}
274+
_ => {}
275+
}
276+
277+
self.super_statement(block, statement, location);
278+
279+
// Remove self-assignments resulting from replaced move chains.
280+
match statement.kind {
281+
StatementKind::Assign(Lvalue::Local(dest),
282+
Rvalue::Use(Operand::Move(Lvalue::Local(src)))) if dest == src => {
283+
statement.kind = StatementKind::Nop;
284+
}
285+
_ => {}
286+
}
287+
}
288+
}

Diff for: ‎src/test/codegen/align-struct.rs

+3
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@
1515
#![feature(repr_align)]
1616

1717
#[repr(align(64))]
18+
#[derive(Copy, Clone)]
1819
pub struct Align64(i32);
1920

21+
#[derive(Copy, Clone)]
2022
pub struct Nested64 {
2123
a: Align64,
2224
b: i32,
2325
c: i32,
2426
d: i8,
2527
}
2628

29+
#[derive(Copy, Clone)]
2730
pub enum Enum64 {
2831
A(Align64),
2932
B(i32),

Diff for: ‎src/test/codegen/zip.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
// compile-flags: -C no-prepopulate-passes -O
11+
// compile-flags: -O
1212

1313
#![crate_type = "lib"]
1414

Diff for: ‎src/test/mir-opt/copy_propagation.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,17 @@ fn main() {
2222
// START rustc.test.CopyPropagation.before.mir
2323
// bb0: {
2424
// ...
25-
// _3 = _1;
25+
// _2 = _1;
2626
// ...
27-
// _2 = move _3;
28-
// ...
29-
// _4 = _2;
30-
// _0 = move _4;
27+
// _0 = _2;
3128
// ...
3229
// return;
3330
// }
3431
// END rustc.test.CopyPropagation.before.mir
3532
// START rustc.test.CopyPropagation.after.mir
3633
// bb0: {
3734
// ...
38-
// _0 = move _1;
35+
// _0 = _1;
3936
// ...
4037
// return;
4138
// }

Diff for: ‎src/test/mir-opt/inline-closure-borrows-arg.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,9 @@ fn foo<T: Copy>(_t: T, q: &i32) -> i32 {
3838
// ...
3939
// _7 = &(*_2);
4040
// _5 = (move _6, move _7);
41-
// _9 = move (_5.0: &i32);
42-
// _10 = move (_5.1: &i32);
43-
// StorageLive(_8);
44-
// _8 = (*_9);
45-
// _0 = move _8;
41+
// _8 = move (_5.0: &i32);
42+
// _9 = move (_5.1: &i32);
43+
// _0 = (*_8);
4644
// ...
4745
// return;
4846
// }

Diff for: ‎src/test/mir-opt/inline-closure.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fn foo<T: Copy>(_t: T, q: i32) -> i32 {
3636
// _5 = (move _6, move _7);
3737
// _8 = move (_5.0: i32);
3838
// _9 = move (_5.1: i32);
39-
// _0 = move _8;
39+
// _0 = _8;
4040
// ...
4141
// return;
4242
// }

0 commit comments

Comments
 (0)