Skip to content

Commit 7f000ab

Browse files
authored
Auto merge of rust-lang#36639 - pcwalton:const-prop, r=eddyb
librustc_mir: Propagate constants during copy propagation. This optimization kicks in a lot when bootstrapping the compiler. r? @eddyb
2 parents 9b47b0e + 8e4fc56 commit 7f000ab

File tree

4 files changed

+223
-69
lines changed

4 files changed

+223
-69
lines changed

src/librustc_mir/def_use.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -165,22 +165,22 @@ impl<'tcx, F> MutVisitor<'tcx> for MutateUseVisitor<'tcx, F>
165165
/// A small structure that enables various metadata of the MIR to be queried
166166
/// without a reference to the MIR itself.
167167
#[derive(Clone, Copy)]
168-
struct MirSummary {
168+
pub struct MirSummary {
169169
arg_count: usize,
170170
var_count: usize,
171171
temp_count: usize,
172172
}
173173

174174
impl MirSummary {
175-
fn new(mir: &Mir) -> MirSummary {
175+
pub fn new(mir: &Mir) -> MirSummary {
176176
MirSummary {
177177
arg_count: mir.arg_decls.len(),
178178
var_count: mir.var_decls.len(),
179179
temp_count: mir.temp_decls.len(),
180180
}
181181
}
182182

183-
fn local_index<'tcx>(&self, lvalue: &Lvalue<'tcx>) -> Option<Local> {
183+
pub fn local_index<'tcx>(&self, lvalue: &Lvalue<'tcx>) -> Option<Local> {
184184
match *lvalue {
185185
Lvalue::Arg(arg) => Some(Local::new(arg.index())),
186186
Lvalue::Var(var) => Some(Local::new(var.index() + self.arg_count)),

src/librustc_mir/transform/copy_prop.rs

+212-58
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,50 @@
2929
//! (non-mutating) use of `SRC`. These restrictions are conservative and may be relaxed in the
3030
//! future.
3131
32-
use def_use::DefUseAnalysis;
33-
use rustc::mir::repr::{Local, Lvalue, Mir, Operand, Rvalue, StatementKind};
32+
use def_use::{DefUseAnalysis, MirSummary};
33+
use rustc::mir::repr::{Constant, Local, Location, Lvalue, Mir, Operand, Rvalue, StatementKind};
3434
use rustc::mir::transform::{MirPass, MirSource, Pass};
35+
use rustc::mir::visit::MutVisitor;
3536
use rustc::ty::TyCtxt;
3637
use rustc_data_structures::indexed_vec::Idx;
38+
use transform::qualify_consts;
3739

3840
pub struct CopyPropagation;
3941

4042
impl Pass for CopyPropagation {}
4143

4244
impl<'tcx> MirPass<'tcx> for CopyPropagation {
43-
fn run_pass<'a>(&mut self, _: TyCtxt<'a, 'tcx, 'tcx>, _: MirSource, mir: &mut Mir<'tcx>) {
45+
fn run_pass<'a>(&mut self,
46+
tcx: TyCtxt<'a, 'tcx, 'tcx>,
47+
source: MirSource,
48+
mir: &mut Mir<'tcx>) {
49+
match source {
50+
MirSource::Const(_) => {
51+
// Don't run on constants, because constant qualification might reject the
52+
// optimized IR.
53+
return
54+
}
55+
MirSource::Static(..) | MirSource::Promoted(..) => {
56+
// Don't run on statics and promoted statics, because trans might not be able to
57+
// evaluate the optimized IR.
58+
return
59+
}
60+
MirSource::Fn(function_node_id) => {
61+
if qualify_consts::is_const_fn(tcx, tcx.map.local_def_id(function_node_id)) {
62+
// Don't run on const functions, as, again, trans might not be able to evaluate
63+
// the optimized IR.
64+
return
65+
}
66+
}
67+
}
68+
69+
// We only run when the MIR optimization level is at least 1. This avoids messing up debug
70+
// info.
71+
match tcx.sess.opts.debugging_opts.mir_opt_level {
72+
Some(0) | None => return,
73+
_ => {}
74+
}
75+
4476
loop {
4577
let mut def_use_analysis = DefUseAnalysis::new(mir);
4678
def_use_analysis.analyze(mir);
@@ -50,7 +82,7 @@ impl<'tcx> MirPass<'tcx> for CopyPropagation {
5082
let dest_local = Local::new(dest_local_index);
5183
debug!("Considering destination local: {}", mir.format_local(dest_local));
5284

53-
let src_local;
85+
let action;
5486
let location;
5587
{
5688
// The destination must have exactly one def.
@@ -88,66 +120,114 @@ impl<'tcx> MirPass<'tcx> for CopyPropagation {
88120
};
89121

90122
// That use of the source must be an assignment.
91-
let src_lvalue = match statement.kind {
92-
StatementKind::Assign(
93-
ref dest_lvalue,
94-
Rvalue::Use(Operand::Consume(ref src_lvalue)))
95-
if Some(dest_local) == mir.local_index(dest_lvalue) => {
96-
src_lvalue
123+
match statement.kind {
124+
StatementKind::Assign(ref dest_lvalue, Rvalue::Use(ref operand)) if
125+
Some(dest_local) == mir.local_index(dest_lvalue) => {
126+
let maybe_action = match *operand {
127+
Operand::Consume(ref src_lvalue) => {
128+
Action::local_copy(mir, &def_use_analysis, src_lvalue)
129+
}
130+
Operand::Constant(ref src_constant) => {
131+
Action::constant(src_constant)
132+
}
133+
};
134+
match maybe_action {
135+
Some(this_action) => action = this_action,
136+
None => continue,
137+
}
97138
}
98139
_ => {
99140
debug!(" Can't copy-propagate local: source use is not an \
100141
assignment");
101142
continue
102143
}
103-
};
104-
src_local = match mir.local_index(src_lvalue) {
105-
Some(src_local) => src_local,
106-
None => {
107-
debug!(" Can't copy-propagate local: source is not a local");
108-
continue
109-
}
110-
};
111-
112-
// There must be exactly one use of the source used in a statement (not in a
113-
// terminator).
114-
let src_use_info = def_use_analysis.local_info(src_local);
115-
let src_use_count = src_use_info.use_count();
116-
if src_use_count == 0 {
117-
debug!(" Can't copy-propagate local: no uses");
118-
continue
119-
}
120-
if src_use_count != 1 {
121-
debug!(" Can't copy-propagate local: {} uses", src_use_info.use_count());
122-
continue
123-
}
124-
125-
// Verify that the source doesn't change in between. This is done
126-
// conservatively for now, by ensuring that the source has exactly one
127-
// mutation. The goal is to prevent things like:
128-
//
129-
// DEST = SRC;
130-
// SRC = X;
131-
// USE(DEST);
132-
//
133-
// From being misoptimized into:
134-
//
135-
// SRC = X;
136-
// USE(SRC);
137-
let src_def_count = src_use_info.def_count_not_including_drop();
138-
if src_def_count != 1 {
139-
debug!(" Can't copy-propagate local: {} defs of src",
140-
src_use_info.def_count_not_including_drop());
141-
continue
142144
}
143145
}
144146

145-
// If all checks passed, then we can eliminate the destination and the assignment.
147+
changed = action.perform(mir, &def_use_analysis, dest_local, location) || changed;
148+
// FIXME(pcwalton): Update the use-def chains to delete the instructions instead of
149+
// regenerating the chains.
150+
break
151+
}
152+
if !changed {
153+
break
154+
}
155+
}
156+
}
157+
}
158+
159+
enum Action<'tcx> {
160+
PropagateLocalCopy(Local),
161+
PropagateConstant(Constant<'tcx>),
162+
}
163+
164+
impl<'tcx> Action<'tcx> {
165+
fn local_copy(mir: &Mir<'tcx>, def_use_analysis: &DefUseAnalysis, src_lvalue: &Lvalue<'tcx>)
166+
-> Option<Action<'tcx>> {
167+
// The source must be a local.
168+
let src_local = match mir.local_index(src_lvalue) {
169+
Some(src_local) => src_local,
170+
None => {
171+
debug!(" Can't copy-propagate local: source is not a local");
172+
return None
173+
}
174+
};
175+
176+
// We're trying to copy propagate a local.
177+
// There must be exactly one use of the source used in a statement (not in a terminator).
178+
let src_use_info = def_use_analysis.local_info(src_local);
179+
let src_use_count = src_use_info.use_count();
180+
if src_use_count == 0 {
181+
debug!(" Can't copy-propagate local: no uses");
182+
return None
183+
}
184+
if src_use_count != 1 {
185+
debug!(" Can't copy-propagate local: {} uses", src_use_info.use_count());
186+
return None
187+
}
188+
189+
// Verify that the source doesn't change in between. This is done conservatively for now,
190+
// by ensuring that the source has exactly one mutation. The goal is to prevent things
191+
// like:
192+
//
193+
// DEST = SRC;
194+
// SRC = X;
195+
// USE(DEST);
196+
//
197+
// From being misoptimized into:
198+
//
199+
// SRC = X;
200+
// USE(SRC);
201+
let src_def_count = src_use_info.def_count_not_including_drop();
202+
if src_def_count != 1 {
203+
debug!(" Can't copy-propagate local: {} defs of src",
204+
src_use_info.def_count_not_including_drop());
205+
return None
206+
}
207+
208+
Some(Action::PropagateLocalCopy(src_local))
209+
}
210+
211+
fn constant(src_constant: &Constant<'tcx>) -> Option<Action<'tcx>> {
212+
Some(Action::PropagateConstant((*src_constant).clone()))
213+
}
214+
215+
fn perform(self,
216+
mir: &mut Mir<'tcx>,
217+
def_use_analysis: &DefUseAnalysis<'tcx>,
218+
dest_local: Local,
219+
location: Location)
220+
-> bool {
221+
match self {
222+
Action::PropagateLocalCopy(src_local) => {
223+
// Eliminate the destination and the assignment.
146224
//
147225
// First, remove all markers.
148226
//
149227
// FIXME(pcwalton): Don't do this. Merge live ranges instead.
150-
debug!(" Replacing all uses of {}", mir.format_local(dest_local));
228+
debug!(" Replacing all uses of {} with {} (local)",
229+
mir.format_local(dest_local),
230+
mir.format_local(src_local));
151231
for lvalue_use in &def_use_analysis.local_info(dest_local).defs_and_uses {
152232
if lvalue_use.context.is_storage_marker() {
153233
mir.make_statement_nop(lvalue_use.location)
@@ -159,22 +239,96 @@ impl<'tcx> MirPass<'tcx> for CopyPropagation {
159239
}
160240
}
161241

162-
// Now replace all uses of the destination local with the source local.
242+
// Replace all uses of the destination local with the source local.
163243
let src_lvalue = Lvalue::from_local(mir, src_local);
164244
def_use_analysis.replace_all_defs_and_uses_with(dest_local, mir, src_lvalue);
165245

166246
// Finally, zap the now-useless assignment instruction.
247+
debug!(" Deleting assignment");
167248
mir.make_statement_nop(location);
168249

169-
changed = true;
170-
// FIXME(pcwalton): Update the use-def chains to delete the instructions instead of
171-
// regenerating the chains.
172-
break
250+
true
173251
}
174-
if !changed {
175-
break
252+
Action::PropagateConstant(src_constant) => {
253+
// First, remove all markers.
254+
//
255+
// FIXME(pcwalton): Don't do this. Merge live ranges instead.
256+
debug!(" Replacing all uses of {} with {:?} (constant)",
257+
mir.format_local(dest_local),
258+
src_constant);
259+
let dest_local_info = def_use_analysis.local_info(dest_local);
260+
for lvalue_use in &dest_local_info.defs_and_uses {
261+
if lvalue_use.context.is_storage_marker() {
262+
mir.make_statement_nop(lvalue_use.location)
263+
}
264+
}
265+
266+
// Replace all uses of the destination local with the constant.
267+
let mut visitor = ConstantPropagationVisitor::new(MirSummary::new(mir),
268+
dest_local,
269+
src_constant);
270+
for dest_lvalue_use in &dest_local_info.defs_and_uses {
271+
visitor.visit_location(mir, dest_lvalue_use.location)
272+
}
273+
274+
// Zap the assignment instruction if we eliminated all the uses. We won't have been
275+
// able to do that if the destination was used in a projection, because projections
276+
// must have lvalues on their LHS.
277+
let use_count = dest_local_info.use_count();
278+
if visitor.uses_replaced == use_count {
279+
debug!(" {} of {} use(s) replaced; deleting assignment",
280+
visitor.uses_replaced,
281+
use_count);
282+
mir.make_statement_nop(location);
283+
true
284+
} else if visitor.uses_replaced == 0 {
285+
debug!(" No uses replaced; not deleting assignment");
286+
false
287+
} else {
288+
debug!(" {} of {} use(s) replaced; not deleting assignment",
289+
visitor.uses_replaced,
290+
use_count);
291+
true
292+
}
293+
}
294+
}
295+
}
296+
}
297+
298+
struct ConstantPropagationVisitor<'tcx> {
299+
dest_local: Local,
300+
constant: Constant<'tcx>,
301+
mir_summary: MirSummary,
302+
uses_replaced: usize,
303+
}
304+
305+
impl<'tcx> ConstantPropagationVisitor<'tcx> {
306+
fn new(mir_summary: MirSummary, dest_local: Local, constant: Constant<'tcx>)
307+
-> ConstantPropagationVisitor<'tcx> {
308+
ConstantPropagationVisitor {
309+
dest_local: dest_local,
310+
constant: constant,
311+
mir_summary: mir_summary,
312+
uses_replaced: 0,
313+
}
314+
}
315+
}
316+
317+
impl<'tcx> MutVisitor<'tcx> for ConstantPropagationVisitor<'tcx> {
318+
fn visit_operand(&mut self, operand: &mut Operand<'tcx>, location: Location) {
319+
self.super_operand(operand, location);
320+
321+
match *operand {
322+
Operand::Consume(ref lvalue) => {
323+
if self.mir_summary.local_index(lvalue) != Some(self.dest_local) {
324+
return
325+
}
176326
}
327+
Operand::Constant(_) => return,
177328
}
329+
330+
*operand = Operand::Constant(self.constant.clone());
331+
self.uses_replaced += 1
178332
}
179333
}
180334

src/librustc_mir/transform/qualify_consts.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ impl fmt::Display for Mode {
116116
}
117117
}
118118

119-
fn is_const_fn(tcx: TyCtxt, def_id: DefId) -> bool {
119+
pub fn is_const_fn(tcx: TyCtxt, def_id: DefId) -> bool {
120120
if let Some(node_id) = tcx.map.as_local_node_id(def_id) {
121121
let fn_like = FnLikeNode::from_node(tcx.map.get(node_id));
122122
match fn_like.map(|f| f.kind()) {

src/test/mir-opt/storage_ranges.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,25 @@ fn main() {
1919
}
2020

2121
// END RUST SOURCE
22-
// START rustc.node4.PreTrans.after.mir
22+
// START rustc.node4.TypeckMir.before.mir
2323
// bb0: {
24-
// nop; // scope 0 at storage_ranges.rs:14:9: 14:10
24+
// StorageLive(var0); // scope 0 at storage_ranges.rs:14:9: 14:10
2525
// var0 = const 0i32; // scope 0 at storage_ranges.rs:14:13: 14:14
2626
// StorageLive(var1); // scope 1 at storage_ranges.rs:16:13: 16:14
2727
// StorageLive(tmp1); // scope 1 at storage_ranges.rs:16:18: 16:25
28-
// nop; // scope 1 at storage_ranges.rs:16:23: 16:24
29-
// nop; // scope 1 at storage_ranges.rs:16:23: 16:24
30-
// tmp1 = std::option::Option<i32>::Some(var0,); // scope 1 at storage_ranges.rs:16:18: 16:25
28+
// StorageLive(tmp2); // scope 1 at storage_ranges.rs:16:23: 16:24
29+
// tmp2 = var0; // scope 1 at storage_ranges.rs:16:23: 16:24
30+
// tmp1 = std::option::Option<i32>::Some(tmp2,); // scope 1 at storage_ranges.rs:16:18: 16:25
3131
// var1 = &tmp1; // scope 1 at storage_ranges.rs:16:17: 16:25
32-
// nop; // scope 1 at storage_ranges.rs:16:23: 16:24
32+
// StorageDead(tmp2); // scope 1 at storage_ranges.rs:16:23: 16:24
3333
// tmp0 = (); // scope 2 at storage_ranges.rs:15:5: 17:6
3434
// StorageDead(tmp1); // scope 1 at storage_ranges.rs:16:18: 16:25
3535
// StorageDead(var1); // scope 1 at storage_ranges.rs:16:13: 16:14
3636
// StorageLive(var2); // scope 1 at storage_ranges.rs:18:9: 18:10
3737
// var2 = const 1i32; // scope 1 at storage_ranges.rs:18:13: 18:14
3838
// return = (); // scope 3 at storage_ranges.rs:13:11: 19:2
3939
// StorageDead(var2); // scope 1 at storage_ranges.rs:18:9: 18:10
40-
// nop; // scope 0 at storage_ranges.rs:14:9: 14:10
40+
// StorageDead(var0); // scope 0 at storage_ranges.rs:14:9: 14:10
4141
// goto -> bb1; // scope 0 at storage_ranges.rs:13:1: 19:2
4242
// }
4343
//

0 commit comments

Comments
 (0)