| /* |
| * Copyright (C) 2011, 2012, 2013 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "DFGCSEPhase.h" |
| |
| #if ENABLE(DFG_JIT) |
| |
| #include "DFGGraph.h" |
| #include "DFGPhase.h" |
| #include <wtf/FastBitVector.h> |
| |
| namespace JSC { namespace DFG { |
| |
| class CSEPhase : public Phase { |
| public: |
| CSEPhase(Graph& graph) |
| : Phase(graph, "common subexpression elimination") |
| { |
| } |
| |
| bool run() |
| { |
| m_changed = false; |
| |
| for (unsigned block = 0; block < m_graph.m_blocks.size(); ++block) |
| performBlockCSE(m_graph.m_blocks[block].get()); |
| |
| return m_changed; |
| } |
| |
| private: |
| |
| NodeIndex canonicalize(NodeIndex nodeIndex) |
| { |
| if (nodeIndex == NoNode) |
| return NoNode; |
| |
| if (m_graph[nodeIndex].op() == ValueToInt32) |
| nodeIndex = m_graph[nodeIndex].child1().index(); |
| |
| return nodeIndex; |
| } |
| NodeIndex canonicalize(Edge nodeUse) |
| { |
| return canonicalize(nodeUse.indexUnchecked()); |
| } |
| |
| unsigned endIndexForPureCSE() |
| { |
| unsigned result = m_lastSeen[m_graph[m_compileIndex].op()]; |
| if (result == UINT_MAX) |
| result = 0; |
| else |
| result++; |
| ASSERT(result <= m_indexInBlock); |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" limit %u: ", result); |
| #endif |
| return result; |
| } |
| |
| NodeIndex pureCSE(Node& node) |
| { |
| NodeIndex child1 = canonicalize(node.child1()); |
| NodeIndex child2 = canonicalize(node.child2()); |
| NodeIndex child3 = canonicalize(node.child3()); |
| |
| for (unsigned i = endIndexForPureCSE(); i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| if (index == child1 || index == child2 || index == child3) |
| break; |
| |
| Node& otherNode = m_graph[index]; |
| if (!otherNode.shouldGenerate()) |
| continue; |
| |
| if (node.op() != otherNode.op()) |
| continue; |
| |
| if (node.arithNodeFlags() != otherNode.arithNodeFlags()) |
| continue; |
| |
| NodeIndex otherChild = canonicalize(otherNode.child1()); |
| if (otherChild == NoNode) |
| return index; |
| if (otherChild != child1) |
| continue; |
| |
| otherChild = canonicalize(otherNode.child2()); |
| if (otherChild == NoNode) |
| return index; |
| if (otherChild != child2) |
| continue; |
| |
| otherChild = canonicalize(otherNode.child3()); |
| if (otherChild == NoNode) |
| return index; |
| if (otherChild != child3) |
| continue; |
| |
| return index; |
| } |
| return NoNode; |
| } |
| |
| NodeIndex constantCSE(Node& node) |
| { |
| for (unsigned i = endIndexForPureCSE(); i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| Node& otherNode = m_graph[index]; |
| if (otherNode.op() != JSConstant) |
| continue; |
| |
| if (otherNode.constantNumber() != node.constantNumber()) |
| continue; |
| |
| return index; |
| } |
| return NoNode; |
| } |
| |
| NodeIndex weakConstantCSE(Node& node) |
| { |
| for (unsigned i = endIndexForPureCSE(); i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| Node& otherNode = m_graph[index]; |
| if (otherNode.op() != WeakJSConstant) |
| continue; |
| |
| if (otherNode.weakConstant() != node.weakConstant()) |
| continue; |
| |
| return index; |
| } |
| return NoNode; |
| } |
| |
| NodeIndex getCalleeLoadElimination(InlineCallFrame* inlineCallFrame) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| if (node.codeOrigin.inlineCallFrame != inlineCallFrame) |
| continue; |
| switch (node.op()) { |
| case GetCallee: |
| return index; |
| case SetCallee: |
| return node.child1().index(); |
| default: |
| break; |
| } |
| } |
| return NoNode; |
| } |
| |
| NodeIndex getArrayLengthElimination(NodeIndex array) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case GetArrayLength: |
| if (node.child1() == array) |
| return index; |
| break; |
| |
| case PutByVal: |
| if (!m_graph.byValIsPure(node)) |
| return NoNode; |
| if (node.arrayMode().mayStoreToHole()) |
| return NoNode; |
| break; |
| |
| default: |
| if (m_graph.clobbersWorld(index)) |
| return NoNode; |
| } |
| } |
| return NoNode; |
| } |
| |
| NodeIndex globalVarLoadElimination(WriteBarrier<Unknown>* registerPointer) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case GetGlobalVar: |
| if (node.registerPointer() == registerPointer) |
| return index; |
| break; |
| case PutGlobalVar: |
| if (node.registerPointer() == registerPointer) |
| return node.child1().index(); |
| break; |
| default: |
| break; |
| } |
| if (m_graph.clobbersWorld(index)) |
| break; |
| } |
| return NoNode; |
| } |
| |
| NodeIndex scopedVarLoadElimination(NodeIndex registers, unsigned varNumber) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case GetScopedVar: { |
| if (node.child1() == registers && node.varNumber() == varNumber) |
| return index; |
| break; |
| } |
| case PutScopedVar: { |
| if (node.child2() == registers && node.varNumber() == varNumber) |
| return node.child3().index(); |
| break; |
| } |
| case SetLocal: { |
| VariableAccessData* variableAccessData = node.variableAccessData(); |
| if (variableAccessData->isCaptured() |
| && variableAccessData->local() == static_cast<VirtualRegister>(varNumber)) |
| return NoNode; |
| break; |
| } |
| default: |
| break; |
| } |
| if (m_graph.clobbersWorld(index)) |
| break; |
| } |
| return NoNode; |
| } |
| |
| bool globalVarWatchpointElimination(WriteBarrier<Unknown>* registerPointer) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case GlobalVarWatchpoint: |
| if (node.registerPointer() == registerPointer) |
| return true; |
| break; |
| case PutGlobalVar: |
| if (node.registerPointer() == registerPointer) |
| return false; |
| break; |
| default: |
| break; |
| } |
| if (m_graph.clobbersWorld(index)) |
| break; |
| } |
| return false; |
| } |
| |
| NodeIndex globalVarStoreElimination(WriteBarrier<Unknown>* registerPointer) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case PutGlobalVar: |
| case PutGlobalVarCheck: |
| if (node.registerPointer() == registerPointer) |
| return index; |
| break; |
| |
| case GetGlobalVar: |
| if (node.registerPointer() == registerPointer) |
| return NoNode; |
| break; |
| |
| default: |
| break; |
| } |
| if (m_graph.clobbersWorld(index) || node.canExit()) |
| return NoNode; |
| } |
| return NoNode; |
| } |
| |
| NodeIndex scopedVarStoreElimination(NodeIndex scope, NodeIndex registers, unsigned varNumber) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case PutScopedVar: { |
| if (node.child1() == scope && node.child2() == registers && node.varNumber() == varNumber) |
| return index; |
| break; |
| } |
| |
| case GetScopedVar: { |
| // Let's be conservative. |
| if (node.varNumber() == varNumber) |
| return NoNode; |
| break; |
| } |
| |
| case GetLocal: { |
| VariableAccessData* variableAccessData = node.variableAccessData(); |
| if (variableAccessData->isCaptured() |
| && variableAccessData->local() == static_cast<VirtualRegister>(varNumber)) |
| return NoNode; |
| break; |
| } |
| |
| default: |
| break; |
| } |
| if (m_graph.clobbersWorld(index) || node.canExit()) |
| return NoNode; |
| } |
| return NoNode; |
| } |
| |
| NodeIndex getByValLoadElimination(NodeIndex child1, NodeIndex child2) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| if (index == child1 || index == canonicalize(child2)) |
| break; |
| |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case GetByVal: |
| if (!m_graph.byValIsPure(node)) |
| return NoNode; |
| if (node.child1() == child1 && canonicalize(node.child2()) == canonicalize(child2)) |
| return index; |
| break; |
| case PutByVal: |
| case PutByValAlias: { |
| if (!m_graph.byValIsPure(node)) |
| return NoNode; |
| if (m_graph.varArgChild(node, 0) == child1 && canonicalize(m_graph.varArgChild(node, 1)) == canonicalize(child2)) |
| return m_graph.varArgChild(node, 2).index(); |
| // 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; |
| default: |
| if (m_graph.clobbersWorld(index)) |
| return NoNode; |
| break; |
| } |
| } |
| return NoNode; |
| } |
| |
| bool checkFunctionElimination(JSCell* function, NodeIndex child1) |
| { |
| for (unsigned i = endIndexForPureCSE(); i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| if (index == child1) |
| break; |
| |
| Node& node = m_graph[index]; |
| if (node.op() == CheckFunction && node.child1() == child1 && node.function() == function) |
| return true; |
| } |
| return false; |
| } |
| |
| bool checkExecutableElimination(ExecutableBase* executable, NodeIndex child1) |
| { |
| for (unsigned i = endIndexForPureCSE(); i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| if (index == child1) |
| break; |
| |
| Node& node = m_graph[index]; |
| if (node.op() == CheckExecutable && node.child1() == child1 && node.executable() == executable) |
| return true; |
| } |
| return false; |
| } |
| |
| bool checkStructureElimination(const StructureSet& structureSet, NodeIndex child1) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| if (index == child1) |
| break; |
| |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case CheckStructure: |
| case ForwardCheckStructure: |
| if (node.child1() == child1 |
| && structureSet.isSupersetOf(node.structureSet())) |
| return true; |
| break; |
| |
| case StructureTransitionWatchpoint: |
| case ForwardStructureTransitionWatchpoint: |
| if (node.child1() == child1 |
| && structureSet.contains(node.structure())) |
| return true; |
| break; |
| |
| case PutStructure: |
| if (node.child1() == child1 |
| && structureSet.contains(node.structureTransitionData().newStructure)) |
| return true; |
| if (structureSet.contains(node.structureTransitionData().previousStructure)) |
| return false; |
| break; |
| |
| case PutByOffset: |
| // Setting a property cannot change the structure. |
| break; |
| |
| 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 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 (m_graph.clobbersWorld(index)) |
| return false; |
| break; |
| } |
| } |
| return false; |
| } |
| |
| bool structureTransitionWatchpointElimination(Structure* structure, NodeIndex child1) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| if (index == child1) |
| break; |
| |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case CheckStructure: |
| case ForwardCheckStructure: |
| if (node.child1() == child1 |
| && node.structureSet().containsOnly(structure)) |
| return true; |
| break; |
| |
| case PutStructure: |
| ASSERT(node.structureTransitionData().previousStructure != structure); |
| break; |
| |
| case PutByOffset: |
| // Setting a property cannot change the structure. |
| break; |
| |
| 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 false; |
| |
| case StructureTransitionWatchpoint: |
| case ForwardStructureTransitionWatchpoint: |
| 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(index)) |
| return false; |
| break; |
| } |
| } |
| return false; |
| } |
| |
| NodeIndex putStructureStoreElimination(NodeIndex child1) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| if (index == child1) |
| break; |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case CheckStructure: |
| case ForwardCheckStructure: |
| return NoNode; |
| |
| case PhantomPutStructure: |
| if (node.child1() == child1) // No need to retrace our steps. |
| return NoNode; |
| break; |
| |
| case PutStructure: |
| if (node.child1() == child1) |
| return index; |
| 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 StrCat: |
| case ToPrimitive: |
| case NewRegexp: |
| case NewArrayBuffer: |
| case NewArray: |
| case NewObject: |
| case CreateThis: |
| case AllocatePropertyStorage: |
| case ReallocatePropertyStorage: |
| case TypeOf: |
| return NoNode; |
| |
| case GetIndexedPropertyStorage: |
| if (node.arrayMode().getIndexedPropertyStorageMayTriggerGC()) |
| return NoNode; |
| break; |
| |
| default: |
| break; |
| } |
| if (m_graph.clobbersWorld(index) || node.canExit()) |
| return NoNode; |
| } |
| return NoNode; |
| } |
| |
| NodeIndex getByOffsetLoadElimination(unsigned identifierNumber, NodeIndex child1) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| if (index == child1) |
| break; |
| |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case GetByOffset: |
| if (node.child1() == child1 |
| && m_graph.m_storageAccessData[node.storageAccessDataIndex()].identifierNumber == identifierNumber) |
| return index; |
| break; |
| |
| case PutByOffset: |
| if (m_graph.m_storageAccessData[node.storageAccessDataIndex()].identifierNumber == identifierNumber) { |
| if (node.child1() == child1) // Must be same property storage. |
| return node.child3().index(); |
| return NoNode; |
| } |
| break; |
| |
| case PutStructure: |
| // Changing the structure cannot change the outcome of a property get. |
| break; |
| |
| 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 NoNode; |
| |
| default: |
| if (m_graph.clobbersWorld(index)) |
| return NoNode; |
| break; |
| } |
| } |
| return NoNode; |
| } |
| |
| NodeIndex putByOffsetStoreElimination(unsigned identifierNumber, NodeIndex child1) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| if (index == child1) |
| break; |
| |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case GetByOffset: |
| if (m_graph.m_storageAccessData[node.storageAccessDataIndex()].identifierNumber == identifierNumber) |
| return NoNode; |
| break; |
| |
| case PutByOffset: |
| if (m_graph.m_storageAccessData[node.storageAccessDataIndex()].identifierNumber == identifierNumber) { |
| if (node.child1() == child1) // Must be same property storage. |
| return index; |
| return NoNode; |
| } |
| break; |
| |
| case PutByVal: |
| 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; |
| |
| default: |
| if (m_graph.clobbersWorld(index)) |
| return NoNode; |
| break; |
| } |
| if (node.canExit()) |
| return NoNode; |
| } |
| return NoNode; |
| } |
| |
| NodeIndex getPropertyStorageLoadElimination(NodeIndex child1) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| if (index == child1) |
| break; |
| |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case GetButterfly: |
| if (node.child1() == child1) |
| return index; |
| 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 index; |
| // 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 NoNode; |
| |
| case PutByOffset: |
| case PutStructure: |
| // Changing the structure or putting to the storage cannot |
| // change the property storage pointer. |
| break; |
| |
| 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 NoNode; |
| |
| case Arrayify: |
| case ArrayifyToStructure: |
| // We could check if the arrayification could affect our butterfly. |
| // But that seems like it would take Effort. |
| return NoNode; |
| |
| default: |
| if (m_graph.clobbersWorld(index)) |
| return NoNode; |
| break; |
| } |
| } |
| return NoNode; |
| } |
| |
| bool checkArrayElimination(NodeIndex child1, ArrayMode arrayMode) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| if (index == child1) |
| break; |
| |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case PutByOffset: |
| case PutStructure: |
| // Changing the structure or putting to the storage cannot |
| // change the property storage pointer. |
| break; |
| |
| 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(index)) |
| return false; |
| break; |
| } |
| } |
| return false; |
| } |
| |
| NodeIndex getIndexedPropertyStorageLoadElimination(NodeIndex child1, ArrayMode arrayMode) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| if (index == child1) |
| break; |
| |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case GetIndexedPropertyStorage: { |
| if (node.child1() == child1 && node.arrayMode() == arrayMode) |
| return index; |
| break; |
| } |
| |
| case PutByOffset: |
| case PutStructure: |
| // Changing the structure or putting to the storage cannot |
| // change the property storage pointer. |
| break; |
| |
| default: |
| if (m_graph.clobbersWorld(index)) |
| return NoNode; |
| break; |
| } |
| } |
| return NoNode; |
| } |
| |
| NodeIndex getMyScopeLoadElimination(InlineCallFrame* inlineCallFrame) |
| { |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| if (node.codeOrigin.inlineCallFrame != inlineCallFrame) |
| continue; |
| switch (node.op()) { |
| case CreateActivation: |
| // This may cause us to return a different scope. |
| return NoNode; |
| case GetMyScope: |
| return index; |
| case SetMyScope: |
| return node.child1().index(); |
| default: |
| break; |
| } |
| } |
| return NoNode; |
| } |
| |
| NodeIndex getLocalLoadElimination(VirtualRegister local, NodeIndex& relevantLocalOp, bool careAboutClobbering) |
| { |
| relevantLocalOp = NoNode; |
| |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case GetLocal: |
| if (node.local() == local) { |
| relevantLocalOp = index; |
| return index; |
| } |
| break; |
| |
| case GetLocalUnlinked: |
| if (node.unlinkedLocal() == local) { |
| relevantLocalOp = index; |
| return index; |
| } |
| break; |
| |
| case SetLocal: |
| if (node.local() == local) { |
| relevantLocalOp = index; |
| return node.child1().index(); |
| } |
| break; |
| |
| case PutScopedVar: |
| if (static_cast<VirtualRegister>(node.varNumber()) == local) |
| return NoNode; |
| break; |
| |
| default: |
| if (careAboutClobbering && m_graph.clobbersWorld(index)) |
| return NoNode; |
| break; |
| } |
| } |
| return NoNode; |
| } |
| |
| struct SetLocalStoreEliminationResult { |
| SetLocalStoreEliminationResult() |
| : mayBeAccessed(false) |
| , mayExit(false) |
| , mayClobberWorld(false) |
| { |
| } |
| |
| bool mayBeAccessed; |
| bool mayExit; |
| bool mayClobberWorld; |
| }; |
| SetLocalStoreEliminationResult setLocalStoreElimination( |
| VirtualRegister local, NodeIndex expectedNodeIndex) |
| { |
| SetLocalStoreEliminationResult result; |
| for (unsigned i = m_indexInBlock; i--;) { |
| NodeIndex index = m_currentBlock->at(i); |
| Node& node = m_graph[index]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case GetLocal: |
| case Flush: |
| if (node.local() == local) |
| result.mayBeAccessed = true; |
| break; |
| |
| case GetLocalUnlinked: |
| if (node.unlinkedLocal() == local) |
| result.mayBeAccessed = true; |
| break; |
| |
| case SetLocal: { |
| if (node.local() != local) |
| break; |
| if (index != expectedNodeIndex) |
| result.mayBeAccessed = true; |
| if (m_graph[index].refCount() > 1) |
| result.mayBeAccessed = true; |
| return result; |
| } |
| |
| case GetScopedVar: |
| if (static_cast<VirtualRegister>(node.varNumber()) == local) |
| result.mayBeAccessed = true; |
| break; |
| |
| case GetMyScope: |
| case SkipTopScope: |
| if (m_graph.uncheckedActivationRegisterFor(node.codeOrigin) == local) |
| result.mayBeAccessed = true; |
| break; |
| |
| case CheckArgumentsNotCreated: |
| case GetMyArgumentsLength: |
| case GetMyArgumentsLengthSafe: |
| if (m_graph.uncheckedArgumentsRegisterFor(node.codeOrigin) == local) |
| result.mayBeAccessed = true; |
| break; |
| |
| case GetMyArgumentByVal: |
| case GetMyArgumentByValSafe: |
| result.mayBeAccessed = true; |
| break; |
| |
| case GetByVal: |
| // If this is accessing arguments then it's potentially accessing locals. |
| if (m_graph[node.child1()].shouldSpeculateArguments()) |
| result.mayBeAccessed = true; |
| 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. |
| result.mayBeAccessed = true; |
| break; |
| |
| default: |
| break; |
| } |
| result.mayExit |= node.canExit(); |
| result.mayClobberWorld |= m_graph.clobbersWorld(index); |
| } |
| RELEASE_ASSERT_NOT_REACHED(); |
| // Be safe in release mode. |
| result.mayBeAccessed = true; |
| return result; |
| } |
| |
| void performSubstitution(Edge& child, bool addRef = true) |
| { |
| // Check if this operand is actually unused. |
| if (!child) |
| return; |
| |
| // Check if there is any replacement. |
| NodeIndex replacement = m_graph[child.index()].replacement; |
| if (replacement == NoNode) |
| return; |
| |
| child.setIndex(replacement); |
| |
| // There is definitely a replacement. Assert that the replacement does not |
| // have a replacement. |
| ASSERT(m_graph[child.index()].replacement == NoNode); |
| |
| if (addRef) |
| m_graph.ref(child); |
| } |
| |
| 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 (m_graph[edge].flags() & NodeRelevantToOSR) |
| continue; |
| |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLog(" Eliminating edge @", m_compileIndex, " -> @", edge.index()); |
| #endif |
| m_graph.deref(edge); |
| node.children.removeEdgeFromBag(i--); |
| m_changed = true; |
| } |
| } |
| |
| enum PredictionHandlingMode { RequireSamePrediction, AllowPredictionMismatch }; |
| bool setReplacement(NodeIndex replacement, PredictionHandlingMode predictionHandlingMode = RequireSamePrediction) |
| { |
| if (replacement == NoNode) |
| return false; |
| |
| // Be safe. Don't try to perform replacements if the predictions don't |
| // agree. |
| if (predictionHandlingMode == RequireSamePrediction |
| && m_graph[m_compileIndex].prediction() != m_graph[replacement].prediction()) |
| return false; |
| |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Replacing @%u -> @%u", m_compileIndex, replacement); |
| #endif |
| |
| Node& node = m_graph[m_compileIndex]; |
| node.setOpAndDefaultFlags(Phantom); |
| node.setRefCount(1); |
| eliminateIrrelevantPhantomChildren(node); |
| |
| // At this point we will eliminate all references to this node. |
| m_graph[m_compileIndex].replacement = replacement; |
| |
| m_changed = true; |
| |
| return true; |
| } |
| |
| void eliminate() |
| { |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" Eliminating @%u", m_compileIndex); |
| #endif |
| |
| Node& node = m_graph[m_compileIndex]; |
| ASSERT(node.refCount() == 1); |
| ASSERT(node.mustGenerate()); |
| node.setOpAndDefaultFlags(Phantom); |
| eliminateIrrelevantPhantomChildren(node); |
| |
| m_changed = true; |
| } |
| |
| void eliminate(NodeIndex nodeIndex, NodeType phantomType = Phantom) |
| { |
| if (nodeIndex == NoNode) |
| return; |
| Node& node = m_graph[nodeIndex]; |
| if (node.refCount() != 1) |
| return; |
| ASSERT(node.mustGenerate()); |
| node.setOpAndDefaultFlags(phantomType); |
| if (phantomType == Phantom) |
| eliminateIrrelevantPhantomChildren(node); |
| |
| m_changed = true; |
| } |
| |
| void performNodeCSE(Node& node) |
| { |
| 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 (node.op() == SetLocal) |
| m_graph[node.child1()].mergeFlags(NodeRelevantToOSR); |
| |
| if (!shouldGenerate) |
| return; |
| |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF(" %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. |
| |
| switch (node.op()) { |
| |
| case Identity: |
| setReplacement(node.child1().index()); |
| break; |
| |
| // Handle the pure nodes. These nodes never have any side-effects. |
| case BitAnd: |
| case BitOr: |
| case BitXor: |
| 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 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 GetScopeRegisters: |
| case GetScope: |
| case TypeOf: |
| setReplacement(pureCSE(node)); |
| break; |
| |
| case GetCallee: |
| setReplacement(getCalleeLoadElimination(node.codeOrigin.inlineCallFrame)); |
| break; |
| |
| case GetLocal: { |
| VariableAccessData* variableAccessData = node.variableAccessData(); |
| if (!variableAccessData->isCaptured()) |
| break; |
| NodeIndex relevantLocalOp; |
| NodeIndex possibleReplacement = getLocalLoadElimination(variableAccessData->local(), relevantLocalOp, variableAccessData->isCaptured()); |
| if (relevantLocalOp == NoNode) |
| break; |
| if (m_graph[relevantLocalOp].op() != GetLocalUnlinked |
| && m_graph[relevantLocalOp].variableAccessData() != variableAccessData) |
| break; |
| NodeIndex phiIndex = node.child1().index(); |
| if (!setReplacement(possibleReplacement)) |
| break; |
| // If the GetLocal we replaced used to refer to a SetLocal, then it now |
| // should refer to the child of the SetLocal instead. |
| if (m_graph[phiIndex].op() == SetLocal) { |
| ASSERT(node.child1().index() == phiIndex); |
| m_graph.changeEdge(node.children.child1(), m_graph[phiIndex].child1()); |
| } |
| NodeIndex oldTailIndex = m_currentBlock->variablesAtTail.operand( |
| variableAccessData->local()); |
| if (oldTailIndex == m_compileIndex) { |
| m_currentBlock->variablesAtTail.operand(variableAccessData->local()) = |
| relevantLocalOp; |
| |
| // Maintain graph integrity: since we're replacing a GetLocal with a GetLocalUnlinked, |
| // make sure that the GetLocalUnlinked is now linked. |
| if (m_graph[relevantLocalOp].op() == GetLocalUnlinked) { |
| m_graph[relevantLocalOp].setOp(GetLocal); |
| m_graph[relevantLocalOp].children.child1() = Edge(phiIndex); |
| m_graph.ref(phiIndex); |
| } |
| } |
| m_changed = true; |
| break; |
| } |
| |
| case GetLocalUnlinked: { |
| NodeIndex relevantLocalOpIgnored; |
| setReplacement(getLocalLoadElimination(node.unlinkedLocal(), relevantLocalOpIgnored, true)); |
| break; |
| } |
| |
| case Flush: { |
| VariableAccessData* variableAccessData = node.variableAccessData(); |
| VirtualRegister local = variableAccessData->local(); |
| NodeIndex replacementIndex = node.child1().index(); |
| Node& replacement = m_graph[replacementIndex]; |
| 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 (m_graph.m_fixpointState == FixpointNotConverged) { |
| // Need to be conservative at this time; if the SetLocal has any chance of performing |
| // any speculations then we cannot do anything. |
| if (variableAccessData->isCaptured()) { |
| // Captured SetLocals never speculate and hence never exit. |
| } else { |
| if (variableAccessData->shouldUseDoubleFormat()) |
| break; |
| SpeculatedType prediction = variableAccessData->argumentAwarePrediction(); |
| if (isInt32Speculation(prediction)) |
| break; |
| if (isArraySpeculation(prediction)) |
| break; |
| if (isBooleanSpeculation(prediction)) |
| break; |
| } |
| } else { |
| if (replacement.canExit()) |
| break; |
| } |
| SetLocalStoreEliminationResult result = |
| setLocalStoreElimination(local, replacementIndex); |
| if (result.mayBeAccessed || result.mayClobberWorld) |
| break; |
| ASSERT(replacement.op() == SetLocal); |
| ASSERT(replacement.refCount() == 1); |
| ASSERT(replacement.shouldGenerate()); |
| // FIXME: Investigate using mayExit as a further optimization. |
| node.setOpAndDefaultFlags(Phantom); |
| NodeIndex dataNodeIndex = replacement.child1().index(); |
| ASSERT(m_graph[dataNodeIndex].hasResult()); |
| m_graph.clearAndDerefChild1(node); |
| node.children.child1() = Edge(dataNodeIndex); |
| m_graph.ref(dataNodeIndex); |
| NodeIndex oldTailIndex = m_currentBlock->variablesAtTail.operand(local); |
| if (oldTailIndex == m_compileIndex) |
| m_currentBlock->variablesAtTail.operand(local) = replacementIndex; |
| m_changed = true; |
| break; |
| } |
| |
| case JSConstant: |
| // 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), AllowPredictionMismatch); |
| break; |
| |
| case WeakJSConstant: |
| // FIXME: have CSE for weak constants against strong constants and vice-versa. |
| setReplacement(weakConstantCSE(node)); |
| break; |
| |
| case GetArrayLength: |
| setReplacement(getArrayLengthElimination(node.child1().index())); |
| break; |
| |
| case GetMyScope: |
| setReplacement(getMyScopeLoadElimination(node.codeOrigin.inlineCallFrame)); |
| 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 (m_graph.isPredictedNumerical(node)) { |
| NodeIndex replacementIndex = pureCSE(node); |
| if (replacementIndex != NoNode && m_graph.isPredictedNumerical(m_graph[replacementIndex])) |
| setReplacement(replacementIndex); |
| } |
| 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.registerPointer())); |
| break; |
| |
| case GetScopedVar: { |
| setReplacement(scopedVarLoadElimination(node.child1().index(), node.varNumber())); |
| break; |
| } |
| |
| case GlobalVarWatchpoint: |
| if (globalVarWatchpointElimination(node.registerPointer())) |
| eliminate(); |
| break; |
| |
| case PutGlobalVar: |
| case PutGlobalVarCheck: |
| if (m_graph.m_fixpointState == FixpointNotConverged) |
| break; |
| eliminate(globalVarStoreElimination(node.registerPointer())); |
| break; |
| |
| case PutScopedVar: { |
| if (m_graph.m_fixpointState == FixpointNotConverged) |
| break; |
| eliminate(scopedVarStoreElimination(node.child1().index(), node.child2().index(), node.varNumber())); |
| break; |
| } |
| |
| case GetByVal: |
| if (m_graph.byValIsPure(node)) |
| setReplacement(getByValLoadElimination(node.child1().index(), node.child2().index())); |
| break; |
| |
| case PutByVal: { |
| Edge child1 = m_graph.varArgChild(node, 0); |
| Edge child2 = m_graph.varArgChild(node, 1); |
| if (node.arrayMode().canCSEStorage()) { |
| NodeIndex nodeIndex = getByValLoadElimination(child1.index(), child2.index()); |
| if (nodeIndex == NoNode) |
| break; |
| node.setOp(PutByValAlias); |
| } |
| break; |
| } |
| |
| case CheckStructure: |
| case ForwardCheckStructure: |
| if (checkStructureElimination(node.structureSet(), node.child1().index())) |
| eliminate(); |
| break; |
| |
| case StructureTransitionWatchpoint: |
| case ForwardStructureTransitionWatchpoint: |
| if (structureTransitionWatchpointElimination(node.structure(), node.child1().index())) |
| eliminate(); |
| break; |
| |
| case PutStructure: |
| if (m_graph.m_fixpointState == FixpointNotConverged) |
| break; |
| eliminate(putStructureStoreElimination(node.child1().index()), PhantomPutStructure); |
| break; |
| |
| case CheckFunction: |
| if (checkFunctionElimination(node.function(), node.child1().index())) |
| eliminate(); |
| break; |
| |
| case CheckExecutable: |
| if (checkExecutableElimination(node.executable(), node.child1().index())) |
| eliminate(); |
| break; |
| |
| case CheckArray: |
| if (checkArrayElimination(node.child1().index(), node.arrayMode())) |
| eliminate(); |
| break; |
| |
| case GetIndexedPropertyStorage: { |
| setReplacement(getIndexedPropertyStorageLoadElimination(node.child1().index(), node.arrayMode())); |
| break; |
| } |
| |
| case GetButterfly: |
| setReplacement(getPropertyStorageLoadElimination(node.child1().index())); |
| break; |
| |
| case GetByOffset: |
| setReplacement(getByOffsetLoadElimination(m_graph.m_storageAccessData[node.storageAccessDataIndex()].identifierNumber, node.child1().index())); |
| break; |
| |
| case PutByOffset: |
| if (m_graph.m_fixpointState == FixpointNotConverged) |
| break; |
| eliminate(putByOffsetStoreElimination(m_graph.m_storageAccessData[node.storageAccessDataIndex()].identifierNumber, node.child1().index())); |
| break; |
| |
| case Phantom: |
| // FIXME: we ought to remove Phantom's that have no children. |
| |
| eliminateIrrelevantPhantomChildren(node); |
| break; |
| |
| default: |
| // do nothing. |
| break; |
| } |
| |
| m_lastSeen[node.op()] = m_indexInBlock; |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF("\n"); |
| #endif |
| } |
| |
| void performBlockCSE(BasicBlock* block) |
| { |
| if (!block) |
| return; |
| if (!block->isReachable) |
| return; |
| |
| m_currentBlock = block; |
| for (unsigned i = 0; i < LastNodeType; ++i) |
| m_lastSeen[i] = UINT_MAX; |
| |
| // All Phis need to already be marked as relevant to OSR, and have their |
| // replacements cleared, so we don't get confused while doing substitutions on |
| // GetLocal's. |
| for (unsigned i = 0; i < block->phis.size(); ++i) { |
| ASSERT(m_graph[block->phis[i]].flags() & NodeRelevantToOSR); |
| m_graph[block->phis[i]].replacement = NoNode; |
| } |
| |
| // Make all of my SetLocal and GetLocal nodes relevant to OSR, and do some other |
| // necessary bookkeeping. |
| for (unsigned i = 0; i < block->size(); ++i) { |
| NodeIndex nodeIndex = block->at(i); |
| Node& node = m_graph[nodeIndex]; |
| |
| node.replacement = NoNode; |
| |
| 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 (m_indexInBlock = 0; m_indexInBlock < block->size(); ++m_indexInBlock) { |
| m_compileIndex = block->at(m_indexInBlock); |
| performNodeCSE(m_graph[m_compileIndex]); |
| } |
| } |
| |
| BasicBlock* m_currentBlock; |
| NodeIndex m_compileIndex; |
| unsigned m_indexInBlock; |
| FixedArray<unsigned, LastNodeType> m_lastSeen; |
| bool m_changed; // Only tracks changes that have a substantive effect on other optimizations. |
| }; |
| |
| bool performCSE(Graph& graph) |
| { |
| SamplingRegion samplingRegion("DFG CSE Phase"); |
| return runPhase<CSEPhase>(graph); |
| } |
| |
| } } // namespace JSC::DFG |
| |
| #endif // ENABLE(DFG_JIT) |
| |
| |