| // 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 '../closure.dart' show CapturedLoopScope; |
| import '../elements/jumps.dart'; |
| import '../io/source_information.dart'; |
| |
| import 'builder.dart'; |
| import 'jump_handler.dart'; |
| import 'locals_handler.dart'; |
| import 'nodes.dart'; |
| |
| /// Builds the SSA graph for loop nodes. |
| abstract class LoopHandler { |
| final KernelSsaGraphBuilder builder; |
| |
| LoopHandler(this.builder); |
| |
| /// Builds a graph for the given [loop] node. |
| /// |
| /// The [condition] function must return a boolean result. |
| /// None of the functions must leave anything on the stack. |
| void handleLoop( |
| ir.TreeNode loop, |
| CapturedLoopScope loopClosureInfo, |
| JumpTarget? jumpTarget, |
| void Function() initialize, |
| HInstruction Function() condition, |
| void Function() update, |
| void Function() body, |
| SourceInformation? sourceInformation, |
| ) { |
| // Generate: |
| // <initializer> |
| // loop-entry: |
| // if (!<condition>) goto loop-exit; |
| // <body> |
| // <updates> |
| // goto loop-entry; |
| // loop-exit: |
| |
| builder.localsHandler.startLoop(loopClosureInfo, sourceInformation); |
| |
| // The initializer. |
| SubExpression? initializerGraph; |
| HBasicBlock startBlock = builder.openNewBlock(); |
| initialize(); |
| assert(!builder.isAborted()); |
| initializerGraph = SubExpression(startBlock, builder.current!); |
| |
| builder.loopDepth++; |
| JumpHandler jumpHandler = beginLoopHeader(loop, jumpTarget); |
| final conditionBlock = builder.current!; |
| final loopInfo = conditionBlock.loopInformation!; |
| |
| HInstruction conditionInstruction = condition(); |
| HBasicBlock conditionEndBlock = builder.close( |
| HLoopBranch(conditionInstruction), |
| ); |
| SubExpression conditionExpression = 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 = LocalsHandler.from(builder.localsHandler); |
| |
| // The body. |
| HBasicBlock beginBodyBlock = builder.addNewBlock(); |
| conditionEndBlock.addSuccessor(beginBodyBlock); |
| builder.open(beginBodyBlock); |
| |
| builder.localsHandler.enterLoopBody(loopClosureInfo, sourceInformation); |
| body(); |
| |
| SubGraph bodyGraph = SubGraph(beginBodyBlock, builder.lastOpenedBlock); |
| final bodyBlock = builder.current; |
| if (bodyBlock != null) builder.close(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; |
| if (labels.isNotEmpty) { |
| beginBodyBlock.setBlockFlow( |
| HLabeledBlockInformation( |
| HSubGraphBlockInformation(bodyGraph), |
| jumpHandler.labels, |
| isContinue: true, |
| ), |
| updateBlock, |
| ); |
| } else if (jumpTarget != null && jumpTarget.isContinueTarget) { |
| beginBodyBlock.setBlockFlow( |
| HLabeledBlockInformation.implicit( |
| HSubGraphBlockInformation(bodyGraph), |
| jumpTarget, |
| isContinue: true, |
| ), |
| updateBlock, |
| ); |
| } |
| |
| builder.localsHandler.enterLoopUpdates( |
| loopClosureInfo, |
| sourceInformation, |
| ); |
| |
| update(); |
| |
| HBasicBlock updateEndBlock = builder.close(HGoto()); |
| // The back-edge completing the cycle. |
| updateEndBlock.addSuccessor(conditionBlock); |
| updateGraph = SubExpression(updateBlock, updateEndBlock); |
| |
| // Avoid a critical edge from the condition to the loop-exit body. |
| HBasicBlock conditionExitBlock = builder.addNewBlock(); |
| builder.open(conditionExitBlock); |
| builder.close(HGoto()); |
| conditionEndBlock.addSuccessor(conditionExitBlock); |
| |
| endLoop(conditionBlock, conditionExitBlock, jumpHandler, savedLocals); |
| |
| conditionBlock.postProcessLoopHeader(); |
| HLoopBlockInformation info = HLoopBlockInformation( |
| loopKind(loop), |
| builder.wrapExpressionGraph(initializerGraph), |
| builder.wrapExpressionGraph(conditionExpression), |
| builder.wrapStatementGraph(bodyGraph), |
| builder.wrapExpressionGraph(updateGraph), |
| loopInfo.target, |
| loopInfo.labels, |
| sourceInformation, |
| ); |
| |
| 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(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 = 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(HIf(condition)); |
| conditionEndBlock.addSuccessor(elseBlock); |
| conditionEndBlock.remove(conditionEndBlock.last!); |
| HIfBlockInformation info = HIfBlockInformation( |
| builder.wrapExpressionGraph(conditionExpression), |
| builder.wrapStatementGraph(bodyGraph), |
| builder.wrapStatementGraph(elseGraph), |
| ); |
| |
| conditionEndBlock.setBlockFlow(info, builder.current); |
| final ifBlock = conditionEndBlock.last as HIf; |
| ifBlock.blockInformation = conditionEndBlock.blockFlow; |
| |
| // If the body has any break, attach a synthesized label to the |
| // if block. |
| if (jumpHandler.hasAnyBreak()) { |
| LabelDefinition label = jumpTarget!.addLabel( |
| 'loop', |
| isBreakTarget: true, |
| ); |
| SubGraph labelGraph = SubGraph(conditionBlock, builder.current!); |
| HLabeledBlockInformation labelInfo = HLabeledBlockInformation( |
| HSubGraphBlockInformation(labelGraph), |
| <LabelDefinition>[label], |
| ); |
| |
| conditionBlock.setBlockFlow(labelInfo, builder.current); |
| |
| jumpHandler.forEachBreak((HBreak breakInstruction, _) { |
| final block = breakInstruction.block!; |
| block.addAtExit(HBreak.toLabel(label, sourceInformation)); |
| 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(ir.TreeNode node, JumpTarget? jumpTarget) { |
| assert(!builder.isAborted()); |
| HBasicBlock previousBlock = builder.close(HGoto()); |
| |
| JumpHandler jumpHandler = createJumpHandler( |
| node, |
| jumpTarget, |
| 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.isNotEmpty) { |
| 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; |
| } |
| } |
| |
| /// Determine what kind of loop [node] represents. |
| LoopBlockInformationKind loopKind(ir.TreeNode 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( |
| ir.TreeNode node, |
| JumpTarget? jumpTarget, { |
| required bool isLoopJump, |
| }); |
| } |
| |
| // 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 { |
| KernelLoopHandler(super.builder); |
| |
| @override |
| JumpHandler createJumpHandler( |
| ir.TreeNode node, |
| JumpTarget? jumpTarget, { |
| required bool isLoopJump, |
| }) => builder.createJumpHandler(node, jumpTarget, isLoopJump: isLoopJump); |
| |
| @override |
| LoopBlockInformationKind loopKind(ir.TreeNode node) => |
| node.accept(_KernelLoopTypeVisitor()); |
| } |
| |
| class _KernelLoopTypeVisitor extends ir.VisitorDefault<LoopBlockInformationKind> |
| with ir.VisitorDefaultValueMixin<LoopBlockInformationKind> { |
| @override |
| LoopBlockInformationKind get defaultValue => |
| LoopBlockInformationKind.notALoop; |
| |
| @override |
| LoopBlockInformationKind visitWhileStatement(ir.WhileStatement node) => |
| LoopBlockInformationKind.whileLoop; |
| |
| @override |
| LoopBlockInformationKind visitForStatement(ir.ForStatement node) => |
| LoopBlockInformationKind.forLoop; |
| |
| @override |
| LoopBlockInformationKind visitDoStatement(ir.DoStatement node) => |
| LoopBlockInformationKind.doWhileLoop; |
| |
| @override |
| LoopBlockInformationKind visitForInStatement(ir.ForInStatement node) => |
| LoopBlockInformationKind.forInLoop; |
| |
| @override |
| LoopBlockInformationKind visitSwitchStatement(ir.SwitchStatement node) => |
| LoopBlockInformationKind.switchContinueLoop; |
| } |