Skip to content

Commit

Permalink
dart2js cps: Emit unlabeled breaks and continues when possible.
Browse files Browse the repository at this point in the history
  • Loading branch information
asgerf committed Jul 10, 2015
1 parent a07f023 commit fe2c4c5
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 45 deletions.
96 changes: 58 additions & 38 deletions pkg/compiler/lib/src/js_backend/codegen/codegen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ class CodeGenerator extends tree_ir.StatementVisitor

final tree_ir.FallthroughStack fallthrough = new tree_ir.FallthroughStack();

/// Stacks whose top element is the current target of an unlabeled break
/// or continue. For continues, this is the loop node itself.
final tree_ir.FallthroughStack shortBreak = new tree_ir.FallthroughStack();
final tree_ir.FallthroughStack shortContinue =
new tree_ir.FallthroughStack();

Set<tree_ir.Label> usedLabels = new Set<tree_ir.Label>();

List<js.Statement> accumulator = new List<js.Statement>();
Expand Down Expand Up @@ -390,27 +396,36 @@ class CodeGenerator extends tree_ir.StatementVisitor
@override
void visitContinue(tree_ir.Continue node) {
tree_ir.Statement next = fallthrough.target;
if (node.target.binding == next) {
// Fall through to continue target
fallthrough.use();
} else if (next is tree_ir.Continue && next.target == node.target) {
// Fall through to equivalent continue
if (node.target.binding == next ||
next is tree_ir.Continue && node.target == next.target) {
// Fall through to continue target or to equivalent continue.
fallthrough.use();
} else if (node.target.binding == shortContinue.target) {
// The target is the immediately enclosing loop.
shortContinue.use();
accumulator.add(new js.Continue(null));
} else {
usedLabels.add(node.target);
accumulator.add(new js.Continue(node.target.name));
}
}

/// True if [other] is the target of [node] or is a [Break] with the same
/// target. This means jumping to [other] is equivalent to executing [node].
bool isEffectiveBreakTarget(tree_ir.Break node, tree_ir.Statement other) {
return node.target.binding.next == other ||
other is tree_ir.Break && node.target == other.target;
}

@override
void visitBreak(tree_ir.Break node) {
tree_ir.Statement next = fallthrough.target;
if (node.target.binding.next == next) {
// Fall through to break target
fallthrough.use();
} else if (next is tree_ir.Break && next.target == node.target) {
// Fall through to equivalent break
if (isEffectiveBreakTarget(node, fallthrough.target)) {
// Fall through to break target or to equivalent break.
fallthrough.use();
} else if (isEffectiveBreakTarget(node, shortBreak.target)) {
// Unlabeled break to the break target or to an equivalent break.
shortBreak.use();
accumulator.add(new js.Break(null));
} else {
usedLabels.add(node.target);
accumulator.add(new js.Break(node.target.name));
Expand Down Expand Up @@ -443,22 +458,20 @@ class CodeGenerator extends tree_ir.StatementVisitor

@override
void visitLabeledStatement(tree_ir.LabeledStatement node) {
accumulator.add(buildLabeled(() => buildBodyStatement(node.body),
node.label,
node.next));
fallthrough.push(node.next);
js.Statement body = buildBodyStatement(node.body);
fallthrough.pop();
accumulator.add(insertLabel(node.label, body));
visitStatement(node.next);
}

js.Statement buildLabeled(js.Statement buildBody(),
tree_ir.Label label,
tree_ir.Statement fallthroughStatement) {
fallthrough.push(fallthroughStatement);
js.Statement result = buildBody();
/// Wraps a node in a labeled statement unless the label is unused.
js.Statement insertLabel(tree_ir.Label label, js.Statement node) {
if (usedLabels.remove(label)) {
result = new js.LabeledStatement(label.name, result);
return new js.LabeledStatement(label.name, node);
} else {
return node;
}
fallthrough.pop();
return result;
}

/// Returns the current [accumulator] wrapped in a block if neccessary.
Expand Down Expand Up @@ -491,29 +504,36 @@ class CodeGenerator extends tree_ir.StatementVisitor
return result;
}

js.Statement buildWhile(js.Expression condition,
tree_ir.Statement body,
tree_ir.Label label,
tree_ir.Statement fallthroughStatement) {
return buildLabeled(() => new js.While(condition, buildBodyStatement(body)),
label,
fallthroughStatement);
}

@override
void visitWhileCondition(tree_ir.WhileCondition node) {
accumulator.add(
buildWhile(visitExpression(node.condition),
node.body,
node.label,
node));
js.Expression condition = visitExpression(node.condition);
shortBreak.push(node.next);
shortContinue.push(node);
fallthrough.push(node);
js.Statement jsBody = buildBodyStatement(node.body);
fallthrough.pop();
shortContinue.pop();
shortBreak.pop();
accumulator.add(insertLabel(node.label, new js.While(condition, jsBody)));
visitStatement(node.next);
}

@override
void visitWhileTrue(tree_ir.WhileTrue node) {
accumulator.add(
buildWhile(new js.LiteralBool(true), node.body, node.label, node));
js.Expression condition = new js.LiteralBool(true);
// A short break in the while will jump to the current fallthrough target.
shortBreak.push(fallthrough.target);
shortContinue.push(node);
fallthrough.push(node);
js.Statement jsBody = buildBodyStatement(node.body);
fallthrough.pop();
shortContinue.pop();
if (shortBreak.useCount > 0) {
// Short breaks use the current fallthrough target.
fallthrough.use();
}
shortBreak.pop();
accumulator.add(insertLabel(node.label, new js.While(condition, jsBody)));
}

bool isNull(tree_ir.Expression node) {
Expand Down
13 changes: 6 additions & 7 deletions tests/compiler/dart2js/js_backend_cps_ir_control_flow_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,12 @@ main() {
}""", """
function() {
var i = 0;
L2:
while (P.identical(V.foo(true), true)) {
P.print(1);
if (P.identical(V.foo(false), true))
break L2;
i = V.foo(i);
}
while (P.identical(V.foo(true), true)) {
P.print(1);
if (P.identical(V.foo(false), true))
break;
i = V.foo(i);
}
P.print(2);
}"""),
const TestEntry("""
Expand Down

0 comments on commit fe2c4c5

Please sign in to comment.