Index: pkg/compiler/lib/src/ssa/loop_handler.dart |
diff --git a/pkg/compiler/lib/src/ssa/loop_handler.dart b/pkg/compiler/lib/src/ssa/loop_handler.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e897820562d5be5c3631c76020dece051a5e7fca |
--- /dev/null |
+++ b/pkg/compiler/lib/src/ssa/loop_handler.dart |
@@ -0,0 +1,414 @@ |
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+import 'package:kernel/ast.dart' as ir; |
+ |
+import '../elements/elements.dart'; |
+import '../io/source_information.dart'; |
+import '../tree/tree.dart' as ast; |
+ |
+import 'builder.dart'; |
+import 'builder_kernel.dart'; |
+import 'graph_builder.dart'; |
+import 'jump_handler.dart'; |
+import 'kernel_ast_adapter.dart'; |
+import 'locals_handler.dart'; |
+import 'nodes.dart'; |
+ |
+/// Builds the SSA graph for loop nodes. |
+abstract class LoopHandler<T> { |
+ final GraphBuilder builder; |
+ |
+ LoopHandler(this.builder); |
+ |
+ /// Builds a graph for the given [loop] node. |
+ /// |
+ /// For while loops, [initialize] and [update] are null. |
+ /// The [condition] function must return a boolean result. |
+ /// None of the functions must leave anything on the stack. |
+ void handleLoop(T loop, void initialize(), HInstruction condition(), |
+ void update(), void body()) { |
+ // Generate: |
+ // <initializer> |
+ // loop-entry: |
+ // if (!<condition>) goto loop-exit; |
+ // <body> |
+ // <updates> |
+ // goto loop-entry; |
+ // loop-exit: |
+ |
+ builder.localsHandler.startLoop(getNode(loop)); |
+ |
+ // The initializer. |
+ SubExpression initializerGraph = null; |
+ HBasicBlock startBlock; |
+ if (initialize != null) { |
+ HBasicBlock initializerBlock = builder.openNewBlock(); |
+ startBlock = initializerBlock; |
+ initialize(); |
+ assert(!builder.isAborted()); |
+ initializerGraph = new SubExpression(initializerBlock, builder.current); |
+ } |
+ |
+ builder.loopDepth++; |
+ JumpHandler jumpHandler = beginLoopHeader(loop); |
+ HLoopInformation loopInfo = builder.current.loopInformation; |
+ HBasicBlock conditionBlock = builder.current; |
+ if (startBlock == null) startBlock = conditionBlock; |
+ |
+ HInstruction conditionInstruction = condition(); |
+ HBasicBlock conditionEndBlock = |
+ builder.close(new HLoopBranch(conditionInstruction)); |
+ SubExpression conditionExpression = |
+ new SubExpression(conditionBlock, conditionEndBlock); |
+ |
+ // Save the values of the local variables at the end of the condition |
+ // block. These are the values that will flow to the loop exit if the |
+ // condition fails. |
+ LocalsHandler savedLocals = new LocalsHandler.from(builder.localsHandler); |
+ |
+ // The body. |
+ HBasicBlock beginBodyBlock = builder.addNewBlock(); |
+ conditionEndBlock.addSuccessor(beginBodyBlock); |
+ builder.open(beginBodyBlock); |
+ |
+ builder.localsHandler.enterLoopBody(getNode(loop)); |
+ body(); |
+ |
+ SubGraph bodyGraph = new SubGraph(beginBodyBlock, builder.lastOpenedBlock); |
+ HBasicBlock bodyBlock = builder.current; |
+ if (builder.current != null) builder.close(new HGoto()); |
+ |
+ SubExpression updateGraph; |
+ |
+ bool loopIsDegenerate = !jumpHandler.hasAnyContinue() && bodyBlock == null; |
+ if (!loopIsDegenerate) { |
+ // Update. |
+ // We create an update block, even when we are in a while loop. There the |
+ // update block is the jump-target for continue statements. We could avoid |
+ // the creation if there is no continue, but for now we always create it. |
+ HBasicBlock updateBlock = builder.addNewBlock(); |
+ |
+ List<LocalsHandler> continueHandlers = <LocalsHandler>[]; |
+ jumpHandler |
+ .forEachContinue((HContinue instruction, LocalsHandler locals) { |
+ instruction.block.addSuccessor(updateBlock); |
+ continueHandlers.add(locals); |
+ }); |
+ |
+ if (bodyBlock != null) { |
+ continueHandlers.add(builder.localsHandler); |
+ bodyBlock.addSuccessor(updateBlock); |
+ } |
+ |
+ builder.open(updateBlock); |
+ builder.localsHandler = |
+ continueHandlers[0].mergeMultiple(continueHandlers, updateBlock); |
+ |
+ List<LabelDefinition> labels = jumpHandler.labels(); |
+ JumpTarget target = getTargetDefinition(loop); |
+ if (!labels.isEmpty) { |
+ beginBodyBlock.setBlockFlow( |
+ new HLabeledBlockInformation( |
+ new HSubGraphBlockInformation(bodyGraph), jumpHandler.labels(), |
+ isContinue: true), |
+ updateBlock); |
+ } else if (target != null && target.isContinueTarget) { |
+ beginBodyBlock.setBlockFlow( |
+ new HLabeledBlockInformation.implicit( |
+ new HSubGraphBlockInformation(bodyGraph), target, |
+ isContinue: true), |
+ updateBlock); |
+ } |
+ |
+ builder.localsHandler.enterLoopUpdates(getNode(loop)); |
+ |
+ update(); |
+ |
+ HBasicBlock updateEndBlock = builder.close(new HGoto()); |
+ // The back-edge completing the cycle. |
+ updateEndBlock.addSuccessor(conditionBlock); |
+ updateGraph = new SubExpression(updateBlock, updateEndBlock); |
+ |
+ // Avoid a critical edge from the condition to the loop-exit body. |
+ HBasicBlock conditionExitBlock = builder.addNewBlock(); |
+ builder.open(conditionExitBlock); |
+ builder.close(new HGoto()); |
+ conditionEndBlock.addSuccessor(conditionExitBlock); |
+ |
+ endLoop(conditionBlock, conditionExitBlock, jumpHandler, savedLocals); |
+ |
+ conditionBlock.postProcessLoopHeader(); |
+ HLoopBlockInformation info = new HLoopBlockInformation( |
+ loopKind(loop), |
+ builder.wrapExpressionGraph(initializerGraph), |
+ builder.wrapExpressionGraph(conditionExpression), |
+ builder.wrapStatementGraph(bodyGraph), |
+ builder.wrapExpressionGraph(updateGraph), |
+ conditionBlock.loopInformation.target, |
+ conditionBlock.loopInformation.labels, |
+ loopSourceInformation(loop)); |
+ |
+ startBlock.setBlockFlow(info, builder.current); |
+ loopInfo.loopBlockInformation = info; |
+ } else { |
+ // The body of the for/while loop always aborts, so there is no back edge. |
+ // We turn the code into: |
+ // if (condition) { |
+ // body; |
+ // } else { |
+ // // We always create an empty else block to avoid critical edges. |
+ // } |
+ // |
+ // If there is any break in the body, we attach a synthetic |
+ // label to the if. |
+ HBasicBlock elseBlock = builder.addNewBlock(); |
+ builder.open(elseBlock); |
+ builder.close(new HGoto()); |
+ // Pass the elseBlock as the branchBlock, because that's the block we go |
+ // to just before leaving the 'loop'. |
+ endLoop(conditionBlock, elseBlock, jumpHandler, savedLocals); |
+ |
+ SubGraph elseGraph = new SubGraph(elseBlock, elseBlock); |
+ // Remove the loop information attached to the header. |
+ conditionBlock.loopInformation = null; |
+ |
+ // Remove the [HLoopBranch] instruction and replace it with |
+ // [HIf]. |
+ HInstruction condition = conditionEndBlock.last.inputs[0]; |
+ conditionEndBlock.addAtExit(new HIf(condition)); |
+ conditionEndBlock.addSuccessor(elseBlock); |
+ conditionEndBlock.remove(conditionEndBlock.last); |
+ HIfBlockInformation info = new HIfBlockInformation( |
+ builder.wrapExpressionGraph(conditionExpression), |
+ builder.wrapStatementGraph(bodyGraph), |
+ builder.wrapStatementGraph(elseGraph)); |
+ |
+ conditionEndBlock.setBlockFlow(info, builder.current); |
+ HIf ifBlock = conditionEndBlock.last; |
+ ifBlock.blockInformation = conditionEndBlock.blockFlow; |
+ |
+ // If the body has any break, attach a synthesized label to the |
+ // if block. |
+ if (jumpHandler.hasAnyBreak()) { |
+ JumpTarget target = getTargetDefinition(loop); |
+ LabelDefinition label = target.addLabel(null, 'loop'); |
+ label.setBreakTarget(); |
+ SubGraph labelGraph = new SubGraph(conditionBlock, builder.current); |
+ HLabeledBlockInformation labelInfo = new HLabeledBlockInformation( |
+ new HSubGraphBlockInformation(labelGraph), |
+ <LabelDefinition>[label]); |
+ |
+ conditionBlock.setBlockFlow(labelInfo, builder.current); |
+ |
+ jumpHandler.forEachBreak((HBreak breakInstruction, _) { |
+ HBasicBlock block = breakInstruction.block; |
+ block.addAtExit(new HBreak.toLabel(label)); |
+ block.remove(breakInstruction); |
+ }); |
+ } |
+ } |
+ jumpHandler.close(); |
+ builder.loopDepth--; |
+ } |
+ |
+ /// Creates a new loop-header block. The previous [current] block |
+ /// is closed with an [HGoto] and replaced by the newly created block. |
+ /// Also notifies the locals handler that we're entering a loop. |
+ JumpHandler beginLoopHeader(T node) { |
+ assert(!builder.isAborted()); |
+ HBasicBlock previousBlock = builder.close(new HGoto()); |
+ |
+ JumpHandler jumpHandler = createJumpHandler(node, isLoopJump: true); |
+ HBasicBlock loopEntry = builder.graph |
+ .addNewLoopHeaderBlock(jumpHandler.target, jumpHandler.labels()); |
+ previousBlock.addSuccessor(loopEntry); |
+ builder.open(loopEntry); |
+ |
+ builder.localsHandler.beginLoopHeader(loopEntry); |
+ return jumpHandler; |
+ } |
+ |
+ /// Ends the loop. |
+ /// |
+ /// It does this by: |
+ /// - creating a new block and adding it as successor to the [branchExitBlock] |
+ /// and any blocks that end in break. |
+ /// - opening the new block (setting as [current]). |
+ /// - notifying the locals handler that we're exiting a loop. |
+ /// |
+ /// [savedLocals] are the locals from the end of the loop condition. |
+ /// |
+ /// [branchExitBlock] is the exit (branching) block of the condition. |
+ /// Generally this is not the top of the loop, since this would lead to |
+ /// critical edges. It is null for degenerate do-while loops that have no back |
+ /// edge because they abort (throw/return/break in the body and have no |
+ /// continues). |
+ void endLoop(HBasicBlock loopEntry, HBasicBlock branchExitBlock, |
+ JumpHandler jumpHandler, LocalsHandler savedLocals) { |
+ HBasicBlock loopExitBlock = builder.addNewBlock(); |
+ |
+ List<LocalsHandler> breakHandlers = <LocalsHandler>[]; |
+ // Collect data for the successors and the phis at each break. |
+ jumpHandler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) { |
+ breakInstruction.block.addSuccessor(loopExitBlock); |
+ breakHandlers.add(locals); |
+ }); |
+ |
+ // The exit block is a successor of the loop condition if it is reached. |
+ // We don't add the successor in the case of a while/for loop that aborts |
+ // because the caller of endLoop will be wiring up a special empty else |
+ // block instead. |
+ if (branchExitBlock != null) { |
+ branchExitBlock.addSuccessor(loopExitBlock); |
+ } |
+ // Update the phis at the loop entry with the current values of locals. |
+ builder.localsHandler.endLoop(loopEntry); |
+ |
+ // Start generating code for the exit block. |
+ builder.open(loopExitBlock); |
+ |
+ // Create a new localsHandler for the loopExitBlock with the correct phis. |
+ if (!breakHandlers.isEmpty) { |
+ if (branchExitBlock != null) { |
+ // Add the values of the locals at the end of the condition block to |
+ // the phis. These are the values that flow to the exit if the |
+ // condition fails. |
+ breakHandlers.add(savedLocals); |
+ } |
+ builder.localsHandler = |
+ savedLocals.mergeMultiple(breakHandlers, loopExitBlock); |
+ } else { |
+ builder.localsHandler = savedLocals; |
+ } |
+ } |
+ |
+ /// Returns the jump target defined by [node]. |
+ JumpTarget getTargetDefinition(T node); |
+ |
+ /// Returns the corresponding AST node for [node]. |
+ ast.Node getNode(T node); |
+ |
+ /// Determine what kind of loop [node] represents. |
+ /// |
+ /// The result is one of the kinds defined in [HLoopBlockInformation]. |
+ int loopKind(T node); |
+ |
+ /// Returns the source information for the loop [node]. |
+ SourceInformation loopSourceInformation(T node); |
+ |
+ /// Creates a [JumpHandler] for a statement. The node must be a jump |
+ /// target. If there are no breaks or continues targeting the statement, |
+ /// a special "null handler" is returned. |
+ /// |
+ /// [isLoopJump] is [:true:] when the jump handler is for a loop. This is used |
+ /// to distinguish the synthesized loop created for a switch statement with |
+ /// continue statements from simple switch statements. |
+ JumpHandler createJumpHandler(T node, {bool isLoopJump}); |
+} |
+ |
+/// A loop handler for the builder that just uses AST nodes directly. |
+class SsaLoopHandler extends LoopHandler<ast.Node> { |
+ final SsaBuilder builder; |
+ |
+ SsaLoopHandler(SsaBuilder builder) |
+ : this.builder = builder, |
+ super(builder); |
+ |
+ @override |
+ JumpTarget getTargetDefinition(ast.Node node) { |
+ return builder.elements.getTargetDefinition(node); |
+ } |
+ |
+ @override |
+ ast.Node getNode(ast.Node node) => node; |
+ |
+ @override |
+ int loopKind(ast.Node node) => node.accept(const _SsaLoopTypeVisitor()); |
+ |
+ @override |
+ SourceInformation loopSourceInformation(ast.Node node) => |
+ builder.sourceInformationBuilder.buildLoop(node); |
+ |
+ @override |
+ JumpHandler createJumpHandler(ast.Node node, {bool isLoopJump}) => |
+ builder.createJumpHandler(node, isLoopJump: isLoopJump); |
+} |
+ |
+class _SsaLoopTypeVisitor extends ast.Visitor { |
+ const _SsaLoopTypeVisitor(); |
+ int visitNode(ast.Node node) => HLoopBlockInformation.NOT_A_LOOP; |
+ int visitWhile(ast.While node) => HLoopBlockInformation.WHILE_LOOP; |
+ int visitFor(ast.For node) => HLoopBlockInformation.FOR_LOOP; |
+ int visitDoWhile(ast.DoWhile node) => HLoopBlockInformation.DO_WHILE_LOOP; |
+ int visitAsyncForIn(ast.AsyncForIn node) => HLoopBlockInformation.FOR_IN_LOOP; |
+ int visitSyncForIn(ast.SyncForIn node) => HLoopBlockInformation.FOR_IN_LOOP; |
+ int visitSwitchStatement(ast.SwitchStatement node) => |
+ HLoopBlockInformation.SWITCH_CONTINUE_LOOP; |
+} |
+ |
+// TODO(het): Since kernel simplifies loop breaks and continues, we should |
+// rewrite the loop handler from scratch to account for the simplified structure |
+class KernelLoopHandler extends LoopHandler<ir.Node> { |
+ final KernelSsaBuilder builder; |
+ |
+ KernelAstAdapter get astAdapter => builder.astAdapter; |
+ |
+ KernelLoopHandler(KernelSsaBuilder builder) |
+ : this.builder = builder, |
+ super(builder); |
+ |
+ @override |
+ JumpHandler createJumpHandler(ir.Node node, {bool isLoopJump}) { |
+ JumpTarget element = getTargetDefinition(node); |
+ if (element == null || !identical(element.statement, node)) { |
+ // No breaks or continues to this node. |
+ return new NullJumpHandler(builder.compiler.reporter); |
+ } |
+ if (isLoopJump && node is ast.SwitchStatement) { |
+ // Create a special jump handler for loops created for switch statements |
+ // with continue statements. |
+ return new SwitchCaseJumpHandler(builder, element, getNode(node)); |
+ } |
+ return new JumpHandler(builder, element); |
+ } |
+ |
+ @override |
+ ast.Node getNode(ir.Node node) => astAdapter.getNode(node); |
+ |
+ @override |
+ JumpTarget getTargetDefinition(ir.Node node) => |
+ astAdapter.getTargetDefinition(node); |
+ |
+ @override |
+ int loopKind(ir.Node node) => node.accept(new _KernelLoopTypeVisitor()); |
+ |
+ // TODO(het): return the actual source information |
+ @override |
+ SourceInformation loopSourceInformation(ir.Node node) => null; |
+} |
+ |
+class _KernelLoopTypeVisitor extends ir.Visitor<int> { |
+ @override |
+ int defaultNode(ir.Node node) => HLoopBlockInformation.NOT_A_LOOP; |
+ |
+ @override |
+ int visitWhileStatement(ir.WhileStatement node) => |
+ HLoopBlockInformation.WHILE_LOOP; |
+ |
+ @override |
+ int visitForStatement(ir.ForStatement node) => HLoopBlockInformation.FOR_LOOP; |
+ |
+ @override |
+ int visitDoStatement(ir.DoStatement node) => |
+ HLoopBlockInformation.DO_WHILE_LOOP; |
+ |
+ @override |
+ int visitForInStatement(ir.ForInStatement node) => |
+ HLoopBlockInformation.FOR_IN_LOOP; |
+ |
+ @override |
+ int visitSwitchStatement(ir.SwitchStatement node) => |
+ HLoopBlockInformation.SWITCH_CONTINUE_LOOP; |
+} |