Skip to content

Commit f78274c

Browse files
committed
Clean-up the ConstLattice; Propagate global state
Previous version of the lattice was a mess from a number of experimets I’ve previously done with copy/move propagations and reviewers were sure to notice the mess. This cleans up the mess by ignoring anything related to non-constants. In addition, this commit adds propagation of constants through global mutable statics.
1 parent b15bb6d commit f78274c

File tree

5 files changed

+116
-117
lines changed

5 files changed

+116
-117
lines changed

src/librustc_driver/driver.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -992,7 +992,7 @@ pub fn phase_4_translate_to_llvm<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
992992
passes.push_pass(box mir::transform::erase_regions::EraseRegions);
993993

994994
passes.push_pass(box mir::transform::deaggregator::Deaggregator);
995-
passes.push_pass(box mir::transform::cs_propagate::CsPropagate);
995+
passes.push_pass(box mir::transform::const_propagate::ConstPropagate);
996996
passes.push_pass(box mir::transform::simplify_cfg::SimplifyCfg::new("ccs-propagate"));
997997
passes.push_pass(box mir::transform::deadcode::DeadCode);
998998

src/librustc_mir/transform/const_propagate.rs

Lines changed: 109 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -8,80 +8,72 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
//! This is Constant-Simplify propagation pass. This is a composition of three distinct
12-
//! dataflow passes: alias-propagation, constant-propagation and terminator simplification.
11+
//! This is Constant-Simplify propagation pass. This is a composition of two distinct
12+
//! passes: constant-propagation and terminator simplification.
1313
//!
14-
//! All these are very similar in their nature:
14+
//! Note that having these passes interleaved results in a strictly more potent optimisation pass
15+
//! which is able to optimise CFG in a way which two passes couldn’t do separately even if they
16+
//! were run indefinitely one after another.
17+
//!
18+
//! Both these passes are very similar in their nature:
1519
//!
1620
//! | Constant | Simplify |
1721
//! |----------------|-----------|-----------|
1822
//! | Lattice Domain | Lvalue | Lvalue |
1923
//! | Lattice Value | Constant | Constant |
2024
//! | Transfer | x = const | x = const |
2125
//! | Rewrite | x → const | T(x) → T' |
22-
//! | Bottom | {} | {} |
26+
//! | Top | {} | {} |
2327
//! | Join | intersect | intersect |
28+
//! |----------------|-----------|-----------|
2429
//!
25-
//! For all of them we will be using a lattice of `HashMap<Lvalue, Either<Lvalue, Constant, Top>>`.
30+
//! It might be a good idea to eventually interleave move propagation here, as it has very
31+
//! similar lattice to the constant propagation pass.
2632
2733
use rustc_data_structures::fnv::FnvHashMap;
2834
use rustc::middle::const_val::ConstVal;
35+
use rustc::hir::def_id::DefId;
2936
use rustc::mir::repr::*;
3037
use rustc::mir::tcx::binop_ty;
3138
use rustc::mir::transform::{Pass, MirPass, MirSource};
3239
use rustc::mir::visit::{MutVisitor};
3340
use rustc::ty::TyCtxt;
3441
use rustc_const_eval::{eval_const_binop, eval_const_unop, cast_const};
35-
use std::collections::hash_map::Entry;
3642

3743
use super::dataflow::*;
3844

39-
pub struct CsPropagate;
45+
pub struct ConstPropagate;
4046

41-
impl Pass for CsPropagate {}
47+
impl Pass for ConstPropagate {}
4248

