dart2js: CPS translation of switches with continue to their labels.
Implement switches with continue to their labels by a first 'non-recursive' switch followed by a second (only) 'recursive' switch inside a loop. Continue is implemented by assignment to a state variable that the second switch switches over. R=asgerf@google.com Review URL: https://codereview.chromium.org/1585503002 .
This commit is contained in:
@@ -166,6 +166,11 @@ abstract class JumpCollector {
|
||||
JumpCollector.retrn(this._continuation)
|
||||
: _continuationEnvironment = null, target = null;
|
||||
|
||||
/// Construct a collector for collecting goto jumps.
|
||||
///
|
||||
/// There is no continuation or environment at the destination.
|
||||
JumpCollector.goto(this.target) : _continuationEnvironment = null;
|
||||
|
||||
/// True if the collector has not recorded any jumps to its continuation.
|
||||
bool get isEmpty;
|
||||
|
||||
@@ -421,6 +426,31 @@ class ReturnJumpCollector extends JumpCollector {
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect 'goto' jumps, continue to a labeled case from within a switch.
|
||||
///
|
||||
/// These jumps are unrestricted within the switch. They can be forward or
|
||||
/// backward. They are implemented by assigning to a state variable.
|
||||
class GotoJumpCollector extends JumpCollector {
|
||||
bool isEmpty = true;
|
||||
final ir.Continuation continuation = null;
|
||||
final Environment environment = null;
|
||||
|
||||
int _stateVariableIndex;
|
||||
int _stateValue;
|
||||
JumpCollector _breakJoin;
|
||||
|
||||
GotoJumpCollector(JumpTarget target, this._stateVariableIndex,
|
||||
this._stateValue, this._breakJoin) : super.goto(target);
|
||||
|
||||
void addJump(IrBuilder builder,
|
||||
[ir.Primitive value, SourceInformation sourceInformation]) {
|
||||
isEmpty = false;
|
||||
ir.Primitive constant = builder.buildIntegerConstant(_stateValue);
|
||||
builder.environment.index2value[_stateVariableIndex] = constant;
|
||||
builder.jumpTo(_breakJoin);
|
||||
}
|
||||
}
|
||||
|
||||
/// Function for building a node in the context of the current builder.
|
||||
typedef ir.Node BuildFunction(node);
|
||||
|
||||
@@ -532,7 +562,7 @@ class ThisParameterLocal implements Local {
|
||||
///
|
||||
/// The IR fragment is an expression with a hole in it. The hole represents
|
||||
/// the focus where new expressions can be added. The fragment is implemented
|
||||
/// by [_root] which is the root of the expression and [_current] which is the
|
||||
/// by [root] which is the root of the expression and [_current] which is the
|
||||
/// expression that immediately contains the hole. Not all expressions have a
|
||||
/// hole (e.g., invocations, which always occur in tail position, do not have a
|
||||
/// hole). Expressions with a hole have a plug method.
|
||||
@@ -559,7 +589,7 @@ class IrBuilder {
|
||||
/// side effects.
|
||||
Map<Local, ir.MutableVariable> mutableVariables;
|
||||
|
||||
ir.Expression _root = null;
|
||||
ir.Expression root = null;
|
||||
ir.Expression _current = null;
|
||||
|
||||
GlobalProgramInformation get program => state.program;
|
||||
@@ -613,7 +643,7 @@ class IrBuilder {
|
||||
return mutableVariables[local];
|
||||
}
|
||||
|
||||
bool get isOpen => _root == null || _current != null;
|
||||
bool get isOpen => root == null || _current != null;
|
||||
|
||||
List<ir.Primitive> buildFunctionHeader(Iterable<Local> parameters,
|
||||
{ClosureScope closureScope,
|
||||
@@ -640,8 +670,8 @@ class IrBuilder {
|
||||
/// new value of current.
|
||||
void add(ir.Expression expr) {
|
||||
assert(isOpen);
|
||||
if (_root == null) {
|
||||
_root = _current = expr;
|
||||
if (root == null) {
|
||||
root = _current = expr;
|
||||
} else {
|
||||
_current = _current.plug(expr);
|
||||
}
|
||||
@@ -789,8 +819,8 @@ class IrBuilder {
|
||||
// if condition (then, else)
|
||||
ir.Continuation thenContinuation = new ir.Continuation([]);
|
||||
ir.Continuation elseContinuation = new ir.Continuation([]);
|
||||
thenContinuation.body = thenBuilder._root;
|
||||
elseContinuation.body = elseBuilder._root;
|
||||
thenContinuation.body = thenBuilder.root;
|
||||
elseContinuation.body = elseBuilder.root;
|
||||
add(new ir.LetCont(join.continuation,
|
||||
new ir.LetCont.two(thenContinuation, elseContinuation,
|
||||
new ir.Branch.strict(condition,
|
||||
@@ -812,7 +842,7 @@ class IrBuilder {
|
||||
_current = null;
|
||||
}
|
||||
|
||||
/// Create a [ir.FunctionDefinition] using [_root] as the body.
|
||||
/// Create a [ir.FunctionDefinition] using [root] as the body.
|
||||
///
|
||||
/// The protocol for building a function is:
|
||||
/// 1. Call [buildFunctionHeader].
|
||||
@@ -825,7 +855,7 @@ class IrBuilder {
|
||||
state.thisParameter,
|
||||
state.functionParameters,
|
||||
state.returnContinuation,
|
||||
_root);
|
||||
root);
|
||||
}
|
||||
|
||||
/// Create a invocation of the [method] on the super class where the call
|
||||
@@ -1107,17 +1137,17 @@ class IrBuilder {
|
||||
// case that one of them is null, it must be the only one that is open
|
||||
// and thus contains the new hole in the context. This case is handled
|
||||
// after the branch is plugged into the current hole.
|
||||
thenContinuation.body = thenBuilder._root;
|
||||
elseContinuation.body = elseBuilder._root;
|
||||
thenContinuation.body = thenBuilder.root;
|
||||
elseContinuation.body = elseBuilder.root;
|
||||
|
||||
add(result);
|
||||
if (join == null) {
|
||||
// At least one subexpression is closed.
|
||||
if (thenBuilder.isOpen) {
|
||||
if (thenBuilder._root != null) _current = thenBuilder._current;
|
||||
if (thenBuilder.root != null) _current = thenBuilder._current;
|
||||
environment = thenBuilder.environment;
|
||||
} else if (elseBuilder.isOpen) {
|
||||
if (elseBuilder._root != null) _current = elseBuilder._current;
|
||||
if (elseBuilder.root != null) _current = elseBuilder._current;
|
||||
environment = elseBuilder.environment;
|
||||
} else {
|
||||
_current = null;
|
||||
@@ -1243,16 +1273,16 @@ class IrBuilder {
|
||||
// it is guaranteed that the updateBuilder has a non-empty term.
|
||||
if (hasContinues) {
|
||||
outerBodyBuilder.add(new ir.LetCont(continueCollector.continuation,
|
||||
innerBodyBuilder._root));
|
||||
continueCollector.continuation.body = updateBuilder._root;
|
||||
innerBodyBuilder.root));
|
||||
continueCollector.continuation.body = updateBuilder.root;
|
||||
} else {
|
||||
outerBodyBuilder.add(innerBodyBuilder._root);
|
||||
outerBodyBuilder.add(innerBodyBuilder.root);
|
||||
}
|
||||
|
||||
// Create loop exit and body entry continuations and a branch to them.
|
||||
ir.Continuation exitContinuation = new ir.Continuation([]);
|
||||
ir.Continuation bodyContinuation = new ir.Continuation([]);
|
||||
bodyContinuation.body = outerBodyBuilder._root;
|
||||
bodyContinuation.body = outerBodyBuilder.root;
|
||||
// Note the order of continuations: the first one is the one that will
|
||||
// be filled by LetCont.plug.
|
||||
ir.LetCont branch =
|
||||
@@ -1268,7 +1298,7 @@ class IrBuilder {
|
||||
if (hasBreaks) {
|
||||
IrBuilder exitBuilder = makeDelimitedBuilder();
|
||||
exitBuilder.jumpTo(breakCollector);
|
||||
exitContinuation.body = exitBuilder._root;
|
||||
exitContinuation.body = exitBuilder.root;
|
||||
letBreak = new ir.LetCont(breakCollector.continuation, branch);
|
||||
add(letBreak);
|
||||
environment = breakCollector.environment;
|
||||
@@ -1411,7 +1441,7 @@ class IrBuilder {
|
||||
// in branch condition (body, exit)
|
||||
ir.Continuation exitContinuation = new ir.Continuation([]);
|
||||
ir.Continuation bodyContinuation = new ir.Continuation([]);
|
||||
bodyContinuation.body = bodyBuilder._root;
|
||||
bodyContinuation.body = bodyBuilder.root;
|
||||
// Note the order of continuations: the first one is the one that will
|
||||
// be filled by LetCont.plug.
|
||||
ir.LetCont branch =
|
||||
@@ -1427,7 +1457,7 @@ class IrBuilder {
|
||||
if (hasBreaks) {
|
||||
IrBuilder exitBuilder = makeDelimitedBuilder();
|
||||
exitBuilder.jumpTo(breakCollector);
|
||||
exitContinuation.body = exitBuilder._root;
|
||||
exitContinuation.body = exitBuilder.root;
|
||||
letBreak = new ir.LetCont(breakCollector.continuation, branch);
|
||||
add(letBreak);
|
||||
environment = breakCollector.environment;
|
||||
@@ -1486,7 +1516,7 @@ class IrBuilder {
|
||||
// Create body entry and loop exit continuations and a branch to them.
|
||||
ir.Continuation exitContinuation = new ir.Continuation([]);
|
||||
ir.Continuation bodyContinuation = new ir.Continuation([]);
|
||||
bodyContinuation.body = bodyBuilder._root;
|
||||
bodyContinuation.body = bodyBuilder.root;
|
||||
// Note the order of continuations: the first one is the one that will
|
||||
// be filled by LetCont.plug.
|
||||
ir.LetCont branch =
|
||||
@@ -1502,7 +1532,7 @@ class IrBuilder {
|
||||
if (hasBreaks) {
|
||||
IrBuilder exitBuilder = makeDelimitedBuilder();
|
||||
exitBuilder.jumpTo(breakCollector);
|
||||
exitContinuation.body = exitBuilder._root;
|
||||
exitContinuation.body = exitBuilder.root;
|
||||
letBreak = new ir.LetCont(breakCollector.continuation, branch);
|
||||
add(letBreak);
|
||||
environment = breakCollector.environment;
|
||||
@@ -1569,18 +1599,18 @@ class IrBuilder {
|
||||
ir.Continuation exitContinuation = new ir.Continuation([]);
|
||||
IrBuilder exitBuilder = continueBuilder.makeDelimitedBuilder();
|
||||
exitBuilder.jumpTo(breakCollector);
|
||||
exitContinuation.body = exitBuilder._root;
|
||||
exitContinuation.body = exitBuilder.root;
|
||||
ir.Continuation repeatContinuation = new ir.Continuation([]);
|
||||
IrBuilder repeatBuilder = continueBuilder.makeDelimitedBuilder();
|
||||
repeatBuilder.jumpTo(loop);
|
||||
repeatContinuation.body = repeatBuilder._root;
|
||||
repeatContinuation.body = repeatBuilder.root;
|
||||
|
||||
continueBuilder.add(
|
||||
new ir.LetCont.two(exitContinuation, repeatContinuation,
|
||||
new ir.Branch.strict(condition,
|
||||
repeatContinuation,
|
||||
exitContinuation)));
|
||||
continueCollector.continuation.body = continueBuilder._root;
|
||||
continueCollector.continuation.body = continueBuilder.root;
|
||||
|
||||
// Construct the loop continuation (i.e., the body and condition).
|
||||
// <Loop> =
|
||||
@@ -1589,56 +1619,25 @@ class IrBuilder {
|
||||
// in [[body]]; continue(v, ...)
|
||||
loopBuilder.add(
|
||||
new ir.LetCont(continueCollector.continuation,
|
||||
bodyBuilder._root));
|
||||
bodyBuilder.root));
|
||||
|
||||
// And tie it all together.
|
||||
add(new ir.LetCont(breakCollector.continuation, loopBuilder._root));
|
||||
add(new ir.LetCont(breakCollector.continuation, loopBuilder.root));
|
||||
environment = breakCollector.environment;
|
||||
}
|
||||
|
||||
void buildSimpleSwitch(JumpTarget target,
|
||||
ir.Primitive value,
|
||||
void buildSimpleSwitch(JumpCollector join,
|
||||
List<SwitchCaseInfo> cases,
|
||||
SwitchCaseInfo defaultCase,
|
||||
Element error,
|
||||
SourceInformation sourceInformation) {
|
||||
assert(isOpen);
|
||||
JumpCollector join = new ForwardJumpCollector(environment, target: target);
|
||||
|
||||
SubbuildFunction buildDefaultBody) {
|
||||
IrBuilder casesBuilder = makeDelimitedBuilder();
|
||||
casesBuilder.state.breakCollectors.add(join);
|
||||
for (SwitchCaseInfo caseInfo in cases) {
|
||||
buildConditionsFrom(int index) => (IrBuilder builder) {
|
||||
ir.Primitive comparison = builder.buildIdentical(
|
||||
value, caseInfo.constants[index]);
|
||||
return (index == caseInfo.constants.length - 1)
|
||||
? comparison
|
||||
: builder.buildLogicalOperator(
|
||||
comparison, buildConditionsFrom(index + 1), isLazyOr: true);
|
||||
};
|
||||
|
||||
ir.Primitive condition = buildConditionsFrom(0)(casesBuilder);
|
||||
ir.Primitive condition = caseInfo.buildCondition(casesBuilder);
|
||||
IrBuilder thenBuilder = makeDelimitedBuilder();
|
||||
caseInfo.buildBody(thenBuilder);
|
||||
if (thenBuilder.isOpen) {
|
||||
// It is a runtime error to reach the end of a switch case, unless
|
||||
// it is the last case.
|
||||
if (caseInfo == cases.last && defaultCase == null) {
|
||||
thenBuilder.jumpTo(join);
|
||||
} else {
|
||||
ir.Primitive exception = thenBuilder.buildInvokeStatic(
|
||||
error,
|
||||
new Selector.fromElement(error),
|
||||
<ir.Primitive>[],
|
||||
sourceInformation);
|
||||
thenBuilder.buildThrow(exception);
|
||||
}
|
||||
}
|
||||
|
||||
ir.Continuation thenContinuation = new ir.Continuation([]);
|
||||
thenContinuation.body = thenBuilder._root;
|
||||
thenContinuation.body = thenBuilder.root;
|
||||
ir.Continuation elseContinuation = new ir.Continuation([]);
|
||||
// A LetCont.many term has a hole as the body of the first listed
|
||||
// A LetCont.two term has a hole as the body of the first listed
|
||||
// continuation, to be plugged by the translation. Therefore put the
|
||||
// else continuation first.
|
||||
casesBuilder.add(
|
||||
@@ -1648,18 +1647,17 @@ class IrBuilder {
|
||||
elseContinuation)));
|
||||
}
|
||||
|
||||
if (defaultCase != null) {
|
||||
defaultCase.buildBody(casesBuilder);
|
||||
if (buildDefaultBody == null) {
|
||||
casesBuilder.jumpTo(join);
|
||||
} else {
|
||||
buildDefaultBody(casesBuilder);
|
||||
}
|
||||
if (casesBuilder.isOpen) casesBuilder.jumpTo(join);
|
||||
|
||||
casesBuilder.state.breakCollectors.removeLast();
|
||||
|
||||
if (!join.isEmpty) {
|
||||
add(new ir.LetCont(join.continuation, casesBuilder._root));
|
||||
add(new ir.LetCont(join.continuation, casesBuilder.root));
|
||||
environment = join.environment;
|
||||
} else if (casesBuilder._root != null) {
|
||||
add(casesBuilder._root);
|
||||
} else if (casesBuilder.root != null) {
|
||||
add(casesBuilder.root);
|
||||
_current = casesBuilder._current;
|
||||
environment = casesBuilder.environment;
|
||||
} else {
|
||||
@@ -1721,11 +1719,11 @@ class IrBuilder {
|
||||
|
||||
List<ir.Parameter> catchParameters = buildCatch(catchBuilder, join);
|
||||
ir.Continuation catchContinuation = new ir.Continuation(catchParameters);
|
||||
catchContinuation.body = catchBuilder._root;
|
||||
catchContinuation.body = catchBuilder.root;
|
||||
tryCatchBuilder.add(
|
||||
new ir.LetHandler(catchContinuation, tryBuilder._root));
|
||||
new ir.LetHandler(catchContinuation, tryBuilder.root));
|
||||
|
||||
leaveTryCatch(this, join, tryCatchBuilder._root);
|
||||
leaveTryCatch(this, join, tryCatchBuilder.root);
|
||||
}
|
||||
|
||||
/// Translates a try/catch.
|
||||
@@ -1829,7 +1827,7 @@ class IrBuilder {
|
||||
}
|
||||
clause.buildCatchBlock(clauseBuilder);
|
||||
if (clauseBuilder.isOpen) clauseBuilder.jumpTo(join);
|
||||
return clauseBuilder._root;
|
||||
return clauseBuilder.root;
|
||||
}
|
||||
|
||||
// Expand multiple catch clauses into an explicit if/then/else. Iterate
|
||||
@@ -1855,7 +1853,7 @@ class IrBuilder {
|
||||
new ir.Branch.strict(typeMatches,
|
||||
thenContinuation,
|
||||
elseContinuation)));
|
||||
catchBody = checkBuilder._root;
|
||||
catchBody = checkBuilder.root;
|
||||
}
|
||||
builder.add(catchBody);
|
||||
|
||||
@@ -1960,7 +1958,7 @@ class IrBuilder {
|
||||
IrBuilder builder = makeDelimitedBuilder(newCollector.environment);
|
||||
buildFinallyBlock(builder);
|
||||
if (builder.isOpen) builder.jumpTo(originalCollector);
|
||||
newCollector.continuation.body = builder._root;
|
||||
newCollector.continuation.body = builder.root;
|
||||
exits.add(newCollector.continuation);
|
||||
}
|
||||
for (int i = 0; i < newBreaks.length; ++i) {
|
||||
@@ -1974,7 +1972,7 @@ class IrBuilder {
|
||||
ir.Primitive value = builder.environment.discard(1);
|
||||
buildFinallyBlock(builder);
|
||||
if (builder.isOpen) builder.buildReturn(value: value);
|
||||
newReturn.continuation.body = builder._root;
|
||||
newReturn.continuation.body = builder.root;
|
||||
exits.add(newReturn.continuation);
|
||||
}
|
||||
builder.add(new ir.LetCont.many(exits, body));
|
||||
@@ -2101,10 +2099,10 @@ class IrBuilder {
|
||||
bool hasBreaks = !join.isEmpty;
|
||||
if (hasBreaks) {
|
||||
if (innerBuilder.isOpen) innerBuilder.jumpTo(join);
|
||||
add(new ir.LetCont(join.continuation, innerBuilder._root));
|
||||
add(new ir.LetCont(join.continuation, innerBuilder.root));
|
||||
environment = join.environment;
|
||||
} else if (innerBuilder._root != null) {
|
||||
add(innerBuilder._root);
|
||||
} else if (innerBuilder.root != null) {
|
||||
add(innerBuilder.root);
|
||||
_current = innerBuilder._current;
|
||||
environment = innerBuilder.environment;
|
||||
} else {
|
||||
@@ -2240,8 +2238,8 @@ class IrBuilder {
|
||||
ir.Continuation leftFalseContinuation = new ir.Continuation([]);
|
||||
ir.Continuation rightTrueContinuation = new ir.Continuation([]);
|
||||
ir.Continuation rightFalseContinuation = new ir.Continuation([]);
|
||||
rightTrueContinuation.body = rightTrueBuilder._root;
|
||||
rightFalseContinuation.body = rightFalseBuilder._root;
|
||||
rightTrueContinuation.body = rightTrueBuilder.root;
|
||||
rightFalseContinuation.body = rightFalseBuilder.root;
|
||||
// The right subexpression has two continuations.
|
||||
rightBuilder.add(
|
||||
new ir.LetCont.two(rightTrueContinuation, rightFalseContinuation,
|
||||
@@ -2252,11 +2250,11 @@ class IrBuilder {
|
||||
// either the right subexpression or an invocation of the join-point
|
||||
// continuation.
|
||||
if (isLazyOr) {
|
||||
leftTrueContinuation.body = emptyBuilder._root;
|
||||
leftFalseContinuation.body = rightBuilder._root;
|
||||
leftTrueContinuation.body = emptyBuilder.root;
|
||||
leftFalseContinuation.body = rightBuilder.root;
|
||||
} else {
|
||||
leftTrueContinuation.body = rightBuilder._root;
|
||||
leftFalseContinuation.body = emptyBuilder._root;
|
||||
leftTrueContinuation.body = rightBuilder.root;
|
||||
leftFalseContinuation.body = emptyBuilder.root;
|
||||
}
|
||||
|
||||
add(new ir.LetCont(join.continuation,
|
||||
@@ -2838,10 +2836,8 @@ class CatchClauseInfo {
|
||||
}
|
||||
|
||||
class SwitchCaseInfo {
|
||||
final List<ir.Primitive> constants = <ir.Primitive>[];
|
||||
final SubbuildFunction buildCondition;
|
||||
final SubbuildFunction buildBody;
|
||||
|
||||
SwitchCaseInfo(this.buildBody);
|
||||
|
||||
void addConstant(ir.Primitive constant) => constants.add(constant);
|
||||
SwitchCaseInfo(this.buildCondition, this.buildBody);
|
||||
}
|
||||
|
||||
@@ -1172,44 +1172,270 @@ class IrBuilderVisitor extends ast.Visitor<ir.Primitive>
|
||||
}
|
||||
|
||||
visitSwitchStatement(ast.SwitchStatement node) {
|
||||
// Dart switch cases can be labeled and be the target of continue from
|
||||
// within the switch. Such cases are 'recursive'. If there are any
|
||||
// recursive cases, we implement the switch using a pair of switches with
|
||||
// the second one switching over a state variable in a loop. The first
|
||||
// switch contains the non-recursive cases, and the second switch contains
|
||||
// the recursive ones.
|
||||
//
|
||||
// For example, for the Dart switch:
|
||||
//
|
||||
// switch (E) {
|
||||
// case 0:
|
||||
// BODY0;
|
||||
// break;
|
||||
// LABEL0: case 1:
|
||||
// BODY1;
|
||||
// break;
|
||||
// case 2:
|
||||
// BODY2;
|
||||
// continue LABEL1;
|
||||
// LABEL1: case 3:
|
||||
// BODY3;
|
||||
// continue LABEL0;
|
||||
// default:
|
||||
// BODY4;
|
||||
// }
|
||||
//
|
||||
// We translate it as if it were the JavaScript:
|
||||
//
|
||||
// var state = -1;
|
||||
// switch (E) {
|
||||
// case 0:
|
||||
// BODY0;
|
||||
// break;
|
||||
// case 1:
|
||||
// state = 0; // Recursive, label ID = 0.
|
||||
// break;
|
||||
// case 2:
|
||||
// BODY2;
|
||||
// state = 1; // Continue to label ID = 1.
|
||||
// break;
|
||||
// case 3:
|
||||
// state = 1; // Recursive, label ID = 1.
|
||||
// break;
|
||||
// default:
|
||||
// BODY4;
|
||||
// }
|
||||
// L: while (state != -1) {
|
||||
// case 0:
|
||||
// BODY1;
|
||||
// break L; // Break from switch becomes break from loop.
|
||||
// case 1:
|
||||
// BODY2;
|
||||
// state = 0; // Continue to label ID = 0.
|
||||
// break;
|
||||
// }
|
||||
assert(irBuilder.isOpen);
|
||||
// We do not handle switch statements with continue to labeled cases.
|
||||
for (ast.SwitchCase switchCase in node.cases) {
|
||||
// Preprocess: compute a list of cases that are the target of continue.
|
||||
// These are the so-called 'recursive' cases.
|
||||
List<JumpTarget> continueTargets = <JumpTarget>[];
|
||||
List<ast.SwitchCase> switchCases = node.cases.nodes.toList();
|
||||
for (ast.SwitchCase switchCase in switchCases) {
|
||||
for (ast.Node labelOrCase in switchCase.labelsAndCases) {
|
||||
if (labelOrCase is ast.Label) {
|
||||
LabelDefinition definition = elements.getLabelDefinition(labelOrCase);
|
||||
if (definition != null && definition.isContinueTarget) {
|
||||
return giveup(node, "continue to a labeled switch case");
|
||||
continueTargets.add(definition.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Each switch case contains a list of interleaved labels and expressions
|
||||
// and a non-empty body. We can ignore the labels because they are not
|
||||
// jump targets.
|
||||
List<SwitchCaseInfo> cases = <SwitchCaseInfo>[];
|
||||
SwitchCaseInfo defaultCase;
|
||||
for (ast.SwitchCase switchCase in node.cases) {
|
||||
SwitchCaseInfo caseInfo =
|
||||
new SwitchCaseInfo(subbuildSequence(switchCase.statements));
|
||||
if (switchCase.isDefaultCase) {
|
||||
defaultCase = caseInfo;
|
||||
} else {
|
||||
cases.add(caseInfo);
|
||||
for (ast.Node labelOrCase in switchCase.labelsAndCases) {
|
||||
if (labelOrCase is ast.CaseMatch) {
|
||||
ir.Primitive constant = translateConstant(labelOrCase.expression);
|
||||
caseInfo.addConstant(constant);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If any cases are continue targets, use an anonymous local value to
|
||||
// implement a state machine. The initial value is -1.
|
||||
ir.Primitive initial;
|
||||
int stateIndex;
|
||||
if (continueTargets.isNotEmpty) {
|
||||
initial = irBuilder.buildIntegerConstant(-1);
|
||||
stateIndex = irBuilder.environment.length;
|
||||
irBuilder.environment.extend(null, initial);
|
||||
}
|
||||
|
||||
// Use a simple switch for the non-recursive cases. A break will go to the
|
||||
// join-point after the switch. A continue to a labeled case will assign
|
||||
// to the state variable and go to the join-point.
|
||||
ir.Primitive value = visit(node.expression);
|
||||
JumpTarget target = elements.getTargetDefinition(node);
|
||||
Element error = helpers.fallThroughError;
|
||||
irBuilder.buildSimpleSwitch(target, value, cases, defaultCase, error,
|
||||
sourceInformationBuilder.buildGeneric(node));
|
||||
JumpCollector join = new ForwardJumpCollector(irBuilder.environment,
|
||||
target: elements.getTargetDefinition(node));
|
||||
irBuilder.state.breakCollectors.add(join);
|
||||
for (int i = 0; i < continueTargets.length; ++i) {
|
||||
// The state value is i, the case's position in the list of recursive
|
||||
// cases.
|
||||
irBuilder.state.continueCollectors.add(new GotoJumpCollector(
|
||||
continueTargets[i], stateIndex, i, join));
|
||||
}
|
||||
|
||||
// For each non-default case use a pair of functions, one to translate the
|
||||
// condition and one to translate the body. For the default case use a
|
||||
// function to translate the body. Use continueTargetIterator as a pointer
|
||||
// to the next recursive case.
|
||||
Iterator<JumpTarget> continueTargetIterator = continueTargets.iterator;
|
||||
continueTargetIterator.moveNext();
|
||||
List<SwitchCaseInfo> cases = <SwitchCaseInfo>[];
|
||||
SubbuildFunction buildDefaultBody;
|
||||
for (ast.SwitchCase switchCase in switchCases) {
|
||||
JumpTarget nextContinueTarget = continueTargetIterator.current;
|
||||
if (switchCase.isDefaultCase) {
|
||||
if (nextContinueTarget != null &&
|
||||
switchCase == nextContinueTarget.statement) {
|
||||
// In this simple switch, recursive cases are as if they immediately
|
||||
// continued to themselves.
|
||||
buildDefaultBody = nested(() {
|
||||
irBuilder.buildContinue(nextContinueTarget);
|
||||
});
|
||||
continueTargetIterator.moveNext();
|
||||
} else {
|
||||
// Non-recursive cases consist of the translation of the body.
|
||||
// For the default case, there is implicitly a break if control
|
||||
// flow reaches the end.
|
||||
buildDefaultBody = nested(() {
|
||||
irBuilder.buildSequence(switchCase.statements, visit);
|
||||
if (irBuilder.isOpen) irBuilder.jumpTo(join);
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
ir.Primitive buildCondition(IrBuilder builder) {
|
||||
// There can be multiple cases sharing the same body, because empty
|
||||
// cases are allowed to fall through to the next one. Each case is
|
||||
// a comparison, build a short-circuited disjunction of all of them.
|
||||
return withBuilder(builder, () {
|
||||
ir.Primitive condition;
|
||||
for (ast.Node labelOrCase in switchCase.labelsAndCases) {
|
||||
if (labelOrCase is ast.CaseMatch) {
|
||||
ir.Primitive buildComparison() {
|
||||
ir.Primitive constant =
|
||||
translateConstant(labelOrCase.expression);
|
||||
return irBuilder.buildIdentical(value, constant);
|
||||
}
|
||||
|
||||
if (condition == null) {
|
||||
condition = buildComparison();
|
||||
} else {
|
||||
condition = irBuilder.buildLogicalOperator(condition,
|
||||
nested(buildComparison), isLazyOr: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return condition;
|
||||
});
|
||||
}
|
||||
|
||||
SubbuildFunction buildBody;
|
||||
if (nextContinueTarget != null &&
|
||||
switchCase == nextContinueTarget.statement) {
|
||||
// Recursive cases are as if they immediately continued to themselves.
|
||||
buildBody = nested(() {
|
||||
irBuilder.buildContinue(nextContinueTarget);
|
||||
});
|
||||
continueTargetIterator.moveNext();
|
||||
} else {
|
||||
// Non-recursive cases consist of the translation of the body. It is a
|
||||
// runtime error if control-flow reaches the end of the body of any but
|
||||
// the last case.
|
||||
buildBody = (IrBuilder builder) {
|
||||
withBuilder(builder, () {
|
||||
irBuilder.buildSequence(switchCase.statements, visit);
|
||||
if (irBuilder.isOpen) {
|
||||
if (switchCase == switchCases.last) {
|
||||
irBuilder.jumpTo(join);
|
||||
} else {
|
||||
Element error = helpers.fallThroughError;
|
||||
ir.Primitive exception = irBuilder.buildInvokeStatic(
|
||||
error,
|
||||
new Selector.fromElement(error),
|
||||
<ir.Primitive>[],
|
||||
sourceInformationBuilder.buildGeneric(node));
|
||||
irBuilder.buildThrow(exception);
|
||||
}
|
||||
}
|
||||
});
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
cases.add(new SwitchCaseInfo(buildCondition, buildBody));
|
||||
}
|
||||
|
||||
irBuilder.buildSimpleSwitch(join, cases, buildDefaultBody);
|
||||
irBuilder.state.breakCollectors.removeLast();
|
||||
irBuilder.state.continueCollectors.length -= continueTargets.length;
|
||||
if (continueTargets.isEmpty) return;
|
||||
|
||||
// If there were recursive cases build a while loop whose body is a
|
||||
// switch containing (only) the recursive cases. The condition is
|
||||
// 'state != initialValue' so the loop is not taken when the state variable
|
||||
// has not been assigned.
|
||||
//
|
||||
// 'loop' is the join-point of the exits from the inner switch which will
|
||||
// perform another iteration of the loop. 'exit' is the join-point of the
|
||||
// breaks from the switch, outside the loop.
|
||||
JumpCollector loop = new ForwardJumpCollector(irBuilder.environment);
|
||||
JumpCollector exit = new ForwardJumpCollector(irBuilder.environment,
|
||||
target: elements.getTargetDefinition(node));
|
||||
irBuilder.state.breakCollectors.add(exit);
|
||||
for (int i = 0; i < continueTargets.length; ++i) {
|
||||
irBuilder.state.continueCollectors.add(new GotoJumpCollector(
|
||||
continueTargets[i], stateIndex, i, loop));
|
||||
}
|
||||
cases.clear();
|
||||
for (int i = 0; i < continueTargets.length; ++i) {
|
||||
// The conditions compare to the recursive case index.
|
||||
ir.Primitive buildCondition(IrBuilder builder) {
|
||||
ir.Primitive constant = builder.buildIntegerConstant(i);
|
||||
return builder.buildIdentical(
|
||||
builder.environment.index2value[stateIndex], constant);
|
||||
}
|
||||
|
||||
ir.Primitive buildBody(IrBuilder builder) {
|
||||
withBuilder(builder, () {
|
||||
ast.SwitchCase switchCase = continueTargets[i].statement;
|
||||
irBuilder.buildSequence(switchCase.statements, visit);
|
||||
if (irBuilder.isOpen) {
|
||||
if (switchCase == switchCases.last) {
|
||||
irBuilder.jumpTo(exit);
|
||||
} else {
|
||||
Element error = helpers.fallThroughError;
|
||||
ir.Primitive exception = irBuilder.buildInvokeStatic(
|
||||
error,
|
||||
new Selector.fromElement(error),
|
||||
<ir.Primitive>[],
|
||||
sourceInformationBuilder.buildGeneric(node));
|
||||
irBuilder.buildThrow(exception);
|
||||
}
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
cases.add(new SwitchCaseInfo(buildCondition, buildBody));
|
||||
}
|
||||
|
||||
// A loop with a simple switch in the body.
|
||||
IrBuilder whileBuilder = irBuilder.makeDelimitedBuilder();
|
||||
whileBuilder.buildWhile(
|
||||
buildCondition: (IrBuilder builder) {
|
||||
ir.Primitive condition = builder.buildIdentical(
|
||||
builder.environment.index2value[stateIndex], initial);
|
||||
return builder.buildNegation(condition);
|
||||
},
|
||||
buildBody: (IrBuilder builder) {
|
||||
builder.buildSimpleSwitch(loop, cases, null);
|
||||
});
|
||||
// Jump to the exit continuation. This jump is the body of the loop exit
|
||||
// continuation, so the loop exit continuation can be eta-reduced. The
|
||||
// jump is here for simplicity because `buildWhile` does not expose the
|
||||
// loop's exit continuation directly and has already emitted all jumps
|
||||
// to it anyway.
|
||||
whileBuilder.jumpTo(exit);
|
||||
irBuilder.add(new ir.LetCont(exit.continuation, whileBuilder.root));
|
||||
irBuilder.environment = exit.environment;
|
||||
irBuilder.environment.discard(1); // Discard the state variable.
|
||||
irBuilder.state.breakCollectors.removeLast();
|
||||
irBuilder.state.continueCollectors.length -= continueTargets.length;
|
||||
}
|
||||
|
||||
visitTryStatement(ast.TryStatement node) {
|
||||
|
||||
@@ -30,6 +30,7 @@ class SExpressionStringifier extends Indentation implements Visitor<String> {
|
||||
}
|
||||
|
||||
String access(Reference<Definition> r) {
|
||||
if (r == null) return '**** NULL ****';
|
||||
return decorator(r, namer.getName(r.definition));
|
||||
}
|
||||
|
||||
@@ -192,7 +193,9 @@ class SExpressionStringifier extends Indentation implements Visitor<String> {
|
||||
String visitInvokeContinuation(InvokeContinuation node) {
|
||||
String name = access(node.continuation);
|
||||
if (node.isRecursive) name = 'rec $name';
|
||||
String args = node.arguments.map(access).join(' ');
|
||||
String args = node.arguments == null
|
||||
? '**** NULL ****'
|
||||
: node.arguments.map(access).join(' ');
|
||||
String escaping = node.isEscapingTry ? ' escape' : '';
|
||||
return '$indentation(InvokeContinuation $name ($args)$escaping)';
|
||||
}
|
||||
|
||||
@@ -9616,9 +9616,6 @@ WebPlatformTest/webstorage/storage_builtins_t01: RuntimeError # Please triage th
|
||||
WebPlatformTest/webstorage/storage_local_setitem_quotaexceedederr_t01: Skip # Times out. Please triage this failure
|
||||
|
||||
[ $compiler == dart2js && $cps_ir ]
|
||||
Language/Statements/Labels/scope_t04: Crash # (switch (i){L:case 0:flag=true;break;case 2:continue L;}): continue to a labeled switch case
|
||||
Language/Statements/Continue/label_t12: Crash # (switch (2){L:case 1:flag=true;break;case 2:continue L;}): continue to a labeled switch case
|
||||
Language/Statements/Continue/label_t13: Crash # (switch (2){case 2:continue L;L:case 1:flag=true;}): continue to a labeled switch case
|
||||
Language/Types/Interface_Types/subtype_t09: Crash # Pending static: JSArray
|
||||
Language/Types/Interface_Types/subtype_t39: RuntimeError # Please triage this failure.
|
||||
LibTest/collection/ListBase/ListBase_class_A01_t02: Pass, Timeout
|
||||
|
||||
@@ -68,4 +68,3 @@ big_allocation_expression_test: Crash # Issue 24635
|
||||
|
||||
[ $compiler == dart2js && $cps_ir ]
|
||||
async_stacktrace_test/asyncStar: Crash # (foo()async*{try {tr... cannot handle sync*/async* functions
|
||||
switch_test/none: Crash # (switch (val){foo:ba... continue to a labeled switch case
|
||||
|
||||
@@ -251,9 +251,9 @@ async_await_syntax_test/d03a: Crash # (()async*{}): cannot handle sync*/async* f
|
||||
async_await_syntax_test/d06a: Crash # (await for(var o in st){}): await for
|
||||
async_await_syntax_test/d09a: Crash # (()async*{yield 0;}): cannot handle sync*/async* functions
|
||||
async_await_syntax_test/d10a: Crash # (()async*{yield* [] ;}): cannot handle sync*/async* functions
|
||||
async_await_test/02: Crash # (switch (v){label:ca... continue to a labeled switch case
|
||||
async_await_test/03: Crash # (switch (v){label:ca... continue to a labeled switch case
|
||||
async_await_test/none: Crash # (switch (v){label:ca... continue to a labeled switch case
|
||||
async_await_test/02: Crash # bailout: (await for
|
||||
async_await_test/03: Crash # bailout: (await for
|
||||
async_await_test/none: Crash # bailout: (await for
|
||||
async_or_generator_return_type_stacktrace_test/02: Crash # (void badReturnTypeAsyncStar()async*{}): cannot handle sync*/async* functions
|
||||
async_return_types_test/nestedFuture: Crash # cannot handle sync*/async* functions
|
||||
async_return_types_test/none: Crash # cannot handle sync*/async* functions
|
||||
@@ -287,14 +287,12 @@ generic2_test: RuntimeError # Please triage this failure.
|
||||
generic_instanceof_test: RuntimeError # Please triage this failure.
|
||||
generic_native_test: RuntimeError # Please triage this failure.
|
||||
gc_test: Crash # Internal Error: Pending statics (see above).
|
||||
infinite_switch_label_test: Crash # (switch (target){l0:... continue to a labeled switch case
|
||||
instanceof2_test: RuntimeError # Please triage this failure.
|
||||
instanceof4_test/01: RuntimeError # Please triage this failure.
|
||||
invocation_mirror_test: Crash # (super[37]=42): visitUnresolvedSuperIndexSet
|
||||
list_is_test: RuntimeError # Please triage this failure.
|
||||
list_test: RuntimeError # Please triage this failure.
|
||||
many_generic_instanceof_test: RuntimeError # Please triage this failure.
|
||||
nested_switch_label_test: Crash # (switch (target){out... continue to a labeled switch case
|
||||
regress_23500_test/01: Crash # (await for(var c in new Stream.fromIterable([] )){}): await for
|
||||
savannah_test: RuntimeError # Success depends on the variable hints.
|
||||
super_call4_test: RuntimeError # Please triage this failure.
|
||||
@@ -304,9 +302,6 @@ super_operator_index7_test: Crash # (super[0]=42): visitUnresolvedSuperIndexSet
|
||||
super_operator_index8_test: Crash # (super[f()]=g()): visitUnresolvedSuperIndexSet
|
||||
super_operator_index_test/03: Crash # (super[4]=42): visitUnresolvedSuperIndexSet
|
||||
super_operator_index_test/05: Crash # (super[4]=42): visitUnresolvedSuperIndexSet
|
||||
switch_label2_test: Crash # (switch (target){cas... continue to a labeled switch case
|
||||
switch_label_test: Crash # (switch (animal){cas... continue to a labeled switch case
|
||||
switch_try_catch_test: Crash # (switch (0){_0:case ... continue to a labeled switch case
|
||||
switch8_test: Crash # Pending statics: JSArray
|
||||
type_variable_closure2_test: RuntimeError # Issue 25309: T lost in List<T>
|
||||
type_variable_field_initializer_closure_test: RuntimeError # Issue 25309: T lost in List<T>
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
[ $compiler == dart2js && $host_checked ]
|
||||
dummy_compiler_test: Crash # Issue 22809
|
||||
|
||||
[ $compiler == dart2js ]
|
||||
[ $compiler == dart2js && $host_checked == false ]
|
||||
dummy_compiler_test: Slow, Pass
|
||||
|
||||
[ $compiler == dart2js ]
|
||||
recursive_import_test: Slow, Pass
|
||||
source_mirrors_test: Slow, Pass
|
||||
|
||||
@@ -25,10 +27,5 @@ dummy_compiler_test: Pass, RuntimeError # Issue 17662
|
||||
recursive_import_test: Pass, RuntimeError # Issue 17662
|
||||
source_mirrors_test: Pass, RuntimeError # Issue 17662
|
||||
|
||||
[ $compiler == dart2js && $cps_ir ]
|
||||
dummy_compiler_test: Crash # (switch (function.na... continue to a labeled switch case
|
||||
recursive_import_test: Crash # (switch (function.na... continue to a labeled switch case
|
||||
source_mirrors_test: Crash, Slow # (switch (function.na... continue to a labeled switch case
|
||||
|
||||
[ ($noopt || $compiler == precompiler) ]
|
||||
source_mirrors_test: SkipByDesign # Imports dart:mirrors
|
||||
|
||||
Reference in New Issue
Block a user