rust/src/rustc/middle/liveness.rs
2012-06-13 11:30:45 -07:00

1737 lines
57 KiB
Rust

/*
A classic liveness analysis based on dataflow over the AST. Computes,
for each local variable in a function, whether that variable is live
at a given point. Program execution points are identified by their
id.
# Basic idea
The basic model is that each local variable is assigned an index. We
represent sets of local variables using a vector indexed by this
index. The value in the vector is either 0, indicating the variable
is dead, or the id of an expression that uses the variable.
We conceptually walk over the AST in reverse execution order. If we
find a use of a variable, we add it to the set of live variables. If
we find an assignment to a variable, we remove it from the set of live
variables. When we have to merge two flows, we take the union of
those two flows---if the variable is live on both paths, we simply
pick one id. In the event of loops, we continue doing this until a
fixed point is reached.
## Checking initialization
At the function entry point, all variables must be dead. If this is
not the case, we can report an error using the id found in the set of
live variables, which identifies a use of the variable which is not
dominated by an assignment.
## Checking moves
After each explicit move, the variable must be dead.
## Computing last uses
Any use of the variable where the variable is dead afterwards is a
last use.
# Extension to handle constructors
Each field is assigned an index just as with local variables. A use of
`self` is considered a use of all fields. A use of `self.f` is just a use
of `f`.
*/
import dvec::{dvec, extensions};
import std::map::{hashmap, int_hash, str_hash, box_str_hash};
import syntax::{visit, ast_util};
import syntax::print::pprust::{expr_to_str};
import visit::vt;
import syntax::codemap::span;
import syntax::ast::*;
import driver::session::session;
import io::writer_util;
import capture::{cap_move, cap_drop, cap_copy, cap_ref};
export check_crate;
export last_use_map;
// Maps from an expr id to a list of variable ids for which this expr
// is the last use. Typically, the expr is a path and the node id is
// the local/argument/etc that the path refers to. However, it also
// possible for the expr to be a closure, in which case the list is a
// list of closed over variables that can be moved into the closure.
type last_use_map = hashmap<node_id, @dvec<node_id>>;
enum variable = uint;
enum live_node = uint;
enum live_node_kind {
lnk_freevar(span),
lnk_expr(span),
lnk_vdef(span),
lnk_exit
}
fn check_crate(tcx: ty::ctxt,
method_map: typeck::method_map,
crate: @crate) -> last_use_map {
let visitor = visit::mk_vt(@{
visit_fn: visit_fn,
visit_local: visit_local,
visit_expr: visit_expr
with *visit::default_visitor()
});
let last_use_map = int_hash();
let initial_maps = @ir_maps(tcx, method_map,
last_use_map);
visit::visit_crate(*crate, initial_maps, visitor);
tcx.sess.abort_if_errors();
ret last_use_map;
}
impl of to_str::to_str for live_node {
fn to_str() -> str { #fmt["ln(%u)", *self] }
}
impl of to_str::to_str for variable {
fn to_str() -> str { #fmt["v(%u)", *self] }
}
// ______________________________________________________________________
// Creating ir_maps
//
// This is the first pass and the one that drives the main
// computation. It walks up and down the IR once. On the way down,
// we count for each function the number of variables as well as
// liveness nodes. A liveness node is basically an expression or
// capture clause that does something of interest: either it has
// interesting control flow or it uses/defines a local variable.
//
// On the way back up, at each function node we create liveness sets
// (we now know precisely how big to make our various vectors and so
// forth) and then do the data-flow propagation to compute the set
// of live variables at each program point.
//
// Finally, we run back over the IR one last time and, using the
// computed liveness, check various safety conditions. For example,
// there must be no live nodes at the definition site for a variable
// unless it has an initializer. Similarly, each non-mutable local
// variable must not be assigned if there is some successor
// assignment. And so forth.
impl methods for live_node {
pure fn is_valid() -> bool { *self != uint::max_value }
}
fn invalid_node() -> live_node { live_node(uint::max_value) }
enum relevant_def { rdef_var(node_id), rdef_self }
type capture_info = {ln: live_node, is_move: bool, rv: relevant_def};
enum var_kind {
vk_arg(node_id, ident, rmode),
vk_local(node_id, ident),
vk_field(ident),
vk_self,
vk_implicit_ret
}
fn relevant_def(def: def) -> option<relevant_def> {
alt def {
def_self(_) {some(rdef_self)}
def_arg(nid, _) | def_local(nid, _) {some(rdef_var(nid))}
_ {none}
}
}
class ir_maps {
let tcx: ty::ctxt;
let method_map: typeck::method_map;
let last_use_map: last_use_map;
let mut num_live_nodes: uint;
let mut num_vars: uint;
let live_node_map: hashmap<node_id, live_node>;
let variable_map: hashmap<node_id, variable>;
let field_map: hashmap<ident, variable>;
let capture_map: hashmap<node_id, @[capture_info]>;
let mut var_kinds: [var_kind];
let mut lnks: [live_node_kind];
new(tcx: ty::ctxt, method_map: typeck::method_map,
last_use_map: last_use_map) {
self.tcx = tcx;
self.method_map = method_map;
self.last_use_map = last_use_map;
self.num_live_nodes = 0u;
self.num_vars = 0u;
self.live_node_map = int_hash();
self.variable_map = int_hash();
self.capture_map = int_hash();
self.field_map = box_str_hash();
self.var_kinds = [];
self.lnks = [];
}
fn add_live_node(lnk: live_node_kind) -> live_node {
let ln = live_node(self.num_live_nodes);
self.lnks += [lnk];
self.num_live_nodes += 1u;
#debug["%s is of kind %?", ln.to_str(), lnk];
ln
}
fn add_live_node_for_node(node_id: node_id, lnk: live_node_kind) {
let ln = self.add_live_node(lnk);
self.live_node_map.insert(node_id, ln);
#debug["%s is node %d", ln.to_str(), node_id];
}
fn add_variable(vk: var_kind) -> variable {
let v = variable(self.num_vars);
self.var_kinds += [vk];
self.num_vars += 1u;
alt vk {
vk_local(node_id, _) | vk_arg(node_id, _, _) {
self.variable_map.insert(node_id, v);
}
vk_field(name) {
self.field_map.insert(name, v);
}
vk_self | vk_implicit_ret {
}
}
#debug["%s is %?", v.to_str(), vk];
v
}
fn variable(node_id: node_id, span: span) -> variable {
alt self.variable_map.find(node_id) {
some(var) {var}
none {
self.tcx.sess.span_bug(
span, "No variable registered for this id");
}
}
}
fn variable_name(var: variable) -> ident {
alt self.var_kinds[*var] {
vk_local(_, name) | vk_arg(_, name, _) {name}
vk_field(name) {@("self." + *name)}
vk_self {@"self"}
vk_implicit_ret {@"<implicit-ret>"}
}
}
fn set_captures(node_id: node_id, +cs: [capture_info]) {
self.capture_map.insert(node_id, @cs);
}
fn captures(expr: @expr) -> @[capture_info] {
alt self.capture_map.find(expr.id) {
some(caps) {caps}
none {
self.tcx.sess.span_bug(expr.span, "no registered caps");
}
}
}
fn lnk(ln: live_node) -> live_node_kind {
self.lnks[*ln]
}
fn add_last_use(expr_id: node_id, var: variable) {
let vk = self.var_kinds[*var];
#debug["Node %d is a last use of variable %?", expr_id, vk];
alt vk {
vk_arg(id, name, by_move) |
vk_arg(id, name, by_copy) |
vk_local(id, name) {
let v = alt self.last_use_map.find(expr_id) {
some(v) { v }
none {
let v = @dvec();
self.last_use_map.insert(expr_id, v);
v
}
};
(*v).push(id);
}
vk_arg(_, _, by_ref) | vk_arg(_, _, by_mutbl_ref) |
vk_arg(_, _, by_val) | vk_self | vk_field(_) | vk_implicit_ret {
#debug["--but it is not owned"];
}
}
}
}
fn visit_fn(fk: visit::fn_kind, decl: fn_decl, body: blk,
sp: span, id: node_id, &&self: @ir_maps, v: vt<@ir_maps>) {
#debug["visit_fn: id=%d", id];
let _i = util::common::indenter();
// swap in a new set of IR maps for this function body:
let fn_maps = @ir_maps(self.tcx, self.method_map,
self.last_use_map);
#debug["creating fn_maps: %x", ptr::addr_of(*fn_maps) as uint];
for decl.inputs.each { |arg|
#debug["adding argument %d", arg.id];
let mode = ty::resolved_mode(self.tcx, arg.mode);
(*fn_maps).add_variable(vk_arg(arg.id, arg.ident, mode));
};
// gather up the various local variables, significant expressions,
// and so forth:
visit::visit_fn(fk, decl, body, sp, id, fn_maps, v);
alt fk {
visit::fk_ctor(_, _, _, class_did) {
add_class_fields(fn_maps, class_did);
}
_ {}
}
// Special nodes and variables:
// - exit_ln represents the end of the fn, either by ret or fail
// - implicit_ret_var is a pseudo-variable that represents
// an implicit return
let specials = {
exit_ln: (*fn_maps).add_live_node(lnk_exit),
fallthrough_ln: (*fn_maps).add_live_node(lnk_exit),
no_ret_var: (*fn_maps).add_variable(vk_implicit_ret),
self_var: (*fn_maps).add_variable(vk_self)
};
// compute liveness
let lsets = @liveness(fn_maps, specials);
let entry_ln = (*lsets).compute(decl, body);
// check for various error conditions
let check_vt = visit::mk_vt(@{
visit_fn: check_fn,
visit_local: check_local,
visit_expr: check_expr
with *visit::default_visitor()
});
check_vt.visit_block(body, lsets, check_vt);
lsets.check_ret(id, sp, fk, entry_ln);
lsets.check_fields(sp, entry_ln);
lsets.warn_about_unused_args(sp, decl, entry_ln);
}
fn add_class_fields(self: @ir_maps, did: def_id) {
for ty::lookup_class_fields(self.tcx, did).each { |field_ty|
assert field_ty.id.crate == local_crate;
let var = (*self).add_variable(vk_field(field_ty.ident));
self.field_map.insert(field_ty.ident, var);
}
}
fn visit_local(local: @local, &&self: @ir_maps, vt: vt<@ir_maps>) {
let def_map = self.tcx.def_map;
pat_util::pat_bindings(def_map, local.node.pat) { |p_id, sp, path|
#debug["adding local variable %d", p_id];
let name = ast_util::path_to_ident(path);
(*self).add_live_node_for_node(p_id, lnk_vdef(sp));
(*self).add_variable(vk_local(p_id, name));
}
visit::visit_local(local, self, vt);
}
fn visit_expr(expr: @expr, &&self: @ir_maps, vt: vt<@ir_maps>) {
alt expr.node {
// live nodes required for uses or definitions of variables:
expr_path(_) {
let def = self.tcx.def_map.get(expr.id);
#debug["expr %d: path that leads to %?", expr.id, def];
if relevant_def(def).is_some() {
(*self).add_live_node_for_node(expr.id, lnk_expr(expr.span));
}
visit::visit_expr(expr, self, vt);
}
expr_fn(_, _, _, cap_clause) |
expr_fn_block(_, _, cap_clause) {
// Make a live_node for each captured variable, with the span
// being the location that the variable is used. This results
// in better error messages than just pointing at the closure
// construction site.
let proto = ty::ty_fn_proto(ty::expr_ty(self.tcx, expr));
let cvs = capture::compute_capture_vars(self.tcx, expr.id,
proto, cap_clause);
let mut call_caps = [];
for cvs.each { |cv|
alt relevant_def(cv.def) {
some(rv) {
let cv_ln = (*self).add_live_node(lnk_freevar(cv.span));
let is_move = alt cv.mode {
cap_move | cap_drop {true} // var must be dead afterwards
cap_copy | cap_ref {false} // var can still be used
};
call_caps += [{ln: cv_ln, is_move: is_move, rv: rv}];
}
none {}
}
}
(*self).set_captures(expr.id, call_caps);
visit::visit_expr(expr, self, vt);
}
// live nodes required for interesting control flow:
expr_if_check(*) | expr_if(*) | expr_alt(*) |
expr_while(*) | expr_loop(*) {
(*self).add_live_node_for_node(expr.id, lnk_expr(expr.span));
visit::visit_expr(expr, self, vt);
}
expr_binary(op, _, _) if ast_util::lazy_binop(op) {
(*self).add_live_node_for_node(expr.id, lnk_expr(expr.span));
visit::visit_expr(expr, self, vt);
}
// otherwise, live nodes are not required:
expr_index(*) | expr_field(*) | expr_vstore(*) |
expr_vec(*) | expr_rec(*) | expr_call(*) | expr_tup(*) |
expr_bind(*) | expr_new(*) | expr_log(*) | expr_binary(*) |
expr_assert(*) | expr_check(*) | expr_addr_of(*) | expr_copy(*) |
expr_loop_body(*) | expr_cast(*) | expr_unary(*) | expr_fail(*) |
expr_break | expr_cont | expr_lit(_) | expr_ret(*) |
expr_block(*) | expr_move(*) | expr_assign(*) | expr_swap(*) |
expr_assign_op(*) | expr_mac(*) {
visit::visit_expr(expr, self, vt);
}
}
}
// ______________________________________________________________________
// Computing liveness sets
//
// Actually we compute just a bit more than just liveness, but we use
// the same basic propagation framework in all cases.
type users = {
reader: live_node,
writer: live_node,
used: bool
};
fn invalid_users() -> users {
{reader: invalid_node(), writer: invalid_node(), used: false}
}
type specials = {
exit_ln: live_node,
fallthrough_ln: live_node,
no_ret_var: variable,
self_var: variable
};
const ACC_READ: uint = 1u;
const ACC_WRITE: uint = 2u;
const ACC_USE: uint = 4u;
class liveness {
let tcx: ty::ctxt;
let ir: @ir_maps;
let s: specials;
let successors: [mut live_node];
let users: [mut users];
let mut break_ln: live_node;
let mut cont_ln: live_node;
new(ir: @ir_maps, specials: specials) {
self.ir = ir;
self.tcx = ir.tcx;
self.s = specials;
self.successors =
vec::to_mut(
vec::from_elem(self.ir.num_live_nodes,
invalid_node()));
self.users =
vec::to_mut(
vec::from_elem(self.ir.num_live_nodes * self.ir.num_vars,
invalid_users()));
self.break_ln = invalid_node();
self.cont_ln = invalid_node();
}
// _______________________________________________________________________
fn live_node(node_id: node_id, span: span) -> live_node {
alt self.ir.live_node_map.find(node_id) {
some(ln) {ln}
none {
// This must be a mismatch between the ir_map construction
// above and the propagation code below; the two sets of
// code have to agree about which AST nodes are worth
// creating liveness nodes for.
self.tcx.sess.span_bug(
span, #fmt["No live node registered for node %d",
node_id]);
}
}
}
fn variable_from_rdef(rv: relevant_def, span: span) -> variable {
alt rv {
rdef_self {self.s.self_var}
rdef_var(nid) {self.variable(nid, span)}
}
}
fn variable_from_path(expr: @expr) -> option<variable> {
alt expr.node {
expr_path(_) {
let def = self.tcx.def_map.get(expr.id);
relevant_def(def).map { |rdef|
self.variable_from_rdef(rdef, expr.span)
}
}
_ {none}
}
}
fn variable(node_id: node_id, span: span) -> variable {
(*self.ir).variable(node_id, span)
}
fn variable_from_def_map(node_id: node_id,
span: span) -> option<variable> {
alt self.tcx.def_map.find(node_id) {
some(def) {
relevant_def(def).map { |rdef|
self.variable_from_rdef(rdef, span)
}
}
none {
self.tcx.sess.span_bug(
span, "Not present in def map")
}
}
}
fn pat_bindings(pat: @pat, f: fn(live_node, variable, span)) {
let def_map = self.tcx.def_map;
pat_util::pat_bindings(def_map, pat) {|p_id, sp, _n|
let ln = self.live_node(p_id, sp);
let var = self.variable(p_id, sp);
f(ln, var, sp);
}
}
fn idx(ln: live_node, var: variable) -> uint {
*ln * self.ir.num_vars + *var
}
fn live_on_entry(ln: live_node, var: variable)
-> option<live_node_kind> {
assert ln.is_valid();
let reader = self.users[self.idx(ln, var)].reader;
if reader.is_valid() {some((*self.ir).lnk(reader))} else {none}
}
fn live_on_exit(ln: live_node, var: variable)
-> option<live_node_kind> {
self.live_on_entry(copy self.successors[*ln], var)
}
fn used_on_entry(ln: live_node, var: variable) -> bool {
assert ln.is_valid();
self.users[self.idx(ln, var)].used
}
fn assigned_on_entry(ln: live_node, var: variable)
-> option<live_node_kind> {
assert ln.is_valid();
let writer = self.users[self.idx(ln, var)].writer;
if writer.is_valid() {some((*self.ir).lnk(writer))} else {none}
}
fn assigned_on_exit(ln: live_node, var: variable)
-> option<live_node_kind> {
self.assigned_on_entry(copy self.successors[*ln], var)
}
fn indices(ln: live_node, op: fn(uint)) {
let node_base_idx = self.idx(ln, variable(0u));
for uint::range(0u, self.ir.num_vars) { |var_idx|
op(node_base_idx + var_idx)
}
}
fn indices2(ln: live_node, succ_ln: live_node,
op: fn(uint, uint)) {
let node_base_idx = self.idx(ln, variable(0u));
let succ_base_idx = self.idx(succ_ln, variable(0u));
for uint::range(0u, self.ir.num_vars) { |var_idx|
op(node_base_idx + var_idx, succ_base_idx + var_idx);
}
}
fn write_vars(wr: io::writer,
ln: live_node,
test: fn(uint) -> live_node) {
let node_base_idx = self.idx(ln, variable(0u));
for uint::range(0u, self.ir.num_vars) { |var_idx|
let idx = node_base_idx + var_idx;
if test(idx).is_valid() {
wr.write_str(" ");
wr.write_str(variable(var_idx).to_str());
}
}
}
fn ln_str(ln: live_node) -> str {
io::with_str_writer { |wr|
wr.write_str("[ln(");
wr.write_uint(*ln);
wr.write_str(") of kind ");
wr.write_str(#fmt["%?", copy self.ir.lnks[*ln]]);
wr.write_str(" reads");
self.write_vars(wr, ln, {|idx| self.users[idx].reader});
wr.write_str(" writes");
self.write_vars(wr, ln, {|idx| self.users[idx].writer});
wr.write_str(" ");
wr.write_str(" precedes ");
wr.write_str((copy self.successors[*ln]).to_str());
wr.write_str("]");
}
}
fn init_empty(ln: live_node, succ_ln: live_node) {
self.successors[*ln] = succ_ln;
// It is not necessary to initialize the
// values to empty because this is the value
// they have when they are created, and the sets
// only grow during iterations.
//
// self.indices(ln) { |idx|
// self.users[idx] = invalid_users();
// }
}
fn init_from_succ(ln: live_node, succ_ln: live_node) {
// more efficient version of init_empty() / merge_from_succ()
self.successors[*ln] = succ_ln;
self.indices2(ln, succ_ln) { |idx, succ_idx|
self.users[idx] = self.users[succ_idx];
}
#debug["init_from_succ(ln=%s, succ=%s)",
self.ln_str(ln), self.ln_str(succ_ln)];
}
fn merge_from_succ(ln: live_node, succ_ln: live_node,
first_merge: bool) -> bool {
if ln == succ_ln { ret false; }
let mut changed = false;
self.indices2(ln, succ_ln) { |idx, succ_idx|
changed |= copy_if_invalid(copy self.users[succ_idx].reader,
self.users[idx].reader);
changed |= copy_if_invalid(copy self.users[succ_idx].writer,
self.users[idx].writer);
if self.users[succ_idx].used && !self.users[idx].used {
self.users[idx].used = true;
changed = true;
}
}
#debug["merge_from_succ(ln=%s, succ=%s, first_merge=%b, changed=%b)",
ln.to_str(), self.ln_str(succ_ln), first_merge, changed];
ret changed;
fn copy_if_invalid(src: live_node, &dst: live_node) -> bool {
if src.is_valid() {
if !dst.is_valid() {
dst = src;
ret true;
}
}
ret false;
}
}
// Indicates that a local variable was *defined*; we know that no
// uses of the variable can precede the definition (resolve checks
// this) so we just clear out all the data.
fn define(writer: live_node, var: variable) {
let idx = self.idx(writer, var);
self.users[idx].reader = invalid_node();
self.users[idx].writer = invalid_node();
#debug["%s defines %s (idx=%u): %s", writer.to_str(), var.to_str(),
idx, self.ln_str(writer)];
}
// Either read, write, or both depending on the acc bitset
fn acc(ln: live_node, var: variable, acc: uint) {
let idx = self.idx(ln, var);
let user = &mut self.users[idx];
if (acc & ACC_WRITE) != 0u {
user.reader = invalid_node();
user.writer = ln;
}
// Important: if we both read/write, must do read second
// or else the write will override.
if (acc & ACC_READ) != 0u {
user.reader = ln;
}
if (acc & ACC_USE) != 0u {
self.users[idx].used = true;
}
#debug["%s accesses[%x] %s: %s",
ln.to_str(), acc, var.to_str(), self.ln_str(ln)];
}
// _______________________________________________________________________
fn compute(decl: fn_decl, body: blk) -> live_node {
// if there is a `break` or `cont` at the top level, then it's
// effectively a return---this only occurs in `for` loops,
// where the body is really a closure.
let entry_ln: live_node =
self.with_loop_nodes(self.s.exit_ln, self.s.exit_ln) {||
self.propagate_through_fn_block(decl, body)
};
// hack to skip the loop unless #debug is enabled:
#debug["^^ liveness computation results for body %d (entry=%s)",
{
for uint::range(0u, self.ir.num_live_nodes) { |ln_idx|
#debug["%s", self.ln_str(live_node(ln_idx))];
}
body.node.id
},
entry_ln.to_str()];
entry_ln
}
fn propagate_through_fn_block(decl: fn_decl, blk: blk) -> live_node {
// inputs passed by & mode should be considered live on exit:
for decl.inputs.each { |arg|
alt ty::resolved_mode(self.tcx, arg.mode) {
by_mutbl_ref | by_ref | by_val {
// These are "non-owned" modes, so register a read at
// the end. This will prevent us from moving out of
// such variables but also prevent us from registering
// last uses and so forth.
let var = self.variable(arg.id, blk.span);
self.acc(self.s.exit_ln, var, ACC_READ);
}
by_move | by_copy {
// These are owned modes. If we don't use the
// variable, nobody will.
}
}
}
// as above, the "self" variable is a non-owned variable
self.acc(self.s.exit_ln, self.s.self_var, ACC_READ);
// in a ctor, there is an implicit use of self.f for all fields f:
for self.ir.field_map.each_value { |var|
self.acc(self.s.exit_ln, var, ACC_READ|ACC_USE);
}
// the fallthrough exit is only for those cases where we do not
// explicitly return:
self.init_from_succ(self.s.fallthrough_ln, self.s.exit_ln);
if blk.node.expr.is_none() {
self.acc(self.s.fallthrough_ln, self.s.no_ret_var, ACC_READ)
}
self.propagate_through_block(blk, self.s.fallthrough_ln)
}
fn propagate_through_block(blk: blk, succ: live_node) -> live_node {
let succ = self.propagate_through_opt_expr(blk.node.expr, succ);
blk.node.stmts.foldr(succ) { |stmt, succ|
self.propagate_through_stmt(stmt, succ)
}
}
fn propagate_through_stmt(stmt: @stmt, succ: live_node) -> live_node {
alt stmt.node {
stmt_decl(decl, _) {
ret self.propagate_through_decl(decl, succ);
}
stmt_expr(expr, _) | stmt_semi(expr, _) {
ret self.propagate_through_expr(expr, succ);
}
}
}
fn propagate_through_decl(decl: @decl, succ: live_node) -> live_node {
alt decl.node {
decl_local(locals) {
locals.foldr(succ) { |local, succ|
self.propagate_through_local(local, succ)
}
}
decl_item(_) {
succ
}
}
}
fn propagate_through_local(local: @local, succ: live_node) -> live_node {
// Note: we mark the variable as defined regardless of whether
// there is an initializer. Initially I had thought to only mark
// the live variable as defined if it was initialized, and then we
// could check for uninit variables just by scanning what is live
// at the start of the function. But that doesn't work so well for
// immutable variables defined in a loop:
// loop { let x; x = 5; }
// because the "assignment" loops back around and generates an error.
//
// So now we just check that variables defined w/o an
// initializer are not live at the point of their
// initialization, which is mildly more complex than checking
// once at the func header but otherwise equivalent.
let opt_init = local.node.init.map { |i| i.expr };
let mut succ = self.propagate_through_opt_expr(opt_init, succ);
self.pat_bindings(local.node.pat) { |ln, var, _sp|
self.init_from_succ(ln, succ);
self.define(ln, var);
succ = ln;
}
succ
}
fn propagate_through_exprs(exprs: [@expr], succ: live_node) -> live_node {
exprs.foldr(succ) { |expr, succ|
self.propagate_through_expr(expr, succ)
}
}
fn propagate_through_opt_expr(opt_expr: option<@expr>,
succ: live_node) -> live_node {
opt_expr.foldl(succ) { |succ, expr|
self.propagate_through_expr(expr, succ)
}
}
fn propagate_through_expr(expr: @expr, succ: live_node) -> live_node {
alt expr.node {
// Interesting cases with control flow or which gen/kill
expr_path(_) {
self.access_path(expr, succ, ACC_READ | ACC_USE)
}
expr_field(e, nm, _) {
// If this is a reference to `self.f` inside of a ctor,
// then we treat it as a read of that variable.
// Otherwise, we ignore it and just propagate down to
// process `e`.
alt self.as_self_field(e, nm) {
some((ln, var)) {
self.init_from_succ(ln, succ);
self.acc(ln, var, ACC_READ | ACC_USE);
ln
}
none {
self.propagate_through_expr(e, succ)
}
}
}
expr_fn(*) | expr_fn_block(*) {
// the construction of a closure itself is not important,
// but we have to consider the closed over variables.
let caps = (*self.ir).captures(expr);
(*caps).foldr(succ) { |cap, succ|
self.init_from_succ(cap.ln, succ);
let var = self.variable_from_rdef(cap.rv, expr.span);
self.acc(cap.ln, var, ACC_READ | ACC_USE);
cap.ln
}
}
expr_if_check(cond, then, els) |
expr_if(cond, then, els) {
//
// (cond)
// |
// v
// (expr)
// / \
// | |
// v v
// (then)(els)
// | |
// v v
// ( succ )
//
let else_ln = self.propagate_through_opt_expr(els, succ);
let then_ln = self.propagate_through_block(then, succ);
let ln = self.live_node(expr.id, expr.span);
self.init_from_succ(ln, else_ln);
self.merge_from_succ(ln, then_ln, false);
self.propagate_through_expr(cond, ln)
}
expr_while(cond, blk) {
self.propagate_through_loop(expr, some(cond), blk, succ)
}
expr_loop(blk) {
self.propagate_through_loop(expr, none, blk, succ)
}
expr_alt(e, arms, _) {
//
// (e)
// |
// v
// (expr)
// / | \
// | | |
// v v v
// (..arms..)
// | | |
// v v v
// ( succ )
//
//
let ln = self.live_node(expr.id, expr.span);
self.init_empty(ln, succ);
let mut first_merge = true;
for arms.each { |arm|
let arm_succ =
self.propagate_through_opt_expr(
arm.guard,
self.propagate_through_block(arm.body, succ));
self.merge_from_succ(ln, arm_succ, first_merge);
first_merge = false;
};
self.propagate_through_expr(e, ln)
}
expr_ret(o_e) | expr_fail(o_e) { // ignore succ and subst exit_ln:
self.propagate_through_opt_expr(o_e, self.s.exit_ln)
}
expr_break {
if !self.break_ln.is_valid() {
self.tcx.sess.span_bug(
expr.span, "break with invalid break_ln");
}
self.break_ln
}
expr_cont {
if !self.cont_ln.is_valid() {
self.tcx.sess.span_bug(
expr.span, "cont with invalid cont_ln");
}
self.cont_ln
}
expr_move(l, r) | expr_assign(l, r) {
// see comment on lvalues in
// propagate_through_lvalue_components()
let succ = self.write_lvalue(l, succ, ACC_WRITE);
let succ = self.propagate_through_lvalue_components(l, succ);
self.propagate_through_expr(r, succ)
}
expr_swap(l, r) {
// see comment on lvalues in
// propagate_through_lvalue_components()
// I count swaps as `used` cause it might be something like:
// foo.bar <-> x
// and I am too lazy to distinguish this case from
// y <-> x
// (where both x, y are unused) just for a warning.
let succ = self.write_lvalue(r, succ, ACC_WRITE|ACC_READ|ACC_USE);
let succ = self.write_lvalue(l, succ, ACC_WRITE|ACC_READ|ACC_USE);
let succ = self.propagate_through_lvalue_components(r, succ);
self.propagate_through_lvalue_components(l, succ)
}
expr_assign_op(_, l, r) {
// see comment on lvalues in
// propagate_through_lvalue_components()
let succ = self.write_lvalue(l, succ, ACC_WRITE|ACC_READ);
let succ = self.propagate_through_expr(r, succ);
self.propagate_through_lvalue_components(l, succ)
}
// Uninteresting cases: just propagate in rev exec order
expr_vstore(expr, _) {
self.propagate_through_expr(expr, succ)
}
expr_vec(exprs, _) {
self.propagate_through_exprs(exprs, succ)
}
expr_rec(fields, with_expr) {
let succ = self.propagate_through_opt_expr(with_expr, succ);
fields.foldr(succ) { |field, succ|
self.propagate_through_expr(field.node.expr, succ)
}
}
expr_call(f, args, _) {
// calling a fn with bot return type means that the fn
// will fail, and hence the successors can be ignored
let t_ret = ty::ty_fn_ret(ty::expr_ty(self.tcx, f));
let succ = if ty::type_is_bot(t_ret) {self.s.exit_ln}
else {succ};
let succ = self.propagate_through_exprs(args, succ);
self.propagate_through_expr(f, succ)
}
expr_tup(exprs) {
self.propagate_through_exprs(exprs, succ)
}
expr_bind(f, args) {
let succ = args.foldr(succ) { |arg, succ|
alt arg {
none {succ}
some(e) {self.propagate_through_expr(e, succ)}
}
};
self.propagate_through_expr(f, succ)
}
expr_binary(op, l, r) if ast_util::lazy_binop(op) {
let r_succ = self.propagate_through_expr(r, succ);
let ln = self.live_node(expr.id, expr.span);
self.init_from_succ(ln, succ);
self.merge_from_succ(ln, r_succ, false);
self.propagate_through_expr(l, ln)
}
expr_new(l, _, r) |
expr_log(_, l, r) |
expr_index(l, r) |
expr_binary(_, l, r) {
self.propagate_through_exprs([l, r], succ)
}
expr_assert(e) |
expr_check(_, e) |
expr_addr_of(_, e) |
expr_copy(e) |
expr_loop_body(e) |
expr_cast(e, _) |
expr_unary(_, e) {
self.propagate_through_expr(e, succ)
}
expr_lit(*) {
succ
}
expr_block(blk) {
self.propagate_through_block(blk, succ)
}
expr_mac(*) {
self.tcx.sess.span_bug(expr.span, "unexpanded macro");
}
}
}
fn propagate_through_lvalue_components(expr: @expr,
succ: live_node) -> live_node {
// # Lvalues
//
// In general, the full flow graph structure for an
// assignment/move/etc can be handled in one of two ways,
// depending on whether what is being assigned is a "tracked
// value" or not. A tracked value is basically a local variable
// or argument, or a self-field (`self.f`) in a ctor.
//
// The two kinds of graphs are:
//
// Tracked lvalue Untracked lvalue
// ----------------------++-----------------------
// ||
// | || |
// v || v
// (rvalue) || (rvalue)
// | || |
// v || v
// (write of lvalue) || (lvalue components)
// | || |
// v || v
// (succ) || (succ)
// ||
// ----------------------++-----------------------
//
// I will cover the two cases in turn:
//
// # Tracked lvalues
//
// A tracked lvalue is either a local variable/argument `x` or
// else it is a self-field `self.f` in a constructor. In
// these cases, the link_node where the write occurs is linked
// to node id of `x` or `self`, respectively. The
// `write_lvalue()` routine generates the contents of this
// node. There are no subcomponents to consider.
//
// # Non-tracked lvalues
//
// These are lvalues like `x[5]` or `x.f`. In that case, we
// basically ignore the value which is written to but generate
// reads for the components---`x` in these two examples. The
// components reads are generated by
// `propagate_through_lvalue_components()` (this fn).
//
// # Illegal lvalues
//
// It is still possible to observe assignments to non-lvalues;
// these errors are detected in the later pass borrowck. We
// just ignore such cases and treat them as reads.
alt expr.node {
expr_path(_) {
succ
}
expr_field(e, nm, _) {
alt self.as_self_field(e, nm) {
some(_) {succ}
none {self.propagate_through_expr(e, succ)}
}
}
_ {
self.propagate_through_expr(expr, succ)
}
}
}
// see comment on propagate_through_lvalue()
fn write_lvalue(expr: @expr,
succ: live_node,
acc: uint) -> live_node {
alt expr.node {
expr_path(_) {
self.access_path(expr, succ, acc)
}
expr_field(e, nm, _) {
alt self.as_self_field(e, nm) {
some((ln, var)) {
self.init_from_succ(ln, succ);
self.acc(ln, var, acc);
ln
}
none {
succ
}
}
}
// We do not track other lvalues, so just propagate through
// to their subcomponents. Also, it may happen that
// non-lvalues occur here, because those are detected in the
// later pass borrowck.
_ {succ}
}
}
fn access_path(expr: @expr, succ: live_node, acc: uint) -> live_node {
let def = self.tcx.def_map.get(expr.id);
alt relevant_def(def) {
some(rdef_self) {
// Accessing `self` is like accessing every field of
// the current object. This allows something like
// `self = ...;` (it will be considered a write to
// every field, sensibly enough), though the borrowck
// pass will reject it later on.
//
// Also, note that, within a ctor at least, an
// expression like `self.f` is "shortcircuiting"
// before it reaches this point by the code for
// expr_field.
let ln = self.live_node(expr.id, expr.span);
if acc != 0u {
self.init_from_succ(ln, succ);
for self.ir.field_map.each_value { |var|
self.acc(ln, var, acc);
}
}
ln
}
some(rdef_var(nid)) {
let ln = self.live_node(expr.id, expr.span);
if acc != 0u {
self.init_from_succ(ln, succ);
let var = self.variable(nid, expr.span);
self.acc(ln, var, acc);
}
ln
}
none {
succ
}
}
}
fn as_self_field(expr: @expr,
fld: ident) -> option<(live_node,variable)> {
// If we checking a constructor, then we treat self.f as a
// variable. we use the live_node id that will be assigned to
// the reference to self but the variable id for `f`.
alt expr.node {
expr_path(_) {
let def = self.tcx.def_map.get(expr.id);
alt def {
def_self(_) {
// Note: the field_map is empty unless we are in a ctor
ret self.ir.field_map.find(fld).map { |var|
let ln = self.live_node(expr.id, expr.span);
(ln, var)
};
}
_ { ret none; }
}
}
_ { ret none; }
}
}
fn propagate_through_loop(expr: @expr,
cond: option<@expr>,
body: blk,
succ: live_node) -> live_node {
/*
We model control flow like this:
(cond) <--+
| |
v |
+-- (expr) |
| | |
| v |
| (body) ---+
|
|
v
(succ)
*/
// first iteration:
let mut first_merge = true;
let ln = self.live_node(expr.id, expr.span);
self.init_empty(ln, succ);
if cond.is_some() {
// if there is a condition, then it's possible we bypass
// the body altogether. otherwise, the only way is via a
// break in the loop body.
self.merge_from_succ(ln, succ, first_merge);
first_merge = false;
}
let cond_ln = self.propagate_through_opt_expr(cond, ln);
let body_ln = self.with_loop_nodes(succ, ln) {||
self.propagate_through_block(body, cond_ln)
};
// repeat until fixed point is reached:
while self.merge_from_succ(ln, body_ln, first_merge) {
first_merge = false;
assert cond_ln == self.propagate_through_opt_expr(cond, ln);
assert body_ln == self.with_loop_nodes(succ, ln) {||
self.propagate_through_block(body, cond_ln)
};
}
cond_ln
}
fn with_loop_nodes<R>(break_ln: live_node,
cont_ln: live_node,
f: fn() -> R) -> R {
let bl = self.break_ln, cl = self.cont_ln;
self.break_ln = break_ln;
self.cont_ln = cont_ln;
let r <- f();
self.break_ln = bl;
self.cont_ln = cl;
ret r;
}
}
// _______________________________________________________________________
// Checking for error conditions
fn check_local(local: @local, &&self: @liveness, vt: vt<@liveness>) {
alt local.node.init {
some({op: op, expr: expr}) {
// Initializer:
alt op {
init_move {self.check_move_from_expr(expr, vt)}
init_assign {}
}
self.warn_about_unused_or_dead_vars_in_pat(local.node.pat);
if !local.node.is_mutbl {
self.check_for_reassignments_in_pat(local.node.pat);
}
}
none {
// No initializer: the variable might be unused; if not, it
// should not be live at this point.
#debug["check_local() with no initializer"];
(*self).pat_bindings(local.node.pat) { |ln, var, sp|
if !self.warn_about_unused(sp, ln, var) {
alt (*self).live_on_exit(ln, var) {
none { /* not live: good */ }
some(lnk) {
self.report_illegal_read(
local.span, lnk, var,
possibly_uninitialized_variable);
}
}
}
}
}
}
visit::visit_local(local, self, vt);
}
fn check_expr(expr: @expr, &&self: @liveness, vt: vt<@liveness>) {
alt expr.node {
expr_path(_) {
for (*self).variable_from_def_map(expr.id, expr.span).each { |var|
let ln = (*self).live_node(expr.id, expr.span);
self.consider_last_use(expr, ln, var);
}
visit::visit_expr(expr, self, vt);
}
expr_fn(_, _, _, cap_clause) | expr_fn_block(_, _, cap_clause) {
let caps = (*self.ir).captures(expr);
for (*caps).each { |cap|
let var = (*self).variable_from_rdef(cap.rv, expr.span);
self.consider_last_use(expr, cap.ln, var);
if cap.is_move {
self.check_move_from_var(expr.span, cap.ln, var);
}
}
visit::visit_expr(expr, self, vt);
}
expr_assign(l, r) {
self.check_lvalue(l, vt);
vt.visit_expr(r, self, vt);
visit::visit_expr(expr, self, vt);
}
expr_move(l, r) {
self.check_lvalue(l, vt);
self.check_move_from_expr(r, vt);
visit::visit_expr(expr, self, vt);
}
expr_assign_op(_, l, _) {
self.check_lvalue(l, vt);
visit::visit_expr(expr, self, vt);
}
expr_call(f, args, _) {
let targs = ty::ty_fn_args(ty::expr_ty(self.tcx, f));
vt.visit_expr(f, self, vt);
vec::iter2(args, targs) { |arg_expr, arg_ty|
alt ty::resolved_mode(self.tcx, arg_ty.mode) {
by_val | by_copy | by_ref | by_mutbl_ref{
vt.visit_expr(arg_expr, self, vt);
}
by_move {
self.check_move_from_expr(arg_expr, vt);
}
}
}
}
// no correctness conditions related to liveness
expr_if_check(*) | expr_if(*) | expr_alt(*) |
expr_while(*) | expr_loop(*) |
expr_index(*) | expr_field(*) | expr_vstore(*) |
expr_vec(*) | expr_rec(*) | expr_tup(*) |
expr_bind(*) | expr_new(*) | expr_log(*) | expr_binary(*) |
expr_assert(*) | expr_check(*) | expr_copy(*) |
expr_loop_body(*) | expr_cast(*) | expr_unary(*) | expr_fail(*) |
expr_ret(*) | expr_break | expr_cont | expr_lit(_) |
expr_block(*) | expr_swap(*) | expr_mac(*) | expr_addr_of(*) {
visit::visit_expr(expr, self, vt);
}
}
}
fn check_fn(_fk: visit::fn_kind, _decl: fn_decl,
_body: blk, _sp: span, _id: node_id,
&&_self: @liveness, _v: vt<@liveness>) {
// do not check contents of nested fns
}
enum read_kind {
possibly_uninitialized_variable,
possibly_uninitialized_field,
moved_variable
}
impl check_methods for @liveness {
fn check_fields(sp: span, entry_ln: live_node) {
for self.ir.field_map.each { |nm, var|
alt (*self).live_on_entry(entry_ln, var) {
none { /* ok */ }
some(lnk_exit) {
self.tcx.sess.span_err(
sp, #fmt["field `self.%s` is never initialized", *nm]);
}
some(lnk) {
self.report_illegal_read(
sp, lnk, var, possibly_uninitialized_field);
}
}
}
}
fn check_ret(id: node_id, sp: span, fk: visit::fn_kind,
entry_ln: live_node) {
if (*self).live_on_entry(entry_ln, self.s.no_ret_var).is_some() {
// if no_ret_var is live, then we fall off the end of the
// function without any kind of return expression:
let t_ret = ty::ty_fn_ret(ty::node_id_to_type(self.tcx, id));
if ty::type_is_nil(t_ret) {
// for nil return types, it is ok to not return a value expl.
} else if ty::type_is_bot(t_ret) {
// for bot return types, not ok. Function should fail.
self.tcx.sess.span_err(
sp, "some control paths may return");
} else {
alt fk {
visit::fk_ctor(*) {
// ctors are written as though they are unit.
}
_ {
self.tcx.sess.span_err(
sp, "not all control paths return a value");
}
}
}
}
}
fn check_move_from_var(span: span, ln: live_node, var: variable) {
#debug["check_move_from_var(%s, %s)",
ln.to_str(), var.to_str()];
alt (*self).live_on_exit(ln, var) {
none { }
some(lnk) {
self.report_illegal_move(span, lnk, var);
}
}
}
fn consider_last_use(expr: @expr, ln: live_node, var: variable) {
alt (*self).live_on_exit(ln, var) {
some(_) {}
none {
(*self.ir).add_last_use(expr.id, var);
}
}
}
fn check_move_from_expr(expr: @expr, vt: vt<@liveness>) {
#debug["check_move_from_expr(node %d: %s)",
expr.id, expr_to_str(expr)];
if self.ir.method_map.contains_key(expr.id) {
// actually an rvalue, since this calls a method
ret vt.visit_expr(expr, self, vt);
}
alt expr.node {
expr_path(_) {
alt (*self).variable_from_path(expr) {
some(var) {
let ln = (*self).live_node(expr.id, expr.span);
self.check_move_from_var(expr.span, ln, var);
}
none {}
}
}
expr_field(base, _, _) {
// Moving from x.y is allowed if x is never used later.
// (Note that the borrowck guarantees that anything
// being moved from is uniquely tied to the stack frame)
self.check_move_from_expr(base, vt);
}
expr_index(base, idx) {
// Moving from x[y] is allowed if x is never used later.
// (Note that the borrowck guarantees that anything
// being moved from is uniquely tied to the stack frame)
self.check_move_from_expr(base, vt);
vt.visit_expr(idx, self, vt);
}
_ {
// For other kinds of lvalues, no checks are required,
// and any embedded expressions are actually rvalues
vt.visit_expr(expr, self, vt);
}
}
}
fn check_lvalue(expr: @expr, vt: vt<@liveness>) {
alt expr.node {
expr_path(_) {
alt self.tcx.def_map.get(expr.id) {
def_local(nid, false) {
// Assignment to an immutable variable or argument:
// only legal if there is no later assignment.
let ln = (*self).live_node(expr.id, expr.span);
let var = (*self).variable(nid, expr.span);
self.check_for_reassignment(ln, var, expr.span);
self.warn_about_dead_assign(expr.span, ln, var);
}
def {
alt relevant_def(def) {
some(rdef_var(nid)) {
let ln = (*self).live_node(expr.id, expr.span);
let var = (*self).variable(nid, expr.span);
self.warn_about_dead_assign(expr.span, ln, var);
}
some(rdef_self) {}
none {}
}
}
}
}
_ {
// For other kinds of lvalues, no checks are required,
// and any embedded expressions are actually rvalues
visit::visit_expr(expr, self, vt);
}
}
}
fn check_for_reassignments_in_pat(pat: @pat) {
(*self).pat_bindings(pat) { |ln, var, sp|
self.check_for_reassignment(ln, var, sp);
}
}
fn check_for_reassignment(ln: live_node, var: variable,
orig_span: span) {
alt (*self).assigned_on_exit(ln, var) {
some(lnk_expr(span)) {
self.tcx.sess.span_err(
span,
"re-assignment of immutable variable");
self.tcx.sess.span_note(
orig_span,
"prior assignment occurs here");
}
some(lnk) {
self.tcx.sess.span_bug(
orig_span,
#fmt["illegal writer: %?", lnk]);
}
none {}
}
}
fn report_illegal_move(move_span: span,
lnk: live_node_kind,
var: variable) {
// the only time that it is possible to have a moved variable
// used by lnk_exit would be arguments or fields in a ctor.
// we give a slightly different error message in those cases.
if lnk == lnk_exit {
let vk = self.ir.var_kinds[*var];
alt vk {
vk_arg(_, name, _) {
self.tcx.sess.span_err(
move_span,
#fmt["illegal move from argument `%s`, which is not \
copy or move mode", *name]);
ret;
}
vk_field(name) {
self.tcx.sess.span_err(
move_span,
#fmt["illegal move from field `%s`", *name]);
ret;
}
vk_local(*) | vk_self | vk_implicit_ret {
self.tcx.sess.span_bug(
move_span,
#fmt["illegal reader (%?) for `%?`",
lnk, vk]);
}
}
}
self.report_illegal_read(move_span, lnk, var, moved_variable);
self.tcx.sess.span_note(
move_span, "move of variable occurred here");
}
fn report_illegal_read(chk_span: span,
lnk: live_node_kind,
var: variable,
rk: read_kind) {
let msg = alt rk {
possibly_uninitialized_variable {"possibly uninitialized variable"}
possibly_uninitialized_field {"possibly uninitialized field"}
moved_variable {"moved variable"}
};
let name = (*self.ir).variable_name(var);
alt lnk {
lnk_freevar(span) {
self.tcx.sess.span_err(
span,
#fmt["capture of %s: `%s`", msg, *name]);
}
lnk_expr(span) {
self.tcx.sess.span_err(
span,
#fmt["use of %s: `%s`", msg, *name]);
}
lnk_exit |
lnk_vdef(_) {
self.tcx.sess.span_bug(
chk_span,
#fmt["illegal reader: %?", lnk]);
}
}
}
fn should_warn(var: variable) -> option<ident> {
let name = (*self.ir).variable_name(var);
if (*name)[0] == ('_' as u8) {none} else {some(name)}
}
fn warn_about_unused_args(sp: span, decl: fn_decl, entry_ln: live_node) {
for decl.inputs.each { |arg|
let var = (*self).variable(arg.id, arg.ty.span);
alt ty::resolved_mode(self.tcx, arg.mode) {
by_mutbl_ref {
// for mutable reference arguments, something like
// x = 1;
// is not worth warning about, as it has visible
// side effects outside the fn.
alt (*self).assigned_on_entry(entry_ln, var) {
some(_) { /*ok*/ }
none {
// but if it is not written, it ought to be used
self.warn_about_unused(sp, entry_ln, var);
}
}
}
by_val | by_ref | by_move | by_copy {
self.warn_about_unused(sp, entry_ln, var);
}
}
}
}
fn warn_about_unused_or_dead_vars_in_pat(pat: @pat) {
(*self).pat_bindings(pat) { |ln, var, sp|
if !self.warn_about_unused(sp, ln, var) {
self.warn_about_dead_assign(sp, ln, var);
}
}
}
fn warn_about_unused(sp: span, ln: live_node, var: variable) -> bool {
if !(*self).used_on_entry(ln, var) {
for self.should_warn(var).each { |name|
// annoying: for parameters in funcs like `fn(x: int)
// {ret}`, there is only one node, so asking about
// assigned_on_exit() is not meaningful.
let is_assigned = if ln == self.s.exit_ln {
false
} else {
(*self).assigned_on_exit(ln, var).is_some()
};
if is_assigned {
self.tcx.sess.span_warn(
sp, #fmt["variable `%s` is assigned to, \
but never used", *name]);
} else {
self.tcx.sess.span_warn(
sp, #fmt["unused variable: `%s`", *name]);
}
}
ret true;
}
ret false;
}
fn warn_about_dead_assign(sp: span, ln: live_node, var: variable) {
if (*self).live_on_exit(ln, var).is_none() {
for self.should_warn(var).each { |name|
self.tcx.sess.span_warn(
sp,
#fmt["value assigned to `%s` is never read", *name]);
}
}
}
}