From 65ae116a01381bb2ddc8a22c15042b6944ebdee2 Mon Sep 17 00:00:00 2001 From: Kevin Millikin Date: Tue, 26 Jan 2016 09:43:13 +0100 Subject: [PATCH] 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 . --- .../lib/src/cps_ir/cps_ir_builder.dart | 178 ++++++----- .../lib/src/cps_ir/cps_ir_builder_task.dart | 278 ++++++++++++++++-- .../lib/src/cps_ir/cps_ir_nodes_sexpr.dart | 5 +- tests/co19/co19-dart2js.status | 3 - .../dart2js_extra/dart2js_extra.status | 1 - tests/language/language_dart2js.status | 11 +- tests/utils/utils.status | 9 +- 7 files changed, 349 insertions(+), 136 deletions(-) diff --git a/pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart b/pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart index 9777a243b92..4974de815f4 100644 --- a/pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart +++ b/pkg/compiler/lib/src/cps_ir/cps_ir_builder.dart @@ -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 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 buildFunctionHeader(Iterable 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). // = @@ -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 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), - [], - 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 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 constants = []; + final SubbuildFunction buildCondition; final SubbuildFunction buildBody; - SwitchCaseInfo(this.buildBody); - - void addConstant(ir.Primitive constant) => constants.add(constant); + SwitchCaseInfo(this.buildCondition, this.buildBody); } 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 f256d75a492..76ac705646a 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 } 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 continueTargets = []; + List 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 cases = []; - 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 continueTargetIterator = continueTargets.iterator; + continueTargetIterator.moveNext(); + List cases = []; + 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), + [], + 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), + [], + 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) { diff --git a/pkg/compiler/lib/src/cps_ir/cps_ir_nodes_sexpr.dart b/pkg/compiler/lib/src/cps_ir/cps_ir_nodes_sexpr.dart index 7b3558c194f..2271f269f2f 100644 --- a/pkg/compiler/lib/src/cps_ir/cps_ir_nodes_sexpr.dart +++ b/pkg/compiler/lib/src/cps_ir/cps_ir_nodes_sexpr.dart @@ -30,6 +30,7 @@ class SExpressionStringifier extends Indentation implements Visitor { } String access(Reference r) { + if (r == null) return '**** NULL ****'; return decorator(r, namer.getName(r.definition)); } @@ -192,7 +193,9 @@ class SExpressionStringifier extends Indentation implements Visitor { 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)'; } diff --git a/tests/co19/co19-dart2js.status b/tests/co19/co19-dart2js.status index 72fc13a3618..6785d8413fe 100644 --- a/tests/co19/co19-dart2js.status +++ b/tests/co19/co19-dart2js.status @@ -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 diff --git a/tests/compiler/dart2js_extra/dart2js_extra.status b/tests/compiler/dart2js_extra/dart2js_extra.status index 96a3776960b..2cb3918e076 100644 --- a/tests/compiler/dart2js_extra/dart2js_extra.status +++ b/tests/compiler/dart2js_extra/dart2js_extra.status @@ -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 diff --git a/tests/language/language_dart2js.status b/tests/language/language_dart2js.status index 26f8a382c18..ee85c3683fe 100644 --- a/tests/language/language_dart2js.status +++ b/tests/language/language_dart2js.status @@ -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 type_variable_field_initializer_closure_test: RuntimeError # Issue 25309: T lost in List diff --git a/tests/utils/utils.status b/tests/utils/utils.status index bf69360d0f7..e81c8043d92 100644 --- a/tests/utils/utils.status +++ b/tests/utils/utils.status @@ -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