rust/src/comp/middle/last_use.rs
2012-01-23 19:06:33 -08:00

307 lines
9.3 KiB
Rust

import syntax::{visit, ast_util};
import syntax::ast::*;
import syntax::codemap::span;
import std::list::{is_not_empty, list, nil, cons, tail};
import core::{vec, option};
import std::list;
// Last use analysis pass.
//
// Finds the last read of each value stored in a local variable or
// callee-owned argument (arguments with by-move or by-copy passing
// style). This is a limited form of liveness analysis, peformed
// (perhaps foolishly) directly on the AST.
//
// The algorithm walks the AST, keeping a set of (def, last_use)
// pairs. When the function is exited, or the local is overwritten,
// the current set of last uses is marked with 'true' in a table.
// Other branches may later overwrite them with 'false' again, since
// they may find a use coming after them. (Marking an expression as a
// last use is only done if it has not already been marked with
// 'false'.)
//
// Some complexity is added to deal with joining control flow branches
// (by `break` or conditionals), and for handling loops.
// Marks expr_paths that are last uses.
type last_uses = std::map::hashmap<node_id, ()>;
enum seen { unset, seen(node_id), }
enum block_type { func, loop, }
type set = [{def: node_id, exprs: list<node_id>}];
type bl = @{type: block_type, mutable second: bool, mutable exits: [set]};
type ctx = {last_uses: std::map::hashmap<node_id, bool>,
def_map: resolve::def_map,
ref_map: alias::ref_map,
tcx: ty::ctxt,
// The current set of local last uses
mutable current: set,
mutable blocks: list<bl>};
fn find_last_uses(c: @crate, def_map: resolve::def_map,
ref_map: alias::ref_map, tcx: ty::ctxt) -> last_uses {
let v = visit::mk_vt(@{visit_expr: visit_expr,
visit_fn: visit_fn
with *visit::default_visitor()});
let cx = {last_uses: std::map::new_int_hash(),
def_map: def_map,
ref_map: ref_map,
tcx: tcx,
mutable current: [],
mutable blocks: nil};
visit::visit_crate(*c, cx, v);
let mini_table = std::map::new_int_hash();
cx.last_uses.items {|key, val|
if val {
mini_table.insert(key, ());
let def_node = ast_util::def_id_of_def(def_map.get(key)).node;
mini_table.insert(def_node, ());
}
}
ret mini_table;
}
fn ex_is_blockish(cx: ctx, id: node_id) -> bool {
alt ty::struct(cx.tcx, ty::node_id_to_monotype(cx.tcx, id)) {
ty::ty_fn({proto: p, _}) if is_blockish(p) { true }
_ { false }
}
}
fn visit_expr(ex: @expr, cx: ctx, v: visit::vt<ctx>) {
alt ex.node {
expr_ret(oexpr) {
visit::visit_expr_opt(oexpr, cx, v);
if !add_block_exit(cx, func) { leave_fn(cx); }
}
expr_fail(oexpr) {
visit::visit_expr_opt(oexpr, cx, v);
leave_fn(cx);
}
expr_break { add_block_exit(cx, loop); }
expr_while(_, _) | expr_do_while(_, _) {
visit_block(loop, cx) {|| visit::visit_expr(ex, cx, v);}
}
expr_for(_, coll, blk) {
v.visit_expr(coll, cx, v);
visit_block(loop, cx) {|| visit::visit_block(blk, cx, v);}
}
expr_ternary(_, _, _) {
v.visit_expr(ast_util::ternary_to_if(ex), cx, v);
}
expr_alt(input, arms) {
v.visit_expr(input, cx, v);
let before = cx.current, sets = [];
for arm in arms {
cx.current = before;
v.visit_arm(arm, cx, v);
sets += [cx.current];
}
cx.current = join_branches(sets);
}
expr_if(cond, then, els) {
v.visit_expr(cond, cx, v);
let cur = cx.current;
visit::visit_block(then, cx, v);
cx.current <-> cur;
visit::visit_expr_opt(els, cx, v);
cx.current = join_branches([cur, cx.current]);
}
expr_path(_) {
let my_def = ast_util::def_id_of_def(cx.def_map.get(ex.id)).node;
alt cx.ref_map.find(my_def) {
option::some(root_id) { clear_in_current(cx, root_id, false); }
_ {
alt clear_if_path(cx, ex, v, false) {
option::some(my_def) {
cx.current += [{def: my_def, exprs: cons(ex.id, @nil)}];
}
_ {}
}
}
}
}
expr_swap(lhs, rhs) {
clear_if_path(cx, lhs, v, false);
clear_if_path(cx, rhs, v, false);
}
expr_move(dest, src) | expr_assign(dest, src) {
v.visit_expr(src, cx, v);
clear_if_path(cx, dest, v, true);
}
expr_assign_op(_, dest, src) {
v.visit_expr(src, cx, v);
v.visit_expr(dest, cx, v);
clear_if_path(cx, dest, v, true);
}
expr_fn(_, _, _, cap_clause) {
// n.b.: safe to ignore copies, as if they are unused
// then they are ignored, otherwise they will show up
// as freevars in the body.
vec::iter(cap_clause.moves) {|ci|
clear_def_if_path(cx, cx.def_map.get(ci.id), true);
}
visit::visit_expr(ex, cx, v);
}
expr_call(f, args, _) {
v.visit_expr(f, cx, v);
let i = 0u, fns = [];
let arg_ts = ty::ty_fn_args(cx.tcx, ty::expr_ty(cx.tcx, f));
for arg in args {
alt arg.node {
expr_fn(p, _, _, _) if is_blockish(p) {
fns += [arg];
}
expr_fn_block(_, _) if ex_is_blockish(cx, arg.id) {
fns += [arg];
}
_ {
alt arg_ts[i].mode {
by_mut_ref { clear_if_path(cx, arg, v, false); }
_ { v.visit_expr(arg, cx, v); }
}
}
}
i += 1u;
}
for f in fns { v.visit_expr(f, cx, v); }
}
_ { visit::visit_expr(ex, cx, v); }
}
}
fn visit_fn(fk: visit::fn_kind, decl: fn_decl, body: blk,
sp: span, id: node_id,
cx: ctx, v: visit::vt<ctx>) {
let fty = ty::node_id_to_type(cx.tcx, id);
let proto = ty::ty_fn_proto(cx.tcx, fty);
alt proto {
proto_any | proto_block {
visit_block(func, cx, {||
visit::visit_fn(fk, decl, body, sp, id, cx, v);
});
}
proto_box | proto_uniq | proto_bare {
alt cx.tcx.freevars.find(id) {
some(vars) {
for v in *vars {
clear_in_current(cx, ast_util::def_id_of_def(v.def).node,
false);
}
}
_ {}
}
let old = nil;
cx.blocks <-> old;
visit::visit_fn(fk, decl, body, sp, id, cx, v);
cx.blocks <-> old;
leave_fn(cx);
}
}
}
fn visit_block(tp: block_type, cx: ctx, visit: fn()) {
let local = @{type: tp, mutable second: false, mutable exits: []};
cx.blocks = cons(local, @cx.blocks);
visit();
local.second = true;
visit();
let cx_blocks = cx.blocks;
check is_not_empty(cx_blocks);
cx.blocks = tail(cx_blocks);
cx.current = join_branches(local.exits);
}
fn add_block_exit(cx: ctx, tp: block_type) -> bool {
let cur = cx.blocks;
while cur != nil {
alt cur {
cons(b, tail) {
if (b.type == tp) {
if !b.second { b.exits += [cx.current]; }
ret true;
}
cur = *tail;
}
}
}
ret false;
}
fn join_branches(branches: [set]) -> set {
let found: set = [], i = 0u, l = vec::len(branches);
for set in branches {
i += 1u;
for {def, exprs} in set {
if !vec::any(found, {|v| v.def == def}) {
let j = i, nne = exprs;
while j < l {
for {def: d2, exprs} in branches[j] {
if d2 == def {
list::iter(exprs) {|e|
if !list::has(nne, e) { nne = cons(e, @nne); }
}
}
}
j += 1u;
}
found += [{def: def, exprs: nne}];
}
}
}
ret found;
}
fn leave_fn(cx: ctx) {
for {def, exprs} in cx.current {
list::iter(exprs) {|ex_id|
if !cx.last_uses.contains_key(ex_id) {
cx.last_uses.insert(ex_id, true);
}
}
}
}
fn clear_in_current(cx: ctx, my_def: node_id, to: bool) {
for {def, exprs} in cx.current {
if def == my_def {
list::iter(exprs) {|expr|
if !to || !cx.last_uses.contains_key(expr) {
cx.last_uses.insert(expr, to);
}
}
cx.current = vec::filter(copy cx.current,
{|x| x.def != my_def});
break;
}
}
}
fn clear_def_if_path(cx: ctx, d: def, to: bool)
-> option<node_id> {
alt d {
def_local(def_id, let_copy) | def_arg(def_id, by_copy) |
def_arg(def_id, by_move) {
clear_in_current(cx, def_id.node, to);
some(def_id.node)
}
_ {
none
}
}
}
fn clear_if_path(cx: ctx, ex: @expr, v: visit::vt<ctx>, to: bool)
-> option::t<node_id> {
alt ex.node {
expr_path(_) {
ret clear_def_if_path(cx, cx.def_map.get(ex.id), to);
}
_ { v.visit_expr(ex, cx, v); }
}
ret option::none;
}