// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.


/*!
 * A module for propagating forward dataflow information. The analysis
 * assumes that the items to be propagated can be represented as bits
 * and thus uses bitvectors. Your job is simply to specify the so-called
 * GEN and KILL bits for each expression.
 */


use std::cast;
use std::io;
use std::uint;
use std::vec;
use std::hashmap::HashMap;
use syntax::ast;
use syntax::ast_util;
use syntax::ast_util::id_range;
use syntax::print::{pp, pprust};
use middle::ty;
use middle::typeck;
use util::ppaux::Repr;

#[deriving(Clone)]
pub struct DataFlowContext<O> {
    priv tcx: ty::ctxt,
    priv method_map: typeck::method_map,

    /// the data flow operator
    priv oper: O,

    /// number of bits to propagate per id
    priv bits_per_id: uint,

    /// number of words we will use to store bits_per_id.
    /// equal to bits_per_id/uint::bits rounded up.
    priv words_per_id: uint,

    // mapping from node to bitset index.
    priv nodeid_to_bitset: HashMap<ast::NodeId,uint>,

    // Bit sets per id.  The following three fields (`gens`, `kills`,
    // and `on_entry`) all have the same structure. For each id in
    // `id_range`, there is a range of words equal to `words_per_id`.
    // So, to access the bits for any given id, you take a slice of
    // the full vector (see the method `compute_id_range()`).

    /// bits generated as we exit the scope `id`. Updated by `add_gen()`.
    priv gens: ~[uint],

    /// bits killed as we exit the scope `id`. Updated by `add_kill()`.
    priv kills: ~[uint],

    /// bits that are valid on entry to the scope `id`. Updated by
    /// `propagate()`.
    priv on_entry: ~[uint]
}

/// Parameterization for the precise form of data flow that is used.
pub trait DataFlowOperator {
    /// Specifies the initial value for each bit in the `on_entry` set
    fn initial_value(&self) -> bool;

    /// Joins two predecessor bits together, typically either `|` or `&`
    fn join(&self, succ: uint, pred: uint) -> uint;

    /// True if we should propagate through closures
    fn walk_closures(&self) -> bool;
}

struct PropagationContext<'self, O> {
    dfcx: &'self mut DataFlowContext<O>,
    changed: bool
}

struct LoopScope<'self> {
    loop_id: ast::NodeId,
    break_bits: ~[uint]
}

impl<O:DataFlowOperator> pprust::pp_ann for DataFlowContext<O> {
    fn pre(&self, node: pprust::ann_node) {
        let (ps, id) = match node {
            pprust::node_expr(ps, expr) => (ps, expr.id),
            pprust::node_block(ps, blk) => (ps, blk.id),
            pprust::node_item(ps, _) => (ps, 0),
            pprust::node_pat(ps, pat) => (ps, pat.id)
        };

        if self.nodeid_to_bitset.contains_key(&id) {
            let (start, end) = self.compute_id_range_frozen(id);
            let on_entry = self.on_entry.slice(start, end);
            let entry_str = bits_to_str(on_entry);

            let gens = self.gens.slice(start, end);
            let gens_str = if gens.iter().any(|&u| u != 0) {
                fmt!(" gen: %s", bits_to_str(gens))
            } else {
                ~""
            };

            let kills = self.kills.slice(start, end);
            let kills_str = if kills.iter().any(|&u| u != 0) {
                fmt!(" kill: %s", bits_to_str(kills))
            } else {
                ~""
            };

            let comment_str = fmt!("id %d: %s%s%s",
                                   id, entry_str, gens_str, kills_str);
            pprust::synth_comment(ps, comment_str);
            pp::space(ps.s);
        }
    }
}

impl<O:DataFlowOperator> DataFlowContext<O> {
    pub fn new(tcx: ty::ctxt,
               method_map: typeck::method_map,
               oper: O,
               id_range: id_range,
               bits_per_id: uint) -> DataFlowContext<O> {
        let words_per_id = (bits_per_id + uint::bits - 1) / uint::bits;

        debug!("DataFlowContext::new(id_range=%?, bits_per_id=%?, words_per_id=%?)",
               id_range, bits_per_id, words_per_id);

        let gens = ~[];
        let kills = ~[];
        let on_entry = ~[];

        DataFlowContext {
            tcx: tcx,
            method_map: method_map,
            words_per_id: words_per_id,
            nodeid_to_bitset: HashMap::new(),
            bits_per_id: bits_per_id,
            oper: oper,
            gens: gens,
            kills: kills,
            on_entry: on_entry
        }
    }