43-
impl<'tcx> MirPass<'tcx> for CsPropagate {
49+
impl<'tcx> MirPass<'tcx> for ConstPropagate {
4450
fn run_pass<'a>(&mut self, tcx: TyCtxt<'a, 'tcx, 'tcx>, _: MirSource, mir: &mut Mir<'tcx>) {
45-
*mir = Dataflow::forward(mir, CsTransfer { tcx: tcx },
51+
*mir = Dataflow::forward(mir,
52+
ConstTransfer { tcx: tcx },
4653
ConstRewrite { tcx: tcx }.and_then(SimplifyRewrite));
4754
}
4855
}
4956

50-
#[derive(PartialEq, Debug, Clone)]
51-
enum Either<'tcx> {
52-
Lvalue(Lvalue<'tcx>),
53-
Const(Constant<'tcx>),
54-
Top
57+
#[derive(Debug, Clone)]
58+
struct ConstLattice<'tcx> {
59+
statics: FnvHashMap<DefId, Constant<'tcx>>,
60+
// key must not be a static or a projection.
61+
locals: FnvHashMap<Lvalue<'tcx>, Constant<'tcx>>,
5562
}
5663

57-
impl<'tcx> Lattice for Either<'tcx> {
58-
fn bottom() -> Self { unimplemented!() }
59-
fn join(&mut self, other: Self) -> bool {
60-
if !other.eq(self) {
61-
if Either::Top.eq(self) {
62-
false
63-
} else {
64-
*self = Either::Top;
65-
true
66-
}
67-
} else {
68-
false
64+
impl<'tcx> ConstLattice<'tcx> {
65+
fn new() -> ConstLattice<'tcx> {
66+
ConstLattice {
67+
statics: FnvHashMap(),
68+
locals: FnvHashMap()
6969
}
7070
}
71-
}
72-
73-
#[derive(Debug, Clone)]
74-
struct CsLattice<'tcx> {
75-
values: FnvHashMap<Lvalue<'tcx>, Either<'tcx>>
76-
}
7771

78-
impl<'tcx> CsLattice<'tcx> {
79-
fn insert(&mut self, key: &Lvalue<'tcx>, val: Either<'tcx>) {
72+
fn insert(&mut self, key: &Lvalue<'tcx>, val: Constant<'tcx>) {
8073
// FIXME: HashMap has no way to insert stuff without cloning the key even if it exists
8174
// already.
8275
match *key {
83-
// Do not bother with statics – global state.
84-
Lvalue::Static(_) => {}
76+
Lvalue::Static(defid) => self.statics.insert(defid, val),
8577
// I feel like this could be handled, but needs special care. For example in code like
8678
// this:
8779
//
@@ -95,77 +87,90 @@ impl<'tcx> CsLattice<'tcx> {
9587
// projections of var and not just var itself. Currently we handle this by not
9688
// keeping any knowledge about projections at all, but I think eventually we
9789
// want to do so.
98-
Lvalue::Projection(_) => {},
99-
_ => match self.values.entry(key.clone()) {
100-
Entry::Vacant(e) => {
101-
e.insert(val);
102-
}
103-
Entry::Occupied(mut e) => {
104-
e.get_mut().join(val);
105-
}
106-
}
107-
}
90+
Lvalue::Projection(_) => None,
91+
_ => self.locals.insert(key.clone(), val)
92+
};
10893
}
109-
fn remove(&mut self, key: &Lvalue<'tcx>) -> Option<Either<'tcx>> {
110-
self.values.remove(key)
94+
95+
fn get(&self, key: &Lvalue<'tcx>) -> Option<&Constant<'tcx>> {
96+
match *key {
97+
Lvalue::Static(ref defid) => self.statics.get(defid),
98+
Lvalue::Projection(_) => None,
99+
_ => self.locals.get(key),
100+
}
111101
}
102+
112103
fn top(&mut self, key: &Lvalue<'tcx>) {
113-
self.insert(key, Either::Top);
104+
match *key {
105+
Lvalue::Static(ref defid) => { self.statics.remove(defid); }
106+
Lvalue::Projection(_) => {}
107+
_ => { self.locals.remove(key); }
108+
}
114109
}
115110
}
116111

117-
impl<'tcx> Lattice for CsLattice<'tcx> {
118-
fn bottom() -> Self { CsLattice { values: FnvHashMap() } }
119-
fn join(&mut self, mut other: Self) -> bool {
120-
// Calculate inteersection this way:
121-
let mut changed = false;
122-
// First, drain the self.values into a list of equal values common to both maps.
123-
let mut common_keys = vec![];
124-
for (key, mut value) in self.values.drain() {
125-
match other.values.remove(&key) {
126-
// self had the key, but not other, so removing
127-
None => {
128-
changed = true;
129-
}
130-
Some(ov) => {
131-
changed |= value.join(ov);
112+
fn intersect_map<K, V>(this: &mut FnvHashMap<K, V>, mut other: FnvHashMap<K, V>) -> bool
113+
where K: Eq + ::std::hash::Hash, V: PartialEq
114+
{
115+
let mut changed = false;
116+
// Calculate inteersection this way:
117+
// First, drain the self.values into a list of equal values common to both maps.
118+
let mut common_keys = vec![];
119+
for (key, value) in this.drain() {
120+
match other.remove(&key) {
121+
None => {
122+
// self had the key, but not other, so it is Top (i.e. removed)
123+
changed = true;
124+
}
125+
Some(ov) => {
126+
// Similarly, if the values are not equal…
127+
changed |= if ov != value {
128+
true
129+
} else {
132130
common_keys.push((key, value));
131+
false
133132
}
134133
}
135134
}
136-
// Now, put each common key with equal value back into the map.
137-
for (key, value) in common_keys {
138-
self.values.insert(key, value);
139-
}
140-
changed
135+
}
136+
// Now, put each common key with equal value back into the map.
137+
for (key, value) in common_keys {
138+
this.insert(key, value);
139+
}
140+
changed
141+
}
142+
143+
impl<'tcx> Lattice for ConstLattice<'tcx> {
144+
fn bottom() -> Self { unimplemented!() }
145+
fn join(&mut self, other: Self) -> bool {
146+
intersect_map(&mut self.locals, other.locals) |
147+
intersect_map(&mut self.statics, other.statics)
141148
}
142149
}
143150

144-
struct CsTransfer<'a, 'tcx: 'a> {
151+
struct ConstTransfer<'a, 'tcx: 'a> {
145152
tcx: TyCtxt<'a, 'tcx, 'tcx>,
146153
}
147154

148-
impl<'a, 'tcx> Transfer<'tcx> for CsTransfer<'a, 'tcx> {
149-
type Lattice = CsLattice<'tcx>;
155+
impl<'a, 'tcx> Transfer<'tcx> for ConstTransfer<'a, 'tcx> {
156+
type Lattice = ConstLattice<'tcx>;
150157
type TerminatorReturn = Vec<Self::Lattice>;
151158

152159
fn stmt(&self, s: &Statement<'tcx>, mut lat: Self::Lattice)
153160
-> Self::Lattice
154161
{
155162
let StatementKind::Assign(ref lval, ref rval) = s.kind;
156163
match *rval {
157-
Rvalue::Use(Operand::Consume(ref nlval)) => {
158-
lat.insert(lval, Either::Lvalue(nlval.clone()));
159-
// Consider moved.
160-
lat.remove(nlval);
164+
Rvalue::Use(Operand::Consume(_)) => {
165+
lat.top(lval);
161166
},
162167
Rvalue::Use(Operand::Constant(ref cnst)) => {
163-
lat.insert(lval, Either::Const(cnst.clone()));
168+
lat.insert(lval, cnst.clone());
164169
},
165170
// We do not want to deal with references and pointers here. Not yet and not without
166171
// a way to query stuff about reference/pointer aliasing.
167172
Rvalue::Ref(_, _, ref referee) => {
168-
lat.remove(lval);
173+
lat.top(lval);
169174
lat.top(referee);
170175
}
171176
// FIXME: should calculate length of statically sized arrays and store it.
@@ -182,14 +187,16 @@ impl<'a, 'tcx> Transfer<'tcx> for CsTransfer<'a, 'tcx> {
182187
Rvalue::Box(_) => { lat.top(lval); }
183188
// Not handled, but could be. Disaggregation helps to not bother with this.
184189
Rvalue::Aggregate(..) => { lat.top(lval); }
185-
// Not handled, invalidate any knowledge about any variables used by this. Dangerous
190+
// Not handled, invalidate any knowledge about any variables touched by this. Dangerous
186191
// stuff and other dragons be here.
187192
Rvalue::InlineAsm { ref outputs, ref inputs, asm: _ } => {
188193
lat.top(lval);
189194
for output in outputs { lat.top(output); }
190195
for input in inputs {
191196
if let Operand::Consume(ref lval) = *input { lat.top(lval); }
192197
}
198+
// Clear the statics, because inline assembly may mutate global state at will.
199+
lat.statics.clear();
193200
}
194201
};
195202
lat
@@ -200,11 +207,11 @@ impl<'a, 'tcx> Transfer<'tcx> for CsTransfer<'a, 'tcx> {
200207
{
201208
let span = t.source_info.span;
202209
let succ_count = t.successors().len();
203-
let bool_const = |b: bool| Either::Const(Constant {
210+
let bool_const = |b: bool| Constant {
204211
span: span,
205212
ty: self.tcx.mk_bool(),
206213
literal: Literal::Value { value: ConstVal::Bool(b) },
207-
});
214+
};
208215
match t.kind {
209216
TerminatorKind::If { cond: Operand::Consume(ref lval), .. } => {
210217
let mut falsy = lat.clone();
@@ -215,46 +222,42 @@ impl<'a, 'tcx> Transfer<'tcx> for CsTransfer<'a, 'tcx> {
215222
TerminatorKind::SwitchInt { ref discr, ref values, switch_ty, .. } => {
216223
let mut vec: Vec<_> = values.iter().map(|val| {
217224
let mut branch = lat.clone();
218-
branch.insert(discr, Either::Const(Constant {
225+
branch.insert(discr, Constant {
219226
span: span,
220227
ty: switch_ty,
221228
literal: Literal::Value { value: val.clone() }
222-
}));
229+
});
223230
branch
224231
}).collect();
225232
vec.push(lat);
226233
vec
227234
}
228235
TerminatorKind::Drop { ref location, .. } => {
229-
lat.remove(location);
236+
lat.top(location);
237+
// See comment in Call.
238+
lat.statics.clear();
230239
vec![lat; succ_count]
231240
}
232-
TerminatorKind::DropAndReplace { ref location, ref unwind, ref value, .. } => {
241+
TerminatorKind::DropAndReplace { ref location, ref value, .. } => {
233242
match *value {
234-
Operand::Consume(ref lval) => {
235-
lat.remove(location);
236-
lat.remove(lval);
237-
},
238-
Operand::Constant(ref cnst) => {
239-
lat.insert(location, Either::Const(cnst.clone()));
240-
}
241-
}
242-
if unwind.is_some() {
243-
let mut unwind = lat.clone();
244-
unwind.remove(location);
245-
vec![lat, unwind]
246-
} else {
247-
vec![lat]
243+
Operand::Consume(_) => lat.top(location),
244+
Operand::Constant(ref cnst) => lat.insert(location, cnst.clone()),
248245
}
246+
// See comment in Call.
247+
lat.statics.clear();
248+
vec![lat; succ_count]
249249
}
250250
TerminatorKind::Call { ref destination, ref args, .. } => {
251251
for arg in args {
252252
if let Operand::Consume(ref lval) = *arg {
253253
// FIXME: Probably safe to not remove any non-projection lvals.
254-
lat.remove(lval);
254+
lat.top(lval);
255255
}
256256
}
257257
destination.as_ref().map(|&(ref lval, _)| lat.top(lval));
258+
// Clear all knowledge about statics, because call may mutate any global state
259+
// without us knowing about it.
260+
lat.statics.clear();
258261
vec![lat; succ_count]
259262
}
260263
TerminatorKind::Assert { ref cond, expected, ref cleanup, .. } => {
@@ -288,35 +291,31 @@ struct ConstRewrite<'a, 'tcx: 'a> {
288291
tcx: TyCtxt<'a, 'tcx, 'tcx>
289292
}
290293
impl<'a, 'tcx, T> Rewrite<'tcx, T> for ConstRewrite<'a, 'tcx>
291-
where T: Transfer<'tcx, Lattice=CsLattice<'tcx>>
294+
where T: Transfer<'tcx, Lattice=ConstLattice<'tcx>>
292295
{
293296
fn stmt(&self, stmt: &Statement<'tcx>, fact: &T::Lattice) -> StatementChange<'tcx> {
294297
let mut stmt = stmt.clone();
295-
let mut vis = RewriteConstVisitor(&fact.values);
298+
let mut vis = RewriteConstVisitor(&fact);
296299
vis.visit_statement(START_BLOCK, &mut stmt);
297300
ConstEvalVisitor(self.tcx).visit_statement(START_BLOCK, &mut stmt);
298301
StatementChange::Statement(stmt)
299302
}
300303

301304
fn term(&self, term: &Terminator<'tcx>, fact: &T::Lattice) -> TerminatorChange<'tcx> {
302305
let mut term = term.clone();
303-
let mut vis = RewriteConstVisitor(&fact.values);
306+
let mut vis = RewriteConstVisitor(&fact);
304307
vis.visit_terminator(START_BLOCK, &mut term);
305308
ConstEvalVisitor(self.tcx).visit_terminator(START_BLOCK, &mut term);
306309
TerminatorChange::Terminator(term)
307310
}
308311
}
309312

310-
struct RewriteConstVisitor<'a, 'tcx: 'a>(&'a FnvHashMap<Lvalue<'tcx>, Either<'tcx>>);
313+
struct RewriteConstVisitor<'a, 'tcx: 'a>(&'a ConstLattice<'tcx>);
311314
impl<'a, 'tcx> MutVisitor<'tcx> for RewriteConstVisitor<'a, 'tcx> {
312315
fn visit_operand(&mut self, op: &mut Operand<'tcx>) {
313316
// To satisy borrow checker, modify `op` after inspecting it
314317
let repl = if let Operand::Consume(ref lval) = *op {
315-
if let Some(&Either::Const(ref c)) = self.0.get(lval) {
316-
Some(c.clone())
317-
} else {
318-
None
319-
}
318+
self.0.get(lval).cloned()
320319
} else {
321320
None
322321
};

0 commit comments

Comments
 (0)