blob: 52cebf80f15d18591626a592ddb4769b66af5059 [file] [log] [blame]
/*
* 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<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)