| /* |
| * Copyright (C) 2012 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "DFGCFGSimplificationPhase.h" |
| |
| #if ENABLE(DFG_JIT) |
| |
| #include "DFGAbstractState.h" |
| #include "DFGBasicBlockInlines.h" |
| #include "DFGGraph.h" |
| #include "DFGInsertionSet.h" |
| #include "DFGPhase.h" |
| #include "DFGValidate.h" |
| #include "Operations.h" |
| |
| namespace JSC { namespace DFG { |
| |
| class CFGSimplificationPhase : public Phase { |
| public: |
| CFGSimplificationPhase(Graph& graph) |
| : Phase(graph, "CFG simplification") |
| { |
| } |
| |
| bool run() |
| { |
| const bool extremeLogging = false; |
| |
| bool outerChanged = false; |
| bool innerChanged; |
| |
| do { |
| innerChanged = false; |
| for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) { |
| BasicBlock* block = m_graph.m_blocks[blockIndex].get(); |
| if (!block) |
| continue; |
| ASSERT(block->isReachable); |
| |
| switch (m_graph[block->last()].op()) { |
| case Jump: { |
| // Successor with one predecessor -> merge. |
| if (m_graph.m_blocks[m_graph.successor(block, 0)]->m_predecessors.size() == 1) { |
| ASSERT(m_graph.m_blocks[m_graph.successor(block, 0)]->m_predecessors[0] |
| == blockIndex); |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF("CFGSimplify: Jump merge on Block #%u to Block #%u.\n", |
| blockIndex, m_graph.successor(block, 0)); |
| #endif |
| if (extremeLogging) |
| m_graph.dump(); |
| mergeBlocks(blockIndex, m_graph.successor(block, 0), NoBlock); |
| innerChanged = outerChanged = true; |
| break; |
| } else { |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF("Not jump merging on Block #%u to Block #%u because predecessors = ", |
| blockIndex, m_graph.successor(block, 0)); |
| for (unsigned i = 0; i < m_graph.m_blocks[m_graph.successor(block, 0)]->m_predecessors.size(); ++i) { |
| if (i) |
| dataLogF(", "); |
| dataLogF("#%u", m_graph.m_blocks[m_graph.successor(block, 0)]->m_predecessors[i]); |
| } |
| dataLogF(".\n"); |
| #endif |
| } |
| |
| // FIXME: Block only has a jump -> remove. This is tricky though because of |
| // liveness. What we really want is to slam in a phantom at the end of the |
| // block, after the terminal. But we can't right now. :-( |
| // Idea: what if I slam the ghosties into my successor? Nope, that's |
| // suboptimal, because if my successor has multiple predecessors then we'll |
| // be keeping alive things on other predecessor edges unnecessarily. |
| // What we really need is the notion of end-of-block ghosties! |
| break; |
| } |
| |
| case Branch: { |
| // Branch on constant -> jettison the not-taken block and merge. |
| if (isKnownDirection(block->cfaBranchDirection)) { |
| bool condition = branchCondition(block->cfaBranchDirection); |
| BasicBlock* targetBlock = m_graph.m_blocks[ |
| m_graph.successorForCondition(block, condition)].get(); |
| if (targetBlock->m_predecessors.size() == 1) { |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF("CFGSimplify: Known condition (%s) branch merge on Block #%u to Block #%u, jettisoning Block #%u.\n", |
| condition ? "true" : "false", |
| blockIndex, m_graph.successorForCondition(block, condition), |
| m_graph.successorForCondition(block, !condition)); |
| #endif |
| if (extremeLogging) |
| m_graph.dump(); |
| mergeBlocks( |
| blockIndex, |
| m_graph.successorForCondition(block, condition), |
| m_graph.successorForCondition(block, !condition)); |
| } else { |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF("CFGSimplify: Known condition (%s) branch->jump conversion on Block #%u to Block #%u, jettisoning Block #%u.\n", |
| condition ? "true" : "false", |
| blockIndex, m_graph.successorForCondition(block, condition), |
| m_graph.successorForCondition(block, !condition)); |
| #endif |
| if (extremeLogging) |
| m_graph.dump(); |
| BlockIndex takenBlockIndex = m_graph.successorForCondition(block, condition); |
| BlockIndex notTakenBlockIndex = m_graph.successorForCondition(block, !condition); |
| |
| ASSERT(m_graph[block->last()].isTerminal()); |
| CodeOrigin boundaryCodeOrigin = m_graph[block->last()].codeOrigin; |
| m_graph[block->last()].setOpAndDefaultFlags(Phantom); |
| ASSERT(m_graph[block->last()].refCount() == 1); |
| |
| jettisonBlock(blockIndex, notTakenBlockIndex, boundaryCodeOrigin); |
| |
| block->appendNode( |
| m_graph, DontRefChildren, DontRefNode, SpecNone, Jump, |
| boundaryCodeOrigin, OpInfo(takenBlockIndex)); |
| } |
| innerChanged = outerChanged = true; |
| break; |
| } |
| |
| if (m_graph.successor(block, 0) == m_graph.successor(block, 1)) { |
| BlockIndex targetBlockIndex = m_graph.successor(block, 0); |
| BasicBlock* targetBlock = m_graph.m_blocks[targetBlockIndex].get(); |
| ASSERT(targetBlock); |
| ASSERT(targetBlock->isReachable); |
| if (targetBlock->m_predecessors.size() == 1) { |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF("CFGSimplify: Branch to same successor merge on Block #%u to Block #%u.\n", |
| blockIndex, targetBlockIndex); |
| #endif |
| mergeBlocks(blockIndex, targetBlockIndex, NoBlock); |
| } else { |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF("CFGSimplify: Branch->jump conversion to same successor on Block #%u to Block #%u.\n", |
| blockIndex, targetBlockIndex); |
| #endif |
| ASSERT(m_graph[block->last()].isTerminal()); |
| Node& branch = m_graph[block->last()]; |
| ASSERT(branch.isTerminal()); |
| ASSERT(branch.op() == Branch); |
| branch.setOpAndDefaultFlags(Phantom); |
| ASSERT(branch.refCount() == 1); |
| |
| block->appendNode( |
| m_graph, DontRefChildren, DontRefNode, SpecNone, Jump, |
| branch.codeOrigin, OpInfo(targetBlockIndex)); |
| } |
| innerChanged = outerChanged = true; |
| break; |
| } |
| |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF("Not branch simplifying on Block #%u because the successors differ and the condition is not known.\n", |
| blockIndex); |
| #endif |
| |
| // Branch to same destination -> jump. |
| // FIXME: this will currently not be hit because of the lack of jump-only |
| // block simplification. |
| |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| |
| if (innerChanged) { |
| // Here's the reason for this pass: |
| // Blocks: A, B, C, D, E, F |
| // A -> B, C |
| // B -> F |
| // C -> D, E |
| // D -> F |
| // E -> F |
| // |
| // Assume that A's branch is determined to go to B. Then the rest of this phase |
| // is smart enough to simplify down to: |
| // A -> B |
| // B -> F |
| // C -> D, E |
| // D -> F |
| // E -> F |
| // |
| // We will also merge A and B. But then we don't have any other mechanism to |
| // remove D, E as predecessors for F. Worse, the rest of this phase does not |
| // know how to fix the Phi functions of F to ensure that they no longer refer |
| // to variables in D, E. In general, we need a way to handle Phi simplification |
| // upon: |
| // 1) Removal of a predecessor due to branch simplification. The branch |
| // simplifier already does that. |
| // 2) Invalidation of a predecessor because said predecessor was rendered |
| // unreachable. We do this here. |
| // |
| // This implies that when a block is unreachable, we must inspect its |
| // successors' Phi functions to remove any references from them into the |
| // removed block. |
| |
| m_graph.resetReachability(); |
| |
| for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) { |
| BasicBlock* block = m_graph.m_blocks[blockIndex].get(); |
| if (!block) |
| continue; |
| if (block->isReachable) |
| continue; |
| |
| killUnreachable(blockIndex); |
| } |
| } |
| |
| validate(m_graph); |
| } while (innerChanged); |
| |
| return outerChanged; |
| } |
| |
| private: |
| void killUnreachable(BlockIndex blockIndex) |
| { |
| BasicBlock* block = m_graph.m_blocks[blockIndex].get(); |
| |
| ASSERT(block); |
| ASSERT(!block->isReachable); |
| |
| // 1) Remove references from other blocks to this block. |
| for (unsigned i = m_graph.numSuccessors(block); i--;) |
| fixPhis(blockIndex, m_graph.successor(block, i)); |
| |
| // 2) Kill the block |
| m_graph.m_blocks[blockIndex].clear(); |
| } |
| |
| void keepOperandAlive(BasicBlock* block, CodeOrigin codeOrigin, int operand) |
| { |
| NodeIndex nodeIndex = block->variablesAtTail.operand(operand); |
| if (nodeIndex == NoNode) |
| return; |
| if (m_graph[nodeIndex].variableAccessData()->isCaptured()) |
| return; |
| if (m_graph[nodeIndex].op() == SetLocal) |
| nodeIndex = m_graph[nodeIndex].child1().index(); |
| Node& node = m_graph[nodeIndex]; |
| if (!node.shouldGenerate()) |
| return; |
| ASSERT(m_graph[nodeIndex].op() != SetLocal); |
| block->appendNode( |
| m_graph, RefChildren, DontRefNode, SpecNone, Phantom, codeOrigin, nodeIndex); |
| } |
| |
| void fixPossibleGetLocal(BasicBlock* block, Edge& edge, bool changeRef) |
| { |
| Node& child = m_graph[edge]; |
| if (child.op() != GetLocal) |
| return; |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Considering GetLocal at @%u, local r%d.\n", edge.index(), child.local()); |
| #endif |
| if (child.variableAccessData()->isCaptured()) { |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" It's captured.\n"); |
| #endif |
| return; |
| } |
| NodeIndex originalNodeIndex = block->variablesAtTail.operand(child.local()); |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Dealing with original @%u.\n", originalNodeIndex); |
| #endif |
| ASSERT(originalNodeIndex != NoNode); |
| Node* originalNode = &m_graph[originalNodeIndex]; |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Original has local r%d.\n", originalNode->local()); |
| #endif |
| ASSERT(child.local() == originalNode->local()); |
| // Possibilities: |
| // SetLocal -> the secondBlock is getting the value of something that is immediately |
| // available in the first block with a known NodeIndex. |
| // GetLocal -> the secondBlock is getting the value of something that the first |
| // block also gets. |
| // Phi -> the secondBlock is asking for keep-alive on an operand that the first block |
| // was also asking for keep-alive on. |
| // SetArgument -> the secondBlock is asking for keep-alive on an operand that the |
| // first block was keeping alive by virtue of the firstBlock being the root and |
| // the operand being an argument. |
| // Flush -> the secondBlock is asking for keep-alive on an operand that the first |
| // block was forcing to be alive, so the second block should refer child of |
| // the flush. |
| if (originalNode->op() == Flush) { |
| originalNodeIndex = originalNode->child1().index(); |
| originalNode = &m_graph[originalNodeIndex]; |
| } |
| switch (originalNode->op()) { |
| case SetLocal: { |
| if (changeRef) |
| ASSERT(originalNode->shouldGenerate()); |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" It's a SetLocal.\n"); |
| #endif |
| m_graph.changeIndex(edge, originalNode->child1().index(), changeRef); |
| break; |
| } |
| case GetLocal: { |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" It's a GetLocal.\n"); |
| #endif |
| if (originalNode->shouldGenerate()) |
| m_graph.changeIndex(edge, originalNodeIndex, changeRef); |
| // If we have a GetLocal that points to a child GetLocal that is dead, then |
| // we have no need to do anything: this original GetLocal is still valid. |
| break; |
| } |
| case Phi: |
| case SetArgument: { |
| if (changeRef) |
| ASSERT(originalNode->shouldGenerate()); |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" It's Phi/SetArgument.\n"); |
| #endif |
| // Keep the GetLocal! |
| break; |
| } |
| default: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| } |
| |
| void jettisonBlock(BlockIndex blockIndex, BlockIndex jettisonedBlockIndex, CodeOrigin boundaryCodeOrigin) |
| { |
| BasicBlock* block = m_graph.m_blocks[blockIndex].get(); |
| BasicBlock* jettisonedBlock = m_graph.m_blocks[jettisonedBlockIndex].get(); |
| |
| for (size_t i = 0; i < jettisonedBlock->variablesAtHead.numberOfArguments(); ++i) |
| keepOperandAlive(block, boundaryCodeOrigin, argumentToOperand(i)); |
| for (size_t i = 0; i < jettisonedBlock->variablesAtHead.numberOfLocals(); ++i) |
| keepOperandAlive(block, boundaryCodeOrigin, i); |
| |
| fixJettisonedPredecessors(blockIndex, jettisonedBlockIndex); |
| } |
| |
| void fixPhis(BlockIndex sourceBlockIndex, BlockIndex destinationBlockIndex) |
| { |
| BasicBlock* sourceBlock = m_graph.m_blocks[sourceBlockIndex].get(); |
| BasicBlock* destinationBlock = m_graph.m_blocks[destinationBlockIndex].get(); |
| if (!destinationBlock) { |
| // If we're trying to kill off the source block and the destination block is already |
| // dead, then we're done! |
| return; |
| } |
| for (size_t i = 0; i < destinationBlock->phis.size(); ++i) { |
| NodeIndex phiNodeIndex = destinationBlock->phis[i]; |
| Node& phiNode = m_graph[phiNodeIndex]; |
| NodeIndex myNodeIndex = sourceBlock->variablesAtTail.operand(phiNode.local()); |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF("Considering removing reference from phi @%u to @%u on local r%d:", |
| phiNodeIndex, myNodeIndex, phiNode.local()); |
| #endif |
| if (myNodeIndex == NoNode) { |
| // This will happen if there is a phi in the destination that refers into |
| // the destination itself. |
| continue; |
| } |
| Node& myNode = m_graph[myNodeIndex]; |
| if (myNode.op() == GetLocal) |
| myNodeIndex = myNode.child1().index(); |
| for (unsigned j = 0; j < AdjacencyList::Size; ++j) |
| removePotentiallyDeadPhiReference(myNodeIndex, phiNode, j, sourceBlock->isReachable); |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF("\n"); |
| #endif |
| } |
| } |
| |
| void fixJettisonedPredecessors(BlockIndex blockIndex, BlockIndex jettisonedBlockIndex) |
| { |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF("Fixing predecessors and phis due to jettison of Block #%u from Block #%u.\n", |
| jettisonedBlockIndex, blockIndex); |
| #endif |
| BasicBlock* jettisonedBlock = m_graph.m_blocks[jettisonedBlockIndex].get(); |
| for (unsigned i = 0; i < jettisonedBlock->m_predecessors.size(); ++i) { |
| if (jettisonedBlock->m_predecessors[i] != blockIndex) |
| continue; |
| jettisonedBlock->m_predecessors[i] = jettisonedBlock->m_predecessors.last(); |
| jettisonedBlock->m_predecessors.removeLast(); |
| break; |
| } |
| |
| fixPhis(blockIndex, jettisonedBlockIndex); |
| } |
| |
| void removePotentiallyDeadPhiReference(NodeIndex myNodeIndex, Node& phiNode, unsigned edgeIndex, bool changeRef) |
| { |
| if (phiNode.children.child(edgeIndex).indexUnchecked() != myNodeIndex) |
| return; |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Removing reference at child %u.", edgeIndex); |
| #endif |
| if (changeRef && phiNode.shouldGenerate()) |
| m_graph.deref(myNodeIndex); |
| phiNode.children.removeEdgeFromBag(edgeIndex); |
| } |
| |
| struct OperandSubstitution { |
| OperandSubstitution() |
| : oldChild(NoNode) |
| , newChild(NoNode) |
| { |
| } |
| |
| explicit OperandSubstitution(NodeIndex oldChild) |
| : oldChild(oldChild) |
| , newChild(oldChild) |
| { |
| } |
| |
| OperandSubstitution(NodeIndex oldChild, NodeIndex newChild) |
| : oldChild(oldChild) |
| , newChild(newChild) |
| { |
| ASSERT((oldChild == NoNode) == (newChild == NoNode)); |
| } |
| |
| void dump(FILE* out) |
| { |
| if (oldChild == NoNode) |
| fprintf(out, "-"); |
| else |
| fprintf(out, "@%u -> @%u", oldChild, newChild); |
| } |
| |
| NodeIndex oldChild; |
| NodeIndex newChild; |
| }; |
| |
| NodeIndex skipGetLocal(NodeIndex nodeIndex) |
| { |
| if (nodeIndex == NoNode) |
| return NoNode; |
| Node& node = m_graph[nodeIndex]; |
| if (node.op() == GetLocal) |
| return node.child1().index(); |
| return nodeIndex; |
| } |
| |
| void recordPossibleIncomingReference( |
| BasicBlock* secondBlock, Operands<OperandSubstitution>& substitutions, int operand) |
| { |
| substitutions.operand(operand) = OperandSubstitution( |
| skipGetLocal(secondBlock->variablesAtTail.operand(operand))); |
| } |
| |
| void recordNewTarget(Operands<OperandSubstitution>& substitutions, int operand, NodeIndex nodeIndex) |
| { |
| ASSERT(m_graph[nodeIndex].op() == SetLocal |
| || m_graph[nodeIndex].op() == SetArgument |
| || m_graph[nodeIndex].op() == Flush |
| || m_graph[nodeIndex].op() == Phi); |
| substitutions.operand(operand).newChild = nodeIndex; |
| } |
| |
| void fixTailOperand( |
| BasicBlock* firstBlock, BasicBlock* secondBlock, int operand, |
| Operands<OperandSubstitution>& substitutions) |
| { |
| NodeIndex atSecondTail = secondBlock->variablesAtTail.operand(operand); |
| |
| if (atSecondTail == NoNode) { |
| // If the variable is dead at the end of the second block, then do nothing; essentially |
| // this means that we want the tail state to reflect whatever the first block did. |
| return; |
| } |
| |
| Node& secondNode = m_graph[atSecondTail]; |
| |
| switch (secondNode.op()) { |
| case SetLocal: |
| case Flush: { |
| // The second block did interesting things to the variables, so update the tail |
| // accordingly. |
| firstBlock->variablesAtTail.operand(operand) = atSecondTail; |
| break; |
| } |
| |
| case Phi: { |
| // Keep what was in the first block. |
| ASSERT(firstBlock->variablesAtTail.operand(operand) != NoNode); |
| recordNewTarget(substitutions, operand, skipGetLocal(firstBlock->variablesAtTail.operand(operand))); |
| break; |
| } |
| |
| case GetLocal: { |
| // If it's a GetLocal on a captured var, then definitely keep what was |
| // in the second block. In particular, it's possible that the first |
| // block doesn't even know about this variable. |
| if (secondNode.variableAccessData()->isCaptured()) { |
| firstBlock->variablesAtTail.operand(operand) = atSecondTail; |
| recordNewTarget(substitutions, operand, secondNode.child1().index()); |
| break; |
| } |
| |
| // It's possible that the second block had a GetLocal and the first block |
| // had a SetArgument or a Phi. Then update the tail. Otherwise keep what was in the |
| // first block. |
| NodeIndex atFirstTail = firstBlock->variablesAtTail.operand(operand); |
| ASSERT(atFirstTail != NoNode); |
| switch (m_graph[atFirstTail].op()) { |
| case SetArgument: |
| case Phi: |
| firstBlock->variablesAtTail.operand(operand) = atSecondTail; |
| recordNewTarget(substitutions, operand, secondNode.child1().index()); |
| break; |
| |
| default: |
| // Keep what was in the first block, and adjust the substitution to account for |
| // the fact that successors will refer to the child of the GetLocal. |
| ASSERT(firstBlock->variablesAtTail.operand(operand) != NoNode); |
| recordNewTarget(substitutions, operand, skipGetLocal(firstBlock->variablesAtTail.operand(operand))); |
| break; |
| } |
| break; |
| } |
| |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| void mergeBlocks( |
| BlockIndex firstBlockIndex, BlockIndex secondBlockIndex, BlockIndex jettisonedBlockIndex) |
| { |
| // This will add all of the nodes in secondBlock to firstBlock, but in so doing |
| // it will also ensure that any GetLocals from the second block that refer to |
| // SetLocals in the first block are relinked. If jettisonedBlock is not NoBlock, |
| // then Phantoms are inserted for anything that the jettisonedBlock would have |
| // kept alive. |
| |
| BasicBlock* firstBlock = m_graph.m_blocks[firstBlockIndex].get(); |
| BasicBlock* secondBlock = m_graph.m_blocks[secondBlockIndex].get(); |
| |
| // Remove the terminal of firstBlock since we don't need it anymore. Well, we don't |
| // really remove it; we actually turn it into a Phantom. |
| ASSERT(m_graph[firstBlock->last()].isTerminal()); |
| CodeOrigin boundaryCodeOrigin = m_graph[firstBlock->last()].codeOrigin; |
| m_graph[firstBlock->last()].setOpAndDefaultFlags(Phantom); |
| ASSERT(m_graph[firstBlock->last()].refCount() == 1); |
| |
| if (jettisonedBlockIndex != NoBlock) { |
| BasicBlock* jettisonedBlock = m_graph.m_blocks[jettisonedBlockIndex].get(); |
| |
| // Time to insert ghosties for things that need to be kept alive in case we OSR |
| // exit prior to hitting the firstBlock's terminal, and end up going down a |
| // different path than secondBlock. |
| |
| for (size_t i = 0; i < jettisonedBlock->variablesAtHead.numberOfArguments(); ++i) |
| keepOperandAlive(firstBlock, boundaryCodeOrigin, argumentToOperand(i)); |
| for (size_t i = 0; i < jettisonedBlock->variablesAtHead.numberOfLocals(); ++i) |
| keepOperandAlive(firstBlock, boundaryCodeOrigin, i); |
| } |
| |
| for (size_t i = 0; i < secondBlock->phis.size(); ++i) |
| firstBlock->phis.append(secondBlock->phis[i]); |
| |
| // Before we start changing the second block's graph, record what nodes would |
| // be referenced by successors of the second block. |
| Operands<OperandSubstitution> substitutions( |
| secondBlock->variablesAtTail.numberOfArguments(), |
| secondBlock->variablesAtTail.numberOfLocals()); |
| for (size_t i = 0; i < secondBlock->variablesAtTail.numberOfArguments(); ++i) |
| recordPossibleIncomingReference(secondBlock, substitutions, argumentToOperand(i)); |
| for (size_t i = 0; i < secondBlock->variablesAtTail.numberOfLocals(); ++i) |
| recordPossibleIncomingReference(secondBlock, substitutions, i); |
| |
| for (size_t i = 0; i < secondBlock->size(); ++i) { |
| NodeIndex nodeIndex = secondBlock->at(i); |
| Node& node = m_graph[nodeIndex]; |
| |
| bool childrenAlreadyFixed = false; |
| |
| switch (node.op()) { |
| case Phantom: { |
| if (!node.child1()) |
| break; |
| |
| ASSERT(node.shouldGenerate()); |
| Node& possibleLocalOp = m_graph[node.child1()]; |
| if (possibleLocalOp.op() != GetLocal |
| && possibleLocalOp.hasLocal() |
| && !possibleLocalOp.variableAccessData()->isCaptured()) { |
| NodeIndex setLocalIndex = |
| firstBlock->variablesAtTail.operand(possibleLocalOp.local()); |
| Node& setLocal = m_graph[setLocalIndex]; |
| if (setLocal.op() == SetLocal) { |
| m_graph.changeEdge(node.children.child1(), setLocal.child1()); |
| ASSERT(!node.child2()); |
| ASSERT(!node.child3()); |
| childrenAlreadyFixed = true; |
| } |
| } |
| break; |
| } |
| |
| case Flush: |
| case GetLocal: { |
| // A Flush could use a GetLocal, SetLocal, SetArgument, or a Phi. |
| // If it uses a GetLocal, it'll be taken care of below. If it uses a |
| // SetLocal or SetArgument, then it must be using a node from the |
| // same block. But if it uses a Phi, then we should redirect it to |
| // use whatever the first block advertised as a tail operand. |
| // Similarly for GetLocal; it could use any of those except for |
| // GetLocal. If it uses a Phi then it should be redirected to use a |
| // Phi from the tail operand. |
| if (m_graph[node.child1()].op() != Phi) |
| break; |
| |
| NodeIndex atFirstIndex = firstBlock->variablesAtTail.operand(node.local()); |
| m_graph.changeEdge(node.children.child1(), Edge(skipGetLocal(atFirstIndex)), node.shouldGenerate()); |
| childrenAlreadyFixed = true; |
| break; |
| } |
| |
| default: |
| break; |
| } |
| |
| if (!childrenAlreadyFixed) { |
| bool changeRef = node.shouldGenerate(); |
| |
| // If the child is a GetLocal, then we might like to fix it. |
| if (node.flags() & NodeHasVarArgs) { |
| for (unsigned childIdx = node.firstChild(); |
| childIdx < node.firstChild() + node.numChildren(); |
| ++childIdx) { |
| if (!!m_graph.m_varArgChildren[childIdx]) |
| fixPossibleGetLocal(firstBlock, m_graph.m_varArgChildren[childIdx], changeRef); |
| } |
| } else if (!!node.child1()) { |
| fixPossibleGetLocal(firstBlock, node.children.child1(), changeRef); |
| if (!!node.child2()) { |
| fixPossibleGetLocal(firstBlock, node.children.child2(), changeRef); |
| if (!!node.child3()) |
| fixPossibleGetLocal(firstBlock, node.children.child3(), changeRef); |
| } |
| } |
| } |
| |
| firstBlock->append(nodeIndex); |
| } |
| |
| ASSERT(m_graph[firstBlock->last()].isTerminal()); |
| |
| // Fix the predecessors of my new successors. This is tricky, since we are going to reset |
| // all predecessors anyway due to reachability analysis. But we need to fix the |
| // predecessors eagerly to ensure that we know what they are in case the next block we |
| // consider in this phase wishes to query the predecessors of one of the blocks we |
| // affected. |
| for (unsigned i = m_graph.numSuccessors(firstBlock); i--;) { |
| BasicBlock* successor = m_graph.m_blocks[m_graph.successor(firstBlock, i)].get(); |
| for (unsigned j = 0; j < successor->m_predecessors.size(); ++j) { |
| if (successor->m_predecessors[j] == secondBlockIndex) |
| successor->m_predecessors[j] = firstBlockIndex; |
| } |
| } |
| |
| // Fix the predecessors of my former successors. Again, we'd rather not do this, but it's |
| // an unfortunate necessity. See above comment. |
| if (jettisonedBlockIndex != NoBlock) |
| fixJettisonedPredecessors(firstBlockIndex, jettisonedBlockIndex); |
| |
| // Fix up the variables at tail. |
| for (size_t i = 0; i < secondBlock->variablesAtHead.numberOfArguments(); ++i) |
| fixTailOperand(firstBlock, secondBlock, argumentToOperand(i), substitutions); |
| for (size_t i = 0; i < secondBlock->variablesAtHead.numberOfLocals(); ++i) |
| fixTailOperand(firstBlock, secondBlock, i, substitutions); |
| |
| // Fix up the references from our new successors. |
| for (unsigned i = m_graph.numSuccessors(firstBlock); i--;) { |
| BasicBlock* successor = m_graph.m_blocks[m_graph.successor(firstBlock, i)].get(); |
| for (unsigned j = 0; j < successor->phis.size(); ++j) { |
| NodeIndex phiNodeIndex = successor->phis[j]; |
| Node& phiNode = m_graph[phiNodeIndex]; |
| bool changeRef = phiNode.shouldGenerate(); |
| OperandSubstitution substitution = substitutions.operand(phiNode.local()); |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Performing operand substitution @%u -> @%u.\n", |
| substitution.oldChild, substitution.newChild); |
| #endif |
| if (!phiNode.child1()) |
| continue; |
| if (phiNode.child1().index() == substitution.oldChild) |
| m_graph.changeIndex(phiNode.children.child1(), substitution.newChild, changeRef); |
| if (!phiNode.child2()) |
| continue; |
| if (phiNode.child2().index() == substitution.oldChild) |
| m_graph.changeIndex(phiNode.children.child2(), substitution.newChild, changeRef); |
| if (!phiNode.child3()) |
| continue; |
| if (phiNode.child3().index() == substitution.oldChild) |
| m_graph.changeIndex(phiNode.children.child3(), substitution.newChild, changeRef); |
| } |
| } |
| |
| firstBlock->valuesAtTail = secondBlock->valuesAtTail; |
| firstBlock->cfaBranchDirection = secondBlock->cfaBranchDirection; |
| |
| m_graph.m_blocks[secondBlockIndex].clear(); |
| } |
| }; |
| |
| bool performCFGSimplification(Graph& graph) |
| { |
| SamplingRegion samplingRegion("DFG CFG Simplification Phase"); |
| return runPhase<CFGSimplificationPhase>(graph); |
| } |
| |
| } } // namespace JSC::DFG |
| |
| #endif // ENABLE(DFG_JIT) |
| |
| |