| // Copyright (c) 2011, 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. |
| |
| interface OptimizationPhase { |
| String get name(); |
| void visitGraph(HGraph graph); |
| } |
| |
| class SsaOptimizerTask extends CompilerTask { |
| SsaOptimizerTask(Compiler compiler) : super(compiler); |
| String get name() => 'SSA optimizer'; |
| |
| void runPhases(HGraph graph, List<OptimizationPhase> phases) { |
| for (OptimizationPhase phase in phases) { |
| phase.visitGraph(graph); |
| compiler.tracer.traceGraph(phase.name, graph); |
| } |
| } |
| |
| void optimize(WorkItem work, HGraph graph) { |
| measure(() { |
| List<OptimizationPhase> phases = <OptimizationPhase>[ |
| new SsaConstantFolder(compiler), |
| new SsaRedundantPhiEliminator(), |
| new SsaDeadPhiEliminator(), |
| new SsaGlobalValueNumberer(compiler), |
| new SsaCodeMotion(), |
| new SsaDeadCodeEliminator()]; |
| runPhases(graph, phases); |
| }); |
| } |
| |
| bool trySpeculativeOptimizations(WorkItem work, HGraph graph) { |
| return measure(() { |
| // Run the phases that will generate type guards. We must also run |
| // [SsaCheckInserter] because the type propagator also propagates |
| // types non-speculatively. For example, it propagates the type |
| // array for a call to the List constructor. |
| List<OptimizationPhase> phases = <OptimizationPhase>[ |
| new SsaTypePropagator(compiler), |
| new SsaTypeGuardBuilder(compiler, work), |
| new SsaCheckInserter(compiler)]; |
| runPhases(graph, phases); |
| return !work.guards.isEmpty(); |
| }); |
| } |
| |
| void prepareForSpeculativeOptimizations(WorkItem work, HGraph graph) { |
| measure(() { |
| // In order to generate correct code for the bailout version, we did not |
| // propagate types from the instruction to the type guard. We do it |
| // now to be able to optimize further. |
| work.guards.forEach((HTypeGuard guard) { |
| guard.type = guard.guarded.type; |
| guard.guarded.type = HType.UNKNOWN; |
| }); |
| // We also need to insert range and integer checks for the type guards, |
| // now that they know their type. We did not need to do that |
| // before because instructions that reference a guard would |
| // have not tried to use, e.g. native array access, since the |
| // guard was not typed. |
| runPhases(graph, <OptimizationPhase>[new SsaCheckInserter(compiler)]); |
| }); |
| } |
| } |
| |
| /** |
| * If both inputs to known operations are available execute the operation at |
| * compile-time. |
| */ |
| class SsaConstantFolder extends HBaseVisitor implements OptimizationPhase { |
| final String name = "SsaConstantFolder"; |
| final Compiler compiler; |
| HGraph graph; |
| |
| SsaConstantFolder(this.compiler); |
| |
| void visitGraph(HGraph graph) { |
| this.graph = graph; |
| visitDominatorTree(graph); |
| } |
| |
| visitBasicBlock(HBasicBlock block) { |
| HInstruction instruction = block.first; |
| while (instruction !== null) { |
| HInstruction next = instruction.next; |
| HInstruction replacement = instruction.accept(this); |
| if (replacement !== instruction) { |
| if (!replacement.isInBasicBlock()) { |
| // The constant folding can return an instruction that is already |
| // part of the graph (like an input), so we only add the replacement |
| // if necessary. |
| block.addAfter(instruction, replacement); |
| } |
| block.rewrite(instruction, replacement); |
| block.remove(instruction); |
| // Because the constant folder runs after type propagation, we |
| // must update the type of this instruction manually. Later |
| // phases can then optimize this instruction based on its |
| // type. |
| replacement.updateType(); |
| } |
| instruction = next; |
| } |
| } |
| |
| HInstruction visitInstruction(HInstruction node) { |
| return node; |
| } |
| |
| HInstruction visitBoolify(HBoolify node) { |
| List<HInstruction> inputs = node.inputs; |
| assert(inputs.length == 1); |
| HInstruction input = inputs[0]; |
| if (input.isBoolean()) return input; |
| // All values !== true are boolified to false. |
| if (input.type.isKnown()) { |
| return graph.addConstantBool(false); |
| } |
| return node; |
| } |
| |
| HInstruction visitNot(HNot node) { |
| List<HInstruction> inputs = node.inputs; |
| assert(inputs.length == 1); |
| HInstruction input = inputs[0]; |
| if (input is HConstant) { |
| HConstant constant = input; |
| bool isTrue = constant.constant.isTrue(); |
| return graph.addConstantBool(!isTrue); |
| } |
| return node; |
| } |
| |
| HInstruction visitInvokeUnary(HInvokeUnary node) { |
| HInstruction operand = node.operand; |
| if (operand is HConstant) { |
| UnaryOperation operation = node.operation; |
| HConstant receiver = operand; |
| Constant folded = operation.fold(receiver.constant); |
| if (folded !== null) return graph.addConstant(folded); |
| } |
| return node; |
| } |
| |
| HInstruction visitInvokeInterceptor(HInvokeInterceptor node) { |
| if (node.name == const SourceString('length') && |
| node.inputs[1].isConstantString()) { |
| HConstant input = node.inputs[1]; |
| StringConstant constant = input.constant; |
| DartString string = constant.value; |
| return graph.addConstantInt(string.length); |
| } |
| return node; |
| } |
| |
| HInstruction visitInvokeBinary(HInvokeBinary node) { |
| HInstruction left = node.left; |
| HInstruction right = node.right; |
| if (left is HConstant && right is HConstant) { |
| BinaryOperation operation = node.operation; |
| HConstant op1 = left; |
| HConstant op2 = right; |
| Constant folded = operation.fold(op1.constant, op2.constant); |
| if (folded !== null) return graph.addConstant(folded); |
| } |
| return node; |
| } |
| |
| HInstruction visitEquals(HEquals node) { |
| HInstruction left = node.left; |
| HInstruction right = node.right; |
| if (!left.isConstant() && right.isConstantNull()) { |
| // TODO(floitsch): cache interceptors. |
| HStatic target = new HStatic( |
| compiler.builder.interceptors.getEqualsNullInterceptor()); |
| node.block.addBefore(node,target); |
| return new HEquals(target, node.left, node.right); |
| } |
| // All other cases are dealt with by the [visitInvokeBinary]. |
| return visitInvokeBinary(node); |
| } |
| |
| HInstruction visitTypeGuard(HTypeGuard node) { |
| HInstruction value = node.guarded; |
| return (value.type.combine(node.type) == value.type) ? value : node; |
| } |
| |
| HInstruction visitIntegerCheck(HIntegerCheck node) { |
| HInstruction value = node.value; |
| return value.isInteger() ? value : node; |
| } |
| |
| HInstruction visitIs(HIs node) { |
| Element element = node.typeExpression; |
| if (element.kind === ElementKind.TYPE_VARIABLE) { |
| compiler.unimplemented("visitIs for type variables"); |
| } |
| |
| HType expressionType = node.expression.type; |
| if (element === compiler.objectClass |
| || element === compiler.dynamicClass) { |
| return graph.addConstantBool(true); |
| } else if (expressionType.isInteger()) { |
| if (element === compiler.intClass || element === compiler.numClass) { |
| return graph.addConstantBool(true); |
| } else if (element === compiler.doubleClass) { |
| // We let the JS semantics decide for that check. Currently |
| // the code we emit will always return true. |
| return node; |
| } else { |
| return graph.addConstantBool(false); |
| } |
| } else if (expressionType.isDouble()) { |
| if (element === compiler.doubleClass || element === compiler.numClass) { |
| return graph.addConstantBool(true); |
| } else if (element === compiler.intClass) { |
| // We let the JS semantics decide for that check. Currently |
| // the code we emit will return true for a double that can be |
| // represented as a 31-bit integer. |
| return node; |
| } else { |
| return graph.addConstantBool(false); |
| } |
| } else if (expressionType.isNumber()) { |
| if (element === compiler.numClass) { |
| return graph.addConstantBool(true); |
| } |
| // We cannot just return false, because the expression may be of |
| // type int or double. |
| } else if (expressionType.isString()) { |
| if (element === compiler.stringClass |
| || Elements.isStringSupertype(element, compiler)) { |
| return graph.addConstantBool(true); |
| } else { |
| return graph.addConstantBool(false); |
| } |
| } else if (expressionType.isArray()) { |
| if (element === compiler.listClass |
| || Elements.isListSupertype(element, compiler)) { |
| return graph.addConstantBool(true); |
| } else { |
| return graph.addConstantBool(false); |
| } |
| } |
| return node; |
| } |
| } |
| |
| class SsaCheckInserter extends HBaseVisitor implements OptimizationPhase { |
| final String name = "SsaCheckInserter"; |
| Element lengthInterceptor; |
| |
| SsaCheckInserter(Compiler compiler) { |
| SourceString lengthString = const SourceString('length'); |
| lengthInterceptor = |
| compiler.builder.interceptors.getStaticGetInterceptor(lengthString); |
| } |
| |
| void visitGraph(HGraph graph) { |
| visitDominatorTree(graph); |
| } |
| |
| void visitBasicBlock(HBasicBlock block) { |
| HInstruction instruction = block.first; |
| while (instruction !== null) { |
| HInstruction next = instruction.next; |
| instruction = instruction.accept(this); |
| instruction = next; |
| } |
| } |
| |
| HBoundsCheck insertBoundsCheck(HInstruction node, |
| HInstruction receiver, |
| HInstruction index) { |
| HStatic interceptor = new HStatic(lengthInterceptor); |
| node.block.addBefore(node, interceptor); |
| HInvokeInterceptor length = new HInvokeInterceptor( |
| Selector.INVOCATION_0, |
| const SourceString("length"), |
| true, |
| <HInstruction>[interceptor, receiver]); |
| length.type = HType.NUMBER; |
| node.block.addBefore(node, length); |
| |
| HBoundsCheck check = new HBoundsCheck(length, index); |
| node.block.addBefore(node, check); |
| return check; |
| } |
| |
| HIntegerCheck insertIntegerCheck(HInstruction node, HInstruction value) { |
| HIntegerCheck check = new HIntegerCheck(value); |
| node.block.addBefore(node, check); |
| return check; |
| } |
| |
| void visitIndex(HIndex node) { |
| if (!node.builtin) return; |
| HInstruction index = insertIntegerCheck(node, node.index); |
| index = insertBoundsCheck(node, node.receiver, index); |
| HIndex newInstruction = new HIndex(node.target, node.receiver, index); |
| node.block.addBefore(node, newInstruction); |
| node.block.rewrite(node, newInstruction); |
| node.block.remove(node); |
| } |
| |
| void visitIndexAssign(HIndexAssign node) { |
| if (!node.builtin) return; |
| HInstruction index = insertIntegerCheck(node, node.index); |
| index = insertBoundsCheck(node, node.receiver, index); |
| HIndexAssign newInstruction = |
| new HIndexAssign(node.target, node.receiver, index, node.value); |
| node.block.addBefore(node, newInstruction); |
| node.block.rewrite(node, newInstruction); |
| node.block.remove(node); |
| } |
| } |
| |
| class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase { |
| final String name = "SsaDeadCodeEliminator"; |
| |
| static bool isDeadCode(HInstruction instruction) { |
| // TODO(ngeoffray): the way we handle side effects is not right |
| // (e.g. branching instructions have side effects). |
| return !instruction.hasSideEffects() |
| && instruction.usedBy.isEmpty() |
| && instruction is !HCheck |
| && instruction is !HTypeGuard; |
| } |
| |
| void visitGraph(HGraph graph) { |
| visitPostDominatorTree(graph); |
| } |
| |
| void visitBasicBlock(HBasicBlock block) { |
| HInstruction instruction = block.last; |
| while (instruction !== null) { |
| var previous = instruction.previous; |
| if (isDeadCode(instruction)) block.remove(instruction); |
| instruction = previous; |
| } |
| } |
| } |
| |
| class SsaDeadPhiEliminator implements OptimizationPhase { |
| final String name = "SsaDeadPhiEliminator"; |
| |
| void visitGraph(HGraph graph) { |
| final List<HPhi> worklist = <HPhi>[]; |
| // A set to keep track of the live phis that we found. |
| final Set<HPhi> livePhis = new Set<HPhi>(); |
| |
| // Add to the worklist all live phis: phis referenced by non-phi |
| // instructions. |
| for (final block in graph.blocks) { |
| block.forEachPhi((HPhi phi) { |
| for (final user in phi.usedBy) { |
| if (user is !HPhi) { |
| worklist.add(phi); |
| livePhis.add(phi); |
| break; |
| } |
| } |
| }); |
| } |
| |
| // Process the worklist by propagating liveness to phi inputs. |
| while (!worklist.isEmpty()) { |
| HPhi phi = worklist.removeLast(); |
| for (final input in phi.inputs) { |
| if (input is HPhi && !livePhis.contains(input)) { |
| worklist.add(input); |
| livePhis.add(input); |
| } |
| } |
| } |
| |
| // Remove phis that are not live. |
| // Traverse in reverse order to remove phis with no uses before the |
| // phis that they might use. |
| // NOTICE: Doesn't handle circular references, but we don't currently |
| // create any. |
| List<HBasicBlock> blocks = graph.blocks; |
| for (int i = blocks.length - 1; i >= 0; i--) { |
| HBasicBlock block = blocks[i]; |
| HPhi current = block.phis.first; |
| HPhi next = null; |
| while (current != null) { |
| next = current.next; |
| if (!livePhis.contains(current) |
| // TODO(ahe): Not sure the following is correct. |
| && current.usedBy.isEmpty()) { |
| block.removePhi(current); |
| } |
| current = next; |
| } |
| } |
| } |
| } |
| |
| class SsaRedundantPhiEliminator implements OptimizationPhase { |
| final String name = "SsaRedundantPhiEliminator"; |
| |
| void visitGraph(HGraph graph) { |
| final List<HPhi> worklist = <HPhi>[]; |
| |
| // Add all phis in the worklist. |
| for (final block in graph.blocks) { |
| block.forEachPhi((HPhi phi) => worklist.add(phi)); |
| } |
| |
| while (!worklist.isEmpty()) { |
| HPhi phi = worklist.removeLast(); |
| |
| // If the phi has already been processed, continue. |
| if (!phi.isInBasicBlock()) continue; |
| |
| // Find if the inputs of the phi are the same instruction. |
| // The builder ensures that phi.inputs[0] cannot be the phi |
| // itself. |
| assert(phi.inputs[0] !== phi); |
| HInstruction candidate = phi.inputs[0]; |
| for (int i = 1; i < phi.inputs.length; i++) { |
| HInstruction input = phi.inputs[i]; |
| // If the input is the phi, the phi is still candidate for |
| // elimination. |
| if (input !== candidate && input !== phi) { |
| candidate = null; |
| break; |
| } |
| } |
| |
| // If the inputs are not the same, continue. |
| if (candidate == null) continue; |
| |
| // Because we're updating the users of this phi, we may have new |
| // phis candidate for elimination. Add phis that used this phi |
| // to the worklist. |
| for (final user in phi.usedBy) { |
| if (user is HPhi) worklist.add(user); |
| } |
| phi.block.rewrite(phi, candidate); |
| phi.block.removePhi(phi); |
| } |
| } |
| } |
| |
| class SsaGlobalValueNumberer implements OptimizationPhase { |
| final String name = "SsaGlobalValueNumberer"; |
| final Compiler compiler; |
| final Set<int> visited; |
| |
| List<int> blockChangesFlags; |
| List<int> loopChangesFlags; |
| |
| SsaGlobalValueNumberer(this.compiler) : visited = new Set<int>(); |
| |
| void visitGraph(HGraph graph) { |
| computeChangesFlags(graph); |
| moveLoopInvariantCode(graph); |
| visitBasicBlock(graph.entry, new ValueSet()); |
| } |
| |
| void moveLoopInvariantCode(HGraph graph) { |
| for (int i = graph.blocks.length - 1; i >= 0; i--) { |
| HBasicBlock block = graph.blocks[i]; |
| if (block.isLoopHeader()) { |
| int changesFlags = loopChangesFlags[block.id]; |
| HBasicBlock last = block.loopInformation.getLastBackEdge(); |
| for (int j = block.id; j <= last.id; j++) { |
| moveLoopInvariantCodeFromBlock(graph.blocks[j], block, changesFlags); |
| } |
| } |
| } |
| } |
| |
| void moveLoopInvariantCodeFromBlock(HBasicBlock block, |
| HBasicBlock loopHeader, |
| int changesFlags) { |
| HBasicBlock preheader = loopHeader.predecessors[0]; |
| int dependsFlags = HInstruction.computeDependsOnFlags(changesFlags); |
| HInstruction instruction = block.first; |
| while (instruction != null) { |
| HInstruction next = instruction.next; |
| if (instruction.useGvn() |
| && (instruction is !HCheck) |
| && (instruction.flags & dependsFlags) == 0) { |
| bool loopInvariantInputs = true; |
| List<HInstruction> inputs = instruction.inputs; |
| for (int i = 0, length = inputs.length; i < length; i++) { |
| if (isInputDefinedAfterDominator(inputs[i], preheader)) { |
| loopInvariantInputs = false; |
| break; |
| } |
| } |
| |
| // If the inputs are loop invariant, we can move the |
| // instruction from the current block to the pre-header block. |
| if (loopInvariantInputs) { |
| block.detach(instruction); |
| preheader.moveAtExit(instruction); |
| } |
| } |
| instruction = next; |
| } |
| } |
| |
| bool isInputDefinedAfterDominator(HInstruction input, |
| HBasicBlock dominator) { |
| return input.block.id > dominator.id; |
| } |
| |
| void visitBasicBlock(HBasicBlock block, ValueSet values) { |
| HInstruction instruction = block.first; |
| while (instruction !== null) { |
| HInstruction next = instruction.next; |
| int flags = instruction.getChangesFlags(); |
| if (flags != 0) { |
| assert(!instruction.useGvn()); |
| values.kill(flags); |
| } else if (instruction.useGvn()) { |
| HInstruction other = values.lookup(instruction); |
| if (other !== null) { |
| assert(other.gvnEquals(instruction) && instruction.gvnEquals(other)); |
| block.rewrite(instruction, other); |
| block.remove(instruction); |
| } else { |
| values.add(instruction); |
| } |
| } |
| instruction = next; |
| } |
| |
| List<HBasicBlock> dominatedBlocks = block.dominatedBlocks; |
| for (int i = 0, length = dominatedBlocks.length; i < length; i++) { |
| HBasicBlock dominated = dominatedBlocks[i]; |
| // No need to copy the value set for the last child. |
| ValueSet successorValues = (i == length - 1) ? values : values.copy(); |
| // If we have no values in our set, we do not have to kill |
| // anything. Also, if the range of block ids from the current |
| // block to the dominated block is empty, there is no blocks on |
| // any path from the current block to the dominated block so we |
| // don't have to do anything either. |
| assert(block.id < dominated.id); |
| if (!successorValues.isEmpty() && block.id + 1 < dominated.id) { |
| visited.clear(); |
| int changesFlags = getChangesFlagsForDominatedBlock(block, dominated); |
| successorValues.kill(changesFlags); |
| } |
| visitBasicBlock(dominated, successorValues); |
| } |
| } |
| |
| void computeChangesFlags(HGraph graph) { |
| // Create the changes flags lists. Make sure to initialize the |
| // loop changes flags list to zero so we can use bitwise or when |
| // propagating loop changes upwards. |
| final int length = graph.blocks.length; |
| blockChangesFlags = new List<int>(length); |
| loopChangesFlags = new List<int>(length); |
| for (int i = 0; i < length; i++) loopChangesFlags[i] = 0; |
| |
| // Run through all the basic blocks in the graph and fill in the |
| // changes flags lists. |
| for (int i = length - 1; i >= 0; i--) { |
| final HBasicBlock block = graph.blocks[i]; |
| final int id = block.id; |
| |
| // Compute block changes flags for the block. |
| int changesFlags = 0; |
| HInstruction instruction = block.first; |
| while (instruction !== null) { |
| instruction.prepareGvn(); |
| changesFlags |= instruction.getChangesFlags(); |
| instruction = instruction.next; |
| } |
| assert(blockChangesFlags[id] === null); |
| blockChangesFlags[id] = changesFlags; |
| |
| // Loop headers are part of their loop, so update the loop |
| // changes flags accordingly. |
| if (block.isLoopHeader()) { |
| loopChangesFlags[id] |= changesFlags; |
| } |
| |
| // Propagate loop changes flags upwards. |
| HBasicBlock parentLoopHeader = block.parentLoopHeader; |
| if (parentLoopHeader !== null) { |
| loopChangesFlags[parentLoopHeader.id] |= (block.isLoopHeader()) |
| ? loopChangesFlags[id] |
| : changesFlags; |
| } |
| } |
| } |
| |
| int getChangesFlagsForDominatedBlock(HBasicBlock dominator, |
| HBasicBlock dominated) { |
| int changesFlags = 0; |
| List<HBasicBlock> predecessors = dominated.predecessors; |
| for (int i = 0, length = predecessors.length; i < length; i++) { |
| HBasicBlock block = predecessors[i]; |
| int id = block.id; |
| // If the current predecessor block is on the path from the |
| // dominator to the dominated, it must have an id that is in the |
| // range from the dominator to the dominated. |
| if (dominator.id < id && id < dominated.id && !visited.contains(id)) { |
| visited.add(id); |
| changesFlags |= blockChangesFlags[id]; |
| changesFlags |= getChangesFlagsForDominatedBlock(dominator, block); |
| } |
| } |
| return changesFlags; |
| } |
| } |
| |
| // This phase merges equivalent instructions on different paths into |
| // one instruction in a dominator block. It runs through the graph |
| // post dominator order and computes a ValueSet for each block of |
| // instructions that can be moved to a dominator block. These |
| // instructions are the ones that: |
| // 1) can be used for GVN, and |
| // 2) do not use definitions of their own block. |
| // |
| // A basic block looks at its sucessors and finds the intersection of |
| // these computed ValueSet. It moves all instructions of the |
| // intersection into its own list of instructions. |
| class SsaCodeMotion extends HBaseVisitor implements OptimizationPhase { |
| final String name = "SsaCodeMotion"; |
| |
| List<ValueSet> values; |
| |
| void visitGraph(HGraph graph) { |
| values = new List<ValueSet>(graph.blocks.length); |
| for (int i = 0; i < graph.blocks.length; i++) { |
| values[graph.blocks[i].id] = new ValueSet(); |
| } |
| visitPostDominatorTree(graph); |
| } |
| |
| void visitBasicBlock(HBasicBlock block) { |
| List<HBasicBlock> successors = block.successors; |
| |
| // Phase 1: get the ValueSet of all successors, compute the |
| // intersection and move the instructions of the intersection into |
| // this block. |
| if (successors.length != 0) { |
| ValueSet instructions = values[successors[0].id]; |
| for (int i = 1; i < successors.length; i++) { |
| ValueSet other = values[successors[i].id]; |
| instructions = instructions.intersection(other); |
| } |
| |
| if (!instructions.isEmpty()) { |
| List<HInstruction> list = instructions.toList(); |
| for (HInstruction instruction in list) { |
| // Move the instruction to the current block. |
| instruction.block.detach(instruction); |
| block.moveAtExit(instruction); |
| // Go through all successors and rewrite their instruction |
| // to the shared one. |
| for (final successor in successors) { |
| HInstruction toRewrite = values[successor.id].lookup(instruction); |
| if (toRewrite != instruction) { |
| successor.rewrite(toRewrite, instruction); |
| successor.remove(toRewrite); |
| } |
| } |
| } |
| } |
| } |
| |
| // Don't try to merge instructions to a dominator if we have |
| // multiple predecessors. |
| if (block.predecessors.length != 1) return; |
| |
| // Phase 2: Go through all instructions of this block and find |
| // which instructions can be moved to a dominator block. |
| ValueSet set_ = values[block.id]; |
| HInstruction instruction = block.first; |
| int flags = 0; |
| while (instruction !== null) { |
| int dependsFlags = HInstruction.computeDependsOnFlags(flags); |
| flags |= instruction.getChangesFlags(); |
| |
| HInstruction current = instruction; |
| instruction = instruction.next; |
| |
| // TODO(ngeoffray): this check is needed because we currently do |
| // not have flags to express 'Gvn'able', but not movable. |
| if (current is HCheck) continue; |
| if (!current.useGvn()) continue; |
| if ((current.flags & dependsFlags) != 0) continue; |
| |
| bool canBeMoved = true; |
| for (final HInstruction input in current.inputs) { |
| if (input.block == block) { |
| canBeMoved = false; |
| break; |
| } |
| } |
| if (!canBeMoved) continue; |
| |
| // This is safe because we are running after GVN. |
| // TODO(ngeoffray): ensure GVN has been run. |
| set_.add(current); |
| } |
| } |
| } |