Preliminary support for labeled break/continue for loop
s
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:
parent
46d4bbbae4
commit
dd66e7549b
10 changed files with 232 additions and 98 deletions
|
@ -1180,7 +1180,10 @@ fn print_expr(s: ps, &&expr: @ast::expr) {
|
||||||
ast::expr_loop(blk, opt_ident) => {
|
ast::expr_loop(blk, opt_ident) => {
|
||||||
head(s, ~"loop");
|
head(s, ~"loop");
|
||||||
space(s.s);
|
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);
|
print_block(s, blk);
|
||||||
}
|
}
|
||||||
ast::expr_match(expr, arms) => {
|
ast::expr_match(expr, arms) => {
|
||||||
|
|
|
@ -95,9 +95,9 @@
|
||||||
use dvec::DVec;
|
use dvec::DVec;
|
||||||
use std::map::HashMap;
|
use std::map::HashMap;
|
||||||
use syntax::{visit, ast_util};
|
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 visit::vt;
|
||||||
use syntax::codemap::span;
|
use syntax::codemap::{span, span_to_str};
|
||||||
use syntax::ast::*;
|
use syntax::ast::*;
|
||||||
use io::WriterUtil;
|
use io::WriterUtil;
|
||||||
use capture::{cap_move, cap_drop, cap_copy, cap_ref};
|
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) }
|
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,
|
fn check_crate(tcx: ty::ctxt,
|
||||||
method_map: typeck::method_map,
|
method_map: typeck::method_map,
|
||||||
crate: @crate) -> last_use_map {
|
crate: @crate) -> last_use_map {
|
||||||
|
@ -277,8 +287,8 @@ fn IrMaps(tcx: ty::ctxt, method_map: typeck::method_map,
|
||||||
tcx: tcx,
|
tcx: tcx,
|
||||||
method_map: method_map,
|
method_map: method_map,
|
||||||
last_use_map: last_use_map,
|
last_use_map: last_use_map,
|
||||||
num_live_nodes: 0u,
|
num_live_nodes: 0,
|
||||||
num_vars: 0u,
|
num_vars: 0,
|
||||||
live_node_map: HashMap(),
|
live_node_map: HashMap(),
|
||||||
variable_map: HashMap(),
|
variable_map: HashMap(),
|
||||||
capture_map: HashMap(),
|
capture_map: HashMap(),
|
||||||
|
@ -291,9 +301,10 @@ impl IrMaps {
|
||||||
fn add_live_node(lnk: LiveNodeKind) -> LiveNode {
|
fn add_live_node(lnk: LiveNodeKind) -> LiveNode {
|
||||||
let ln = LiveNode(self.num_live_nodes);
|
let ln = LiveNode(self.num_live_nodes);
|
||||||
self.lnks.push(lnk);
|
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
|
ln
|
||||||
}
|
}
|
||||||
|
@ -308,7 +319,7 @@ impl IrMaps {
|
||||||
fn add_variable(vk: VarKind) -> Variable {
|
fn add_variable(vk: VarKind) -> Variable {
|
||||||
let v = Variable(self.num_vars);
|
let v = Variable(self.num_vars);
|
||||||
self.var_kinds.push(vk);
|
self.var_kinds.push(vk);
|
||||||
self.num_vars += 1u;
|
self.num_vars += 1;
|
||||||
|
|
||||||
match vk {
|
match vk {
|
||||||
Local(LocalInfo {id:node_id, _}) |
|
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(_, _, _, cap_clause) |
|
||||||
expr_fn_block(_, _, 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
|
// Make a live_node for each captured variable, with the span
|
||||||
// being the location that the variable is used. This results
|
// being the location that the variable is used. This results
|
||||||
// in better error messages than just pointing at the closure
|
// in better error messages than just pointing at the closure
|
||||||
|
@ -571,14 +586,22 @@ const ACC_READ: uint = 1u;
|
||||||
const ACC_WRITE: uint = 2u;
|
const ACC_WRITE: uint = 2u;
|
||||||
const ACC_USE: uint = 4u;
|
const ACC_USE: uint = 4u;
|
||||||
|
|
||||||
|
type LiveNodeMap = HashMap<node_id, LiveNode>;
|
||||||
|
|
||||||
struct Liveness {
|
struct Liveness {
|
||||||
tcx: ty::ctxt,
|
tcx: ty::ctxt,
|
||||||
ir: @IrMaps,
|
ir: @IrMaps,
|
||||||
s: Specials,
|
s: Specials,
|
||||||
successors: ~[mut LiveNode],
|
successors: ~[mut LiveNode],
|
||||||
users: ~[mut users],
|
users: ~[mut users],
|
||||||
mut break_ln: LiveNode,
|
// The list of node IDs for the nested loop scopes
|
||||||
mut cont_ln: LiveNode,
|
// 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 {
|
fn Liveness(ir: @IrMaps, specials: Specials) -> Liveness {
|
||||||
|
@ -594,8 +617,9 @@ fn Liveness(ir: @IrMaps, specials: Specials) -> Liveness {
|
||||||
vec::to_mut(
|
vec::to_mut(
|
||||||
vec::from_elem(ir.num_live_nodes * ir.num_vars,
|
vec::from_elem(ir.num_live_nodes * ir.num_vars,
|
||||||
invalid_users())),
|
invalid_users())),
|
||||||
break_ln: invalid_node(),
|
loop_scope: @DVec(),
|
||||||
cont_ln: invalid_node()
|
break_ln: HashMap(),
|
||||||
|
cont_ln: HashMap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -691,6 +715,9 @@ impl Liveness {
|
||||||
if reader.is_valid() {Some((*self.ir).lnk(reader))} else {None}
|
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)
|
fn live_on_exit(ln: LiveNode, var: Variable)
|
||||||
-> Option<LiveNodeKind> {
|
-> Option<LiveNodeKind> {
|
||||||
|
|
||||||
|
@ -717,8 +744,8 @@ impl Liveness {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn indices(ln: LiveNode, op: fn(uint)) {
|
fn indices(ln: LiveNode, op: fn(uint)) {
|
||||||
let node_base_idx = self.idx(ln, Variable(0u));
|
let node_base_idx = self.idx(ln, Variable(0));
|
||||||
for uint::range(0u, self.ir.num_vars) |var_idx| {
|
for uint::range(0, self.ir.num_vars) |var_idx| {
|
||||||
op(node_base_idx + var_idx)
|
op(node_base_idx + var_idx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -735,8 +762,8 @@ impl Liveness {
|
||||||
fn write_vars(wr: io::Writer,
|
fn write_vars(wr: io::Writer,
|
||||||
ln: LiveNode,
|
ln: LiveNode,
|
||||||
test: fn(uint) -> LiveNode) {
|
test: fn(uint) -> LiveNode) {
|
||||||
let node_base_idx = self.idx(ln, Variable(0u));
|
let node_base_idx = self.idx(ln, Variable(0));
|
||||||
for uint::range(0u, self.ir.num_vars) |var_idx| {
|
for uint::range(0, self.ir.num_vars) |var_idx| {
|
||||||
let idx = node_base_idx + var_idx;
|
let idx = node_base_idx + var_idx;
|
||||||
if test(idx).is_valid() {
|
if test(idx).is_valid() {
|
||||||
wr.write_str(~" ");
|
wr.write_str(~" ");
|
||||||
|
@ -745,6 +772,28 @@ impl Liveness {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
fn ln_str(ln: LiveNode) -> ~str {
|
||||||
do io::with_str_writer |wr| {
|
do io::with_str_writer |wr| {
|
||||||
wr.write_str(~"[ln(");
|
wr.write_str(~"[ln(");
|
||||||
|
@ -833,18 +882,18 @@ impl Liveness {
|
||||||
let idx = self.idx(ln, var);
|
let idx = self.idx(ln, var);
|
||||||
let user = &mut self.users[idx];
|
let user = &mut self.users[idx];
|
||||||
|
|
||||||
if (acc & ACC_WRITE) != 0u {
|
if (acc & ACC_WRITE) != 0 {
|
||||||
user.reader = invalid_node();
|
user.reader = invalid_node();
|
||||||
user.writer = ln;
|
user.writer = ln;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Important: if we both read/write, must do read second
|
// Important: if we both read/write, must do read second
|
||||||
// or else the write will override.
|
// or else the write will override.
|
||||||
if (acc & ACC_READ) != 0u {
|
if (acc & ACC_READ) != 0 {
|
||||||
user.reader = ln;
|
user.reader = ln;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (acc & ACC_USE) != 0u {
|
if (acc & ACC_USE) != 0 {
|
||||||
self.users[idx].used = true;
|
self.users[idx].used = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -858,10 +907,13 @@ impl Liveness {
|
||||||
// if there is a `break` or `again` at the top level, then it's
|
// if there is a `break` or `again` at the top level, then it's
|
||||||
// effectively a return---this only occurs in `for` loops,
|
// effectively a return---this only occurs in `for` loops,
|
||||||
// where the body is really a closure.
|
// 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 =
|
let entry_ln: LiveNode =
|
||||||
self.with_loop_nodes(self.s.exit_ln, self.s.exit_ln, || {
|
self.with_loop_nodes(body.node.id, self.s.exit_ln, self.s.exit_ln,
|
||||||
self.propagate_through_fn_block(decl, body)
|
|| { self.propagate_through_fn_block(decl, body) });
|
||||||
});
|
|
||||||
|
|
||||||
// hack to skip the loop unless debug! is enabled:
|
// hack to skip the loop unless debug! is enabled:
|
||||||
debug!("^^ liveness computation results for body %d (entry=%s)",
|
debug!("^^ liveness computation results for body %d (entry=%s)",
|
||||||
|
@ -972,6 +1024,9 @@ impl Liveness {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn propagate_through_expr(expr: @expr, succ: LiveNode) -> LiveNode {
|
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 {
|
match expr.node {
|
||||||
// Interesting cases with control flow or which gen/kill
|
// Interesting cases with control flow or which gen/kill
|
||||||
|
|
||||||
|
@ -983,16 +1038,27 @@ impl Liveness {
|
||||||
self.propagate_through_expr(e, succ)
|
self.propagate_through_expr(e, succ)
|
||||||
}
|
}
|
||||||
|
|
||||||
expr_fn(*) | expr_fn_block(*) => {
|
expr_fn(_, _, blk, _) | expr_fn_block(_, blk, _) => {
|
||||||
// the construction of a closure itself is not important,
|
debug!("%s is an expr_fn or expr_fn_block",
|
||||||
// but we have to consider the closed over variables.
|
expr_to_str(expr, self.tcx.sess.intr()));
|
||||||
let caps = (*self.ir).captures(expr);
|
|
||||||
do (*caps).foldr(succ) |cap, succ| {
|
/*
|
||||||
self.init_from_succ(cap.ln, succ);
|
The next-node for a break is the successor of the entire
|
||||||
let var = self.variable(cap.var_nid, expr.span);
|
loop. The next-node for a continue is the top of this loop.
|
||||||
self.acc(cap.ln, var, ACC_READ | ACC_USE);
|
*/
|
||||||
cap.ln
|
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) => {
|
expr_if(cond, then, els) => {
|
||||||
|
@ -1021,6 +1087,8 @@ impl Liveness {
|
||||||
self.propagate_through_loop(expr, Some(cond), blk, succ)
|
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, _) => {
|
expr_loop(blk, _) => {
|
||||||
self.propagate_through_loop(expr, None, blk, succ)
|
self.propagate_through_loop(expr, None, blk, succ)
|
||||||
}
|
}
|
||||||
|
@ -1062,29 +1130,31 @@ impl Liveness {
|
||||||
}
|
}
|
||||||
|
|
||||||
expr_break(opt_label) => {
|
expr_break(opt_label) => {
|
||||||
if !self.break_ln.is_valid() {
|
// Find which label this break jumps to
|
||||||
self.tcx.sess.span_bug(
|
let sc = self.find_loop_scope(opt_label, expr.id, expr.span);
|
||||||
expr.span, ~"break with invalid break_ln");
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt_label.is_some() {
|
// Now that we know the label we're going to,
|
||||||
self.tcx.sess.span_unimpl(expr.span, ~"labeled break");
|
// 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) => {
|
expr_again(opt_label) => {
|
||||||
if !self.cont_ln.is_valid() {
|
// Find which label this expr continues to to
|
||||||
self.tcx.sess.span_bug(
|
let sc = self.find_loop_scope(opt_label, expr.id, expr.span);
|
||||||
expr.span, ~"cont with invalid cont_ln");
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt_label.is_some() {
|
// Now that we know the label we're going to,
|
||||||
self.tcx.sess.span_unimpl(expr.span, ~"labeled again");
|
// 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) => {
|
expr_move(l, r) | expr_assign(l, r) => {
|
||||||
|
@ -1314,6 +1384,7 @@ impl Liveness {
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
// first iteration:
|
// first iteration:
|
||||||
let mut first_merge = true;
|
let mut first_merge = true;
|
||||||
let ln = self.live_node(expr.id, expr.span);
|
let ln = self.live_node(expr.id, expr.span);
|
||||||
|
@ -1325,8 +1396,11 @@ impl Liveness {
|
||||||
self.merge_from_succ(ln, succ, first_merge);
|
self.merge_from_succ(ln, succ, first_merge);
|
||||||
first_merge = false;
|
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 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)
|
self.propagate_through_block(body, cond_ln)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1334,7 +1408,8 @@ impl Liveness {
|
||||||
while self.merge_from_succ(ln, body_ln, first_merge) {
|
while self.merge_from_succ(ln, body_ln, first_merge) {
|
||||||
first_merge = false;
|
first_merge = false;
|
||||||
assert cond_ln == self.propagate_through_opt_expr(cond, ln);
|
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)
|
self.propagate_through_block(body, cond_ln)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1342,15 +1417,16 @@ impl Liveness {
|
||||||
cond_ln
|
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,
|
cont_ln: LiveNode,
|
||||||
f: fn() -> R) -> R {
|
f: fn() -> R) -> R {
|
||||||
let bl = self.break_ln, cl = self.cont_ln;
|
debug!("with_loop_nodes: %d %u", loop_node_id, *break_ln);
|
||||||
self.break_ln = break_ln;
|
self.loop_scope.push(loop_node_id);
|
||||||
self.cont_ln = cont_ln;
|
self.break_ln.insert(loop_node_id, break_ln);
|
||||||
let r <- f();
|
self.cont_ln.insert(loop_node_id, cont_ln);
|
||||||
self.break_ln = bl;
|
let r = f();
|
||||||
self.cont_ln = cl;
|
self.loop_scope.pop();
|
||||||
move r
|
move r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1526,6 +1602,10 @@ impl @Liveness {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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) {
|
fn check_move_from_var(span: span, ln: LiveNode, var: Variable) {
|
||||||
debug!("check_move_from_var(%s, %s)",
|
debug!("check_move_from_var(%s, %s)",
|
||||||
ln.to_str(), var.to_str());
|
ln.to_str(), var.to_str());
|
||||||
|
|
|
@ -1050,7 +1050,7 @@ fn new_block(cx: fn_ctxt, parent: Option<block>, +kind: block_kind,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn simple_block_scope() -> 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})
|
mut cleanup_paths: ~[], mut landing_pad: None})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1067,10 +1067,11 @@ fn scope_block(bcx: block,
|
||||||
n, opt_node_info);
|
n, opt_node_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loop_scope_block(bcx: block, loop_break: block, n: ~str,
|
fn loop_scope_block(bcx: block, loop_break: block, loop_label: Option<ident>,
|
||||||
opt_node_info: Option<node_info>) -> block {
|
n: ~str, opt_node_info: Option<node_info>) -> block {
|
||||||
return new_block(bcx.fcx, Some(bcx), block_scope({
|
return new_block(bcx.fcx, Some(bcx), block_scope({
|
||||||
loop_break: Some(loop_break),
|
loop_break: Some(loop_break),
|
||||||
|
loop_label: loop_label,
|
||||||
mut cleanups: ~[],
|
mut cleanups: ~[],
|
||||||
mut cleanup_paths: ~[],
|
mut cleanup_paths: ~[],
|
||||||
mut landing_pad: None
|
mut landing_pad: None
|
||||||
|
|
|
@ -445,6 +445,7 @@ enum block_kind {
|
||||||
|
|
||||||
type scope_info = {
|
type scope_info = {
|
||||||
loop_break: Option<block>,
|
loop_break: Option<block>,
|
||||||
|
loop_label: Option<ident>,
|
||||||
// A list of functions that must be run at when leaving this
|
// A list of functions that must be run at when leaving this
|
||||||
// block, cleaning up any variables that were introduced in the
|
// block, cleaning up any variables that were introduced in the
|
||||||
// block.
|
// block.
|
||||||
|
|
|
@ -113,7 +113,9 @@ fn trans_while(bcx: block, cond: @ast::expr, body: ast::blk)
|
||||||
// | body_bcx_out --+
|
// | body_bcx_out --+
|
||||||
// next_bcx
|
// 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 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");
|
let body_bcx_in = scope_block(loop_bcx, body.info(), ~"while loop body");
|
||||||
Br(bcx, loop_bcx.llbb);
|
Br(bcx, loop_bcx.llbb);
|
||||||
|
@ -133,10 +135,11 @@ fn trans_while(bcx: block, cond: @ast::expr, body: ast::blk)
|
||||||
return next_bcx;
|
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 _icx = bcx.insn_ctxt("trans_loop");
|
||||||
let next_bcx = sub_block(bcx, ~"next");
|
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);
|
Br(bcx, body_bcx_in.llbb);
|
||||||
let body_bcx_out = trans_block(body_bcx_in, body, expr::Ignore);
|
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);
|
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 {
|
-> block {
|
||||||
let _icx = bcx.insn_ctxt("trans_break_cont");
|
let _icx = bcx.insn_ctxt("trans_break_cont");
|
||||||
// Locate closest loop block, outputting cleanup as we go.
|
// 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;
|
let mut target;
|
||||||
loop {
|
loop {
|
||||||
match unwind.kind {
|
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 {
|
target = if to_end {
|
||||||
brk
|
brk
|
||||||
} else {
|
} else {
|
||||||
unwind
|
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;
|
return bcx;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trans_break(bcx: block) -> block {
|
fn trans_break(bcx: block, label_opt: Option<ident>) -> block {
|
||||||
return trans_break_cont(bcx, true);
|
return trans_break_cont(bcx, label_opt, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trans_cont(bcx: block) -> block {
|
fn trans_cont(bcx: block, label_opt: Option<ident>) -> block {
|
||||||
return trans_break_cont(bcx, false);
|
return trans_break_cont(bcx, label_opt, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trans_ret(bcx: block, e: Option<@ast::expr>) -> block {
|
fn trans_ret(bcx: block, e: Option<@ast::expr>) -> block {
|
||||||
|
|
|
@ -410,16 +410,10 @@ fn trans_rvalue_stmt_unadjusted(bcx: block, expr: @ast::expr) -> block {
|
||||||
|
|
||||||
match expr.node {
|
match expr.node {
|
||||||
ast::expr_break(label_opt) => {
|
ast::expr_break(label_opt) => {
|
||||||
if label_opt.is_some() {
|
return controlflow::trans_break(bcx, label_opt);
|
||||||
bcx.tcx().sess.span_unimpl(expr.span, ~"labeled break");
|
|
||||||
}
|
|
||||||
return controlflow::trans_break(bcx);
|
|
||||||
}
|
}
|
||||||
ast::expr_again(label_opt) => {
|
ast::expr_again(label_opt) => {
|
||||||
if label_opt.is_some() {
|
return controlflow::trans_cont(bcx, label_opt);
|
||||||
bcx.tcx().sess.span_unimpl(expr.span, ~"labeled again");
|
|
||||||
}
|
|
||||||
return controlflow::trans_cont(bcx);
|
|
||||||
}
|
}
|
||||||
ast::expr_ret(ex) => {
|
ast::expr_ret(ex) => {
|
||||||
return controlflow::trans_ret(bcx, 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) => {
|
ast::expr_while(cond, body) => {
|
||||||
return controlflow::trans_while(bcx, cond, body);
|
return controlflow::trans_while(bcx, cond, body);
|
||||||
}
|
}
|
||||||
ast::expr_loop(body, _) => {
|
ast::expr_loop(body, opt_label) => {
|
||||||
return controlflow::trans_loop(bcx, body);
|
return controlflow::trans_loop(bcx, body, opt_label);
|
||||||
}
|
}
|
||||||
ast::expr_assign(dst, src) => {
|
ast::expr_assign(dst, src) => {
|
||||||
let src_datum = unpack_datum!(bcx, trans_to_datum(bcx, src));
|
let src_datum = unpack_datum!(bcx, trans_to_datum(bcx, src));
|
||||||
|
|
|
@ -46,7 +46,7 @@ use syntax::ast_map::node_id_to_str;
|
||||||
use syntax::ast_util::{local_def, respan, split_trait_methods};
|
use syntax::ast_util::{local_def, respan, split_trait_methods};
|
||||||
use syntax::visit;
|
use syntax::visit;
|
||||||
use metadata::csearch;
|
use metadata::csearch;
|
||||||
use util::common::may_break;
|
use util::common::{block_query, loop_query};
|
||||||
use syntax::codemap::span;
|
use syntax::codemap::span;
|
||||||
use pat_util::{pat_is_variant, pat_id_map, PatIdMap};
|
use pat_util::{pat_is_variant, pat_id_map, PatIdMap};
|
||||||
use middle::ty;
|
use middle::ty;
|
||||||
|
|
|
@ -1665,7 +1665,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
|
||||||
ast::expr_loop(body, _) => {
|
ast::expr_loop(body, _) => {
|
||||||
check_block_no_value(fcx, body);
|
check_block_no_value(fcx, body);
|
||||||
fcx.write_ty(id, ty::mk_nil(tcx));
|
fcx.write_ty(id, ty::mk_nil(tcx));
|
||||||
bot = !may_break(body);
|
bot = !may_break(tcx, expr.id, body);
|
||||||
}
|
}
|
||||||
ast::expr_match(discrim, arms) => {
|
ast::expr_match(discrim, arms) => {
|
||||||
bot = alt::check_alt(fcx, expr, 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,
|
fn check_bounds_are_used(ccx: @crate_ctxt,
|
||||||
span: span,
|
span: span,
|
||||||
tps: ~[ast::ty_param],
|
tps: ~[ast::ty_param],
|
||||||
|
|
|
@ -58,22 +58,19 @@ fn loop_query(b: ast::blk, p: fn@(ast::expr_) -> bool) -> bool {
|
||||||
return *rs;
|
return *rs;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_nonlocal_exits(b: ast::blk) -> bool {
|
// Takes a predicate p, returns true iff p is true for any subexpressions
|
||||||
do loop_query(b) |e| {
|
// of b -- skipping any inner loops (loop, while, loop_body)
|
||||||
match e {
|
fn block_query(b: ast::blk, p: fn@(@ast::expr) -> bool) -> bool {
|
||||||
ast::expr_break(_) | ast::expr_again(_) => true,
|
let rs = @mut false;
|
||||||
_ => false
|
let visit_expr =
|
||||||
}
|
|e: @ast::expr, &&flag: @mut bool, v: visit::vt<@mut bool>| {
|
||||||
}
|
*flag |= p(e);
|
||||||
}
|
visit::visit_expr(e, flag, v)
|
||||||
|
};
|
||||||
fn may_break(b: ast::blk) -> bool {
|
let v = visit::mk_vt(@{visit_expr: visit_expr
|
||||||
do loop_query(b) |e| {
|
,.. *visit::default_visitor()});
|
||||||
match e {
|
visit::visit_block(b, rs, v);
|
||||||
ast::expr_break(_) => true,
|
return *rs;
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn local_rhs_span(l: @ast::local, def: span) -> span {
|
fn local_rhs_span(l: @ast::local, def: span) -> span {
|
||||||
|
|
22
src/test/run-pass/issue-2216.rs
Normal file
22
src/test/run-pass/issue-2216.rs
Normal 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);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue