| // Copyright (c) 2012, 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. |
| |
| part of ssa; |
| |
| class BailoutInfo { |
| int instructionId; |
| int bailoutId; |
| BailoutInfo(this.instructionId, this.bailoutId); |
| } |
| |
| /** |
| * Keeps track of the execution environment for instructions. An |
| * execution environment contains the SSA instructions that are live. |
| */ |
| class Environment { |
| final Set<HInstruction> lives; |
| final List<HBasicBlock> loopMarkers; |
| Environment() : lives = new Set<HInstruction>(), |
| loopMarkers = new List<HBasicBlock>(); |
| Environment.from(Environment other) |
| : lives = new Set<HInstruction>.from(other.lives), |
| loopMarkers = new List<HBasicBlock>.from(other.loopMarkers); |
| |
| void remove(HInstruction instruction) { |
| lives.remove(instruction); |
| } |
| |
| void add(HInstruction instruction) { |
| // If the instruction is a check, we add its checked input |
| // instead. This allows sharing the same environment between |
| // different type guards. |
| // |
| // Also, we don't need to add code motion invariant instructions |
| // in the live set (because we generate them at use-site), except |
| // for parameters that are not 'this', which is always passed as |
| // the receiver. |
| if (instruction is HCheck) { |
| add(instruction.checkedInput); |
| } else if (!instruction.isCodeMotionInvariant() |
| || (instruction is HParameterValue && instruction is !HThis)) { |
| lives.add(instruction); |
| } else { |
| for (int i = 0, len = instruction.inputs.length; i < len; i++) { |
| add(instruction.inputs[i]); |
| } |
| } |
| } |
| |
| void addAll(Environment other) { |
| lives.addAll(other.lives); |
| } |
| |
| bool get isEmpty => lives.isEmpty && loopMarkers.isEmpty; |
| } |
| |
| |
| /** |
| * Visits the graph in dominator order and inserts TypeGuards in places where |
| * we consider the guard to be of value. This phase also does type |
| * propagation to help find valuable type guards. |
| */ |
| class SsaTypeGuardInserter extends SsaNonSpeculativeTypePropagator |
| implements OptimizationPhase { |
| final String name = 'SsaTypeGuardInserter'; |
| final CodegenWorkItem work; |
| bool calledInLoop = false; |
| bool isRecursiveMethod = false; |
| bool hasInsertedChecks = false; |
| int stateId = 1; |
| Map<HInstruction, HType> savedTypes = new Map<HInstruction, HType>(); |
| |
| SsaTypeGuardInserter(compiler, this.work) : super(compiler); |
| |
| void visitGraph(HGraph graph) { |
| // Run the speculative type propagator. This does in-place |
| // update of the type of the instructions, and saves the |
| // previous types in the [savedTypes] map. |
| SsaTypePropagator propagator = |
| new SsaSpeculativeTypePropagator(compiler, savedTypes); |
| propagator.visitGraph(graph); |
| |
| // Put back the original types in the instructions, and save the |
| // speculated types in [savedTypes]. |
| Map<HInstruction, HType> speculativeTypes = new Map<HInstruction, HType>(); |
| |
| savedTypes.forEach((HInstruction instruction, HType type) { |
| speculativeTypes[instruction] = instruction.instructionType; |
| instruction.instructionType = type; |
| }); |
| savedTypes = speculativeTypes; |
| |
| // Propagate types again, and insert type guards in the graph. |
| isRecursiveMethod = graph.isRecursiveMethod; |
| calledInLoop = graph.calledInLoop; |
| work.guards = <HTypeGuard>[]; |
| visitDominatorTree(graph); |
| |
| // We need to disable the guards, and therefore re-run a |
| // non-speculative type propagator that will not use the |
| // speculated types. |
| work.guards.forEach((HTypeGuard guard) { guard.disable(); }); |
| |
| propagator = new SsaNonSpeculativeTypePropagator(compiler); |
| propagator.visitGraph(graph); |
| } |
| |
| // Primitive types that are not null are valuable. These include |
| // indexable arrays. |
| bool typeValuable(HType type) { |
| return type.isPrimitive() && !type.isNull(); |
| } |
| |
| bool get hasTypeGuards => work.guards.length != 0; |
| |
| bool isUsedWithIncompatibleSelector(HInstruction instruction, |
| HType speculativeType) { |
| for (HInstruction user in instruction.usedBy) { |
| if (user is HCheck |
| && isUsedWithIncompatibleSelector(user, speculativeType)) { |
| return true; |
| } else if (user.selector != null |
| && user.getDartReceiver(compiler) == instruction |
| && !speculativeType.computeMask(compiler).willHit( |
| user.selector, compiler)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool typeGuardWouldBeValuable(HInstruction instruction, |
| HType speculativeType) { |
| // If the type itself is not valuable, do not generate a guard for it. |
| if (!typeValuable(speculativeType)) return false; |
| |
| // Do not insert a type guard if the instruction has a type |
| // annotation that disagrees with the speculated type. |
| Element source = instruction.sourceElement; |
| if (source != null) { |
| DartType sourceType = source.computeType(compiler); |
| if (!sourceType.isMalformed && !sourceType.isDynamic && |
| sourceType.kind == TypeKind.INTERFACE) { |
| TypeMask sourceMask = new TypeMask.subtype(sourceType); |
| TypeMask speculatedMask = speculativeType.computeMask(compiler); |
| if (sourceMask.intersection(speculatedMask, compiler).isEmpty) { |
| return false; |
| } |
| } |
| } |
| |
| // Do not insert a type guard if one of the calls on it will hit |
| // [NoSuchMethodError]. |
| if (isUsedWithIncompatibleSelector(instruction, speculativeType)) { |
| return false; |
| } |
| |
| // Insert type guards for recursive methods. |
| if (isRecursiveMethod) return true; |
| |
| // Insert type guards if there are uses in loops. |
| bool isNested(HBasicBlock inner, HBasicBlock outer) { |
| if (identical(inner, outer)) return false; |
| if (outer == null) return true; |
| while (inner != null) { |
| if (identical(inner, outer)) return true; |
| inner = inner.parentLoopHeader; |
| } |
| return false; |
| } |
| |
| // If the instruction is not in a loop then the header will be null. |
| HBasicBlock currentLoopHeader = instruction.block.enclosingLoopHeader; |
| for (HInstruction user in instruction.usedBy) { |
| HBasicBlock userLoopHeader = user.block.enclosingLoopHeader; |
| if (isNested(userLoopHeader, currentLoopHeader)) return true; |
| } |
| |
| bool isIndexOperatorOnIndexablePrimitive(instruction) { |
| return instruction is HIndex |
| || (instruction is HInvokeDynamicMethod |
| && instruction.isIndexOperatorOnIndexablePrimitive()); |
| } |
| |
| // To speed up computations on values loaded from arrays, we |
| // insert type guards for builtin array indexing operations in |
| // nested loops. Since this can blow up code size quite |
| // significantly, we only do it if type guards have already been |
| // inserted for this method. The code size price for an additional |
| // type guard is much smaller than the first one that causes the |
| // generation of a bailout method. |
| if (hasTypeGuards |
| && isIndexOperatorOnIndexablePrimitive(instruction)) { |
| HBasicBlock loopHeader = instruction.block.enclosingLoopHeader; |
| if (loopHeader != null && loopHeader.parentLoopHeader != null) { |
| return true; |
| } |
| } |
| |
| // If the instruction is used by a phi where a guard would be |
| // valuable, put the guard on that instruction. |
| for (HInstruction user in instruction.usedBy) { |
| if (user is HPhi |
| && user.block.id > instruction.id |
| && typeGuardWouldBeValuable(user, speculativeType)) { |
| return true; |
| } |
| } |
| |
| // Insert type guards if the method is likely to be called in a |
| // loop. |
| return calledInLoop; |
| } |
| |
| // Returns whether an invocation of [selector] on [receiver] will throw a |
| // [ArgumentError] if the argument is not of the right type. |
| bool willThrowArgumentError(Selector selector, |
| HInstruction receiver, |
| HType speculativeType) { |
| if (receiver != null && (receiver.isInteger() || receiver.isString())) { |
| return selector.isOperator() |
| && selector.name != const SourceString('==') |
| && (speculativeType.isNumber() && !speculativeType.isInteger()); |
| } |
| return false; |
| } |
| |
| // Returns whether an invocation of [selector] will throw a |
| // [NoSuchMethodError] if the receiver is not of the type |
| // [speculativeType]. |
| bool willThrowNoSuchMethodErrorIfNot(Selector selector, |
| HType speculativeType) { |
| return compiler.world.hasSingleMatch(selector) |
| // In some cases, we want the receiver to be an integer, |
| // but that does not mean we will get a NoSuchMethodError |
| // if it's not: the receiver could be a double. |
| && !speculativeType.isInteger() |
| // We speculate on the [operator==] instruction, but we know it |
| // will never throw a [NoSuchMethodError]. |
| && selector.name != const SourceString('=='); |
| } |
| |
| bool shouldInsertTypeGuard(HInstruction instruction, HType speculativeType) { |
| if (!speculativeType.isUseful()) return false; |
| // If the types agree we don't need to check. |
| if (speculativeType == instruction.instructionType) return false; |
| // If a bailout check is more expensive than doing the actual operation |
| // don't do it either. |
| return typeGuardWouldBeValuable(instruction, speculativeType); |
| } |
| |
| HInstruction computeFirstDominatingUserWithSelector( |
| HInstruction instruction) { |
| // TODO(ngeoffray): We currently only look at the instruction's |
| // block, so that we know it will be executed. We should lift this |
| // limitation. |
| |
| // For a parameter, we look at the first block that contains |
| // user instructions. |
| HBasicBlock userMustBeInBlock = instruction is HParameterValue |
| ? instruction.block.successors[0] |
| : instruction.block; |
| |
| HInstruction firstUser; |
| for (HInstruction user in instruction.usedBy) { |
| if (user.block == userMustBeInBlock && user.selector != null) { |
| if (firstUser == null || user.dominates(firstUser)) { |
| firstUser = user; |
| } |
| } |
| } |
| return firstUser; |
| } |
| |
| /** |
| * Tries to insert a type conversion instruction for [instruction] |
| * instead of a type guard if we know an user will throw. Returns |
| * whether it succeeded at adding a type conversion instruction. |
| */ |
| bool tryTypeConversion(HInstruction instruction, HType speculativeType) { |
| HInstruction firstUser = |
| computeFirstDominatingUserWithSelector(instruction); |
| if (firstUser == null) return false; |
| |
| // If we have found a user with a selector, we find out if it |
| // will throw [NoSuchMethodError] or [ArgumentError]. |
| Selector selector = firstUser.selector; |
| Selector receiverSelectorOnThrow = null; |
| HInstruction receiver = firstUser.getDartReceiver(compiler); |
| bool willThrow = false; |
| if (receiver == instruction) { |
| if (willThrowNoSuchMethodErrorIfNot(selector, speculativeType)) { |
| receiverSelectorOnThrow = selector; |
| willThrow = true; |
| } |
| } else if (willThrowArgumentError(selector, receiver, speculativeType)) { |
| willThrow = true; |
| } |
| |
| if (!willThrow) return false; |
| |
| HTypeConversion check = new HTypeConversion( |
| null, |
| receiverSelectorOnThrow == null |
| ? HTypeConversion.ARGUMENT_TYPE_CHECK |
| : HTypeConversion.RECEIVER_TYPE_CHECK, |
| speculativeType, |
| instruction, |
| receiverSelectorOnThrow); |
| hasInsertedChecks = true; |
| firstUser.block.addBefore(firstUser, check); |
| instruction.replaceAllUsersDominatedBy(firstUser, check); |
| return true; |
| } |
| |
| bool updateType(HInstruction instruction) { |
| bool hasChanged = super.updateType(instruction); |
| HType speculativeType = savedTypes[instruction]; |
| if (speculativeType == null) return hasChanged; |
| |
| if (shouldInsertTypeGuard(instruction, speculativeType) |
| && !tryTypeConversion(instruction, speculativeType)) { |
| HInstruction insertionPoint; |
| if (instruction is HPhi) { |
| insertionPoint = instruction.block.first; |
| } else if (instruction is HParameterValue) { |
| // We insert the type guard at the end of the entry block |
| // because if a parameter is live, it must be kept in the live |
| // environment. Not doing so would mean we could visit a |
| // parameter and remove it from the environment before |
| // visiting a type guard. |
| insertionPoint = instruction.block.last; |
| } else { |
| insertionPoint = instruction.next; |
| } |
| // If the previous instruction is also a type guard, then both |
| // guards have the same environment, and can therefore share the |
| // same state id. |
| HBailoutTarget target; |
| int state; |
| if (insertionPoint.previous is HTypeGuard) { |
| HTypeGuard other = insertionPoint.previous; |
| target = other.bailoutTarget; |
| } else { |
| state = stateId++; |
| target = new HBailoutTarget(state); |
| insertionPoint.block.addBefore(insertionPoint, target); |
| } |
| HTypeGuard guard = new HTypeGuard(speculativeType, instruction, target); |
| work.guards.add(guard); |
| // By setting the type of the guard to the speculated type, we |
| // help the analysis find valuable type guards. This however |
| // requires to run a non-speculative type propagation again |
| // after this analysis. |
| guard.instructionType = speculativeType; |
| instruction.block.rewrite(instruction, guard); |
| insertionPoint.block.addBefore(insertionPoint, guard); |
| } |
| return hasChanged; |
| } |
| } |
| |
| /** |
| * Computes the environment for each SSA instruction: visits the graph |
| * in post-dominator order. Removes an instruction from the environment |
| * and adds its inputs to the environment at the instruction's |
| * definition. |
| * |
| * At the end of the computation, insert type guards in the graph. |
| */ |
| class SsaEnvironmentBuilder extends HBaseVisitor implements OptimizationPhase { |
| final Compiler compiler; |
| final String name = 'SsaEnvironmentBuilder'; |
| |
| final Map<HBailoutTarget, Environment> capturedEnvironments; |
| final Map<HBasicBlock, Environment> liveInstructions; |
| Environment environment; |
| /** |
| * The set of current loop headers that dominate the current block. |
| */ |
| Set<HBasicBlock> loopMarkers; |
| |
| SsaEnvironmentBuilder(Compiler this.compiler) |
| : capturedEnvironments = new Map<HBailoutTarget, Environment>(), |
| liveInstructions = new Map<HBasicBlock, Environment>(), |
| loopMarkers = new Set<HBasicBlock>(); |
| |
| |
| void visitGraph(HGraph graph) { |
| visitPostDominatorTree(graph); |
| if (!liveInstructions[graph.entry].isEmpty) { |
| compiler.internalError('Bailout environment computation', |
| node: compiler.currentElement.parseNode(compiler)); |
| } |
| updateLoopMarkers(); |
| insertCapturedEnvironments(); |
| } |
| |
| void updateLoopMarkers() { |
| // If the block is a loop header, we need to merge the loop |
| // header's live instructions into every environment that contains |
| // the loop marker. |
| // For example with the following loop (read the example in |
| // reverse): |
| // |
| // while (true) { <-- (4) update the marker with the environment |
| // use(x); <-- (3) environment = {x} |
| // bailout; <-- (2) has the marker when computed |
| // } <-- (1) create a loop marker |
| // |
| // The bailout instruction first captures the marker, but it |
| // will be replaced by the live environment at the loop entry, |
| // in this case {x}. |
| capturedEnvironments.forEach((ignoredInstruction, env) { |
| env.loopMarkers.forEach((HBasicBlock header) { |
| env.addAll(liveInstructions[header]); |
| }); |
| env.loopMarkers.clear(); |
| }); |
| } |
| |
| void visitBasicBlock(HBasicBlock block) { |
| environment = new Environment(); |
| |
| // Add to the environment the live instructions of its successor, as well as |
| // the inputs of the phis of the successor that flow from this block. |
| for (int i = 0; i < block.successors.length; i++) { |
| HBasicBlock successor = block.successors[i]; |
| Environment successorEnv = liveInstructions[successor]; |
| if (successorEnv != null) { |
| environment.addAll(successorEnv); |
| } else { |
| // If we haven't computed the liveInstructions of that successor, we |
| // know it must be a loop header. |
| assert(successor.isLoopHeader()); |
| assert(!block.isLoopHeader()); |
| loopMarkers.add(successor); |
| } |
| |
| int index = successor.predecessors.indexOf(block); |
| for (HPhi phi = successor.phis.first; phi != null; phi = phi.next) { |
| environment.add(phi.inputs[index]); |
| } |
| } |
| |
| if (block.isLoopHeader()) { |
| loopMarkers.remove(block); |
| } |
| |
| // If the block is a loop header, we're adding all [loopMarkers] |
| // after removing it from the list of [loopMarkers], because |
| // it will just recompute the loop phis. |
| environment.loopMarkers.addAll(loopMarkers); |
| |
| // Iterate over all instructions to remove an instruction from the |
| // environment and add its inputs. |
| HInstruction instruction = block.last; |
| while (instruction != null) { |
| instruction.accept(this); |
| instruction = instruction.previous; |
| } |
| |
| // We just remove the phis from the environment. The inputs of the |
| // phis will be put in the environment of the predecessors. |
| for (HPhi phi = block.phis.first; phi != null; phi = phi.next) { |
| environment.remove(phi); |
| } |
| |
| // Finally save the liveInstructions of that block. |
| liveInstructions[block] = environment; |
| } |
| |
| void visitBailoutTarget(HBailoutTarget target) { |
| visitInstruction(target); |
| capturedEnvironments[target] = new Environment.from(environment); |
| } |
| |
| void visitInstruction(HInstruction instruction) { |
| environment.remove(instruction); |
| for (int i = 0, len = instruction.inputs.length; i < len; i++) { |
| environment.add(instruction.inputs[i]); |
| } |
| } |
| |
| /** |
| * Stores all live variables in the bailout target and the guards. |
| */ |
| void insertCapturedEnvironments() { |
| capturedEnvironments.forEach((HBailoutTarget target, Environment env) { |
| assert(target.inputs.length == 0); |
| target.inputs.addAll(env.lives); |
| // TODO(floitsch): we should add the bailout-target's input variables |
| // as input to the guards only in the optimized version. The |
| // non-optimized version does not use the bailout guards and it is |
| // unnecessary to keep the variables alive until the check. |
| for (HTypeGuard guard in target.usedBy) { |
| // A type-guard initially only has two inputs: the guarded instruction |
| // and the bailout-target. Only after adding the environment is it |
| // allowed to have more inputs. |
| assert(guard.inputs.length == 2); |
| guard.inputs.addAll(env.lives); |
| } |
| for (HInstruction live in env.lives) { |
| live.usedBy.add(target); |
| live.usedBy.addAll(target.usedBy); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Propagates bailout information to blocks that need it. This visitor |
| * is run before codegen, to know which blocks have to deal with |
| * bailouts. |
| */ |
| class SsaBailoutPropagator extends HBaseVisitor { |
| final Compiler compiler; |
| /** |
| * A list to propagate bailout information to blocks that start a |
| * guarded or labeled list of statements. Currently, these blocks |
| * are: |
| * - first block of a then branch, |
| * - first block of an else branch, |
| * - a loop header, |
| * - labeled block. |
| */ |
| final List<HBasicBlock> blocks; |
| |
| /** |
| * The current subgraph we are visiting. |
| */ |
| SubGraph subGraph; |
| |
| /** |
| * The current block information we are visiting. |
| */ |
| HBlockInformation currentBlockInformation; |
| |
| /** |
| * Max number of arguments to the bailout (not counting the state). |
| */ |
| int bailoutArity; |
| /** |
| * A map from variables to their names. These are the names in the |
| * unoptimized (bailout) version of the function. Their names could be |
| * different in the optimized version. |
| */ |
| VariableNames variableNames; |
| /** |
| * Maps from the variable names to their positions in the argument list of the |
| * bailout instruction. Because of the way the variable allocator works, |
| * several variables can end up with the same name (if their live ranges do |
| * not overlap), therefore they can have the same position in the bailout |
| * argument list |
| */ |
| Map<String, int> parameterNames; |
| |
| /** |
| * If set to true, the graph has either multiple bailouts in |
| * different places, or a bailout inside an if or a loop. For such a |
| * graph, the code generator will emit a generic switch. |
| */ |
| bool hasComplexBailoutTargets = false; |
| |
| /** |
| * The first type guard in the graph. |
| */ |
| HBailoutTarget firstBailoutTarget; |
| |
| /** |
| * If set, it is the first block in the graph where we generate |
| * code. Blocks before this one are dead code in the bailout |
| * version. |
| */ |
| |
| SsaBailoutPropagator(this.compiler, this.variableNames) |
| : blocks = <HBasicBlock>[], |
| bailoutArity = 0, |
| parameterNames = new Map<String, int>(); |
| |
| void visitGraph(HGraph graph) { |
| subGraph = new SubGraph(graph.entry, graph.exit); |
| visitBasicBlock(graph.entry); |
| if (!blocks.isEmpty) { |
| compiler.internalError('Bailout propagation', |
| node: compiler.currentElement.parseNode(compiler)); |
| } |
| } |
| |
| /** |
| * Returns true if we can visit the given [blockFlow]. False |
| * otherwise. Currently, try/catch and switch are not in bailout |
| * methods, so this method only deals with loops and labeled blocks. |
| * If [blockFlow] is a labeled block or a loop, we also visit the |
| * continuation of the block flow. |
| */ |
| bool handleBlockFlow(HBlockFlow blockFlow) { |
| HBlockInformation body = blockFlow.body; |
| |
| // We reach here again when starting to visit a subgraph. Just |
| // return to visiting the block. |
| if (currentBlockInformation == body) return false; |
| |
| HBlockInformation oldInformation = currentBlockInformation; |
| if (body is HLabeledBlockInformation) { |
| currentBlockInformation = body; |
| HLabeledBlockInformation info = body; |
| visitStatements(info.body, newFlow: true); |
| } else if (body is HLoopBlockInformation) { |
| currentBlockInformation = body; |
| HLoopBlockInformation info = body; |
| if (info.initializer != null) { |
| visitExpression(info.initializer); |
| } |
| blocks.add(info.loopHeader); |
| if (!info.isDoWhile()) { |
| visitExpression(info.condition); |
| } |
| visitStatements(info.body, newFlow: false); |
| if (info.isDoWhile()) { |
| visitExpression(info.condition); |
| } |
| if (info.updates != null) { |
| visitExpression(info.updates); |
| } |
| blocks.removeLast(); |
| } else { |
| assert(body is! HTryBlockInformation); |
| assert(body is! HSwitchBlockInformation); |
| // [HIfBlockInformation] is handled by visitIf. |
| return false; |
| } |
| |
| currentBlockInformation = oldInformation; |
| if (blockFlow.continuation != null) { |
| visitBasicBlock(blockFlow.continuation); |
| } |
| return true; |
| } |
| |
| void visitBasicBlock(HBasicBlock block) { |
| // Abort traversal if we are leaving the currently active sub-graph. |
| if (!subGraph.contains(block)) return; |
| |
| HBlockFlow blockFlow = block.blockFlow; |
| if (blockFlow != null && handleBlockFlow(blockFlow)) return; |
| |
| HInstruction instruction = block.first; |
| while (instruction != null) { |
| instruction.accept(this); |
| instruction = instruction.next; |
| } |
| } |
| |
| void visitExpression(HSubExpressionBlockInformation info) { |
| visitSubGraph(info.subExpression); |
| } |
| |
| /** |
| * Visit the statements in [info]. If [newFlow] is true, we add the |
| * first block of [statements] to the list of [blocks]. |
| */ |
| void visitStatements(HSubGraphBlockInformation info, {bool newFlow}) { |
| SubGraph graph = info.subGraph; |
| if (newFlow) blocks.add(graph.start); |
| visitSubGraph(graph); |
| if (newFlow) blocks.removeLast(); |
| } |
| |
| void visitSubGraph(SubGraph graph) { |
| SubGraph oldSubGraph = subGraph; |
| subGraph = graph; |
| visitBasicBlock(graph.start); |
| subGraph = oldSubGraph; |
| } |
| |
| void visitIf(HIf instruction) { |
| int preVisitedBlocks = 0; |
| HIfBlockInformation info = instruction.blockInformation.body; |
| visitStatements(info.thenGraph, newFlow: true); |
| preVisitedBlocks++; |
| visitStatements(info.elseGraph, newFlow: true); |
| preVisitedBlocks++; |
| |
| HBasicBlock joinBlock = instruction.joinBlock; |
| if (joinBlock != null |
| && !identical(joinBlock.dominator, instruction.block)) { |
| // The join block is dominated by a block in one of the branches. |
| // The subgraph traversal never reached it, so we visit it here |
| // instead. |
| visitBasicBlock(joinBlock); |
| } |
| |
| // Visit all the dominated blocks that are not part of the then or else |
| // branches, and is not the join block. |
| // Depending on how the then/else branches terminate |
| // (e.g., return/throw/break) there can be any number of these. |
| List<HBasicBlock> dominated = instruction.block.dominatedBlocks; |
| int dominatedCount = dominated.length; |
| for (int i = preVisitedBlocks; i < dominatedCount; i++) { |
| HBasicBlock dominatedBlock = dominated[i]; |
| visitBasicBlock(dominatedBlock); |
| } |
| } |
| |
| void visitGoto(HGoto goto) { |
| HBasicBlock block = goto.block; |
| HBasicBlock successor = block.successors[0]; |
| if (identical(successor.dominator, block)) { |
| visitBasicBlock(block.successors[0]); |
| } |
| } |
| |
| void visitLoopBranch(HLoopBranch branch) { |
| // For a do-while loop, the body has already been visited. |
| if (!branch.isDoWhile()) { |
| visitBasicBlock(branch.block.dominatedBlocks[0]); |
| } |
| } |
| |
| visitBailoutTarget(HBailoutTarget target) { |
| int inputLength = target.inputs.length; |
| for (HInstruction input in target.inputs) { |
| String inputName = variableNames.getName(input); |
| int position = parameterNames[inputName]; |
| if (position == null) { |
| position = parameterNames[inputName] = bailoutArity++; |
| } |
| } |
| |
| if (blocks.isEmpty) { |
| if (firstBailoutTarget == null) { |
| firstBailoutTarget = target; |
| } else { |
| hasComplexBailoutTargets = true; |
| } |
| } else { |
| hasComplexBailoutTargets = true; |
| blocks.forEach((HBasicBlock block) { |
| block.bailoutTargets.add(target); |
| }); |
| } |
| } |
| } |