| // 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 '../common.dart'; |
| import '../inferrer/abstract_value_domain.dart'; |
| import '../io/source_information.dart'; |
| |
| import 'builder.dart'; |
| import 'locals_handler.dart'; |
| import 'nodes.dart'; |
| |
| class SsaBranch { |
| final SsaBranchBuilder branchBuilder; |
| final HBasicBlock block; |
| LocalsHandler? startLocals; |
| LocalsHandler? exitLocals; |
| SubGraph? graph; |
| |
| SsaBranch(this.branchBuilder) : block = HBasicBlock(); |
| } |
| |
| class SsaBranchBuilder { |
| final KernelSsaGraphBuilder builder; |
| final Spannable? diagnosticNode; |
| |
| SsaBranchBuilder(this.builder, [this.diagnosticNode]); |
| |
| AbstractValueDomain get _abstractValueDomain => |
| builder.closedWorld.abstractValueDomain; |
| |
| void checkNotAborted() { |
| if (builder.isAborted()) { |
| failedAt(diagnosticNode!, "aborted control flow"); |
| } |
| } |
| |
| void buildCondition( |
| void Function() visitCondition, |
| SsaBranch conditionBranch, |
| SsaBranch thenBranch, |
| SsaBranch elseBranch, |
| SourceInformation? sourceInformation, |
| ) { |
| startBranch(conditionBranch); |
| visitCondition(); |
| checkNotAborted(); |
| assert(identical(builder.current, builder.lastOpenedBlock)); |
| HInstruction conditionValue = builder.pop(); |
| HIf branch = HIf(conditionValue)..sourceInformation = sourceInformation; |
| HBasicBlock conditionExitBlock = builder.current!; |
| builder.close(branch); |
| conditionBranch.exitLocals = builder.localsHandler; |
| conditionExitBlock.addSuccessor(thenBranch.block); |
| conditionExitBlock.addSuccessor(elseBranch.block); |
| bool conditionBranchLocalsCanBeReused = mergeLocals( |
| conditionBranch, |
| thenBranch, |
| mayReuseFromLocals: true, |
| ); |
| mergeLocals( |
| conditionBranch, |
| elseBranch, |
| mayReuseFromLocals: conditionBranchLocalsCanBeReused, |
| ); |
| |
| conditionBranch.graph = SubExpression( |
| conditionBranch.block, |
| conditionExitBlock, |
| ); |
| } |
| |
| /// Returns true if the locals of the [fromBranch] may be reused. A [:true:] |
| /// return value implies that [mayReuseFromLocals] was set to [:true:]. |
| bool mergeLocals( |
| SsaBranch fromBranch, |
| SsaBranch toBranch, { |
| required bool mayReuseFromLocals, |
| }) { |
| LocalsHandler fromLocals = fromBranch.exitLocals!; |
| if (toBranch.startLocals == null) { |
| if (mayReuseFromLocals) { |
| toBranch.startLocals = fromLocals; |
| return false; |
| } else { |
| toBranch.startLocals = LocalsHandler.from(fromLocals); |
| return true; |
| } |
| } else { |
| toBranch.startLocals!.mergeWith(fromLocals, toBranch.block); |
| return true; |
| } |
| } |
| |
| void startBranch(SsaBranch branch) { |
| builder.graph.addBlock(branch.block); |
| builder.localsHandler = branch.startLocals!; |
| builder.open(branch.block); |
| } |
| |
| HInstruction? buildBranch( |
| SsaBranch branch, |
| void Function() visitBranch, |
| SsaBranch joinBranch, |
| bool isExpression, |
| ) { |
| startBranch(branch); |
| visitBranch(); |
| branch.graph = SubGraph(branch.block, builder.lastOpenedBlock); |
| branch.exitLocals = builder.localsHandler; |
| if (!builder.isAborted()) { |
| builder.goto(builder.current!, joinBranch.block); |
| mergeLocals(branch, joinBranch, mayReuseFromLocals: true); |
| } |
| if (isExpression) { |
| checkNotAborted(); |
| return builder.pop(); |
| } |
| return null; |
| } |
| |
| void handleIf( |
| void Function() visitCondition, |
| void Function() visitThen, |
| void Function()? visitElse, { |
| SourceInformation? sourceInformation, |
| }) { |
| visitElse ??= () {}; |
| |
| _handleDiamondBranch( |
| visitCondition, |
| visitThen, |
| visitElse, |
| isExpression: false, |
| sourceInformation: sourceInformation, |
| ); |
| } |
| |
| void handleConditional( |
| void Function() visitCondition, |
| void Function() visitThen, |
| void Function() visitElse, |
| ) { |
| _handleDiamondBranch( |
| visitCondition, |
| visitThen, |
| visitElse, |
| isExpression: true, |
| ); |
| } |
| |
| void handleIfNull(void Function() left, void Function() right) { |
| // x ?? y is transformed into: x == null ? y : x |
| late final HInstruction leftExpression; |
| handleConditional( |
| () { |
| left(); |
| leftExpression = builder.pop(); |
| builder.pushCheckNull(leftExpression); |
| }, |
| right, |
| () => builder.stack.add(leftExpression), |
| ); |
| } |
| |
| /// Creates the graph for '&&' or '||' operators. |
| /// |
| /// x && y is transformed into: |
| /// |
| /// t0 = boolify(x); |
| /// if (t0) { |
| /// t1 = boolify(y); |
| /// } |
| /// result = phi(t1, false); |
| /// |
| /// x || y is transformed into: |
| /// |
| /// t0 = boolify(x); |
| /// if (not(t0)) { |
| /// t1 = boolify(y); |
| /// } |
| /// result = phi(t1, true); |
| void handleLogicalBinary( |
| void Function() left, |
| void Function() right, |
| SourceInformation? sourceInformation, { |
| required bool isAnd, |
| }) { |
| late HInstruction boolifiedLeft; |
| late HInstruction boolifiedRight; |
| |
| void visitCondition() { |
| left(); |
| boolifiedLeft = builder.pop(); |
| builder.stack.add(boolifiedLeft); |
| if (!isAnd) { |
| builder.push( |
| HNot(builder.pop(), _abstractValueDomain.boolType) |
| ..sourceInformation = sourceInformation, |
| ); |
| } |
| } |
| |
| void visitThen() { |
| right(); |
| boolifiedRight = builder.pop(); |
| } |
| |
| handleIf( |
| visitCondition, |
| visitThen, |
| null, |
| sourceInformation: sourceInformation, |
| ); |
| HConstant notIsAnd = builder.graph.addConstantBool( |
| !isAnd, |
| builder.closedWorld, |
| ); |
| HPhi result = HPhi.manyInputs(null, [ |
| boolifiedRight, |
| notIsAnd, |
| ], _abstractValueDomain.dynamicType)..sourceInformation = sourceInformation; |
| builder.current!.addPhi(result); |
| builder.stack.add(result); |
| } |
| |
| void _handleDiamondBranch( |
| void Function() visitCondition, |
| void Function() visitThen, |
| void Function() visitElse, { |
| required bool isExpression, |
| SourceInformation? sourceInformation, |
| }) { |
| SsaBranch conditionBranch = SsaBranch(this); |
| SsaBranch thenBranch = SsaBranch(this); |
| SsaBranch elseBranch = SsaBranch(this); |
| SsaBranch joinBranch = SsaBranch(this); |
| |
| conditionBranch.startLocals = builder.localsHandler; |
| builder.goto(builder.current!, conditionBranch.block); |
| |
| buildCondition( |
| visitCondition, |
| conditionBranch, |
| thenBranch, |
| elseBranch, |
| sourceInformation, |
| ); |
| final thenValue = buildBranch( |
| thenBranch, |
| visitThen, |
| joinBranch, |
| isExpression, |
| ); |
| final elseValue = buildBranch( |
| elseBranch, |
| visitElse, |
| joinBranch, |
| isExpression, |
| ); |
| |
| if (isExpression) { |
| HPhi phi = HPhi.manyInputs(null, [ |
| thenValue!, |
| elseValue!, |
| ], _abstractValueDomain.dynamicType); |
| joinBranch.block.addPhi(phi); |
| builder.stack.add(phi); |
| } |
| |
| HBasicBlock? joinBlock; |
| // If at least one branch did not abort, open the joinBranch. |
| if (joinBranch.block.predecessors.isNotEmpty) { |
| startBranch(joinBranch); |
| joinBlock = joinBranch.block; |
| } |
| |
| HIfBlockInformation info = HIfBlockInformation( |
| HSubExpressionBlockInformation(conditionBranch.graph as SubExpression?), |
| HSubGraphBlockInformation(thenBranch.graph), |
| HSubGraphBlockInformation(elseBranch.graph), |
| ); |
| |
| HBasicBlock conditionStartBlock = conditionBranch.block; |
| conditionStartBlock.setBlockFlow(info, joinBlock); |
| final conditionGraph = conditionBranch.graph!; |
| final branch = conditionGraph.end.last as HIf; |
| branch.blockInformation = conditionStartBlock.blockFlow; |
| } |
| } |