X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/6fe7ccc865dc7d7541b93c5bcaf6368d2c98a174..40a37d088818fc2fbeba2ef850dbcaaf294befbf:/dfg/DFGCSEPhase.cpp diff --git a/dfg/DFGCSEPhase.cpp b/dfg/DFGCSEPhase.cpp index 020b1cf..d4e1023 100644 --- a/dfg/DFGCSEPhase.cpp +++ b/dfg/DFGCSEPhase.cpp @@ -1,5 +1,5 @@ /* - * 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 @@ -28,284 +28,449 @@ #if ENABLE(DFG_JIT) +#include "DFGAbstractHeap.h" +#include "DFGClobberize.h" +#include "DFGEdgeUsesStructure.h" #include "DFGGraph.h" #include "DFGPhase.h" +#include "JSCInlines.h" +#include +#include namespace JSC { namespace DFG { +enum CSEMode { NormalCSE, StoreElimination }; + +template 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 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* 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(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* 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(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; @@ -313,9 +478,15 @@ private: // 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. @@ -323,8 +494,14 @@ private: } 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; } @@ -332,230 +509,635 @@ private: 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(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(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: @@ -563,69 +1145,189 @@ private: 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; } @@ -633,42 +1335,150 @@ private: // 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: @@ -676,34 +1486,49 @@ private: 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 m_replacements; - FixedArray m_lastSeen; + std::array 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>(graph); +} + +bool performStoreElimination(Graph& graph) { - runPhase(graph); + SamplingRegion samplingRegion("DFG Store Elimination Phase"); + return runPhase>(graph); } } } // namespace JSC::DFG