    pub fn add_gen(&mut self, id: ast::NodeId, bit: uint) {
        //! Indicates that `id` generates `bit`

        debug!("add_gen(id=%?, bit=%?)", id, bit);
        let (start, end) = self.compute_id_range(id);
        {
            let gens = self.gens.mut_slice(start, end);
            set_bit(gens, bit);
        }
    }

    pub fn add_kill(&mut self, id: ast::NodeId, bit: uint) {
        //! Indicates that `id` kills `bit`

        debug!("add_kill(id=%?, bit=%?)", id, bit);
        let (start, end) = self.compute_id_range(id);
        {
            let kills = self.kills.mut_slice(start, end);
            set_bit(kills, bit);
        }
    }

    fn apply_gen_kill(&mut self, id: ast::NodeId, bits: &mut [uint]) {
        //! Applies the gen and kill sets for `id` to `bits`

        debug!("apply_gen_kill(id=%?, bits=%s) [before]",
               id, mut_bits_to_str(bits));
        let (start, end) = self.compute_id_range(id);
        let gens = self.gens.slice(start, end);
        bitwise(bits, gens, |a, b| a | b);
        let kills = self.kills.slice(start, end);
        bitwise(bits, kills, |a, b| a & !b);

        debug!("apply_gen_kill(id=%?, bits=%s) [after]",
               id, mut_bits_to_str(bits));
    }

    fn apply_kill(&mut self, id: ast::NodeId, bits: &mut [uint]) {
        debug!("apply_kill(id=%?, bits=%s) [before]",
               id, mut_bits_to_str(bits));
        let (start, end) = self.compute_id_range(id);
        let kills = self.kills.slice(start, end);
        bitwise(bits, kills, |a, b| a & !b);
        debug!("apply_kill(id=%?, bits=%s) [after]",
               id, mut_bits_to_str(bits));
    }

    fn compute_id_range_frozen(&self, id: ast::NodeId) -> (uint, uint) {
        let n = *self.nodeid_to_bitset.get(&id);
        let start = n * self.words_per_id;
        let end = start + self.words_per_id;
        (start, end)
    }

    fn compute_id_range(&mut self, id: ast::NodeId) -> (uint, uint) {
        let mut expanded = false;
        let len = self.nodeid_to_bitset.len();
        let n = do self.nodeid_to_bitset.find_or_insert_with(id) |_| {
            expanded = true;
            len
        };
        if expanded {
            let entry = if self.oper.initial_value() { uint::max_value } else {0};
            do self.words_per_id.times {
                self.gens.push(0);
                self.kills.push(0);
                self.on_entry.push(entry);
            }
        }
        let start = *n * self.words_per_id;
        let end = start + self.words_per_id;

        assert!(start < self.gens.len());
        assert!(end <= self.gens.len());
        assert!(self.gens.len() == self.kills.len());
        assert!(self.gens.len() == self.on_entry.len());

        (start, end)
    }


    pub fn each_bit_on_entry_frozen(&self,
                                    id: ast::NodeId,
                                    f: &fn(uint) -> bool) -> bool {
        //! Iterates through each bit that is set on entry to `id`.
        //! Only useful after `propagate()` has been called.
        if !self.nodeid_to_bitset.contains_key(&id) {
            return true;
        }
        let (start, end) = self.compute_id_range_frozen(id);
        let on_entry = self.on_entry.slice(start, end);
        debug!("each_bit_on_entry_frozen(id=%?, on_entry=%s)",
               id, bits_to_str(on_entry));
        self.each_bit(on_entry, f)
    }

    pub fn each_bit_on_entry(&mut self,
                             id: ast::NodeId,
                             f: &fn(uint) -> bool) -> bool {
        //! Iterates through each bit that is set on entry to `id`.
        //! Only useful after `propagate()` has been called.

        let (start, end) = self.compute_id_range(id);
        let on_entry = self.on_entry.slice(start, end);
        debug!("each_bit_on_entry(id=%?, on_entry=%s)",
               id, bits_to_str(on_entry));
        self.each_bit(on_entry, f)
    }

