/* * 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 "DFGBasicBlock.h" #include "DFGGraph.h" #include "DFGInsertionSet.h" #include "DFGPhase.h" #include "DFGValidate.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); NodeIndex jumpNodeIndex = m_graph.size(); Node jump(Jump, boundaryCodeOrigin, OpInfo(takenBlockIndex)); jump.ref(); m_graph.append(jump); block->append(jumpNodeIndex); } 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); Node jump(Jump, branch.codeOrigin, OpInfo(targetBlockIndex)); jump.ref(); NodeIndex jumpNodeIndex = m_graph.size(); m_graph.append(jump); block->append(jumpNodeIndex); } 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); NodeIndex phantomNodeIndex = m_graph.size(); Node phantom(Phantom, codeOrigin, nodeIndex); m_graph.append(phantom); m_graph.ref(phantomNodeIndex); block->append(phantomNodeIndex); } 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& substitutions, int operand) { substitutions.operand(operand) = OperandSubstitution( skipGetLocal(secondBlock->variablesAtTail.operand(operand))); } void recordNewTarget(Operands& 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& 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 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(graph); } } } // namespace JSC::DFG #endif // ENABLE(DFG_JIT)