Preliminary support for labeled break/continue for loops

This patch adds preliminary middle-end support (liveness and trans)
for breaks and `loop`s to `loop` constructs that have labels.

while and for loops can't have labels yet.

Progress on #2216
This commit is contained in:
Tim Chevalier 2012-10-18 12:20:18 -07:00
parent 46d4bbbae4
commit dd66e7549b
10 changed files with 232 additions and 98 deletions

View File

@ -1180,7 +1180,10 @@ fn print_field(s: ps, field: ast::field) {
ast::expr_loop(blk, opt_ident) => {
head(s, ~"loop");
space(s.s);
opt_ident.iter(|ident| {print_ident(s, *ident); space(s.s)});
opt_ident.iter(|ident| {
print_ident(s, *ident);
word_space(s, ~":");
});
print_block(s, blk);
}
ast::expr_match(expr, arms) => {

View File

@ -95,9 +95,9 @@
use dvec::DVec;
use std::map::HashMap;
use syntax::{visit, ast_util};
use syntax::print::pprust::{expr_to_str};
use syntax::print::pprust::{expr_to_str, block_to_str};
use visit::vt;
use syntax::codemap::span;
use syntax::codemap::{span, span_to_str};
use syntax::ast::*;
use io::WriterUtil;
use capture::{cap_move, cap_drop, cap_copy, cap_ref};
@ -167,6 +167,16 @@ impl LiveNodeKind : cmp::Eq {
pure fn ne(other: &LiveNodeKind) -> bool { !self.eq(other) }
}
fn live_node_kind_to_str(lnk: LiveNodeKind, cx: ty::ctxt) -> ~str {
let cm = cx.sess.codemap;
match lnk {
FreeVarNode(s) => fmt!("Free var node [%s]", span_to_str(s, cm)),
ExprNode(s) => fmt!("Expr node [%s]", span_to_str(s, cm)),
VarDefNode(s) => fmt!("Var def node [%s]", span_to_str(s, cm)),
ExitNode => ~"Exit node"
}
}
fn check_crate(tcx: ty::ctxt,
method_map: typeck::method_map,
crate: @crate) -> last_use_map {
@ -277,8 +287,8 @@ fn IrMaps(tcx: ty::ctxt, method_map: typeck::method_map,
tcx: tcx,
method_map: method_map,
last_use_map: last_use_map,
num_live_nodes: 0u,
num_vars: 0u,
num_live_nodes: 0,
num_vars: 0,
live_node_map: HashMap(),
variable_map: HashMap(),
capture_map: HashMap(),
@ -291,9 +301,10 @@ impl IrMaps {
fn add_live_node(lnk: LiveNodeKind) -> LiveNode {
let ln = LiveNode(self.num_live_nodes);
self.lnks.push(lnk);
self.num_live_nodes += 1u;
self.num_live_nodes += 1;
debug!("%s is of kind %?", ln.to_str(), lnk);
debug!("%s is of kind %s", ln.to_str(),
live_node_kind_to_str(lnk, self.tcx));
ln
}
@ -308,7 +319,7 @@ fn add_live_node_for_node(node_id: node_id, lnk: LiveNodeKind) {
fn add_variable(vk: VarKind) -> Variable {
let v = Variable(self.num_vars);
self.var_kinds.push(vk);
self.num_vars += 1u;
self.num_vars += 1;
match vk {
Local(LocalInfo {id:node_id, _}) |
@ -491,6 +502,10 @@ fn visit_expr(expr: @expr, &&self: @IrMaps, vt: vt<@IrMaps>) {
}
expr_fn(_, _, _, cap_clause) |
expr_fn_block(_, _, cap_clause) => {
// Interesting control flow (for loops can contain labeled
// breaks or continues)
self.add_live_node_for_node(expr.id, ExprNode(expr.span));
// 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
@ -571,14 +586,22 @@ fn invalid_users() -> users {
const ACC_WRITE: uint = 2u;
const ACC_USE: uint = 4u;
type LiveNodeMap = HashMap<node_id, LiveNode>;
struct Liveness {
tcx: ty::ctxt,
ir: @IrMaps,
s: Specials,
successors: ~[mut LiveNode],
users: ~[mut users],
mut break_ln: LiveNode,
mut cont_ln: LiveNode,
// The list of node IDs for the nested loop scopes
// we're in.
mut loop_scope: @DVec<node_id>,
// mappings from loop node ID to LiveNode
// ("break" label should map to loop node ID,
// it probably doesn't now)
break_ln: LiveNodeMap,
cont_ln: LiveNodeMap
}
fn Liveness(ir: @IrMaps, specials: Specials) -> Liveness {
@ -594,8 +617,9 @@ fn Liveness(ir: @IrMaps, specials: Specials) -> Liveness {
vec::to_mut(
vec::from_elem(ir.num_live_nodes * ir.num_vars,
invalid_users())),
break_ln: invalid_node(),
cont_ln: invalid_node()
loop_scope: @DVec(),
break_ln: HashMap(),
cont_ln: HashMap()
}
}
@ -691,6 +715,9 @@ fn live_on_entry(ln: LiveNode, var: Variable)
if reader.is_valid() {Some((*self.ir).lnk(reader))} else {None}
}
/*
Is this variable live on entry to any of its successor nodes?
*/
fn live_on_exit(ln: LiveNode, var: Variable)
-> Option<LiveNodeKind> {
@ -717,8 +744,8 @@ fn assigned_on_exit(ln: LiveNode, var: Variable)
}
fn indices(ln: LiveNode, op: fn(uint)) {
let node_base_idx = self.idx(ln, Variable(0u));
for uint::range(0u, self.ir.num_vars) |var_idx| {
let node_base_idx = self.idx(ln, Variable(0));
for uint::range(0, self.ir.num_vars) |var_idx| {
op(node_base_idx + var_idx)
}
}
@ -735,8 +762,8 @@ fn indices2(ln: LiveNode, succ_ln: LiveNode,
fn write_vars(wr: io::Writer,
ln: LiveNode,
test: fn(uint) -> LiveNode) {
let node_base_idx = self.idx(ln, Variable(0u));
for uint::range(0u, self.ir.num_vars) |var_idx| {
let node_base_idx = self.idx(ln, Variable(0));
for uint::range(0, self.ir.num_vars) |var_idx| {
let idx = node_base_idx + var_idx;
if test(idx).is_valid() {
wr.write_str(~" ");
@ -745,6 +772,28 @@ fn write_vars(wr: io::Writer,
}
}
fn find_loop_scope(opt_label: Option<ident>, id: node_id, sp: span)
-> node_id {
match opt_label {
Some(_) => // Refers to a labeled loop. Use the results of resolve
// to find with one
match self.tcx.def_map.find(id) {
Some(def_label(loop_id)) => loop_id,
_ => self.tcx.sess.span_bug(sp, ~"Label on break/loop \
doesn't refer to a loop")
},
None =>
// Vanilla 'break' or 'loop', so use the enclosing
// loop scope
if self.loop_scope.len() == 0 {
self.tcx.sess.span_bug(sp, ~"break outside loop");
}
else {
self.loop_scope.last()
}
}
}
fn ln_str(ln: LiveNode) -> ~str {
do io::with_str_writer |wr| {
wr.write_str(~"[ln(");
@ -833,18 +882,18 @@ fn acc(ln: LiveNode, var: Variable, acc: uint) {
let idx = self.idx(ln, var);
let user = &mut self.users[idx];
if (acc & ACC_WRITE) != 0u {
if (acc & ACC_WRITE) != 0 {
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 {
if (acc & ACC_READ) != 0 {
user.reader = ln;
}
if (acc & ACC_USE) != 0u {
if (acc & ACC_USE) != 0 {
self.users[idx].used = true;
}
@ -858,10 +907,13 @@ fn compute(decl: fn_decl, body: blk) -> LiveNode {
// if there is a `break` or `again` at the top level, then it's
// effectively a return---this only occurs in `for` loops,
// where the body is really a closure.
debug!("compute: using id for block, %s", block_to_str(body,
self.tcx.sess.intr()));
let entry_ln: LiveNode =
self.with_loop_nodes(self.s.exit_ln, self.s.exit_ln, || {
self.propagate_through_fn_block(decl, body)
});
self.with_loop_nodes(body.node.id, 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)",
@ -972,6 +1024,9 @@ fn propagate_through_opt_expr(opt_expr: Option<@expr>,
}
fn propagate_through_expr(expr: @expr, succ: LiveNode) -> LiveNode {
debug!("propagate_through_expr: %s",
expr_to_str(expr, self.tcx.sess.intr()));
match expr.node {
// Interesting cases with control flow or which gen/kill
@ -983,16 +1038,27 @@ fn propagate_through_expr(expr: @expr, succ: LiveNode) -> LiveNode {
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);
do (*caps).foldr(succ) |cap, succ| {
self.init_from_succ(cap.ln, succ);
let var = self.variable(cap.var_nid, expr.span);
self.acc(cap.ln, var, ACC_READ | ACC_USE);
cap.ln
}
expr_fn(_, _, blk, _) | expr_fn_block(_, blk, _) => {
debug!("%s is an expr_fn or expr_fn_block",
expr_to_str(expr, self.tcx.sess.intr()));
/*
The next-node for a break is the successor of the entire
loop. The next-node for a continue is the top of this loop.
*/
self.with_loop_nodes(blk.node.id, succ,
self.live_node(expr.id, expr.span), || {
// the construction of a closure itself is not important,
// but we have to consider the closed over variables.
let caps = (*self.ir).captures(expr);
do (*caps).foldr(succ) |cap, succ| {
self.init_from_succ(cap.ln, succ);
let var = self.variable(cap.var_nid, expr.span);
self.acc(cap.ln, var, ACC_READ | ACC_USE);
cap.ln
}
})
}
expr_if(cond, then, els) => {
@ -1021,6 +1087,8 @@ fn propagate_through_expr(expr: @expr, succ: LiveNode) -> LiveNode {
self.propagate_through_loop(expr, Some(cond), blk, succ)
}
// Note that labels have been resolved, so we don't need to look
// at the label ident
expr_loop(blk, _) => {
self.propagate_through_loop(expr, None, blk, succ)
}
@ -1062,29 +1130,31 @@ fn propagate_through_expr(expr: @expr, succ: LiveNode) -> LiveNode {
}
expr_break(opt_label) => {
if !self.break_ln.is_valid() {
self.tcx.sess.span_bug(
expr.span, ~"break with invalid break_ln");
}
// Find which label this break jumps to
let sc = self.find_loop_scope(opt_label, expr.id, expr.span);
if opt_label.is_some() {
self.tcx.sess.span_unimpl(expr.span, ~"labeled break");
}
// Now that we know the label we're going to,
// look it up in the break loop nodes table
self.break_ln
match self.break_ln.find(sc) {
Some(b) => b,
None => self.tcx.sess.span_bug(expr.span,
~"Break to unknown label")
}
}
expr_again(opt_label) => {
if !self.cont_ln.is_valid() {
self.tcx.sess.span_bug(
expr.span, ~"cont with invalid cont_ln");
}
// Find which label this expr continues to to
let sc = self.find_loop_scope(opt_label, expr.id, expr.span);
if opt_label.is_some() {
self.tcx.sess.span_unimpl(expr.span, ~"labeled again");
}
// Now that we know the label we're going to,
// look it up in the continue loop nodes table
self.cont_ln
match self.cont_ln.find(sc) {
Some(b) => b,
None => self.tcx.sess.span_bug(expr.span,
~"Loop to unknown label")
}
}
expr_move(l, r) | expr_assign(l, r) => {
@ -1314,6 +1384,7 @@ fn propagate_through_loop(expr: @expr,
*/
// first iteration:
let mut first_merge = true;
let ln = self.live_node(expr.id, expr.span);
@ -1325,8 +1396,11 @@ fn propagate_through_loop(expr: @expr,
self.merge_from_succ(ln, succ, first_merge);
first_merge = false;
}
debug!("propagate_through_loop: using id for loop body %d %s",
expr.id, block_to_str(body, self.tcx.sess.intr()));
let cond_ln = self.propagate_through_opt_expr(cond, ln);
let body_ln = self.with_loop_nodes(succ, ln, || {
let body_ln = self.with_loop_nodes(expr.id, succ, ln, || {
self.propagate_through_block(body, cond_ln)
});
@ -1334,7 +1408,8 @@ fn propagate_through_loop(expr: @expr,
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, || {
assert body_ln == self.with_loop_nodes(expr.id, succ, ln,
|| {
self.propagate_through_block(body, cond_ln)
});
}
@ -1342,15 +1417,16 @@ fn propagate_through_loop(expr: @expr,
cond_ln
}
fn with_loop_nodes<R>(break_ln: LiveNode,
fn with_loop_nodes<R>(loop_node_id: node_id,
break_ln: LiveNode,
cont_ln: LiveNode,
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;
debug!("with_loop_nodes: %d %u", loop_node_id, *break_ln);
self.loop_scope.push(loop_node_id);
self.break_ln.insert(loop_node_id, break_ln);
self.cont_ln.insert(loop_node_id, cont_ln);
let r = f();
self.loop_scope.pop();
move r
}
}
@ -1526,6 +1602,10 @@ fn check_ret(id: node_id, sp: span, _fk: visit::fn_kind,
}
}
/*
Checks whether <var> is live on entry to any of the successors of <ln>.
If it is, report an error.
*/
fn check_move_from_var(span: span, ln: LiveNode, var: Variable) {
debug!("check_move_from_var(%s, %s)",
ln.to_str(), var.to_str());

View File

@ -1050,7 +1050,7 @@ fn new_block(cx: fn_ctxt, parent: Option<block>, +kind: block_kind,
}
fn simple_block_scope() -> block_kind {
block_scope({loop_break: None, mut cleanups: ~[],
block_scope({loop_break: None, loop_label: None, mut cleanups: ~[],
mut cleanup_paths: ~[], mut landing_pad: None})
}
@ -1067,10 +1067,11 @@ fn scope_block(bcx: block,
n, opt_node_info);
}
fn loop_scope_block(bcx: block, loop_break: block, n: ~str,
opt_node_info: Option<node_info>) -> block {
fn loop_scope_block(bcx: block, loop_break: block, loop_label: Option<ident>,
n: ~str, opt_node_info: Option<node_info>) -> block {
return new_block(bcx.fcx, Some(bcx), block_scope({
loop_break: Some(loop_break),
loop_label: loop_label,
mut cleanups: ~[],
mut cleanup_paths: ~[],
mut landing_pad: None

View File

@ -445,6 +445,7 @@ enum block_kind {
type scope_info = {
loop_break: Option<block>,
loop_label: Option<ident>,
// A list of functions that must be run at when leaving this
// block, cleaning up any variables that were introduced in the
// block.

View File

@ -113,7 +113,9 @@ fn trans_while(bcx: block, cond: @ast::expr, body: ast::blk)
// | body_bcx_out --+
// next_bcx
let loop_bcx = loop_scope_block(bcx, next_bcx, ~"`while`", body.info());
// tjc: while should have labels...
let loop_bcx = loop_scope_block(bcx, next_bcx, None, ~"`while`",
body.info());
let cond_bcx_in = scope_block(loop_bcx, cond.info(), ~"while loop cond");
let body_bcx_in = scope_block(loop_bcx, body.info(), ~"while loop body");
Br(bcx, loop_bcx.llbb);
@ -133,10 +135,11 @@ fn trans_while(bcx: block, cond: @ast::expr, body: ast::blk)
return next_bcx;
}
fn trans_loop(bcx:block, body: ast::blk) -> block {
fn trans_loop(bcx:block, body: ast::blk, opt_label: Option<ident>) -> block {
let _icx = bcx.insn_ctxt("trans_loop");
let next_bcx = sub_block(bcx, ~"next");
let body_bcx_in = loop_scope_block(bcx, next_bcx, ~"`loop`", body.info());
let body_bcx_in = loop_scope_block(bcx, next_bcx, opt_label, ~"`loop`",
body.info());
Br(bcx, body_bcx_in.llbb);
let body_bcx_out = trans_block(body_bcx_in, body, expr::Ignore);
cleanup_and_Br(body_bcx_out, body_bcx_in, body_bcx_in.llbb);
@ -201,7 +204,7 @@ fn trans_log(log_ex: @ast::expr,
}
}
fn trans_break_cont(bcx: block, to_end: bool)
fn trans_break_cont(bcx: block, opt_label: Option<ident>, to_end: bool)
-> block {
let _icx = bcx.insn_ctxt("trans_break_cont");
// Locate closest loop block, outputting cleanup as we go.
@ -209,13 +212,22 @@ fn trans_break_cont(bcx: block, to_end: bool)
let mut target;
loop {
match unwind.kind {
block_scope({loop_break: Some(brk), _}) => {
block_scope({loop_break: Some(brk), loop_label: l, _}) => {
// If we're looking for a labeled loop, check the label...
target = if to_end {
brk
} else {
unwind
};
break;
match opt_label {
Some(desired) => match l {
Some(actual) if actual == desired => break,
// If it doesn't match the one we want,
// don't break
_ => ()
},
None => break
}
}
_ => ()
}
@ -235,12 +247,12 @@ fn trans_break_cont(bcx: block, to_end: bool)
return bcx;
}
fn trans_break(bcx: block) -> block {
return trans_break_cont(bcx, true);
fn trans_break(bcx: block, label_opt: Option<ident>) -> block {
return trans_break_cont(bcx, label_opt, true);
}
fn trans_cont(bcx: block) -> block {
return trans_break_cont(bcx, false);
fn trans_cont(bcx: block, label_opt: Option<ident>) -> block {
return trans_break_cont(bcx, label_opt, false);
}
fn trans_ret(bcx: block, e: Option<@ast::expr>) -> block {

View File

@ -410,16 +410,10 @@ fn trans_rvalue_stmt_unadjusted(bcx: block, expr: @ast::expr) -> block {
match expr.node {
ast::expr_break(label_opt) => {
if label_opt.is_some() {
bcx.tcx().sess.span_unimpl(expr.span, ~"labeled break");
}
return controlflow::trans_break(bcx);
return controlflow::trans_break(bcx, label_opt);
}
ast::expr_again(label_opt) => {
if label_opt.is_some() {
bcx.tcx().sess.span_unimpl(expr.span, ~"labeled again");
}
return controlflow::trans_cont(bcx);
return controlflow::trans_cont(bcx, label_opt);
}
ast::expr_ret(ex) => {
return controlflow::trans_ret(bcx, ex);
@ -436,8 +430,8 @@ fn trans_rvalue_stmt_unadjusted(bcx: block, expr: @ast::expr) -> block {
ast::expr_while(cond, body) => {
return controlflow::trans_while(bcx, cond, body);
}
ast::expr_loop(body, _) => {
return controlflow::trans_loop(bcx, body);
ast::expr_loop(body, opt_label) => {
return controlflow::trans_loop(bcx, body, opt_label);
}
ast::expr_assign(dst, src) => {
let src_datum = unpack_datum!(bcx, trans_to_datum(bcx, src));

View File

@ -46,7 +46,7 @@
use syntax::ast_util::{local_def, respan, split_trait_methods};
use syntax::visit;
use metadata::csearch;
use util::common::may_break;
use util::common::{block_query, loop_query};
use syntax::codemap::span;
use pat_util::{pat_is_variant, pat_id_map, PatIdMap};
use middle::ty;

View File

@ -1665,7 +1665,7 @@ fn check_field(fcx: @fn_ctxt, expr: @ast::expr, is_callee: bool,
ast::expr_loop(body, _) => {
check_block_no_value(fcx, body);
fcx.write_ty(id, ty::mk_nil(tcx));
bot = !may_break(body);
bot = !may_break(tcx, expr.id, body);
}
ast::expr_match(discrim, arms) => {
bot = alt::check_alt(fcx, expr, discrim, arms);
@ -2544,6 +2544,30 @@ fn ast_expr_vstore_to_vstore(fcx: @fn_ctxt, e: @ast::expr, n: uint,
}
}
// Returns true if b contains a break that can exit from b
fn may_break(cx: ty::ctxt, id: ast::node_id, b: ast::blk) -> bool {
// First: is there an unlabeled break immediately
// inside the loop?
(loop_query(b, |e| {
match e {
ast::expr_break(_) => true,
_ => false
}
})) ||
// Second: is there a labeled break with label
// <id> nested anywhere inside the loop?
(block_query(b, |e| {
match e.node {
ast::expr_break(Some(_)) =>
match cx.def_map.find(e.id) {
Some(ast::def_label(loop_id)) if id == loop_id => true,
_ => false,
},
_ => false
}
}))
}
fn check_bounds_are_used(ccx: @crate_ctxt,
span: span,
tps: ~[ast::ty_param],

View File

@ -58,22 +58,19 @@ fn loop_query(b: ast::blk, p: fn@(ast::expr_) -> bool) -> bool {
return *rs;
}
fn has_nonlocal_exits(b: ast::blk) -> bool {
do loop_query(b) |e| {
match e {
ast::expr_break(_) | ast::expr_again(_) => true,
_ => false
}
}
}
fn may_break(b: ast::blk) -> bool {
do loop_query(b) |e| {
match e {
ast::expr_break(_) => true,
_ => false
}
}
// Takes a predicate p, returns true iff p is true for any subexpressions
// of b -- skipping any inner loops (loop, while, loop_body)
fn block_query(b: ast::blk, p: fn@(@ast::expr) -> bool) -> bool {
let rs = @mut false;
let visit_expr =
|e: @ast::expr, &&flag: @mut bool, v: visit::vt<@mut bool>| {
*flag |= p(e);
visit::visit_expr(e, flag, v)
};
let v = visit::mk_vt(@{visit_expr: visit_expr
,.. *visit::default_visitor()});
visit::visit_block(b, rs, v);
return *rs;
}
fn local_rhs_span(l: @ast::local, def: span) -> span {

View File

@ -0,0 +1,22 @@
fn main() {
let mut x = 0;
loop foo: {
loop bar: {
loop quux: {
if 1 == 2 {
break foo;
}
else {
break bar;
}
}
loop foo;
}
x = 42;
break;
}
error!("%?", x);
assert(x == 42);
}