    pub fn each_gen_bit(&mut self,
                        id: ast::NodeId,
                        f: &fn(uint) -> bool) -> bool {
        //! Iterates through each bit in the gen set for `id`.

        let (start, end) = self.compute_id_range(id);
        let gens = self.gens.slice(start, end);
        debug!("each_gen_bit(id=%?, gens=%s)",
               id, bits_to_str(gens));
        self.each_bit(gens, f)
    }

    pub fn each_gen_bit_frozen(&self,
                               id: ast::NodeId,
                               f: &fn(uint) -> bool) -> bool {
        //! Iterates through each bit in the gen set for `id`.
        if !self.nodeid_to_bitset.contains_key(&id) {
            return true;
        }
        let (start, end) = self.compute_id_range_frozen(id);
        let gens = self.gens.slice(start, end);
        debug!("each_gen_bit(id=%?, gens=%s)",
               id, bits_to_str(gens));
        self.each_bit(gens, f)
    }

    fn each_bit(&self,
                words: &[uint],
                f: &fn(uint) -> bool) -> bool {
        //! Helper for iterating over the bits in a bit set.

        for (word_index, &word) in words.iter().enumerate() {
            if word != 0 {
                let base_index = word_index * uint::bits;
                for offset in range(0u, uint::bits) {
                    let bit = 1 << offset;
                    if (word & bit) != 0 {
                        // NB: we round up the total number of bits
                        // that we store in any given bit set so that
                        // it is an even multiple of uint::bits.  This
                        // means that there may be some stray bits at
                        // the end that do not correspond to any
                        // actual value.  So before we callback, check
                        // whether the bit_index is greater than the
                        // actual value the user specified and stop
                        // iterating if so.
                        let bit_index = base_index + offset;
                        if bit_index >= self.bits_per_id {
                            return true;
                        } else if !f(bit_index) {
                            return false;
                        }
                    }
                }
            }
        }
        return true;
    }
}

impl<O:DataFlowOperator+Clone+'static> DataFlowContext<O> {
//                      ^^^^^^^^^^^^^ only needed for pretty printing
    pub fn propagate(&mut self, blk: &ast::Block) {
        //! Performs the data flow analysis.

        if self.bits_per_id == 0 {
            // Optimize the surprisingly common degenerate case.
            return;
        }

        {
            let mut propcx = PropagationContext {
                dfcx: self,
                changed: true
            };

            let mut temp = vec::from_elem(self.words_per_id, 0u);
            let mut loop_scopes = ~[];

            while propcx.changed {
                propcx.changed = false;
                propcx.reset(temp);
                propcx.walk_block(blk, temp, &mut loop_scopes);
            }
        }

        debug!("Dataflow result:");
        debug!("%s", {
            let this = @(*self).clone();
            this.pretty_print_to(io::stderr(), blk);
            ""
        });
    }

    fn pretty_print_to(@self, wr: @io::Writer, blk: &ast::Block) {
        let ps = pprust::rust_printer_annotated(wr,
                                                self.tcx.sess.intr(),
                                                self as @pprust::pp_ann);
        pprust::cbox(ps, pprust::indent_unit);
        pprust::ibox(ps, 0u);
        pprust::print_block(ps, blk);
        pp::eof(ps.s);
    }
}

impl<'self, O:DataFlowOperator> PropagationContext<'self, O> {
    fn tcx(&self) -> ty::ctxt {
        self.dfcx.tcx
    }

    fn walk_block(&mut self,
                  blk: &ast::Block,
                  in_out: &mut [uint],
                  loop_scopes: &mut ~[LoopScope]) {
        debug!("DataFlowContext::walk_block(blk.id=%?, in_out=%s)",
               blk.id, bits_to_str(reslice(in_out)));

        self.merge_with_entry_set(blk.id, in_out);

        for &stmt in blk.stmts.iter() {
            self.walk_stmt(stmt, in_out, loop_scopes);
        }

        self.walk_opt_expr(blk.expr, in_out, loop_scopes);

        self.dfcx.apply_gen_kill(blk.id, in_out);
    }

