1+ // Copyright 2016 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+ //! The interleaved, speculative MIR dataflow framework. This framework is heavily inspired by
11+ //! Hoopl[1][2], and while it has diverged from its inspiration quite a bit in implementation
12+ //! aproach, the general idea stays the same.
13+ //!
14+ //! [1]: https://github.com/haskell/hoopl
15+ //! [2]: http://research.microsoft.com/en-us/um/people/simonpj/papers/c--/hoopl-haskell10.pdf
16+
117use rustc:: mir:: repr as mir;
218use rustc:: mir:: repr:: { BasicBlock , BasicBlockData , Mir , START_BLOCK } ;
319use rustc_data_structures:: bitvec:: BitVector ;
@@ -9,19 +25,31 @@ mod lattice;
925mod combinators;
1026
1127pub enum StatementChange < ' tcx > {
28+ /// Remove the statement being inspected.
1229 Remove ,
30+ /// Replace the statement with another (or the same) one.
1331 Statement ( mir:: Statement < ' tcx > )
32+ // FIXME: Should grow a way to do replacements with arbitrary graphs.
33+ // Fixing this needs figuring out how to represent such an arbitrary graph in a way that could
34+ // be both analysed (by and_then combinator, for example) and applied to the MIR/constructed
35+ // out from MIR. Such representation needs to allow at least:
36+ // * Adding new blocks, edges between blocks in the replacement as well as edges to the other
37+ // blocks in the MIR;
38+ // * Adding new locals and perhaps changing existing ones (e.g. the type)
1439}
1540pub enum TerminatorChange < ' tcx > {
41+ /// Replace the terminator with another (or the same) one.
1642 Terminator ( mir:: Terminator < ' tcx > )
43+ // FIXME: Should grow a way to do replacements with arbitrary graphs.
1744}
1845
1946pub trait Transfer < ' tcx > {
47+ /// The lattice used with this transfer function.
2048 type Lattice : Lattice ;
2149
2250 /// Return type of terminator transfer function.
2351 ///
24- /// `Vec<Lattice>` for forward analysis, `Lattice` for backward analysis.
52+ /// `Vec<Self:: Lattice>` for forward analysis, `Self:: Lattice` for backward analysis.
2553 type TerminatorReturn ;
2654
2755 /// The transfer function which given a statement and a fact produces another fact which is
@@ -43,12 +71,19 @@ pub trait Rewrite<'tcx, T: Transfer<'tcx>> {
4371 -> TerminatorChange < ' tcx > ;
4472
4573 /// Combine two rewrites using RewriteAndThen combinator.
74+ ///
75+ /// See documentation for the combinator for explanation of its behaviour.
4676 fn and_then < R2 > ( self , other : R2 )
4777 -> RewriteAndThen < ' tcx , T , Self , R2 >
4878 where Self : Sized , R2 : Rewrite < ' tcx , T >
4979 {
5080 RewriteAndThen :: new ( self , other)
5181 }
82+ // FIXME: should gain at least these combinators:
83+ // * Fueled – only runs rewriter a set amount of times (needs saving the state of rewriter at
84+ // certain points);
85+ // * Deep – rewrite graph produced by rewriter with the same rewriter again;
86+ // * maybe a wrapper to hold a tcx?
5287}
5388
5489#[ derive( Clone ) ]
@@ -61,7 +96,9 @@ pub struct Dataflow<'a, 'tcx: 'a, T, R>
6196where T : Transfer < ' tcx > , R : Rewrite < ' tcx , T >
6297{
6398 mir : & ' a Mir < ' tcx > ,
64- // FIXME: bitvector may not be the best choice
99+ // FIXME: bitvector may not be the best choice, I feel like using a FIFO queue should yield
100+ // better results at some cost of space use. This queue needs to be a set (no duplicate
101+ // entries), though, so plain linked-list based queue is not suitable.
65102 queue : BitVector ,
66103 knowledge : IndexVec < BasicBlock , Option < Knowledge < ' tcx , T :: Lattice > > > ,
67104 rewrite : R ,
@@ -73,6 +110,7 @@ where L: Lattice,
73110 T : Transfer < ' tcx , Lattice =L , TerminatorReturn =Vec < L > > ,
74111 R : Rewrite < ' tcx , T >
75112{
113+ /// Execute dataflow in forward direction
76114 pub fn forward ( mir : & ' a Mir < ' tcx > , transfer : T , rewrite : R ) -> Mir < ' tcx > {
77115 let block_count = mir. basic_blocks ( ) . len ( ) ;
78116 let mut queue = BitVector :: new ( block_count) ;
@@ -90,7 +128,6 @@ where L: Lattice,
90128 }
91129
92130 fn forward_block ( & mut self , bb : BasicBlock , mut fact : T :: Lattice ) {
93- // debug!("forward dataflow analysing {:?}", block);
94131 let bb_data = & self . mir [ bb] ;
95132 let mut new_stmts = Vec :: with_capacity ( bb_data. statements . len ( ) ) ;
96133 for stmt in & bb_data. statements {
@@ -108,6 +145,11 @@ where L: Lattice,
108145 } ;
109146 let successors = new_term. successors ( ) . into_owned ( ) ;
110147 assert ! ( successors. len( ) == new_facts. len( ) , "new_facts.len() must match successor count" ) ;
148+ // Replace block first and update facts after. This order is important, because updating
149+ // facts for a block invalidates the block replacement. If you consider a case like a block
150+ // having a backedge into itself, then this particular ordering will correctly invalidate
151+ // the replacement block and put this block back into the queue for repeated analysis,
152+ // whereas the swapped ordering would not invalidate the replacement at all.
111153 self . replace_block ( bb, BasicBlockData {
112154 statements : new_stmts,
113155 terminator : Some ( new_term) ,
@@ -126,6 +168,7 @@ where L: Lattice,
126168 T : Transfer < ' tcx , Lattice =L , TerminatorReturn =L > ,
127169 R : Rewrite < ' tcx , T >
128170{
171+ /// Execute dataflow in backward direction.
129172 pub fn backward ( mir : & ' a Mir < ' tcx > , transfer : T , rewrite : R ) -> Mir < ' tcx > {
130173 let block_count = mir. basic_blocks ( ) . len ( ) ;
131174 let mut queue = BitVector :: new ( block_count) ;
@@ -184,9 +227,9 @@ where T: Transfer<'tcx>,
184227 // problematic, consider a constant propagation pass for a loop.
185228 //
186229 // → --- { idx = 1 } ---
187- // | idx = idx + 1 # replaced with `idx = 2`
188- // | if(...) break; # consider else branch taken
189- // ↑ --- { idx = 2 } ---
230+ // | idx = idx + 1 # replaced with `idx = 2`
231+ // | if(...) break; # consider else branch taken
232+ // ↑ --- { idx = 2 } --- # backedge to the top
190233 //
191234 // Here once we analyse the backedge the fact {idx = 1} is joined with fact {idx = 2}
192235 // producing a Top ({idx = ⊤}) and rendering our replacement of `idx = idx + 1` with `idx =
@@ -208,15 +251,15 @@ where T: Transfer<'tcx>,
208251 fn update_fact ( & mut self , bb : BasicBlock , new_fact : T :: Lattice ) -> bool {
209252 match self . knowledge [ bb] {
210253 ref mut val@None => {
211- // In case of no prior knowledge about this block, we essentially introduce new
212- // data and thus return true.
254+ // In case of no prior knowledge about this block, it means we are introducing new
255+ // knowledge, and therefore, must return true.
213256 * val = Some ( Knowledge { fact : new_fact, new_block : None } ) ;
214257 true
215258 } ,
216259 Some ( Knowledge { ref mut fact, ref mut new_block } ) => {
217260 let join = T :: Lattice :: join ( fact, new_fact) ;
218- // In case of some prior knowledge and provided our knowledge changed, we must
219- // invalidate any new_block that could exist.
261+ // In case of some prior knowledge and provided our knowledge changed, we should
262+ // invalidate any replacement block that could already exist.
220263 if join {
221264 * new_block = None ;
222265 }
@@ -235,6 +278,7 @@ where T: Transfer<'tcx>,
235278 }
236279 }
237280
281+ /// Build the new MIR by combining the replacement blocks and original MIR into a clone.
238282 fn construct_mir ( mut self ) -> Mir < ' tcx > {
239283 let mut new_blocks = IndexVec :: with_capacity ( self . mir . basic_blocks ( ) . len ( ) ) ;
240284 for ( block, old_data) in self . mir . basic_blocks ( ) . iter_enumerated ( ) {
@@ -261,8 +305,8 @@ where T: Transfer<'tcx>,
261305}
262306
263307fn mir_exits < ' tcx > ( mir : & Mir < ' tcx > , exits : & mut BitVector ) {
264- // Do this smartly. First of all, find all the nodes without successors (these are guaranteed
265- // exit nodes) .
308+ // Do this smartly (cough… using bruteforce… cough) . First of all, find all the nodes without
309+ // successors. These are guaranteed exit nodes.
266310 let mir_len = mir. basic_blocks ( ) . len ( ) ;
267311 let mut lead_to_exit = BitVector :: new ( mir_len) ;
268312 for ( block, block_data) in mir. basic_blocks ( ) . iter_enumerated ( ) {
@@ -284,8 +328,8 @@ fn mir_exits<'tcx>(mir: &Mir<'tcx>, exits: &mut BitVector) {
284328 // ```
285329 //
286330 // make it considerably more complex. In the end, it doesn’t matter very much which node we
287- // pick here, as picking any node does not make analysis incorrect, and a bad choice will only
288- // result in dataflow doing an extra pass over some of the blocks.
331+ // pick here. Picking any node inside the loop will make dataflow visit all the nodes, only
332+ // potentially doing an extra pass or two on a few blocks.
289333 lead_to_exit. invert ( ) ;
290334 while let Some ( exit) = lead_to_exit. pop ( ) {
291335 if exit >= mir_len { continue }
0 commit comments