dart2dart: Support for break in loops.

Break is supported by collecting all the exits from the loop body and
processing them after the loop body is translated.  If there are any,
a join-point continuation for all the exits is added.  This
continuation should be in the scope of the nearest common ancestor of
all the exits --- which is the scope of the condition since the branch
on the condition always has an exit.

BUG=
R=floitsch@google.com, sigurdm@google.com

Review URL: https://codereview.chromium.org//469263002

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@39290 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
kmillikin@google.com 2014-08-15 15:12:18 +00:00
parent b0a1da2971
commit ee5635056f
3 changed files with 219 additions and 65 deletions

View file

@ -137,12 +137,19 @@ class _GetterElements {
_GetterElements({this.result, this.index, this.receiver}) ;
}
/**
*
*/
/// A mapping from variable elements to their compile-time values.
///
/// Map elements denoted by parameters and local variables to the
/// [ir.Primitive] that is their value. Parameters and locals are
/// assigned indexes which can be used to refer to them.
class Environment {
/// A map from elements to their environment index.
final Map<Element, int> variable2index;
/// A reverse map from environment indexes to the variable.
final List<Element> index2variable;
/// A map from environment indexes to their value.
final List<ir.Primitive> index2value;
Environment.empty()
@ -150,6 +157,9 @@ class Environment {
index2variable = <Element>[],
index2value = <ir.Primitive>[];
/// Construct an environment that is a copy of another one.
///
/// The mapping from elements to indexes is shared, not copied.
Environment.from(Environment other)
: variable2index = other.variable2index,
index2variable = new List<Element>.from(other.index2variable),
@ -202,6 +212,38 @@ class Environment {
}
}
/// A class to collect breaks or continues.
///
/// When visiting a potential target of breaks or continues, any breaks or
/// continues are collected by a JumpCollector and processed later, on demand.
/// The site of the break or continue is represented by a continuation
/// invocation that will have its target and arguments filled in later.
///
/// The environment of the builder at that point is captured and should not
/// be subsequently mutated until the jump is resolved.
class JumpCollector {
final JumpTarget target;
final List<ir.InvokeContinuation> _invocations = <ir.InvokeContinuation>[];
final List<Environment> _environments = <Environment>[];
JumpCollector(this.target);
bool get isEmpty => _invocations.isEmpty;
int get length => _invocations.length;
List<ir.InvokeContinuation> get invocations => _invocations;
List<Environment> get environments => _environments;
void addJump(IrBuilder builder) {
ir.InvokeContinuation invoke = new ir.InvokeContinuation.uninitialized();
builder.add(invoke);
_invocations.add(invoke);
_environments.add(builder.environment);
builder.current = null;
// TODO(kmillikin): Can we set builder.environment to null to make it
// less likely to mutate it?
}
}
/**
* A tree visitor that builds [IrNodes]. The visit methods add statements using
* to the [builder] and return the last added statement for trees that represent
@ -260,6 +302,9 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
/// A map from variable indexes to their values.
Environment environment;
/// A stack of collectors for breaks.
final List<JumpCollector> breakCollectors;
ConstExpBuilder constantBuilder;
final List<ConstDeclaration> localConstants;
@ -272,6 +317,7 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
: returnContinuation = new ir.Continuation.retrn(),
parameters = <ir.Parameter>[],
environment = new Environment.empty(),
breakCollectors = <JumpCollector>[],
localConstants = <ConstDeclaration>[],
closureLocals = new DetectClosureVariables(elements),
super(elements, compiler) {
@ -289,6 +335,7 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
returnContinuation = parent.returnContinuation,
parameters = <ir.Parameter>[],
environment = new Environment.from(parent.environment),
breakCollectors = parent.breakCollectors,
constantBuilder = parent.constantBuilder,
localConstants = parent.localConstants,
currentFunction = parent.currentFunction,
@ -308,6 +355,7 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
returnContinuation = parent.returnContinuation,
parameters = <ir.Parameter>[],
environment = new Environment.empty(),
breakCollectors = parent.breakCollectors,
constantBuilder = parent.constantBuilder,
localConstants = parent.localConstants,
currentFunction = parent.currentFunction,
@ -420,6 +468,23 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
return null;
}
// Build(BreakStatement L, C) = C[InvokeContinuation(...)]
//
// The continuation and arguments are filled in later after translating
// the body containing the break.
ir.Primitive visitBreakStatement(ast.BreakStatement node) {
assert(isOpen);
JumpTarget target = elements.getTargetOf(node);
for (JumpCollector collector in breakCollectors) {
if (target == collector.target) {
collector.addJump(this);
return null;
}
}
compiler.internalError(node, "'break' target not found");
return null;
}
// Build(EmptyStatement, C) = C
ir.Primitive visitEmptyStatement(ast.EmptyStatement node) {
assert(isOpen);
@ -437,24 +502,24 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
/// Create a non-recursive join-point continuation.
///
/// Given the environment length at the join point and a list of open
/// contexts that should reach the join point, create a join-point
/// Given the environment length at the join point and a list of
/// jumps that should reach the join point, create a join-point
/// continuation. The join-point continuation has a parameter for each
/// variable that has different values reaching on different paths.
///
/// The holes in the contexts are filled with [ir.InvokeContinuation]
/// expressions passing the appropriate arguments.
/// The jumps are uninitialized [ir.InvokeContinuation] expressions.
/// They are filled in with the target continuation and appropriate
/// arguments.
///
/// As a side effect, the environment of this builder is updated to include
/// the join-point continuation parameters.
ir.Continuation createJoin(int environmentLength,
List<IrBuilder> contexts) {
assert(contexts.length >= 2);
ir.Continuation createJoin(int environmentLength, JumpCollector jumps) {
assert(jumps.length >= 2);
// Compute which values are identical on all paths reaching the join.
// Handle the common case of a pair of contexts efficiently.
Environment first = contexts[0].environment;
Environment second = contexts[1].environment;
Environment first = jumps.environments[0];
Environment second = jumps.environments[1];
assert(environmentLength <= first.length);
assert(environmentLength <= second.length);
assert(first.sameDomain(environmentLength, second));
@ -479,10 +544,10 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
for (int i = 0; i < environmentLength; ++i) {
ir.Primitive candidate = common[i];
if (candidate == null) continue;
for (IrBuilder current in contexts.skip(2)) {
assert(environmentLength <= current.environment.length);
assert(first.sameDomain(environmentLength, current.environment));
if (candidate != current.environment[i]) {
for (Environment current in jumps.environments.skip(2)) {
assert(environmentLength <= current.length);
assert(first.sameDomain(environmentLength, current));
if (candidate != current[i]) {
common[i] = null;
++parameterCount;
break;
@ -492,40 +557,49 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
}
}
// Create the join point continuation.
List<ir.Parameter> parameters = <ir.Parameter>[];
parameters.length = parameterCount;
int index = 0;
for (int i = 0; i < environmentLength; ++i) {
if (common[i] == null) {
ir.Parameter parameter = new ir.Parameter(first.index2variable[i]);
parameters[index++] = parameter;
if (i < environment.length) {
// The variable is bound to the join-point parameter in the
// continuation. Variables outside the range of the environment are
// 'phantom' variables used for the values of expressions like
// &&, ||, and ?:.
environment.index2value[i] = parameter;
}
parameters[index++] = new ir.Parameter(first.index2variable[i]);
}
}
assert(index == parameterCount);
ir.Continuation join = new ir.Continuation(parameters);
// Plug all the contexts with continuation invocations.
for (IrBuilder context in contexts) {
// Passing `this` as one of the contexts will not do the right thing
// (this.environment has already been mutated).
assert(context != this);
List<ir.Primitive> arguments = <ir.Primitive>[];
// Fill in all the continuation invocations.
for (int i = 0; i < jumps.length; ++i) {
Environment currentEnvironment = jumps.environments[i];
ir.InvokeContinuation invoke = jumps.invocations[i];
// Sharing this.environment with one of the invocations will not do
// the right thing (this.environment has already been mutated).
List<ir.Reference> arguments = <ir.Reference>[];
arguments.length = parameterCount;
int index = 0;
for (int i = 0; i < environmentLength; ++i) {
if (common[i] == null) {
arguments[index++] = context.environment[i];
arguments[index++] = new ir.Reference(currentEnvironment[i]);
}
}
context.add(new ir.InvokeContinuation(join, arguments));
context.current = null;
invoke.continuation = new ir.Reference(join);
invoke.arguments = arguments;
}
// Mutate this.environment to be the environment at the join point. Do
// this after adding the continuation invocations, because this.environment
// might be collected by the jump collector and so the old environment
// values are needed for the continuation invocation.
//
// Iterate to environment.length because environmentLength includes values
// outside the environment which are 'phantom' variables used for the
// values of expressions like &&, ||, and ?:.
index = 0;
for (int i = 0; i < environment.length; ++i) {
if (common[i] == null) {
environment.index2value[i] = parameters[index++];
}
}
return join;
@ -561,17 +635,23 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
}
}
// For loops use three named continuations: the entry to the condition,
// the entry to the body, and the loop exit (break). The CPS translation
// of [[for (initializer; condition; update) body; successor]] is:
// For loops use four named continuations: the entry to the condition,
// the entry to the body, the loop exit, and the loop successor (break).
// The CPS translation of
// [[for (initializer; condition; update) body; successor]] is:
//
// [[initializer]];
// let cont loop(x, ...) =
// let prim cond = [[condition]] in
// let cont exit() = [[successor]] in
// let cont break() = [[successor]] in
// let cont exit() = break(v, ...) in
// let cont body() = [[body]]; [[update]]; loop(v, ...) in
// branch cond (body, exit) in
// loop(v, ...)
//
// If there are no breaks in the body, the break continuation is inlined
// in the exit continuation (i.e., the translation of the successor
// statement occurs in the exit continuation).
if (node.initializer != null) visit(node.initializer);
@ -585,23 +665,37 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
condition = condBuilder.visit(node.condition);
}
JumpCollector breakCollector =
new JumpCollector(elements.getTargetDefinition(node));
breakCollectors.add(breakCollector);
IrBuilder bodyBuilder = new IrBuilder.delimited(condBuilder);
bodyBuilder.visit(node.body);
for (ast.Node n in node.update) {
if (!bodyBuilder.isOpen) break;
bodyBuilder.visit(n);
}
assert(breakCollectors.last == breakCollector);
breakCollectors.removeLast();
// Create body entry and loop exit continuations and a join-point
// continuation if control flow reaches the end of the body (update).
// Create body entry and loop exit continuations and a branch to them.
ir.Continuation bodyContinuation = new ir.Continuation([]);
ir.Continuation exitContinuation = new ir.Continuation([]);
condBuilder.add(
ir.LetCont branch =
new ir.LetCont(exitContinuation,
new ir.LetCont(bodyContinuation,
new ir.Branch(new ir.IsTrue(condition),
bodyContinuation,
exitContinuation))));
exitContinuation)));
// If there are breaks in the body, then there must be a join-point
// continuation for the normal exit and the breaks.
ir.LetCont letJoin;
if (breakCollector.isEmpty) {
condBuilder.add(branch);
} else {
letJoin = new ir.LetCont(null, branch);
condBuilder.add(letJoin);
condBuilder.current = branch;
}
List<ir.Parameter> parameters = condBuilder.parameters;
ir.Continuation loopContinuation = new ir.Continuation(parameters);
if (bodyBuilder.isOpen) {
@ -613,8 +707,16 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
add(new ir.LetCont(loopContinuation,
new ir.InvokeContinuation(loopContinuation,
environment.index2value)));
current = condBuilder.current;
environment = condBuilder.environment;
if (letJoin == null) {
current = condBuilder.current;
environment = condBuilder.environment;
} else {
current = branch;
environment = condBuilder.environment;
breakCollector.addJump(this);
letJoin.continuation = createJoin(environment.length, breakCollector);
current = letJoin;
}
return null;
}
@ -647,8 +749,10 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
// There is a join-point continuation. Build the term
// 'let cont join(x, ...) = [] in Result' and plug invocations of the
// join-point continuation into the then and else continuations.
joinContinuation =
createJoin(environment.length, [thenBuilder, elseBuilder]);
JumpCollector jumps = new JumpCollector(null);
jumps.addJump(thenBuilder);
jumps.addJump(elseBuilder);
joinContinuation = createJoin(environment.length, jumps);
result = new ir.LetCont(joinContinuation, result);
}
@ -678,36 +782,62 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
return null;
}
ir.Primitive visitLabeledStatement(ast.LabeledStatement node) {
ast.Statement body = node.statement;
return body is ast.Loop
? visit(body)
: giveup(node, 'labeled statement');
}
ir.Primitive visitWhile(ast.While node) {
assert(isOpen);
// While loops use three named continuations: the entry to the body,
// the loop exit (break), and the loop back edge (continue).
// While loops use four named continuations: the entry to the body, the
// loop exit, the loop back edge (continue), and the loop exit (break).
// The CPS translation of [[while (condition) body; successor]] is:
//
// let cont loop(x, ...) =
// let prim cond = [[condition]] in
// let cont exit() = [[successor]] in
// let cont break() = [[successor]] in
// let cont exit() = break(v, ...) in
// let cont body() = [[body]]; continue(v, ...) in
// branch cond (body, exit) in
// loop(v, ...)
//
// If there are no breaks in the body, the break continuation is inlined
// in the exit continuation (i.e., the translation of the successor
// statement occurs in the exit continuation).
// The condition and body are delimited.
IrBuilder condBuilder = new IrBuilder.recursive(this);
ir.Primitive condition = condBuilder.visit(node.condition);
JumpCollector breakCollector =
new JumpCollector(elements.getTargetDefinition(node));
breakCollectors.add(breakCollector);
IrBuilder bodyBuilder = new IrBuilder.delimited(condBuilder);
bodyBuilder.visit(node.body);
assert(breakCollectors.last == breakCollector);
breakCollectors.removeLast();
// Create body entry and loop exit continuations and a join-point
// continuation if control flow reaches the end of the body.
// Create body entry and loop exit continuations and a branch to them.
ir.Continuation bodyContinuation = new ir.Continuation([]);
ir.Continuation exitContinuation = new ir.Continuation([]);
condBuilder.add(
ir.LetCont branch =
new ir.LetCont(exitContinuation,
new ir.LetCont(bodyContinuation,
new ir.Branch(new ir.IsTrue(condition),
bodyContinuation,
exitContinuation))));
exitContinuation)));
// If there are breaks in the body, then there must be a join-point
// continuation for the normal exit and the breaks.
ir.LetCont letJoin;
if (breakCollector.isEmpty) {
condBuilder.add(branch);
} else {
letJoin = new ir.LetCont(null, branch);
condBuilder.add(letJoin);
condBuilder.current = branch;
}
List<ir.Parameter> parameters = condBuilder.parameters;
ir.Continuation loopContinuation = new ir.Continuation(parameters);
if (bodyBuilder.isOpen) {
@ -719,8 +849,16 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
add(new ir.LetCont(loopContinuation,
new ir.InvokeContinuation(loopContinuation,
environment.index2value)));
current = condBuilder.current;
environment = condBuilder.environment;
if (letJoin == null) {
current = condBuilder.current;
environment = condBuilder.environment;
} else {
current = branch;
environment = condBuilder.environment;
breakCollector.addJump(this);
letJoin.continuation = createJoin(environment.length, breakCollector);
current = letJoin;
}
return null;
}
@ -804,8 +942,11 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
assert(environment.length == elseBuilder.environment.length);
thenBuilder.environment.extend(null, thenValue);
elseBuilder.environment.extend(null, elseValue);
JumpCollector jumps = new JumpCollector(null);
jumps.addJump(thenBuilder);
jumps.addJump(elseBuilder);
ir.Continuation joinContinuation =
createJoin(environment.length + 1, [thenBuilder, elseBuilder]);
createJoin(environment.length + 1, jumps);
// Build the term
// let cont join(x, ..., result) = [] in
@ -1155,8 +1296,12 @@ class IrBuilder extends ResolvedVisitor<ir.Primitive> {
// Wire up two continuations for the left subexpression, two continuations
// for the right subexpression, and a three-way join continuation.
ir.Continuation joinContinuation = createJoin(environment.length + 1,
[emptyBuilder, rightTrueBuilder, rightFalseBuilder]);
JumpCollector jumps = new JumpCollector(null);
jumps.addJump(emptyBuilder);
jumps.addJump(rightTrueBuilder);
jumps.addJump(rightFalseBuilder);
ir.Continuation joinContinuation =
createJoin(environment.length + 1, jumps);
ir.Continuation leftTrueContinuation = new ir.Continuation([]);
ir.Continuation leftFalseContinuation = new ir.Continuation([]);
ir.Continuation rightTrueContinuation = new ir.Continuation([]);

View file

@ -12,7 +12,6 @@ import '../elements/elements.dart';
import '../universe/universe.dart' show Selector, SelectorKind;
import '../dart_types.dart' show DartType, GenericType;
import 'const_expression.dart';
import '../helpers/helpers.dart';
abstract class Node {
static int hashCount = 0;
@ -132,13 +131,13 @@ class LetPrim extends Expression implements InteriorNode {
/// During one-pass construction a LetCont with an empty continuation body is
/// used to represent the one-level context 'let cont k(v) = [] in E'.
class LetCont extends Expression implements InteriorNode {
final Continuation continuation;
Continuation continuation;
Expression body;
LetCont(this.continuation, this.body);
Expression plug(Expression expr) {
assert(continuation.body == null);
assert(continuation != null && continuation.body == null);
return continuation.body = expr;
}
@ -387,8 +386,8 @@ class DeclareFunction extends Expression implements InteriorNode {
/// Invoke a continuation in tail position.
class InvokeContinuation extends Expression {
final Reference continuation;
final List<Reference> arguments;
Reference continuation;
List<Reference> arguments;
// An invocation of a continuation is recursive if it occurs in the body of
// the continuation itself.
@ -404,6 +403,16 @@ class InvokeContinuation extends Expression {
if (recursive) cont.isRecursive = true;
}
/// A continuation invocation whose target and arguments will be filled
/// in later.
///
/// Used as a placeholder for a jump whose target is not yet created
/// (e.g., in the translation of break and continue).
InvokeContinuation.uninitialized({recursive: false})
: continuation = null,
arguments = null,
isRecursive = recursive;
accept(Visitor visitor) => visitor.visitInvokeContinuation(this);
}

View file

@ -415,7 +415,7 @@ class ASTEmitter extends tree.Visitor<dynamic, Expression> {
Statement body = new Block(statementBuffer);
Statement statement = new While(new Literal(new dart2js.TrueConstant()),
body);
if (usedLabels.remove(stmt.label.name)) {
if (usedLabels.remove(stmt.label)) {
statement = new LabeledStatement(stmt.label.name, statement);
}
savedBuffer.add(statement);
@ -436,7 +436,7 @@ class ASTEmitter extends tree.Visitor<dynamic, Expression> {
Statement body = new Block(statementBuffer);
Statement statement;
statement = new While(condition, body);
if (usedLabels.remove(stmt.label.name)) {
if (usedLabels.remove(stmt.label)) {
statement = new LabeledStatement(stmt.label.name, statement);
}
savedBuffer.add(statement);