    fn walk_stmt(&mut self,
                 stmt: @ast::Stmt,
                 in_out: &mut [uint],
                 loop_scopes: &mut ~[LoopScope]) {
        match stmt.node {
            ast::StmtDecl(decl, _) => {
                self.walk_decl(decl, in_out, loop_scopes);
            }

            ast::StmtExpr(expr, _) | ast::StmtSemi(expr, _) => {
                self.walk_expr(expr, in_out, loop_scopes);
            }

            ast::StmtMac(*) => {
                self.tcx().sess.span_bug(stmt.span, "unexpanded macro");
            }
        }
    }

    fn walk_decl(&mut self,
                 decl: @ast::Decl,
                 in_out: &mut [uint],
                 loop_scopes: &mut ~[LoopScope]) {
        match decl.node {
            ast::DeclLocal(local) => {
                self.walk_opt_expr(local.init, in_out, loop_scopes);
                self.walk_pat(local.pat, in_out, loop_scopes);
            }

            ast::DeclItem(_) => {}
        }
    }

    fn walk_expr(&mut self,
                 expr: @ast::Expr,
                 in_out: &mut [uint],
                 loop_scopes: &mut ~[LoopScope]) {
        debug!("DataFlowContext::walk_expr(expr=%s, in_out=%s)",
               expr.repr(self.dfcx.tcx), bits_to_str(reslice(in_out)));

        self.merge_with_entry_set(expr.id, in_out);

        match expr.node {
            ast::ExprFnBlock(ref decl, ref body) => {
                if self.dfcx.oper.walk_closures() {
                    // In the absence of once fns, we must assume that
                    // every function body will execute more than
                    // once. Thus we treat every function body like a
                    // loop.
                    //
                    // What is subtle and a bit tricky, also, is how
                    // to deal with the "output" bits---that is, what
                    // do we consider to be the successor of a
                    // function body, given that it could be called
                    // from any point within its lifetime? What we do
                    // is to add their effects immediately as of the
                    // point of creation. Of course we have to ensure
                    // that this is sound for the analyses which make
                    // use of dataflow.
                    //
                    // In the case of the initedness checker (which
                    // does not currently use dataflow, but I hope to
                    // convert at some point), we will simply not walk
                    // closures at all, so it's a moot point.
                    //
                    // In the case of the borrow checker, this means
                    // the loans which would be created by calling a
                    // function come into effect immediately when the
                    // function is created. This is guaranteed to be
                    // earlier than the point at which the loan
                    // actually comes into scope (which is the point
                    // at which the closure is *called*). Because
                    // loans persist until the scope of the loans is
                    // exited, it is always a safe approximation to
                    // have a loan begin earlier than it actually will
                    // at runtime, so this should be sound.
                    //
                    // We stil have to be careful in the region
                    // checker and borrow checker to treat function
                    // bodies like loops, which implies some
                    // limitations. For example, a closure cannot root
                    // a managed box for longer than its body.
                    //
                    // General control flow looks like this:
                    //
                    //  +- (expr) <----------+
                    //  |    |               |
                    //  |    v               |
                    //  |  (body) -----------+--> (exit)
                    //  |    |               |
                    //  |    + (break/loop) -+
                    //  |                    |
                    //  +--------------------+
                    //
                    // This is a bit more conservative than a loop.
                    // Note that we must assume that even after a
                    // `break` occurs (e.g., in a `for` loop) that the
                    // closure may be reinvoked.
                    //
                    // One difference from other loops is that `loop`
                    // and `break` statements which target a closure
                    // both simply add to the `break_bits`.

                    // func_bits represents the state when the function
                    // returns
                    let mut func_bits = reslice(in_out).to_owned();

                    loop_scopes.push(LoopScope {
                        loop_id: expr.id,
                        break_bits: reslice(in_out).to_owned()
                    });
                    for input in decl.inputs.iter() {
                        self.walk_pat(input.pat, func_bits, loop_scopes);
                    }
                    self.walk_block(body, func_bits, loop_scopes);

                    // add the bits from any early return via `break`,
                    // `continue`, or `return` into `func_bits`
                    let loop_scope = loop_scopes.pop();
                    join_bits(&self.dfcx.oper, loop_scope.break_bits, func_bits);

                    // add `func_bits` to the entry bits for `expr`,
                    // since we must assume the function may be called
                    // more than once
                    self.add_to_entry_set(expr.id, reslice(func_bits));

                    // the final exit bits include whatever was present
                    // in the original, joined with the bits from the function
                    join_bits(&self.dfcx.oper, func_bits, in_out);
                }
            }

            ast::ExprIf(cond, ref then, els) => {
                //
                //     (cond)
                //       |
                //       v
                //      ( )
                //     /   \
                //    |     |
                //    v     v
                //  (then)(els)
                //    |     |
                //    v     v
                //   (  succ  )
                //
                self.walk_expr(cond, in_out, loop_scopes);

                let mut then_bits = reslice(in_out).to_owned();
                self.walk_block(then, then_bits, loop_scopes);

                self.walk_opt_expr(els, in_out, loop_scopes);
                join_bits(&self.dfcx.oper, then_bits, in_out);
            }

            ast::ExprWhile(cond, ref blk) => {
                //
                //     (expr) <--+
                //       |       |
                //       v       |
                //  +--(cond)    |
                //  |    |       |
                //  |    v       |
                //  v  (blk) ----+
                //       |
                //    <--+ (break)
                //

                self.walk_expr(cond, in_out, loop_scopes);

                let mut body_bits = reslice(in_out).to_owned();
                loop_scopes.push(LoopScope {
                    loop_id: expr.id,
                    break_bits: reslice(in_out).to_owned()
                });
                self.walk_block(blk, body_bits, loop_scopes);
                self.add_to_entry_set(expr.id, body_bits);
                let new_loop_scope = loop_scopes.pop();
                copy_bits(new_loop_scope.break_bits, in_out);
            }

            ast::ExprForLoop(*) => fail!("non-desugared expr_for_loop"),

            ast::ExprLoop(ref blk, _) => {
                //
                //     (expr) <--+
                //       |       |
                //       v       |
                //     (blk) ----+
                //       |
                //    <--+ (break)
                //

                let mut body_bits = reslice(in_out).to_owned();
                self.reset(in_out);
                loop_scopes.push(LoopScope {
                    loop_id: expr.id,
                    break_bits: reslice(in_out).to_owned()
                });
                self.walk_block(blk, body_bits, loop_scopes);
                self.add_to_entry_set(expr.id, body_bits);

                let new_loop_scope = loop_scopes.pop();
                assert_eq!(new_loop_scope.loop_id, expr.id);
                copy_bits(new_loop_scope.break_bits, in_out);
            }

            ast::ExprMatch(discr, ref arms) => {
                //
                //    (discr)
                //     / | \
                //    |  |  |
                //    v  v  v
                //   (..arms..)
                //    |  |  |
                //    v  v  v
                //   (  succ  )
                //
                //
                self.walk_expr(discr, in_out, loop_scopes);

                let mut guards = reslice(in_out).to_owned();

                // We know that exactly one arm will be taken, so we
                // can start out with a blank slate and just union
                // together the bits from each arm:
                self.reset(in_out);

                for arm in arms.iter() {
                    // in_out reflects the discr and all guards to date
                    self.walk_opt_expr(arm.guard, guards, loop_scopes);

                    // determine the bits for the body and then union
                    // them into `in_out`, which reflects all bodies to date
                    let mut body = reslice(guards).to_owned();
                    self.walk_pat_alternatives(arm.pats, body, loop_scopes);
                    self.walk_block(&arm.body, body, loop_scopes);
                    join_bits(&self.dfcx.oper, body, in_out);
                }
            }

            ast::ExprRet(o_e) => {
                self.walk_opt_expr(o_e, in_out, loop_scopes);
                self.reset(in_out);
            }

            ast::ExprBreak(label) => {
                let scope = self.find_scope(expr, label, loop_scopes);
                self.break_from_to(expr, scope, in_out);
                self.reset(in_out);
            }

            ast::ExprAgain(label) => {
                let scope = self.find_scope(expr, label, loop_scopes);
                self.pop_scopes(expr, scope, in_out);
                self.add_to_entry_set(scope.loop_id, reslice(in_out));
                self.reset(in_out);
            }

            ast::ExprAssign(l, r) |
            ast::ExprAssignOp(_, _, l, r) => {
                self.walk_expr(r, in_out, loop_scopes);
                self.walk_expr(l, in_out, loop_scopes);
            }

            ast::ExprVec(ref exprs, _) => {
                self.walk_exprs(*exprs, in_out, loop_scopes)
            }

            ast::ExprRepeat(l, r, _) => {
                self.walk_expr(l, in_out, loop_scopes);
                self.walk_expr(r, in_out, loop_scopes);
            }

            ast::ExprStruct(_, ref fields, with_expr) => {
                for field in fields.iter() {
                    self.walk_expr(field.expr, in_out, loop_scopes);
                }
                self.walk_opt_expr(with_expr, in_out, loop_scopes);
            }

            ast::ExprCall(f, ref args, _) => {
                self.walk_call(f.id, expr.id,
                               f, *args, in_out, loop_scopes);
            }

            ast::ExprMethodCall(callee_id, rcvr, _, _, ref args, _) => {
                self.walk_call(callee_id, expr.id,
                               rcvr, *args, in_out, loop_scopes);
            }

            ast::ExprIndex(callee_id, l, r) |
            ast::ExprBinary(callee_id, _, l, r) if self.is_method_call(expr) => {
                self.walk_call(callee_id, expr.id,
                               l, [r], in_out, loop_scopes);
            }

            ast::ExprUnary(callee_id, _, e) if self.is_method_call(expr) => {
                self.walk_call(callee_id, expr.id,
                               e, [], in_out, loop_scopes);
            }

            ast::ExprTup(ref exprs) => {
                self.walk_exprs(*exprs, in_out, loop_scopes);
            }

            ast::ExprBinary(_, op, l, r) if ast_util::lazy_binop(op) => {
                self.walk_expr(l, in_out, loop_scopes);
                let temp = reslice(in_out).to_owned();
                self.walk_expr(r, in_out, loop_scopes);
                join_bits(&self.dfcx.oper, temp, in_out);
            }

            ast::ExprLog(l, r) |
            ast::ExprIndex(_, l, r) |
            ast::ExprBinary(_, _, l, r) => {
                self.walk_exprs([l, r], in_out, loop_scopes);
            }

            ast::ExprLit(*) |
            ast::ExprPath(*) |
            ast::ExprSelf => {
            }

            ast::ExprAddrOf(_, e) |
            ast::ExprDoBody(e) |
            ast::ExprCast(e, _) |
            ast::ExprUnary(_, _, e) |
            ast::ExprParen(e) |
            ast::ExprVstore(e, _) |
            ast::ExprField(e, _, _) => {
                self.walk_expr(e, in_out, loop_scopes);
            }

            ast::ExprInlineAsm(ref inline_asm) => {
                for &(_, expr) in inline_asm.inputs.iter() {
                    self.walk_expr(expr, in_out, loop_scopes);
                }
                for &(_, expr) in inline_asm.outputs.iter() {
                    self.walk_expr(expr, in_out, loop_scopes);
                }
            }

            ast::ExprBlock(ref blk) => {
                self.walk_block(blk, in_out, loop_scopes);
            }

            ast::ExprMac(*) => {
                self.tcx().sess.span_bug(expr.span, "unexpanded macro");
            }
        }

        self.dfcx.apply_gen_kill(expr.id, in_out);
    }

