/*
- * Copyright (C) 2011 Apple Inc. All rights reserved.
+ * Copyright (C) 2011, 2012, 2013, 2014 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
#if ENABLE(DFG_JIT)
+#include "DFGAbstractHeap.h"
+#include "DFGClobberize.h"
+#include "DFGEdgeUsesStructure.h"
#include "DFGGraph.h"
#include "DFGPhase.h"
+#include "JSCInlines.h"
+#include <array>
+#include <wtf/FastBitVector.h>
namespace JSC { namespace DFG {
+enum CSEMode { NormalCSE, StoreElimination };
+
+template<CSEMode cseMode>
class CSEPhase : public Phase {
public:
CSEPhase(Graph& graph)
- : Phase(graph, "common subexpression elimination")
- {
- // Replacements are used to implement local common subexpression elimination.
- m_replacements.resize(m_graph.size());
-
- for (unsigned i = 0; i < m_graph.size(); ++i)
- m_replacements[i] = NoNode;
- }
-
- void run()
+ : Phase(graph, cseMode == NormalCSE ? "common subexpression elimination" : "store elimination")
{
- for (unsigned block = 0; block < m_graph.m_blocks.size(); ++block)
- performBlockCSE(*m_graph.m_blocks[block]);
}
-private:
-
- NodeIndex canonicalize(NodeIndex nodeIndex)
+ bool run()
{
- if (nodeIndex == NoNode)
- return NoNode;
+ ASSERT(m_graph.m_fixpointState != BeforeFixpoint);
- if (m_graph[nodeIndex].op() == ValueToInt32)
- nodeIndex = m_graph[nodeIndex].child1().index();
+ m_changed = false;
- return nodeIndex;
- }
- NodeIndex canonicalize(Edge nodeUse)
- {
- return canonicalize(nodeUse.indexUnchecked());
+ m_graph.clearReplacements();
+
+ for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) {
+ BasicBlock* block = m_graph.block(blockIndex);
+ if (!block)
+ continue;
+
+ // All Phis need to already be marked as relevant to OSR.
+ if (!ASSERT_DISABLED) {
+ for (unsigned i = 0; i < block->phis.size(); ++i)
+ ASSERT(block->phis[i]->flags() & NodeRelevantToOSR);
+ }
+
+ for (unsigned i = block->size(); i--;) {
+ Node* node = block->at(i);
+
+ switch (node->op()) {
+ case SetLocal:
+ case GetLocal: // FIXME: The GetLocal case is only necessary until we do https://bugs.webkit.org/show_bug.cgi?id=106707.
+ node->mergeFlags(NodeRelevantToOSR);
+ break;
+ default:
+ node->clearFlags(NodeRelevantToOSR);
+ break;
+ }
+ }
+ }
+
+ for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) {
+ BasicBlock* block = m_graph.block(blockIndex);
+ if (!block)
+ continue;
+
+ for (unsigned i = block->size(); i--;) {
+ Node* node = block->at(i);
+ if (!node->containsMovHint())
+ continue;
+
+ ASSERT(node->op() != ZombieHint);
+ node->child1()->mergeFlags(NodeRelevantToOSR);
+ }
+ }
+
+ if (m_graph.m_form == SSA) {
+ Vector<BasicBlock*> depthFirst;
+ m_graph.getBlocksInDepthFirstOrder(depthFirst);
+ for (unsigned i = 0; i < depthFirst.size(); ++i)
+ performBlockCSE(depthFirst[i]);
+ } else {
+ for (unsigned blockIndex = 0; blockIndex < m_graph.numBlocks(); ++blockIndex)
+ performBlockCSE(m_graph.block(blockIndex));
+ }
+
+ return m_changed;
}
+private:
+
unsigned endIndexForPureCSE()
{
- unsigned result = m_lastSeen[m_graph[m_compileIndex].op()];
+ unsigned result = m_lastSeen[m_currentNode->op()];
if (result == UINT_MAX)
result = 0;
else
result++;
ASSERT(result <= m_indexInBlock);
-#if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE)
- dataLog(" limit %u: ", result);
-#endif
return result;
}
-
- NodeIndex pureCSE(Node& node)
+
+ Node* pureCSE(Node* node)
{
- NodeIndex child1 = canonicalize(node.child1());
- NodeIndex child2 = canonicalize(node.child2());
- NodeIndex child3 = canonicalize(node.child3());
+ Edge child1 = node->child1().sanitized();
+ Edge child2 = node->child2().sanitized();
+ Edge child3 = node->child3().sanitized();
for (unsigned i = endIndexForPureCSE(); i--;) {
- NodeIndex index = m_currentBlock->at(i);
- if (index == child1 || index == child2 || index == child3)
+ Node* otherNode = m_currentBlock->at(i);
+ if (otherNode == child1 || otherNode == child2 || otherNode == child3)
break;
- Node& otherNode = m_graph[index];
- if (node.op() != otherNode.op())
- continue;
-
- if (node.arithNodeFlags() != otherNode.arithNodeFlags())
+ if (node->op() != otherNode->op())
continue;
- NodeIndex otherChild = canonicalize(otherNode.child1());
- if (otherChild == NoNode)
- return index;
+ Edge otherChild = otherNode->child1().sanitized();
+ if (!otherChild)
+ return otherNode;
if (otherChild != child1)
continue;
- otherChild = canonicalize(otherNode.child2());
- if (otherChild == NoNode)
- return index;
+ otherChild = otherNode->child2().sanitized();
+ if (!otherChild)
+ return otherNode;
if (otherChild != child2)
continue;
- otherChild = canonicalize(otherNode.child3());
- if (otherChild == NoNode)
- return index;
+ otherChild = otherNode->child3().sanitized();
+ if (!otherChild)
+ return otherNode;
if (otherChild != child3)
continue;
- return index;
+ return otherNode;
}
- return NoNode;
+ return 0;
}
- bool isPredictedNumerical(Node& node)
+ Node* constantCSE(Node* node)
{
- PredictedType left = m_graph[node.child1()].prediction();
- PredictedType right = m_graph[node.child2()].prediction();
- return isNumberPrediction(left) && isNumberPrediction(right);
+ for (unsigned i = endIndexForPureCSE(); i--;) {
+ Node* otherNode = m_currentBlock->at(i);
+ if (otherNode->op() != node->op())
+ continue;
+
+ if (otherNode->constantNumber() != node->constantNumber())
+ continue;
+
+ return otherNode;
+ }
+ return 0;
}
- bool logicalNotIsPure(Node& node)
+ Node* weakConstantCSE(Node* node)
{
- PredictedType prediction = m_graph[node.child1()].prediction();
- return isBooleanPrediction(prediction) || !prediction;
+ for (unsigned i = endIndexForPureCSE(); i--;) {
+ Node* otherNode = m_currentBlock->at(i);
+ if (otherNode->op() != WeakJSConstant)
+ continue;
+
+ if (otherNode->weakConstant() != node->weakConstant())
+ continue;
+
+ return otherNode;
+ }
+ return 0;
}
- bool byValIsPure(Node& node)
+ Node* constantStoragePointerCSE(Node* node)
{
- return m_graph[node.child2()].shouldSpeculateInteger()
- && ((node.op() == PutByVal || node.op() == PutByValAlias)
- ? isActionableMutableArrayPrediction(m_graph[node.child1()].prediction())
- : isActionableArrayPrediction(m_graph[node.child1()].prediction()));
+ for (unsigned i = endIndexForPureCSE(); i--;) {
+ Node* otherNode = m_currentBlock->at(i);
+ if (otherNode->op() != ConstantStoragePointer)
+ continue;
+
+ if (otherNode->storagePointer() != node->storagePointer())
+ continue;
+
+ return otherNode;
+ }
+ return 0;
}
- bool clobbersWorld(NodeIndex nodeIndex)
+ Node* getCalleeLoadElimination()
{
- Node& node = m_graph[nodeIndex];
- if (node.flags() & NodeClobbersWorld)
- return true;
- if (!(node.flags() & NodeMightClobber))
- return false;
- switch (node.op()) {
- case ValueAdd:
- case CompareLess:
- case CompareLessEq:
- case CompareGreater:
- case CompareGreaterEq:
- case CompareEq:
- return !isPredictedNumerical(node);
- case LogicalNot:
- return !logicalNotIsPure(node);
- case GetByVal:
- return !byValIsPure(node);
- default:
- ASSERT_NOT_REACHED();
- return true; // If by some oddity we hit this case in release build it's safer to have CSE assume the worst.
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ switch (node->op()) {
+ case GetCallee:
+ return node;
+ default:
+ break;
+ }
}
+ return 0;
}
- NodeIndex impureCSE(Node& node)
+ Node* getArrayLengthElimination(Node* array)
{
- NodeIndex child1 = canonicalize(node.child1());
- NodeIndex child2 = canonicalize(node.child2());
- NodeIndex child3 = canonicalize(node.child3());
-
for (unsigned i = m_indexInBlock; i--;) {
- NodeIndex index = m_currentBlock->at(i);
- if (index == child1 || index == child2 || index == child3)
- break;
-
- Node& otherNode = m_graph[index];
- if (node.op() == otherNode.op()
- && node.arithNodeFlags() == otherNode.arithNodeFlags()) {
- NodeIndex otherChild = canonicalize(otherNode.child1());
- if (otherChild == NoNode)
- return index;
- if (otherChild == child1) {
- otherChild = canonicalize(otherNode.child2());
- if (otherChild == NoNode)
- return index;
- if (otherChild == child2) {
- otherChild = canonicalize(otherNode.child3());
- if (otherChild == NoNode)
- return index;
- if (otherChild == child3)
- return index;
- }
- }
- }
- if (clobbersWorld(index))
+ Node* node = m_currentBlock->at(i);
+ switch (node->op()) {
+ case GetArrayLength:
+ if (node->child1() == array)
+ return node;
+ break;
+
+ case PutByValDirect:
+ case PutByVal:
+ if (!m_graph.byValIsPure(node))
+ return 0;
+ if (node->arrayMode().mayStoreToHole())
+ return 0;
break;
+
+ default:
+ if (m_graph.clobbersWorld(node))
+ return 0;
+ }
}
- return NoNode;
+ return 0;
}
- NodeIndex globalVarLoadElimination(unsigned varNumber, JSGlobalObject* globalObject)
+ Node* globalVarLoadElimination(WriteBarrier<Unknown>* registerPointer)
{
for (unsigned i = m_indexInBlock; i--;) {
- NodeIndex index = m_currentBlock->at(i);
- Node& node = m_graph[index];
- switch (node.op()) {
+ Node* node = m_currentBlock->at(i);
+ switch (node->op()) {
case GetGlobalVar:
- if (node.varNumber() == varNumber && codeBlock()->globalObjectFor(node.codeOrigin) == globalObject)
- return index;
+ if (node->registerPointer() == registerPointer)
+ return node;
+ break;
+ case PutGlobalVar:
+ if (node->registerPointer() == registerPointer)
+ return node->child1().node();
+ break;
+ default:
+ break;
+ }
+ if (m_graph.clobbersWorld(node))
+ break;
+ }
+ return 0;
+ }
+
+ Node* scopedVarLoadElimination(Node* registers, int varNumber)
+ {
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ switch (node->op()) {
+ case GetClosureVar: {
+ if (node->child1() == registers && node->varNumber() == varNumber)
+ return node;
+ break;
+ }
+ case PutClosureVar: {
+ if (node->varNumber() != varNumber)
+ break;
+ if (node->child2() == registers)
+ return node->child3().node();
+ return 0;
+ }
+ case SetLocal: {
+ VariableAccessData* variableAccessData = node->variableAccessData();
+ if (variableAccessData->isCaptured()
+ && variableAccessData->local() == static_cast<VirtualRegister>(varNumber))
+ return 0;
+ break;
+ }
+ default:
+ break;
+ }
+ if (m_graph.clobbersWorld(node))
+ break;
+ }
+ return 0;
+ }
+
+ bool varInjectionWatchpointElimination()
+ {
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ if (node->op() == VarInjectionWatchpoint)
+ return true;
+ if (m_graph.clobbersWorld(node))
break;
+ }
+ return false;
+ }
+
+ Node* globalVarStoreElimination(WriteBarrier<Unknown>* registerPointer)
+ {
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ switch (node->op()) {
case PutGlobalVar:
- if (node.varNumber() == varNumber && codeBlock()->globalObjectFor(node.codeOrigin) == globalObject)
- return node.child1().index();
+ if (node->registerPointer() == registerPointer)
+ return node;
break;
+
+ case GetGlobalVar:
+ if (node->registerPointer() == registerPointer)
+ return 0;
+ break;
+
default:
break;
}
- if (clobbersWorld(index))
+ if (m_graph.clobbersWorld(node) || node->canExit())
+ return 0;
+ }
+ return 0;
+ }
+
+ Node* scopedVarStoreElimination(Node* scope, Node* registers, int varNumber)
+ {
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ switch (node->op()) {
+ case PutClosureVar: {
+ if (node->varNumber() != varNumber)
+ break;
+ if (node->child1() == scope && node->child2() == registers)
+ return node;
+ return 0;
+ }
+
+ case GetClosureVar: {
+ // Let's be conservative.
+ if (node->varNumber() == varNumber)
+ return 0;
break;
+ }
+
+ case GetLocal:
+ case SetLocal: {
+ VariableAccessData* variableAccessData = node->variableAccessData();
+ if (variableAccessData->isCaptured()
+ && variableAccessData->local() == static_cast<VirtualRegister>(varNumber))
+ return 0;
+ break;
+ }
+
+ default:
+ break;
+ }
+ if (m_graph.clobbersWorld(node) || node->canExit())
+ return 0;
}
- return NoNode;
+ return 0;
}
- NodeIndex getByValLoadElimination(NodeIndex child1, NodeIndex child2)
+ Node* getByValLoadElimination(Node* child1, Node* child2, ArrayMode arrayMode)
{
for (unsigned i = m_indexInBlock; i--;) {
- NodeIndex index = m_currentBlock->at(i);
- if (index == child1 || index == canonicalize(child2))
+ Node* node = m_currentBlock->at(i);
+ if (node == child1 || node == child2)
break;
- Node& node = m_graph[index];
- switch (node.op()) {
+ switch (node->op()) {
case GetByVal:
- if (!byValIsPure(node))
- return NoNode;
- if (node.child1() == child1 && canonicalize(node.child2()) == canonicalize(child2))
- return index;
+ if (!m_graph.byValIsPure(node))
+ return 0;
+ if (node->child1() == child1
+ && node->child2() == child2
+ && node->arrayMode().type() == arrayMode.type())
+ return node;
break;
+
+ case PutByValDirect:
case PutByVal:
- case PutByValAlias:
- if (!byValIsPure(node))
- return NoNode;
- if (node.child1() == child1 && canonicalize(node.child2()) == canonicalize(child2))
- return node.child3().index();
+ case PutByValAlias: {
+ if (!m_graph.byValIsPure(node))
+ return 0;
+ // Typed arrays
+ if (arrayMode.typedArrayType() != NotTypedArray)
+ return 0;
+ if (m_graph.varArgChild(node, 0) == child1
+ && m_graph.varArgChild(node, 1) == child2
+ && node->arrayMode().type() == arrayMode.type())
+ return m_graph.varArgChild(node, 2).node();
// We must assume that the PutByVal will clobber the location we're getting from.
// FIXME: We can do better; if we know that the PutByVal is accessing an array of a
// different type than the GetByVal, then we know that they won't clobber each other.
- return NoNode;
- case PutStructure:
- case PutByOffset:
- // GetByVal currently always speculates that it's accessing an
- // array with an integer index, which means that it's impossible
- // for a structure change or a put to property storage to affect
- // the GetByVal.
- break;
- case ArrayPush:
- // A push cannot affect previously existing elements in the array.
- break;
+ // ... except of course for typed arrays, where all typed arrays clobber all other
+ // typed arrays! An Int32Array can alias a Float64Array for example, and so on.
+ return 0;
+ }
default:
- if (clobbersWorld(index))
- return NoNode;
+ if (m_graph.clobbersWorld(node))
+ return 0;
break;
}
}
- return NoNode;
+ return 0;
}
- bool checkFunctionElimination(JSFunction* function, NodeIndex child1)
+ bool checkFunctionElimination(JSCell* function, Node* child1)
+ {
+ for (unsigned i = endIndexForPureCSE(); i--;) {
+ Node* node = m_currentBlock->at(i);
+ if (node == child1)
+ break;
+
+ if (node->op() == CheckFunction && node->child1() == child1 && node->function() == function)
+ return true;
+ }
+ return false;
+ }
+
+ bool checkExecutableElimination(ExecutableBase* executable, Node* child1)
{
for (unsigned i = endIndexForPureCSE(); i--;) {
- NodeIndex index = m_currentBlock->at(i);
- if (index == child1)
+ Node* node = m_currentBlock->at(i);
+ if (node == child1)
break;
- Node& node = m_graph[index];
- if (node.op() == CheckFunction && node.child1() == child1 && node.function() == function)
+ if (node->op() == CheckExecutable && node->child1() == child1 && node->executable() == executable)
return true;
}
return false;
}
- bool checkStructureLoadElimination(const StructureSet& structureSet, NodeIndex child1)
+ bool checkStructureElimination(const StructureSet& structureSet, Node* child1)
{
for (unsigned i = m_indexInBlock; i--;) {
- NodeIndex index = m_currentBlock->at(i);
- if (index == child1)
+ Node* node = m_currentBlock->at(i);
+ if (node == child1)
break;
- Node& node = m_graph[index];
- switch (node.op()) {
+ switch (node->op()) {
case CheckStructure:
- if (node.child1() == child1
- && structureSet.isSupersetOf(node.structureSet()))
+ if (node->child1() == child1
+ && structureSet.isSupersetOf(node->structureSet()))
+ return true;
+ break;
+
+ case StructureTransitionWatchpoint:
+ if (node->child1() == child1
+ && structureSet.contains(node->structure()))
return true;
break;
case PutStructure:
- if (node.child1() == child1
- && structureSet.contains(node.structureTransitionData().newStructure))
+ if (node->child1() == child1
+ && structureSet.contains(node->structureTransitionData().newStructure))
return true;
- if (structureSet.contains(node.structureTransitionData().previousStructure))
+ if (structureSet.contains(node->structureTransitionData().previousStructure))
return false;
break;
// Setting a property cannot change the structure.
break;
+ case MultiPutByOffset:
+ if (node->multiPutByOffsetData().writesStructures())
+ return false;
+ break;
+
+ case PutByValDirect:
case PutByVal:
case PutByValAlias:
- if (byValIsPure(node)) {
+ if (m_graph.byValIsPure(node)) {
// If PutByVal speculates that it's accessing an array with an
// integer index, then it's impossible for it to cause a structure
// change.
}
return false;
+ case Arrayify:
+ case ArrayifyToStructure:
+ // We could check if the arrayification could affect our structures.
+ // But that seems like it would take Effort.
+ return false;
+
default:
- if (clobbersWorld(index))
+ if (m_graph.clobbersWorld(node))
return false;
break;
}
return false;
}
- NodeIndex getByOffsetLoadElimination(unsigned identifierNumber, NodeIndex child1)
+ bool structureTransitionWatchpointElimination(Structure* structure, Node* child1)
{
for (unsigned i = m_indexInBlock; i--;) {
- NodeIndex index = m_currentBlock->at(i);
- if (index == child1)
+ Node* node = m_currentBlock->at(i);
+ if (node == child1)
break;
- Node& node = m_graph[index];
- switch (node.op()) {
- case GetByOffset:
- if (node.child1() == child1
- && m_graph.m_storageAccessData[node.storageAccessDataIndex()].identifierNumber == identifierNumber)
- return index;
+ switch (node->op()) {
+ case CheckStructure:
+ if (node->child1() == child1
+ && node->structureSet().containsOnly(structure))
+ return true;
break;
- case PutByOffset:
- if (m_graph.m_storageAccessData[node.storageAccessDataIndex()].identifierNumber == identifierNumber) {
- if (node.child2() == child1)
- return node.child3().index();
- return NoNode;
- }
+ case PutStructure:
+ ASSERT(node->structureTransitionData().previousStructure != structure);
break;
- case PutStructure:
- // Changing the structure cannot change the outcome of a property get.
+ case PutByOffset:
+ // Setting a property cannot change the structure.
+ break;
+
+ case MultiPutByOffset:
+ if (node->multiPutByOffsetData().writesStructures())
+ return false;
break;
+ case PutByValDirect:
case PutByVal:
case PutByValAlias:
- if (byValIsPure(node)) {
+ if (m_graph.byValIsPure(node)) {
// If PutByVal speculates that it's accessing an array with an
// integer index, then it's impossible for it to cause a structure
// change.
break;
}
- return NoNode;
+ return false;
+
+ case StructureTransitionWatchpoint:
+ if (node->structure() == structure && node->child1() == child1)
+ return true;
+ break;
+
+ case Arrayify:
+ case ArrayifyToStructure:
+ // We could check if the arrayification could affect our structures.
+ // But that seems like it would take Effort.
+ return false;
+
+ default:
+ if (m_graph.clobbersWorld(node))
+ return false;
+ break;
+ }
+ }
+ return false;
+ }
+
+ Node* putStructureStoreElimination(Node* child1)
+ {
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ if (node == child1)
+ break;
+ switch (node->op()) {
+ case CheckStructure:
+ return 0;
+
+ case PhantomPutStructure:
+ if (node->child1() == child1) // No need to retrace our steps.
+ return 0;
+ break;
+
+ case PutStructure:
+ if (node->child1() == child1)
+ return node;
+ break;
+
+ // PutStructure needs to execute if we GC. Hence this needs to
+ // be careful with respect to nodes that GC.
+ case CreateArguments:
+ case TearOffArguments:
+ case NewFunctionNoCheck:
+ case NewFunction:
+ case NewFunctionExpression:
+ case CreateActivation:
+ case TearOffActivation:
+ case ToPrimitive:
+ case NewRegexp:
+ case NewArrayBuffer:
+ case NewArray:
+ case NewObject:
+ case CreateThis:
+ case AllocatePropertyStorage:
+ case ReallocatePropertyStorage:
+ case TypeOf:
+ case ToString:
+ case NewStringObject:
+ case MakeRope:
+ case NewTypedArray:
+ case MultiPutByOffset:
+ return 0;
+
+ // This either exits, causes a GC (lazy string allocation), or clobbers
+ // the world. The chances of it not doing any of those things are so
+ // slim that we might as well not even try to reason about it.
+ case GetByVal:
+ return 0;
+
+ case GetIndexedPropertyStorage:
+ if (node->arrayMode().getIndexedPropertyStorageMayTriggerGC())
+ return 0;
+ break;
default:
- if (clobbersWorld(index))
- return NoNode;
break;
}
+ if (m_graph.clobbersWorld(node) || node->canExit())
+ return 0;
+ if (edgesUseStructure(m_graph, node))
+ return 0;
}
- return NoNode;
+ return 0;
}
- NodeIndex getPropertyStorageLoadElimination(NodeIndex child1)
+ Node* getByOffsetLoadElimination(unsigned identifierNumber, Node* base)
{
for (unsigned i = m_indexInBlock; i--;) {
- NodeIndex index = m_currentBlock->at(i);
- if (index == child1)
+ Node* node = m_currentBlock->at(i);
+ if (node == base)
break;
- Node& node = m_graph[index];
- switch (node.op()) {
- case GetPropertyStorage:
- if (node.child1() == child1)
- return index;
+ switch (node->op()) {
+ case GetByOffset:
+ if (node->child2() == base
+ && m_graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber == identifierNumber)
+ return node;
+ break;
+
+ case MultiGetByOffset:
+ if (node->child1() == base
+ && node->multiGetByOffsetData().identifierNumber == identifierNumber)
+ return node;
break;
case PutByOffset:
- case PutStructure:
- // Changing the structure or putting to the storage cannot
- // change the property storage pointer.
+ if (m_graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber == identifierNumber) {
+ if (node->child2() == base) // Must be same property storage.
+ return node->child3().node();
+ return 0;
+ }
break;
+ case MultiPutByOffset:
+ if (node->multiPutByOffsetData().identifierNumber == identifierNumber) {
+ if (node->child1() == base)
+ return node->child2().node();
+ return 0;
+ }
+ break;
+
+ case PutByValDirect:
case PutByVal:
case PutByValAlias:
- if (byValIsPure(node)) {
+ if (m_graph.byValIsPure(node)) {
// If PutByVal speculates that it's accessing an array with an
// integer index, then it's impossible for it to cause a structure
// change.
break;
}
- return NoNode;
+ return 0;
default:
- if (clobbersWorld(index))
- return NoNode;
+ if (m_graph.clobbersWorld(node))
+ return 0;
break;
}
}
- return NoNode;
+ return 0;
}
-
- NodeIndex getIndexedPropertyStorageLoadElimination(NodeIndex child1, bool hasIntegerIndexPrediction)
+
+ Node* putByOffsetStoreElimination(unsigned identifierNumber, Node* child1)
{
for (unsigned i = m_indexInBlock; i--;) {
- NodeIndex index = m_currentBlock->at(i);
- if (index == child1)
+ Node* node = m_currentBlock->at(i);
+ if (node == child1)
break;
- Node& node = m_graph[index];
- switch (node.op()) {
- case GetIndexedPropertyStorage: {
- PredictedType basePrediction = m_graph[node.child2()].prediction();
- bool nodeHasIntegerIndexPrediction = !(!(basePrediction & PredictInt32) && basePrediction);
- if (node.child1() == child1 && hasIntegerIndexPrediction == nodeHasIntegerIndexPrediction)
- return index;
+ switch (node->op()) {
+ case GetByOffset:
+ if (m_graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber == identifierNumber)
+ return 0;
break;
- }
-
+
case PutByOffset:
- case PutStructure:
- // Changing the structure or putting to the storage cannot
- // change the property storage pointer.
+ if (m_graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber == identifierNumber) {
+ if (node->child1() == child1) // Must be same property storage.
+ return node;
+ return 0;
+ }
break;
- case PutByValAlias:
- // PutByValAlias can't change the indexed storage pointer
+ case MultiPutByOffset:
+ if (node->multiPutByOffsetData().identifierNumber == identifierNumber)
+ return 0;
break;
+ case PutByValDirect:
case PutByVal:
- if (isFixedIndexedStorageObjectPrediction(m_graph[node.child1()].prediction()) && byValIsPure(node))
+ case PutByValAlias:
+ case GetByVal:
+ if (m_graph.byValIsPure(node)) {
+ // If PutByVal speculates that it's accessing an array with an
+ // integer index, then it's impossible for it to cause a structure
+ // change.
break;
- return NoNode;
-
+ }
+ return 0;
+
default:
- if (clobbersWorld(index))
- return NoNode;
+ if (m_graph.clobbersWorld(node))
+ return 0;
break;
}
+ if (node->canExit())
+ return 0;
}
- return NoNode;
+ return 0;
}
- NodeIndex getScopeChainLoadElimination(unsigned depth)
+ Node* getPropertyStorageLoadElimination(Node* child1)
{
- for (unsigned i = endIndexForPureCSE(); i--;) {
- NodeIndex index = m_currentBlock->at(i);
- Node& node = m_graph[index];
- if (node.op() == GetScopeChain
- && node.scopeChainDepth() == depth)
- return index;
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ if (node == child1)
+ break;
+
+ switch (node->op()) {
+ case GetButterfly:
+ if (node->child1() == child1)
+ return node;
+ break;
+
+ case AllocatePropertyStorage:
+ case ReallocatePropertyStorage:
+ // If we can cheaply prove this is a change to our object's storage, we
+ // can optimize and use its result.
+ if (node->child1() == child1)
+ return node;
+ // Otherwise, we currently can't prove that this doesn't change our object's
+ // storage, so we conservatively assume that it may change the storage
+ // pointer of any object, including ours.
+ return 0;
+
+ case PutByValDirect:
+ case PutByVal:
+ case PutByValAlias:
+ if (m_graph.byValIsPure(node)) {
+ // If PutByVal speculates that it's accessing an array with an
+ // integer index, then it's impossible for it to cause a structure
+ // change.
+ break;
+ }
+ return 0;
+
+ case Arrayify:
+ case ArrayifyToStructure:
+ // We could check if the arrayification could affect our butterfly.
+ // But that seems like it would take Effort.
+ return 0;
+
+ case MultiPutByOffset:
+ if (node->multiPutByOffsetData().reallocatesStorage())
+ return 0;
+ break;
+
+ default:
+ if (m_graph.clobbersWorld(node))
+ return 0;
+ break;
+ }
}
- return NoNode;
+ return 0;
}
- void performSubstitution(Edge& child, bool addRef = true)
+ bool checkArrayElimination(Node* child1, ArrayMode arrayMode)
{
- // Check if this operand is actually unused.
- if (!child)
- return;
-
- // Check if there is any replacement.
- NodeIndex replacement = m_replacements[child.index()];
- if (replacement == NoNode)
- return;
-
- child.setIndex(replacement);
-
- // There is definitely a replacement. Assert that the replacement does not
- // have a replacement.
- ASSERT(m_replacements[child.index()] == NoNode);
-
- if (addRef)
- m_graph[child].ref();
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ if (node == child1)
+ break;
+
+ switch (node->op()) {
+ case CheckArray:
+ if (node->child1() == child1 && node->arrayMode() == arrayMode)
+ return true;
+ break;
+
+ case Arrayify:
+ case ArrayifyToStructure:
+ // We could check if the arrayification could affect our array.
+ // But that seems like it would take Effort.
+ return false;
+
+ default:
+ if (m_graph.clobbersWorld(node))
+ return false;
+ break;
+ }
+ }
+ return false;
+ }
+
+ Node* getIndexedPropertyStorageLoadElimination(Node* child1, ArrayMode arrayMode)
+ {
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ if (node == child1)
+ break;
+
+ switch (node->op()) {
+ case GetIndexedPropertyStorage: {
+ if (node->child1() == child1 && node->arrayMode() == arrayMode)
+ return node;
+ break;
+ }
+
+ default:
+ if (m_graph.clobbersWorld(node))
+ return 0;
+ break;
+ }
+ }
+ return 0;
}
- void setReplacement(NodeIndex replacement)
+ Node* getTypedArrayByteOffsetLoadElimination(Node* child1)
{
- if (replacement == NoNode)
- return;
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ if (node == child1)
+ break;
+
+ switch (node->op()) {
+ case GetTypedArrayByteOffset: {
+ if (node->child1() == child1)
+ return node;
+ break;
+ }
+
+ default:
+ if (m_graph.clobbersWorld(node))
+ return 0;
+ break;
+ }
+ }
+ return 0;
+ }
+
+ Node* getMyScopeLoadElimination()
+ {
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ switch (node->op()) {
+ case CreateActivation:
+ // This may cause us to return a different scope.
+ return 0;
+ case GetMyScope:
+ return node;
+ default:
+ break;
+ }
+ }
+ return 0;
+ }
+
+ Node* getLocalLoadElimination(VirtualRegister local, Node*& relevantLocalOp, bool careAboutClobbering)
+ {
+ relevantLocalOp = 0;
- // Be safe. Don't try to perform replacements if the predictions don't
- // agree.
- if (m_graph[m_compileIndex].prediction() != m_graph[replacement].prediction())
- return;
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ switch (node->op()) {
+ case GetLocal:
+ if (node->local() == local) {
+ relevantLocalOp = node;
+ return node;
+ }
+ break;
+
+ case GetLocalUnlinked:
+ if (node->unlinkedLocal() == local) {
+ relevantLocalOp = node;
+ return node;
+ }
+ break;
+
+ case SetLocal:
+ if (node->local() == local) {
+ relevantLocalOp = node;
+ return node->child1().node();
+ }
+ break;
+
+ case GetClosureVar:
+ case PutClosureVar:
+ if (static_cast<VirtualRegister>(node->varNumber()) == local)
+ return 0;
+ break;
+
+ default:
+ if (careAboutClobbering && m_graph.clobbersWorld(node))
+ return 0;
+ break;
+ }
+ }
+ return 0;
+ }
+
+ bool uncapturedSetLocalStoreElimination(VirtualRegister local, Node* expectedNode)
+ {
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ switch (node->op()) {
+ case GetLocal:
+ case Flush:
+ if (node->local() == local)
+ return false;
+ break;
+
+ case GetLocalUnlinked:
+ if (node->unlinkedLocal() == local)
+ return false;
+ break;
+
+ case SetLocal: {
+ if (node->local() != local)
+ break;
+ if (node != expectedNode)
+ return false;
+ return true;
+ }
+
+ case GetClosureVar:
+ case PutClosureVar:
+ if (static_cast<VirtualRegister>(node->varNumber()) == local)
+ return false;
+ break;
+
+ case GetMyScope:
+ case SkipTopScope:
+ if (node->origin.semantic.inlineCallFrame)
+ break;
+ if (m_graph.uncheckedActivationRegister() == local)
+ return false;
+ break;
+
+ case CheckArgumentsNotCreated:
+ case GetMyArgumentsLength:
+ case GetMyArgumentsLengthSafe:
+ if (m_graph.uncheckedArgumentsRegisterFor(node->origin.semantic) == local)
+ return false;
+ break;
+
+ case GetMyArgumentByVal:
+ case GetMyArgumentByValSafe:
+ return false;
+
+ case GetByVal:
+ // If this is accessing arguments then it's potentially accessing locals.
+ if (node->arrayMode().type() == Array::Arguments)
+ return false;
+ break;
+
+ case CreateArguments:
+ case TearOffActivation:
+ case TearOffArguments:
+ // If an activation is being torn off then it means that captured variables
+ // are live. We could be clever here and check if the local qualifies as an
+ // argument register. But that seems like it would buy us very little since
+ // any kind of tear offs are rare to begin with.
+ return false;
+
+ default:
+ break;
+ }
+ if (m_graph.clobbersWorld(node))
+ return false;
+ }
+ RELEASE_ASSERT_NOT_REACHED();
+ return false;
+ }
+
+ bool capturedSetLocalStoreElimination(VirtualRegister local, Node* expectedNode)
+ {
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ switch (node->op()) {
+ case GetLocal:
+ case Flush:
+ if (node->local() == local)
+ return false;
+ break;
+
+ case GetLocalUnlinked:
+ if (node->unlinkedLocal() == local)
+ return false;
+ break;
+
+ case SetLocal: {
+ if (node->local() != local)
+ break;
+ if (node != expectedNode)
+ return false;
+ return true;
+ }
+
+ case Phantom:
+ case Check:
+ case HardPhantom:
+ case MovHint:
+ case JSConstant:
+ case DoubleConstant:
+ case Int52Constant:
+ break;
+
+ default:
+ return false;
+ }
+ }
+ RELEASE_ASSERT_NOT_REACHED();
+ return false;
+ }
+
+ bool setLocalStoreElimination(VariableAccessData* variableAccessData, Node* expectedNode)
+ {
+ if (variableAccessData->isCaptured())
+ return capturedSetLocalStoreElimination(variableAccessData->local(), expectedNode);
+ return uncapturedSetLocalStoreElimination(variableAccessData->local(), expectedNode);
+ }
+
+ bool invalidationPointElimination()
+ {
+ for (unsigned i = m_indexInBlock; i--;) {
+ Node* node = m_currentBlock->at(i);
+ if (node->op() == InvalidationPoint)
+ return true;
+ if (writesOverlap(m_graph, node, Watchpoint_fire))
+ break;
+ }
+ return false;
+ }
+
+ void eliminateIrrelevantPhantomChildren(Node* node)
+ {
+ ASSERT(node->op() == Phantom);
+ for (unsigned i = 0; i < AdjacencyList::Size; ++i) {
+ Edge edge = node->children.child(i);
+ if (!edge)
+ continue;
+ if (edge.useKind() != UntypedUse)
+ continue; // Keep the type check.
+ if (edge->flags() & NodeRelevantToOSR)
+ continue;
+
+ node->children.removeEdge(i--);
+ m_changed = true;
+ }
+ }
+
+ bool setReplacement(Node* replacement)
+ {
+ if (!replacement)
+ return false;
-#if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE)
- dataLog(" Replacing @%u -> @%u", m_compileIndex, replacement);
-#endif
+ if (!ASSERT_DISABLED
+ && canonicalResultRepresentation(m_currentNode->result()) != canonicalResultRepresentation(replacement->result())) {
+ startCrashing();
+ dataLog("CSE attempting to replace a node with a mismatched result: ", m_currentNode, " with ", replacement, "\n");
+ dataLog("\n");
+ m_graph.dump();
+ RELEASE_ASSERT_NOT_REACHED();
+ }
- Node& node = m_graph[m_compileIndex];
- node.setOpAndDefaultFlags(Phantom);
- node.setRefCount(1);
+ m_currentNode->convertToPhantom();
+ eliminateIrrelevantPhantomChildren(m_currentNode);
// At this point we will eliminate all references to this node.
- m_replacements[m_compileIndex] = replacement;
+ m_currentNode->misc.replacement = replacement;
+
+ m_changed = true;
+
+ return true;
}
void eliminate()
{
-#if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE)
- dataLog(" Eliminating @%u", m_compileIndex);
-#endif
+ ASSERT(m_currentNode->mustGenerate());
+ m_currentNode->convertToPhantom();
+ eliminateIrrelevantPhantomChildren(m_currentNode);
- Node& node = m_graph[m_compileIndex];
- ASSERT(node.refCount() == 1);
- ASSERT(node.mustGenerate());
- node.setOpAndDefaultFlags(Phantom);
+ m_changed = true;
}
- void performNodeCSE(Node& node)
+ void eliminate(Node* node, NodeType phantomType = Phantom)
{
- bool shouldGenerate = node.shouldGenerate();
-
- if (node.flags() & NodeHasVarArgs) {
- for (unsigned childIdx = node.firstChild(); childIdx < node.firstChild() + node.numChildren(); childIdx++)
- performSubstitution(m_graph.m_varArgChildren[childIdx], shouldGenerate);
- } else {
- performSubstitution(node.children.child1(), shouldGenerate);
- performSubstitution(node.children.child2(), shouldGenerate);
- performSubstitution(node.children.child3(), shouldGenerate);
- }
-
- if (!shouldGenerate)
+ if (!node)
return;
+ ASSERT(node->mustGenerate());
+ node->setOpAndDefaultFlags(phantomType);
+ if (phantomType == Phantom)
+ eliminateIrrelevantPhantomChildren(node);
-#if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE)
- dataLog(" %s @%u: ", Graph::opName(m_graph[m_compileIndex].op()), m_compileIndex);
-#endif
-
- // NOTE: there are some nodes that we deliberately don't CSE even though we
- // probably could, like StrCat and ToPrimitive. That's because there is no
- // evidence that doing CSE on these nodes would result in a performance
- // progression. Hence considering these nodes in CSE would just mean that this
- // code does more work with no win. Of course, we may want to reconsider this,
- // since StrCat is trivially CSE-able. It's not trivially doable for
- // ToPrimitive, but we could change that with some speculations if we really
- // needed to.
+ m_changed = true;
+ }
+
+ void performNodeCSE(Node* node)
+ {
+ if (cseMode == NormalCSE)
+ m_graph.performSubstitution(node);
- switch (node.op()) {
+ switch (node->op()) {
+ case Identity:
+ if (cseMode == StoreElimination)
+ break;
+ setReplacement(node->child1().node());
+ break;
+
// Handle the pure nodes. These nodes never have any side-effects.
case BitAnd:
case BitOr:
case BitRShift:
case BitLShift:
case BitURShift:
- case ArithAdd:
- case ArithSub:
- case ArithNegate:
- case ArithMul:
- case ArithMod:
- case ArithDiv:
case ArithAbs:
case ArithMin:
case ArithMax:
case ArithSqrt:
- case GetInt8ArrayLength:
- case GetInt16ArrayLength:
- case GetInt32ArrayLength:
- case GetUint8ArrayLength:
- case GetUint8ClampedArrayLength:
- case GetUint16ArrayLength:
- case GetUint32ArrayLength:
- case GetFloat32ArrayLength:
- case GetFloat64ArrayLength:
- case GetCallee:
- case GetStringLength:
+ case ArithFRound:
+ case ArithSin:
+ case ArithCos:
case StringCharAt:
case StringCharCodeAt:
- case Int32ToDouble:
case IsUndefined:
case IsBoolean:
case IsNumber:
case IsString:
case IsObject:
case IsFunction:
- case DoubleAsInt32:
+ case LogicalNot:
+ case SkipTopScope:
+ case SkipScope:
+ case GetClosureRegisters:
+ case GetScope:
+ case TypeOf:
+ case CompareEqConstant:
+ case ValueToInt32:
+ case MakeRope:
+ case DoubleRep:
+ case ValueRep:
+ case Int52Rep:
+ case BooleanToNumber:
+ if (cseMode == StoreElimination)
+ break;
setReplacement(pureCSE(node));
break;
- case GetArrayLength:
- setReplacement(impureCSE(node));
+ case ArithAdd:
+ case ArithSub:
+ case ArithNegate:
+ case ArithMul:
+ case ArithDiv:
+ case ArithMod:
+ case UInt32ToNumber:
+ case DoubleAsInt32: {
+ if (cseMode == StoreElimination)
+ break;
+ Node* candidate = pureCSE(node);
+ if (!candidate)
+ break;
+ if (!subsumes(candidate->arithMode(), node->arithMode())) {
+ if (!subsumes(node->arithMode(), candidate->arithMode()))
+ break;
+ candidate->setArithMode(node->arithMode());
+ }
+ setReplacement(candidate);
+ break;
+ }
+
+ case GetCallee:
+ if (cseMode == StoreElimination)
+ break;
+ setReplacement(getCalleeLoadElimination());
+ break;
+
+ case GetLocal: {
+ if (cseMode == StoreElimination)
+ break;
+ VariableAccessData* variableAccessData = node->variableAccessData();
+ if (!variableAccessData->isCaptured())
+ break;
+ Node* relevantLocalOp;
+ Node* possibleReplacement = getLocalLoadElimination(variableAccessData->local(), relevantLocalOp, variableAccessData->isCaptured());
+ if (!relevantLocalOp)
+ break;
+ if (relevantLocalOp->op() != GetLocalUnlinked
+ && relevantLocalOp->variableAccessData() != variableAccessData)
+ break;
+ Node* phi = node->child1().node();
+ if (!setReplacement(possibleReplacement))
+ break;
+
+ m_graph.dethread();
+
+ // If we replace a GetLocal with a GetLocalUnlinked, then turn the GetLocalUnlinked
+ // into a GetLocal.
+ if (relevantLocalOp->op() == GetLocalUnlinked)
+ relevantLocalOp->convertToGetLocal(variableAccessData, phi);
+
+ m_changed = true;
+ break;
+ }
+
+ case GetLocalUnlinked: {
+ if (cseMode == StoreElimination)
+ break;
+ Node* relevantLocalOpIgnored;
+ setReplacement(getLocalLoadElimination(node->unlinkedLocal(), relevantLocalOpIgnored, true));
+ break;
+ }
+
+ case Flush: {
+ ASSERT(m_graph.m_form != SSA);
+ VariableAccessData* variableAccessData = node->variableAccessData();
+ if (!node->child1()) {
+ // FIXME: It's silly that we punt on flush-eliminating here. We don't really
+ // need child1 to figure out what's going on.
+ // https://bugs.webkit.org/show_bug.cgi?id=130521
+ break;
+ }
+ Node* replacement = node->child1().node();
+ if (replacement->op() != SetLocal)
+ break;
+ ASSERT(replacement->variableAccessData() == variableAccessData);
+ // FIXME: We should be able to remove SetLocals that can exit; we just need
+ // to replace them with appropriate type checks.
+ if (cseMode == NormalCSE) {
+ // Need to be conservative at this time; if the SetLocal has any chance of performing
+ // any speculations then we cannot do anything.
+ FlushFormat format = variableAccessData->flushFormat();
+ ASSERT(format != DeadFlush);
+ if (format != FlushedJSValue)
+ break;
+ } else {
+ if (replacement->canExit())
+ break;
+ }
+ if (!setLocalStoreElimination(variableAccessData, replacement))
+ break;
+ ASSERT(replacement->op() == SetLocal);
+ node->convertToPhantom();
+ Node* dataNode = replacement->child1().node();
+ ASSERT(dataNode->hasResult());
+ node->child1() = dataNode->defaultEdge();
+ m_graph.dethread();
+ m_changed = true;
+ break;
+ }
+
+ case JSConstant:
+ case DoubleConstant:
+ case Int52Constant:
+ if (cseMode == StoreElimination)
+ break;
+ // This is strange, but necessary. Some phases will convert nodes to constants,
+ // which may result in duplicated constants. We use CSE to clean this up.
+ setReplacement(constantCSE(node));
+ break;
+
+ case WeakJSConstant:
+ if (cseMode == StoreElimination)
+ break;
+ // FIXME: have CSE for weak constants against strong constants and vice-versa.
+ setReplacement(weakConstantCSE(node));
+ break;
+
+ case ConstantStoragePointer:
+ if (cseMode == StoreElimination)
+ break;
+ setReplacement(constantStoragePointerCSE(node));
break;
- case GetScopeChain:
- setReplacement(getScopeChainLoadElimination(node.scopeChainDepth()));
+ case GetArrayLength:
+ if (cseMode == StoreElimination)
+ break;
+ setReplacement(getArrayLengthElimination(node->child1().node()));
break;
+ case GetMyScope:
+ if (cseMode == StoreElimination)
+ break;
+ setReplacement(getMyScopeLoadElimination());
+ break;
+
// Handle nodes that are conditionally pure: these are pure, and can
// be CSE'd, so long as the prediction is the one we want.
- case ValueAdd:
case CompareLess:
case CompareLessEq:
case CompareGreater:
case CompareGreaterEq:
case CompareEq: {
- if (isPredictedNumerical(node)) {
- NodeIndex replacementIndex = pureCSE(node);
- if (replacementIndex != NoNode && isPredictedNumerical(m_graph[replacementIndex]))
- setReplacement(replacementIndex);
- }
- break;
- }
-
- case LogicalNot: {
- if (logicalNotIsPure(node)) {
- NodeIndex replacementIndex = pureCSE(node);
- if (replacementIndex != NoNode && logicalNotIsPure(m_graph[replacementIndex]))
- setReplacement(replacementIndex);
+ if (cseMode == StoreElimination)
+ break;
+ if (m_graph.isPredictedNumerical(node)) {
+ Node* replacement = pureCSE(node);
+ if (replacement && m_graph.isPredictedNumerical(replacement))
+ setReplacement(replacement);
}
break;
}
// Finally handle heap accesses. These are not quite pure, but we can still
// optimize them provided that some subtle conditions are met.
case GetGlobalVar:
- setReplacement(globalVarLoadElimination(node.varNumber(), codeBlock()->globalObjectFor(node.codeOrigin)));
+ if (cseMode == StoreElimination)
+ break;
+ setReplacement(globalVarLoadElimination(node->registerPointer()));
+ break;
+
+ case GetClosureVar: {
+ if (cseMode == StoreElimination)
+ break;
+ setReplacement(scopedVarLoadElimination(node->child1().node(), node->varNumber()));
+ break;
+ }
+
+ case VarInjectionWatchpoint:
+ if (cseMode == StoreElimination)
+ break;
+ if (varInjectionWatchpointElimination())
+ eliminate();
break;
- case GetByVal:
- if (byValIsPure(node))
- setReplacement(getByValLoadElimination(node.child1().index(), node.child2().index()));
+ case PutGlobalVar:
+ if (cseMode == NormalCSE)
+ break;
+ eliminate(globalVarStoreElimination(node->registerPointer()));
break;
- case PutByVal:
- if (byValIsPure(node) && getByValLoadElimination(node.child1().index(), node.child2().index()) != NoNode)
- node.setOp(PutByValAlias);
+ case PutClosureVar: {
+ if (cseMode == NormalCSE)
+ break;
+ eliminate(scopedVarStoreElimination(node->child1().node(), node->child2().node(), node->varNumber()));
+ break;
+ }
+
+ case GetByVal:
+ if (cseMode == StoreElimination)
+ break;
+ if (m_graph.byValIsPure(node))
+ setReplacement(getByValLoadElimination(node->child1().node(), node->child2().node(), node->arrayMode()));
break;
+
+ case PutByValDirect:
+ case PutByVal: {
+ if (cseMode == StoreElimination)
+ break;
+ Edge child1 = m_graph.varArgChild(node, 0);
+ Edge child2 = m_graph.varArgChild(node, 1);
+ if (node->arrayMode().canCSEStorage()) {
+ Node* replacement = getByValLoadElimination(child1.node(), child2.node(), node->arrayMode());
+ if (!replacement)
+ break;
+ node->setOp(PutByValAlias);
+ }
+ break;
+ }
case CheckStructure:
- if (checkStructureLoadElimination(node.structureSet(), node.child1().index()))
+ if (cseMode == StoreElimination)
+ break;
+ if (checkStructureElimination(node->structureSet(), node->child1().node()))
eliminate();
break;
+
+ case StructureTransitionWatchpoint:
+ if (cseMode == StoreElimination)
+ break;
+ if (structureTransitionWatchpointElimination(node->structure(), node->child1().node()))
+ eliminate();
+ break;
+
+ case PutStructure:
+ if (cseMode == NormalCSE)
+ break;
+ eliminate(putStructureStoreElimination(node->child1().node()), PhantomPutStructure);
+ break;
case CheckFunction:
- if (checkFunctionElimination(node.function(), node.child1().index()))
+ if (cseMode == StoreElimination)
+ break;
+ if (checkFunctionElimination(node->function(), node->child1().node()))
eliminate();
break;
+ case CheckExecutable:
+ if (cseMode == StoreElimination)
+ break;
+ if (checkExecutableElimination(node->executable(), node->child1().node()))
+ eliminate();
+ break;
+
+ case CheckArray:
+ if (cseMode == StoreElimination)
+ break;
+ if (checkArrayElimination(node->child1().node(), node->arrayMode()))
+ eliminate();
+ break;
+
case GetIndexedPropertyStorage: {
- PredictedType basePrediction = m_graph[node.child2()].prediction();
- bool nodeHasIntegerIndexPrediction = !(!(basePrediction & PredictInt32) && basePrediction);
- setReplacement(getIndexedPropertyStorageLoadElimination(node.child1().index(), nodeHasIntegerIndexPrediction));
+ if (cseMode == StoreElimination)
+ break;
+ setReplacement(getIndexedPropertyStorageLoadElimination(node->child1().node(), node->arrayMode()));
+ break;
+ }
+
+ case GetTypedArrayByteOffset: {
+ if (cseMode == StoreElimination)
+ break;
+ setReplacement(getTypedArrayByteOffsetLoadElimination(node->child1().node()));
break;
}
- case GetPropertyStorage:
- setReplacement(getPropertyStorageLoadElimination(node.child1().index()));
+ case GetButterfly:
+ if (cseMode == StoreElimination)
+ break;
+ setReplacement(getPropertyStorageLoadElimination(node->child1().node()));
break;
case GetByOffset:
- setReplacement(getByOffsetLoadElimination(m_graph.m_storageAccessData[node.storageAccessDataIndex()].identifierNumber, node.child1().index()));
+ if (cseMode == StoreElimination)
+ break;
+ setReplacement(getByOffsetLoadElimination(m_graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber, node->child2().node()));
+ break;
+
+ case MultiGetByOffset:
+ if (cseMode == StoreElimination)
+ break;
+ setReplacement(getByOffsetLoadElimination(node->multiGetByOffsetData().identifierNumber, node->child1().node()));
+ break;
+
+ case PutByOffset:
+ if (cseMode == NormalCSE)
+ break;
+ eliminate(putByOffsetStoreElimination(m_graph.m_storageAccessData[node->storageAccessDataIndex()].identifierNumber, node->child1().node()));
+ break;
+
+ case InvalidationPoint:
+ if (invalidationPointElimination())
+ eliminate();
+ break;
+
+ case Phantom:
+ // FIXME: we ought to remove Phantom's that have no children.
+ // NB. It would be incorrect to do this for HardPhantom. In fact, the whole point
+ // of HardPhantom is that we *don't* do this for HardPhantoms, since they signify
+ // a more strict kind of liveness than the Phantom bytecode liveness.
+ eliminateIrrelevantPhantomChildren(node);
break;
default:
break;
}
- m_lastSeen[node.op()] = m_indexInBlock;
-#if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE)
- dataLog("\n");
-#endif
+ m_lastSeen[node->op()] = m_indexInBlock;
}
- void performBlockCSE(BasicBlock& block)
+ void performBlockCSE(BasicBlock* block)
{
- m_currentBlock = █
+ if (!block)
+ return;
+ if (!block->isReachable)
+ return;
+
+ m_currentBlock = block;
for (unsigned i = 0; i < LastNodeType; ++i)
m_lastSeen[i] = UINT_MAX;
-
- for (m_indexInBlock = 0; m_indexInBlock < block.size(); ++m_indexInBlock) {
- m_compileIndex = block[m_indexInBlock];
- performNodeCSE(m_graph[m_compileIndex]);
+
+ for (m_indexInBlock = 0; m_indexInBlock < block->size(); ++m_indexInBlock) {
+ m_currentNode = block->at(m_indexInBlock);
+ performNodeCSE(m_currentNode);
+ }
+
+ if (!ASSERT_DISABLED && cseMode == StoreElimination) {
+ // Nobody should have replacements set.
+ for (unsigned i = 0; i < block->size(); ++i)
+ ASSERT(!block->at(i)->misc.replacement);
}
}
BasicBlock* m_currentBlock;
- NodeIndex m_compileIndex;
+ Node* m_currentNode;
unsigned m_indexInBlock;
- Vector<NodeIndex, 16> m_replacements;
- FixedArray<unsigned, LastNodeType> m_lastSeen;
+ std::array<unsigned, LastNodeType> m_lastSeen;
+ bool m_changed; // Only tracks changes that have a substantive effect on other optimizations.
};
-void performCSE(Graph& graph)
+bool performCSE(Graph& graph)
+{
+ SamplingRegion samplingRegion("DFG CSE Phase");
+ return runPhase<CSEPhase<NormalCSE>>(graph);
+}
+
+bool performStoreElimination(Graph& graph)
{
- runPhase<CSEPhase>(graph);
+ SamplingRegion samplingRegion("DFG Store Elimination Phase");
+ return runPhase<CSEPhase<StoreElimination>>(graph);
}
} } // namespace JSC::DFG