Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(155)

Unified Diff: pkg/compiler/lib/src/cps_ir/cps_ir_builder_task.dart

Issue 1585503002: dart2js: CPS translation of switches with continue to their labels. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Rebase. Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/compiler/lib/src/cps_ir/cps_ir_builder_task.dart
diff --git a/pkg/compiler/lib/src/cps_ir/cps_ir_builder_task.dart b/pkg/compiler/lib/src/cps_ir/cps_ir_builder_task.dart
index 68e7e4ede814002bff1765b7b000b41cd87e1ece..87ee7ef33357a5fcf5be11ed56c3f3438436e8aa 100644
--- a/pkg/compiler/lib/src/cps_ir/cps_ir_builder_task.dart
+++ b/pkg/compiler/lib/src/cps_ir/cps_ir_builder_task.dart
@@ -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.
+ // 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);
+ 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>[];
- SwitchCaseInfo defaultCase;
- for (ast.SwitchCase switchCase in node.cases) {
- SwitchCaseInfo caseInfo =
- new SwitchCaseInfo(subbuildSequence(switchCase.statements));
+ SubbuildFunction buildDefaultBody;
+ for (ast.SwitchCase switchCase in switchCases) {
+ JumpTarget nextContinueTarget = continueTargetIterator.current;
if (switchCase.isDefaultCase) {
- defaultCase = caseInfo;
+ 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 {
- cases.add(caseInfo);
- for (ast.Node labelOrCase in switchCase.labelsAndCases) {
- if (labelOrCase is ast.CaseMatch) {
- ir.Primitive constant = translateConstant(labelOrCase.expression);
- caseInfo.addConstant(constant);
+ // 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));
}
- 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));
+
+ // 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) {
« no previous file with comments | « pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart ('k') | pkg/compiler/lib/src/cps_ir/cps_ir_nodes_sexpr.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698