    fn pop_scopes(&mut self,
                  from_expr: @ast::Expr,
                  to_scope: &mut LoopScope,
                  in_out: &mut [uint]) {
        //! Whenever you have a `break` or a `loop` statement, flow
        //! exits through any number of enclosing scopes on its
        //! way to the new destination. This function applies the kill
        //! sets of those enclosing scopes to `in_out` (those kill sets
        //! concern items that are going out of scope).

        let tcx = self.tcx();
        let region_maps = tcx.region_maps;

        debug!("pop_scopes(from_expr=%s, to_scope=%?, in_out=%s)",
               from_expr.repr(tcx), to_scope.loop_id,
               bits_to_str(reslice(in_out)));

        let mut id = from_expr.id;
        while id != to_scope.loop_id {
            self.dfcx.apply_kill(id, in_out);

            match region_maps.opt_encl_scope(id) {
                Some(i) => { id = i; }
                None => {
                    tcx.sess.span_bug(
                        from_expr.span,
                        fmt!("pop_scopes(from_expr=%s, to_scope=%?) \
                              to_scope does not enclose from_expr",
                             from_expr.repr(tcx), to_scope.loop_id));
                }
            }
        }
    }

    fn break_from_to(&mut self,
                     from_expr: @ast::Expr,
                     to_scope: &mut LoopScope,
                     in_out: &mut [uint]) {
        self.pop_scopes(from_expr, to_scope, in_out);
        self.dfcx.apply_kill(from_expr.id, in_out);
        join_bits(&self.dfcx.oper, reslice(in_out), to_scope.break_bits);
        debug!("break_from_to(from_expr=%s, to_scope=%?) final break_bits=%s",
               from_expr.repr(self.tcx()),
               to_scope.loop_id,
               bits_to_str(reslice(in_out)));
    }

