-
Notifications
You must be signed in to change notification settings - Fork 0
Various fixes and improvements to the MIR Dataflow framework #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: mir-dflow
Are you sure you want to change the base?
Changes from all commits
d4b132a
8fb7a1c
9a46cd7
ca08fdf
c36dc32
ca52586
a1cf39a
e2d48c5
50697a5
a40d9c1
52d150f
92628f7
ab1d316
d5c74f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,10 @@ | ||
use mir::repr as mir; | ||
use mir::cfg::CFG; | ||
use mir::repr::BasicBlock; | ||
use mir::repr::{BasicBlock, START_BLOCK}; | ||
use rustc_data_structures::bitvec::BitVector; | ||
|
||
use mir::transform::lattice::Lattice; | ||
|
||
|
||
pub trait DataflowPass<'tcx> { | ||
type Lattice: Lattice; | ||
type Rewrite: Rewrite<'tcx, Self::Lattice>; | ||
type Transfer: Transfer<'tcx, Self::Lattice>; | ||
} | ||
|
||
pub trait Rewrite<'tcx, L: Lattice> { | ||
/// The rewrite function which given a statement optionally produces an alternative graph to be | ||
/// placed in place of the original statement. | ||
|
@@ -23,7 +16,7 @@ pub trait Rewrite<'tcx, L: Lattice> { | |
/// that is, given some fact `fact` true before both the statement and relacement graph, and | ||
/// a fact `fact2` which is true after the statement, the same `fact2` must be true after the | ||
/// replacement graph too. | ||
fn stmt(&mir::Statement<'tcx>, &L, &mut CFG<'tcx>) -> StatementChange<'tcx>; | ||
fn stmt(&self, &mir::Statement<'tcx>, &L, &mut CFG<'tcx>) -> StatementChange<'tcx>; | ||
|
||
/// The rewrite function which given a terminator optionally produces an alternative graph to | ||
/// be placed in place of the original statement. | ||
|
@@ -35,49 +28,39 @@ pub trait Rewrite<'tcx, L: Lattice> { | |
/// that is, given some fact `fact` true before both the terminator and relacement graph, and | ||
/// a fact `fact2` which is true after the statement, the same `fact2` must be true after the | ||
/// replacement graph too. | ||
fn term(&mir::Terminator<'tcx>, &L, &mut CFG<'tcx>) -> TerminatorChange<'tcx>; | ||
fn term(&self, &mir::Terminator<'tcx>, &L, &mut CFG<'tcx>) -> TerminatorChange<'tcx>; | ||
|
||
fn and_then<R2>(self, other: R2) -> RewriteAndThen<Self, R2> where Self: Sized { | ||
RewriteAndThen(self, other) | ||
} | ||
} | ||
|
||
/// This combinator has the following behaviour: | ||
/// | ||
/// * Rewrite the node with the first rewriter. | ||
/// * if the first rewriter replaced the node, 2nd rewriter is used to rewrite the replacement. | ||
/// * otherwise 2nd rewriter is used to rewrite the original node. | ||
pub struct RewriteAndThen<'tcx, R1, R2>(::std::marker::PhantomData<(&'tcx (), R1, R2)>); | ||
impl<'tcx, L, R1, R2> Rewrite<'tcx, L> for RewriteAndThen<'tcx, R1, R2> | ||
pub struct RewriteAndThen<R1, R2>(R1, R2); | ||
impl<'tcx, L, R1, R2> Rewrite<'tcx, L> for RewriteAndThen<R1, R2> | ||
where L: Lattice, R1: Rewrite<'tcx, L>, R2: Rewrite<'tcx, L> { | ||
fn stmt(s: &mir::Statement<'tcx>, l: &L, c: &mut CFG<'tcx>) -> StatementChange<'tcx> { | ||
let rs = <R1 as Rewrite<L>>::stmt(s, l, c); | ||
fn stmt(&self, s: &mir::Statement<'tcx>, l: &L, c: &mut CFG<'tcx>) -> StatementChange<'tcx> { | ||
let rs = self.0.stmt(s, l, c); | ||
match rs { | ||
StatementChange::None => <R2 as Rewrite<L>>::stmt(s, l, c), | ||
StatementChange::None => self.1.stmt(s, l, c), | ||
StatementChange::Remove => StatementChange::Remove, | ||
StatementChange::Statement(ns) => | ||
match <R2 as Rewrite<L>>::stmt(&ns, l, c) { | ||
match self.1.stmt(&ns, l, c) { | ||
StatementChange::None => StatementChange::Statement(ns), | ||
x => x | ||
}, | ||
StatementChange::Statements(nss) => { | ||
// We expect the common case of all statements in this vector being replaced/not | ||
// replaced by other statements 1:1 | ||
let mut new_new_stmts = Vec::with_capacity(nss.len()); | ||
for s in nss { | ||
match <R2 as Rewrite<L>>::stmt(&s, l, c) { | ||
StatementChange::None => new_new_stmts.push(s), | ||
StatementChange::Remove => {}, | ||
StatementChange::Statement(ns) => new_new_stmts.push(ns), | ||
StatementChange::Statements(nss) => new_new_stmts.extend(nss) | ||
} | ||
} | ||
StatementChange::Statements(new_new_stmts) | ||
} | ||
} | ||
} | ||
|
||
fn term(t: &mir::Terminator<'tcx>, l: &L, c: &mut CFG<'tcx>) -> TerminatorChange<'tcx> { | ||
let rt = <R1 as Rewrite<L>>::term(t, l, c); | ||
fn term(&self, t: &mir::Terminator<'tcx>, l: &L, c: &mut CFG<'tcx>) -> TerminatorChange<'tcx> { | ||
let rt = self.0.term(t, l, c); | ||
match rt { | ||
TerminatorChange::None => <R2 as Rewrite<L>>::term(t, l, c), | ||
TerminatorChange::Terminator(nt) => match <R2 as Rewrite<L>>::term(&nt, l, c) { | ||
TerminatorChange::None => self.1.term(t, l, c), | ||
TerminatorChange::Terminator(nt) => match self.1.term(&nt, l, c) { | ||
TerminatorChange::None => TerminatorChange::Terminator(nt), | ||
x => x | ||
} | ||
|
@@ -99,39 +82,22 @@ pub enum StatementChange<'tcx> { | |
Remove, | ||
/// Replace with another single statement | ||
Statement(mir::Statement<'tcx>), | ||
/// Replace with a list of statements | ||
Statements(Vec<mir::Statement<'tcx>>), | ||
} | ||
|
||
impl<'tcx> StatementChange<'tcx> { | ||
fn normalise(&mut self) { | ||
let old = ::std::mem::replace(self, StatementChange::None); | ||
*self = match old { | ||
StatementChange::Statements(mut stmts) => { | ||
match stmts.len() { | ||
0 => StatementChange::Remove, | ||
1 => StatementChange::Statement(stmts.pop().unwrap()), | ||
_ => StatementChange::Statements(stmts) | ||
} | ||
} | ||
o => o | ||
} | ||
} | ||
} | ||
pub trait Transfer<'tcx> { | ||
type Lattice: Lattice; | ||
|
||
pub trait Transfer<'tcx, L: Lattice> { | ||
type TerminatorOut; | ||
/// The transfer function which given a statement and a fact produces a fact which is true | ||
/// after the statement. | ||
fn stmt(&mir::Statement<'tcx>, L) -> L; | ||
fn stmt(&self, &mir::Statement<'tcx>, Self::Lattice) -> Self::Lattice; | ||
|
||
/// The transfer function which given a terminator and a fact produces a fact for each | ||
/// successor of the terminator. | ||
/// | ||
/// Corectness precondtition: | ||
/// * The list of facts produced should only contain the facts for blocks which are successors | ||
/// of the terminator being transfered. | ||
fn term(&mir::Terminator<'tcx>, L) -> Self::TerminatorOut; | ||
fn term(&self, &mir::Terminator<'tcx>, Self::Lattice) -> Vec<Self::Lattice>; | ||
} | ||
|
||
|
||
|
@@ -160,20 +126,22 @@ impl<F: Lattice> ::std::ops::Index<BasicBlock> for Facts<F> { | |
|
||
impl<F: Lattice> ::std::ops::IndexMut<BasicBlock> for Facts<F> { | ||
fn index_mut(&mut self, index: BasicBlock) -> &mut F { | ||
if let None = self.0.get_mut(index.index()) { | ||
if self.0.get(index.index()).is_none() { | ||
self.put(index, <F as Lattice>::bottom()); | ||
} | ||
self.0.get_mut(index.index()).unwrap() | ||
} | ||
} | ||
|
||
/// Analyse and rewrite using dataflow in the forward direction | ||
pub fn ar_forward<'tcx, T, P>(cfg: &CFG<'tcx>, fs: Facts<P::Lattice>, mut queue: BitVector) | ||
-> (CFG<'tcx>, Facts<P::Lattice>) | ||
// FIXME: shouldn’t need that T generic. | ||
where T: Transfer<'tcx, P::Lattice, TerminatorOut=Vec<P::Lattice>>, | ||
P: DataflowPass<'tcx, Transfer=T> | ||
pub fn ar_forward<'tcx, T, R>(cfg: &CFG<'tcx>, fs: Facts<T::Lattice>, transfer: T, rewrite: R) | ||
-> (CFG<'tcx>, Facts<T::Lattice>) | ||
where T: Transfer<'tcx>, | ||
R: Rewrite<'tcx, T::Lattice> | ||
{ | ||
let mut queue = BitVector::new(cfg.len()); | ||
queue.insert(START_BLOCK.index()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly here, I do not believe that beginning at the start block is always correct/desired/etc. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you give an example of when another location would be desired? For a forwards pass, the start block seems like a natural place to start, and allows for a number of blocks to be transformed only once - if the CFG is acyclic, fixpoint iteration is unnecessary starting at the start block. |
||
|
||
fixpoint(cfg, Direction::Forward, |bb, fact, cfg| { | ||
let new_graph = cfg.start_new_block(); | ||
let mut fact = fact.clone(); | ||
|
@@ -183,33 +151,25 @@ where T: Transfer<'tcx, P::Lattice, TerminatorOut=Vec<P::Lattice>>, | |
for stmt in &old_statements { | ||
// Given a fact and statement produce a new fact and optionally a replacement | ||
// graph. | ||
let mut new_repl = P::Rewrite::stmt(&stmt, &fact, cfg); | ||
new_repl.normalise(); | ||
match new_repl { | ||
match rewrite.stmt(&stmt, &fact, cfg) { | ||
StatementChange::None => { | ||
fact = P::Transfer::stmt(stmt, fact); | ||
fact = transfer.stmt(stmt, fact); | ||
cfg.push(new_graph, stmt.clone()); | ||
} | ||
StatementChange::Remove => changed = true, | ||
StatementChange::Statement(stmt) => { | ||
changed = true; | ||
fact = P::Transfer::stmt(&stmt, fact); | ||
fact = transfer.stmt(&stmt, fact); | ||
cfg.push(new_graph, stmt); | ||
} | ||
StatementChange::Statements(stmts) => { | ||
changed = true; | ||
for stmt in &stmts { fact = P::Transfer::stmt(stmt, fact); } | ||
cfg[new_graph].statements.extend(stmts); | ||
} | ||
|
||
} | ||
} | ||
// Swap the statements back in. | ||
::std::mem::replace(&mut cfg[bb].statements, old_statements); | ||
cfg[bb].statements = old_statements; | ||
|
||
// Handle the terminator replacement and transfer. | ||
let terminator = ::std::mem::replace(&mut cfg[bb].terminator, None).unwrap(); | ||
let repl = P::Rewrite::term(&terminator, &fact, cfg); | ||
let terminator = cfg[bb].terminator.take().unwrap(); | ||
let repl = rewrite.term(&terminator, &fact, cfg); | ||
match repl { | ||
TerminatorChange::None => { | ||
cfg[new_graph].terminator = Some(terminator.clone()); | ||
|
@@ -219,8 +179,8 @@ where T: Transfer<'tcx, P::Lattice, TerminatorOut=Vec<P::Lattice>>, | |
cfg[new_graph].terminator = Some(t); | ||
} | ||
} | ||
let new_facts = P::Transfer::term(cfg[new_graph].terminator(), fact); | ||
::std::mem::replace(&mut cfg[bb].terminator, Some(terminator)); | ||
let new_facts = transfer.term(cfg[new_graph].terminator(), fact); | ||
cfg[bb].terminator = Some(terminator); | ||
|
||
(if changed { Some(new_graph) } else { None }, new_facts) | ||
}, &mut queue, fs) | ||
|
@@ -350,7 +310,7 @@ where T: Transfer<'tcx, P::Lattice, TerminatorOut=Vec<P::Lattice>>, | |
|
||
enum Direction { | ||
Forward, | ||
Backward | ||
// Backward | ||
} | ||
|
||
/// The fixpoint function is the engine of this whole thing. Important part of it is the `f: BF` | ||
|
@@ -389,16 +349,17 @@ where BF: Fn(BasicBlock, &F, &mut CFG<'tcx>) -> (Option<BasicBlock>, Vec<F>), | |
} | ||
|
||
// Then we record the facts in the correct direction. | ||
if let Direction::Forward = direction { | ||
for (f, &target) in new_facts.into_iter() | ||
.zip(cfg[block].terminator().successors().iter()) { | ||
let facts_changed = Lattice::join(&mut init_facts[target], &f); | ||
if facts_changed { | ||
to_visit.insert(target.index()); | ||
match direction { | ||
Direction::Forward => { | ||
for (f, &target) in new_facts.into_iter() | ||
.zip(cfg[block].terminator().successors().iter()) { | ||
let facts_changed = Lattice::join(&mut init_facts[target], &f); | ||
if facts_changed { | ||
to_visit.insert(target.index()); | ||
} | ||
} | ||
} | ||
} else { | ||
unimplemented!() | ||
// Direction::Backward => unimplemented!() | ||
// let mut new_facts = new_facts; | ||
// let fact = new_facts.pop().unwrap().1; | ||
// for pred in cfg.predecessors(block) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,12 +24,13 @@ | |
//! For all of them we will be using a lattice of Hashmap from Lvalue to | ||
//! WTop<Either<Lvalue, Constant>> | ||
//! | ||
//! My personal believ is that it should be possible to make a way to compose two hashmap lattices | ||
//! My personal belief is that it should be possible to make a way to compose two hashmap lattices | ||
//! into one, but I can’t seem to get it just right yet, so we do the composing and decomposing | ||
//! manually here. | ||
use self::AcsLattice::*; | ||
|
||
use rustc_data_structures::fnv::FnvHashMap; | ||
use rustc_data_structures::bitvec::BitVector; | ||
use rustc::mir::repr::*; | ||
use rustc::mir::visit::{MutVisitor, LvalueContext}; | ||
use rustc::mir::transform::lattice::Lattice; | ||
|
@@ -40,64 +41,140 @@ use rustc::middle::const_val::ConstVal; | |
use pretty; | ||
|
||
#[derive(PartialEq, Debug, Eq, Clone)] | ||
pub enum Either<'tcx> { | ||
Top, | ||
enum Either<'tcx> { | ||
Lvalue(Lvalue<'tcx>), | ||
Const(Constant<'tcx>), | ||
} | ||
|
||
impl<'tcx> Lattice for Either<'tcx> { | ||
fn bottom() -> Self { unimplemented!() } | ||
#[derive(Debug, Clone)] | ||
enum AcsLattice<'tcx> { | ||
Bottom, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The bottom element in this lattice is an empty HashMap. It is fine to have it this way, because Similarly, I’m confused as to why the /me shrugs There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed the lattice so that fn foo(mut x: u32) {
if /* random unoptimizable condition */ {
x = 5;
}
println!("{}", x);
} Previously, since the value stored for fn foo(mut x: u32) {
if /* random unoptimizable condition */ {
x = 5;
}
println!("{}", 5);
} which is just wrong. Note that, in terms of inspiration, while the original paper used a union-based lattice for constant propogation, GHC actually uses an intersection based lattice (see here). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, I realized after I wrote that code that |
||
Wrap(FnvHashMap<Lvalue<'tcx>, Either<'tcx>>) | ||
} | ||
|
||
impl<'tcx> Lattice for AcsLattice<'tcx> { | ||
fn bottom() -> Self { Bottom } | ||
fn join(&mut self, other: &Self) -> bool { | ||
if self == other { | ||
false | ||
} else { | ||
*self = Either::Top; | ||
true | ||
let other_map = match *other { | ||
Bottom => return false, | ||
Wrap(ref map) => map | ||
}; | ||
let self_map = match *self { | ||
Bottom => { | ||
*self = Wrap(other_map.clone()); | ||
return true; | ||
}, | ||
Wrap(ref mut map) => map | ||
}; | ||
|
||
let mut changed = false; | ||
|
||
for (k, v) in other_map { | ||
let should_remove = if let Some(cur_v) = self_map.get(k) { | ||
cur_v != v | ||
} else { | ||
false | ||
}; | ||
if should_remove { | ||
self_map.remove(k); | ||
changed = true; | ||
} | ||
} | ||
|
||
changed | ||
} | ||
} | ||
|
||
pub type ACSLattice<'a> = FnvHashMap<Lvalue<'a>, Either<'a>>; | ||
|
||
pub struct ACSPropagate; | ||
pub struct AcsPropagate; | ||
|
||
impl Pass for ACSPropagate {} | ||
impl Pass for AcsPropagate {} | ||
|
||
impl<'tcx> MirPass<'tcx> for ACSPropagate { | ||
impl<'tcx> MirPass<'tcx> for AcsPropagate { | ||
fn run_pass<'a>(&mut self, tcx: TyCtxt<'a, 'tcx, 'tcx>, src: MirSource, mir: &mut Mir<'tcx>) { | ||
let mut q = BitVector::new(mir.cfg.len()); | ||
q.insert(START_BLOCK.index()); | ||
let ret = ar_forward::<ACSPropagateTransfer, ACSPropagate>(&mut mir.cfg, Facts::new(), q); | ||
let ret = ar_forward( | ||
&mut mir.cfg, | ||
Facts::new(), | ||
AcsPropagateTransfer, | ||
AliasRewrite.and_then(ConstRewrite).and_then(SimplifyRewrite) | ||
); | ||
mir.cfg = ret.0; | ||
pretty::dump_mir(tcx, "acs_propagate", &0, src, mir, None); | ||
} | ||
|
||
} | ||
|
||
impl<'tcx> DataflowPass<'tcx> for ACSPropagate { | ||
type Lattice = ACSLattice<'tcx>; | ||
type Rewrite = RewriteAndThen<'tcx, AliasRewrite, | ||
RewriteAndThen<'tcx, ConstRewrite, SimplifyRewrite>>; | ||
type Transfer = ACSPropagateTransfer; | ||
struct AcsPropagateTransfer; | ||
|
||
fn base_lvalue<'a, 'tcx>(mut lval: &'a Lvalue<'tcx>) -> &'a Lvalue<'tcx> { | ||
while let &Lvalue::Projection(ref proj) = lval { | ||
lval = &proj.base; | ||
} | ||
lval | ||
} | ||
|
||
fn invalidate<'tcx>(map: &mut FnvHashMap<Lvalue<'tcx>, Either<'tcx>>, lval: &Lvalue<'tcx>) { | ||
map.remove(lval); | ||
|
||
let mut repl = None; | ||
|
||
for (k, v) in &mut *map { | ||
if let Either::Lvalue(ref mut nlval) = *v { | ||
if nlval == lval { | ||
match repl { | ||
None => { | ||
repl = Some(k.clone()) | ||
}, | ||
Some(ref r) => { | ||
*nlval = r.clone(); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
if let Some(repl) = repl { | ||
map.remove(&repl); | ||
} | ||
} | ||
|
||
pub struct ACSPropagateTransfer; | ||
impl<'tcx> Transfer<'tcx> for AcsPropagateTransfer { | ||
type Lattice = AcsLattice<'tcx>; | ||
|
||
fn stmt(&self, s: &Statement<'tcx>, lat: AcsLattice<'tcx>) -> AcsLattice<'tcx> { | ||
let mut lat_map = match lat { | ||
Bottom => FnvHashMap::default(), | ||
Wrap(map) => map | ||
}; | ||
|
||
impl<'tcx> Transfer<'tcx, ACSLattice<'tcx>> for ACSPropagateTransfer { | ||
type TerminatorOut = Vec<ACSLattice<'tcx>>; | ||
fn stmt(s: &Statement<'tcx>, mut lat: ACSLattice<'tcx>) -> ACSLattice<'tcx> { | ||
let StatementKind::Assign(ref lval, ref rval) = s.kind; | ||
invalidate(&mut lat_map, base_lvalue(lval)); | ||
|
||
if let &Lvalue::Projection(_) = lval { | ||
return Wrap(lat_map); | ||
} | ||
|
||
match *rval { | ||
Rvalue::Use(Operand::Consume(ref nlval)) => | ||
lat.insert(lval.clone(), Either::Lvalue(nlval.clone())), | ||
Rvalue::Use(Operand::Constant(ref c)) => | ||
lat.insert(lval.clone(), Either::Const(c.clone())), | ||
_ => lat.insert(lval.clone(), Either::Top) | ||
Rvalue::Use(Operand::Consume(ref nlval)) => { | ||
lat_map.insert(lval.clone(), Either::Lvalue(nlval.clone())); | ||
}, | ||
Rvalue::Use(Operand::Constant(ref c)) => { | ||
lat_map.insert(lval.clone(), Either::Const(c.clone())); | ||
}, | ||
_ => { } | ||
}; | ||
lat | ||
Wrap(lat_map) | ||
} | ||
fn term(t: &Terminator<'tcx>, lat: ACSLattice<'tcx>) -> Self::TerminatorOut { | ||
|
||
fn term(&self, t: &Terminator<'tcx>, mut lat: AcsLattice<'tcx>) -> Vec<AcsLattice<'tcx>> { | ||
match t.kind { | ||
TerminatorKind::Call { .. } | | ||
TerminatorKind::Drop { .. } => { | ||
// FIXME: Be smarter here by using an alias analysis | ||
lat = Wrap(FnvHashMap::default()); | ||
}, | ||
_ => { } | ||
} | ||
|
||
// FIXME: this should inspect the terminators and set their known values to constants. Esp. | ||
// for the if: in the truthy branch the operand is known to be true and in the falsy branch | ||
// the operand is known to be false. Now we just ignore the potential here. | ||
|
@@ -107,67 +184,86 @@ impl<'tcx> Transfer<'tcx, ACSLattice<'tcx>> for ACSPropagateTransfer { | |
} | ||
} | ||
|
||
pub struct AliasRewrite; | ||
struct AliasRewrite; | ||
|
||
impl<'tcx> Rewrite<'tcx, ACSLattice<'tcx>> for AliasRewrite { | ||
fn stmt(s: &Statement<'tcx>, l: &ACSLattice<'tcx>, cfg: &mut CFG<'tcx>) | ||
impl<'tcx> Rewrite<'tcx, AcsLattice<'tcx>> for AliasRewrite { | ||
fn stmt(&self, s: &Statement<'tcx>, l: &AcsLattice<'tcx>, _: &mut CFG<'tcx>) | ||
-> StatementChange<'tcx> { | ||
let mut ns = s.clone(); | ||
let mut vis = RewriteAliasVisitor(&l, false); | ||
vis.visit_statement(START_BLOCK, &mut ns); | ||
if vis.1 { StatementChange::Statement(ns) } else { StatementChange::None } | ||
if let Wrap(ref map) = *l { | ||
let mut ns = s.clone(); | ||
let mut vis = RewriteAliasVisitor(map, false); | ||
vis.visit_statement(START_BLOCK, &mut ns); | ||
if vis.1 { | ||
return StatementChange::Statement(ns); | ||
} | ||
} | ||
StatementChange::None | ||
} | ||
fn term(t: &Terminator<'tcx>, l: &ACSLattice<'tcx>, cfg: &mut CFG<'tcx>) | ||
|
||
fn term(&self, t: &Terminator<'tcx>, l: &AcsLattice<'tcx>, _: &mut CFG<'tcx>) | ||
-> TerminatorChange<'tcx> { | ||
let mut nt = t.clone(); | ||
let mut vis = RewriteAliasVisitor(&l, false); | ||
vis.visit_terminator(START_BLOCK, &mut nt); | ||
if vis.1 { TerminatorChange::Terminator(nt) } else { TerminatorChange::None } | ||
if let Wrap(ref map) = *l { | ||
let mut nt = t.clone(); | ||
let mut vis = RewriteAliasVisitor(map, false); | ||
vis.visit_terminator(START_BLOCK, &mut nt); | ||
if vis.1 { | ||
return TerminatorChange::Terminator(nt); | ||
} | ||
} | ||
TerminatorChange::None | ||
} | ||
} | ||
|
||
struct RewriteAliasVisitor<'a, 'tcx: 'a>(pub &'a ACSLattice<'tcx>, pub bool); | ||
struct RewriteAliasVisitor<'a, 'tcx: 'a>(&'a FnvHashMap<Lvalue<'tcx>, Either<'tcx>>, bool); | ||
impl<'a, 'tcx> MutVisitor<'tcx> for RewriteAliasVisitor<'a, 'tcx> { | ||
fn visit_lvalue(&mut self, lvalue: &mut Lvalue<'tcx>, context: LvalueContext) { | ||
match context { | ||
LvalueContext::Store | LvalueContext::Call => {} | ||
_ => { | ||
let replacement = self.0.get(lvalue); | ||
match replacement { | ||
Some(&Either::Lvalue(ref nlval)) => { | ||
self.1 = true; | ||
*lvalue = nlval.clone(); | ||
} | ||
_ => {} | ||
LvalueContext::Consume => { | ||
if let Some(&Either::Lvalue(ref nlval)) = self.0.get(lvalue) { | ||
self.1 = true; | ||
*lvalue = nlval.clone(); | ||
} | ||
} | ||
}, | ||
_ => { } | ||
} | ||
self.super_lvalue(lvalue, context); | ||
} | ||
} | ||
|
||
pub struct ConstRewrite; | ||
struct ConstRewrite; | ||
|
||
impl<'tcx> Rewrite<'tcx, ACSLattice<'tcx>> for ConstRewrite { | ||
fn stmt(s: &Statement<'tcx>, l: &ACSLattice<'tcx>, cfg: &mut CFG<'tcx>) | ||
impl<'tcx> Rewrite<'tcx, AcsLattice<'tcx>> for ConstRewrite { | ||
fn stmt(&self, s: &Statement<'tcx>, l: &AcsLattice<'tcx>, _: &mut CFG<'tcx>) | ||
-> StatementChange<'tcx> { | ||
let mut ns = s.clone(); | ||
let mut vis = RewriteConstVisitor(&l, false); | ||
vis.visit_statement(START_BLOCK, &mut ns); | ||
if vis.1 { StatementChange::Statement(ns) } else { StatementChange::None } | ||
if let Wrap(ref map) = *l { | ||
let mut ns = s.clone(); | ||
let mut vis = RewriteConstVisitor(map, false); | ||
vis.visit_statement(START_BLOCK, &mut ns); | ||
if vis.1 { | ||
return StatementChange::Statement(ns); | ||
} | ||
} | ||
StatementChange::None | ||
} | ||
fn term(t: &Terminator<'tcx>, l: &ACSLattice<'tcx>, cfg: &mut CFG<'tcx>) | ||
|
||
fn term(&self, t: &Terminator<'tcx>, l: &AcsLattice<'tcx>, _: &mut CFG<'tcx>) | ||
-> TerminatorChange<'tcx> { | ||
let mut nt = t.clone(); | ||
let mut vis = RewriteConstVisitor(&l, false); | ||
vis.visit_terminator(START_BLOCK, &mut nt); | ||
if vis.1 { TerminatorChange::Terminator(nt) } else { TerminatorChange::None } | ||
if let Wrap(ref map) = *l { | ||
let mut nt = t.clone(); | ||
let mut vis = RewriteConstVisitor(map, false); | ||
vis.visit_terminator(START_BLOCK, &mut nt); | ||
if vis.1 { | ||
return TerminatorChange::Terminator(nt); | ||
} | ||
} | ||
TerminatorChange::None | ||
} | ||
} | ||
|
||
struct RewriteConstVisitor<'a, 'tcx: 'a>(pub &'a ACSLattice<'tcx>, pub bool); | ||
struct RewriteConstVisitor<'a, 'tcx: 'a>(&'a FnvHashMap<Lvalue<'tcx>, Either<'tcx>>, bool); | ||
impl<'a, 'tcx> MutVisitor<'tcx> for RewriteConstVisitor<'a, 'tcx> { | ||
fn visit_operand(&mut self, op: &mut Operand<'tcx>) { | ||
// To satisy borrow checker, modify `op` after inspecting it | ||
let repl = if let Operand::Consume(ref lval) = *op { | ||
if let Some(&Either::Const(ref c)) = self.0.get(lval) { | ||
Some(c.clone()) | ||
|
@@ -178,21 +274,24 @@ impl<'a, 'tcx> MutVisitor<'tcx> for RewriteConstVisitor<'a, 'tcx> { | |
None | ||
}; | ||
if let Some(c) = repl { | ||
self.1 = true; | ||
*op = Operand::Constant(c); | ||
} | ||
|
||
self.super_operand(op); | ||
} | ||
} | ||
|
||
|
||
pub struct SimplifyRewrite; | ||
struct SimplifyRewrite; | ||
|
||
impl<'tcx> Rewrite<'tcx, ACSLattice<'tcx>> for SimplifyRewrite { | ||
fn stmt(s: &Statement<'tcx>, l: &ACSLattice<'tcx>, cfg: &mut CFG<'tcx>) | ||
impl<'tcx, L: Lattice> Rewrite<'tcx, L> for SimplifyRewrite { | ||
fn stmt(&self, _: &Statement<'tcx>, _: &L, _: &mut CFG<'tcx>) | ||
-> StatementChange<'tcx> { | ||
StatementChange::None | ||
} | ||
fn term(t: &Terminator<'tcx>, l: &ACSLattice<'tcx>, cfg: &mut CFG<'tcx>) | ||
|
||
fn term(&self, t: &Terminator<'tcx>, _: &L, _: &mut CFG<'tcx>) | ||
-> TerminatorChange<'tcx> { | ||
match t.kind { | ||
TerminatorKind::If { ref targets, .. } if targets.0 == targets.1 => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change makes the trait not very reusable for backward analysis, where terminators only have a single edge regardless of how many edges the terminator has in the forward direction. That is the primary motivation for TerminatorOut associated type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, but I'm not sure that the same trait should be reused for the forward and backwards cases. Regardless, even if the two directions share a trait, I'd prefer a clearer way of denoting which direction a pass goes than using
Vec<Lattice>
for forward andLattice
for backwards.Transfer
could have another argument (Direction
) which itself has an associated type denoting what outputterm
should have.I got rid of the associated
TerminatorOut
because it felt clumsy, it cause recursive trait bounds when I moved in the associated lattice type, and because it wasn't necessary yet. When backwards transformation gets implemented, I'd be happy for this to be generalized.