    fn walk_exprs(&mut self,
                  exprs: &[@ast::Expr],
                  in_out: &mut [uint],
                  loop_scopes: &mut ~[LoopScope]) {
        for &expr in exprs.iter() {
            self.walk_expr(expr, in_out, loop_scopes);
        }
    }

    fn walk_opt_expr(&mut self,
                     opt_expr: Option<@ast::Expr>,
                     in_out: &mut [uint],
                     loop_scopes: &mut ~[LoopScope]) {
        for &expr in opt_expr.iter() {
            self.walk_expr(expr, in_out, loop_scopes);
        }
    }

    fn walk_call(&mut self,
                 _callee_id: ast::NodeId,
                 call_id: ast::NodeId,
                 arg0: @ast::Expr,
                 args: &[@ast::Expr],
                 in_out: &mut [uint],
                 loop_scopes: &mut ~[LoopScope]) {
        self.walk_expr(arg0, in_out, loop_scopes);
        self.walk_exprs(args, in_out, loop_scopes);

        // FIXME(#6268) nested method calls
        // self.merge_with_entry_set(callee_id, in_out);
        // self.dfcx.apply_gen_kill(callee_id, in_out);

        let return_ty = ty::node_id_to_type(self.tcx(), call_id);
        let fails = ty::type_is_bot(return_ty);
        if fails {
            self.reset(in_out);
        }
    }

    fn walk_pat(&mut self,
                pat: @ast::Pat,
                in_out: &mut [uint],
                _loop_scopes: &mut ~[LoopScope]) {
        debug!("DataFlowContext::walk_pat(pat=%s, in_out=%s)",
               pat.repr(self.dfcx.tcx), bits_to_str(reslice(in_out)));

        do ast_util::walk_pat(pat) |p| {
            debug!("  p.id=%? in_out=%s", p.id, bits_to_str(reslice(in_out)));
            self.merge_with_entry_set(p.id, in_out);
            self.dfcx.apply_gen_kill(p.id, in_out);
            true
        };
    }

    fn walk_pat_alternatives(&mut self,
                             pats: &[@ast::Pat],
                             in_out: &mut [uint],
                             loop_scopes: &mut ~[LoopScope]) {
        if pats.len() == 1 {
            // Common special case:
            return self.walk_pat(pats[0], in_out, loop_scopes);
        }

        // In the general case, the patterns in `pats` are
        // alternatives, so we must treat this like an N-way select
        // statement.
        let initial_state = reslice(in_out).to_owned();
        for &pat in pats.iter() {
            let mut temp = initial_state.clone();
            self.walk_pat(pat, temp, loop_scopes);
            join_bits(&self.dfcx.oper, temp, in_out);
        }
    }

    fn find_scope<'a>(&self,
                      expr: @ast::Expr,
                      label: Option<ast::Ident>,
                      loop_scopes: &'a mut ~[LoopScope]) -> &'a mut LoopScope {
        let index = match label {
            None => {
                let len = loop_scopes.len();
                len - 1
            }

            Some(_) => {
                match self.tcx().def_map.find(&expr.id) {
                    Some(&ast::DefLabel(loop_id)) => {
                        match loop_scopes.iter().position(|l| l.loop_id == loop_id) {
                            Some(i) => i,
                            None => {
                                self.tcx().sess.span_bug(
                                    expr.span,
                                    fmt!("No loop scope for id %?", loop_id));
                            }
                        }
                    }

                    r => {
                        self.tcx().sess.span_bug(
                            expr.span,
                            fmt!("Bad entry `%?` in def_map for label", r));
                    }
                }
            }
        };

        &mut loop_scopes[index]
    }

    fn is_method_call(&self, expr: @ast::Expr) -> bool {
        self.dfcx.method_map.contains_key(&expr.id)
    }

    fn reset(&mut self, bits: &mut [uint]) {
        let e = if self.dfcx.oper.initial_value() {uint::max_value} else {0};
        for b in bits.mut_iter() { *b = e; }
    }

    fn add_to_entry_set(&mut self, id: ast::NodeId, pred_bits: &[uint]) {
        debug!("add_to_entry_set(id=%?, pred_bits=%s)",
               id, bits_to_str(pred_bits));
        let (start, end) = self.dfcx.compute_id_range(id);
        let changed = { // FIXME(#5074) awkward construction
            let on_entry = self.dfcx.on_entry.mut_slice(start, end);
            join_bits(&self.dfcx.oper, pred_bits, on_entry)
        };
        if changed {
            debug!("changed entry set for %? to %s",
                   id, bits_to_str(self.dfcx.on_entry.slice(start, end)));
            self.changed = true;
        }
    }

    fn merge_with_entry_set(&mut self,
                            id: ast::NodeId,
                            pred_bits: &mut [uint]) {
        debug!("merge_with_entry_set(id=%?, pred_bits=%s)",
               id, mut_bits_to_str(pred_bits));
        let (start, end) = self.dfcx.compute_id_range(id);
        let changed = { // FIXME(#5074) awkward construction
            let on_entry = self.dfcx.on_entry.mut_slice(start, end);
            let changed = join_bits(&self.dfcx.oper, reslice(pred_bits), on_entry);
            copy_bits(reslice(on_entry), pred_bits);
            changed
        };
        if changed {
            debug!("changed entry set for %? to %s",
                   id, bits_to_str(self.dfcx.on_entry.slice(start, end)));
            self.changed = true;
        }
    }
}

fn mut_bits_to_str(words: &mut [uint]) -> ~str {
    bits_to_str(reslice(words))
}

fn bits_to_str(words: &[uint]) -> ~str {
    let mut result = ~"";
    let mut sep = '[';

    // Note: this is a little endian printout of bytes.

    for &word in words.iter() {
        let mut v = word;
        for _ in range(0u, uint::bytes) {
            result.push_char(sep);
            result.push_str(fmt!("%02x", v & 0xFF));
            v >>= 8;
            sep = '-';
        }
    }
    result.push_char(']');
    return result;
}

fn copy_bits(in_vec: &[uint], out_vec: &mut [uint]) -> bool {
    bitwise(out_vec, in_vec, |_, b| b)
}

fn join_bits<O:DataFlowOperator>(oper: &O,
                                 in_vec: &[uint],
                                 out_vec: &mut [uint]) -> bool {
    bitwise(out_vec, in_vec, |a, b| oper.join(a, b))
}

#[inline]
fn bitwise(out_vec: &mut [uint],
           in_vec: &[uint],
           op: &fn(uint, uint) -> uint) -> bool {
    assert_eq!(out_vec.len(), in_vec.len());
    let mut changed = false;
    for (out_elt, in_elt) in out_vec.mut_iter().zip(in_vec.iter()) {
        let old_val = *out_elt;
        let new_val = op(old_val, *in_elt);
        *out_elt = new_val;
        changed |= (old_val != new_val);
    }
    changed
}

fn set_bit(words: &mut [uint], bit: uint) -> bool {
    debug!("set_bit: words=%s bit=%s",
           mut_bits_to_str(words), bit_str(bit));
    let word = bit / uint::bits;
    let bit_in_word = bit % uint::bits;
    let bit_mask = 1 << bit_in_word;
    debug!("word=%u bit_in_word=%u bit_mask=%u", word, bit_in_word, word);
    let oldv = words[word];
    let newv = oldv | bit_mask;
    words[word] = newv;
    oldv != newv
}

fn bit_str(bit: uint) -> ~str {
    let byte = bit >> 8;
    let lobits = 1 << (bit & 0xFF);
    fmt!("[%u:%u-%02x]", bit, byte, lobits)
}

fn reslice<'a>(v: &'a mut [uint]) -> &'a [uint] {
    // bFIXME(#5074) this function should not be necessary at all
    unsafe {
        cast::transmute(v)
    }
}
