| /* |
| * Copyright (C) 2012 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "DFGArgumentsSimplificationPhase.h" |
| |
| #if ENABLE(DFG_JIT) |
| |
| #include "DFGAbstractState.h" |
| #include "DFGBasicBlock.h" |
| #include "DFGGraph.h" |
| #include "DFGInsertionSet.h" |
| #include "DFGPhase.h" |
| #include "DFGValidate.h" |
| #include "DFGVariableAccessDataDump.h" |
| #include <wtf/HashSet.h> |
| #include <wtf/HashMap.h> |
| |
| namespace JSC { namespace DFG { |
| |
| namespace { |
| |
| struct ArgumentsAliasingData { |
| InlineCallFrame* callContext; |
| bool callContextSet; |
| bool multipleCallContexts; |
| |
| bool assignedFromArguments; |
| bool assignedFromManyThings; |
| |
| bool escapes; |
| |
| ArgumentsAliasingData() |
| : callContext(0) |
| , callContextSet(false) |
| , multipleCallContexts(false) |
| , assignedFromArguments(false) |
| , assignedFromManyThings(false) |
| , escapes(false) |
| { |
| } |
| |
| void mergeCallContext(InlineCallFrame* newCallContext) |
| { |
| if (multipleCallContexts) |
| return; |
| |
| if (!callContextSet) { |
| callContext = newCallContext; |
| callContextSet = true; |
| return; |
| } |
| |
| if (callContext == newCallContext) |
| return; |
| |
| multipleCallContexts = true; |
| } |
| |
| bool callContextIsValid() |
| { |
| return callContextSet && !multipleCallContexts; |
| } |
| |
| void mergeArgumentsAssignment() |
| { |
| assignedFromArguments = true; |
| } |
| |
| void mergeNonArgumentsAssignment() |
| { |
| assignedFromManyThings = true; |
| } |
| |
| bool argumentsAssignmentIsValid() |
| { |
| return assignedFromArguments && !assignedFromManyThings; |
| } |
| |
| bool isValid() |
| { |
| return callContextIsValid() && argumentsAssignmentIsValid() && !escapes; |
| } |
| }; |
| |
| } // end anonymous namespace |
| |
| class ArgumentsSimplificationPhase : public Phase { |
| public: |
| ArgumentsSimplificationPhase(Graph& graph) |
| : Phase(graph, "arguments simplification") |
| { |
| } |
| |
| bool run() |
| { |
| if (!m_graph.m_hasArguments) |
| return false; |
| |
| bool changed = false; |
| |
| // Record which arguments are known to escape no matter what. |
| for (unsigned i = codeBlock()->inlineCallFrames().size(); i--;) { |
| InlineCallFrame* inlineCallFrame = &codeBlock()->inlineCallFrames()[i]; |
| if (m_graph.m_executablesWhoseArgumentsEscaped.contains( |
| m_graph.executableFor(inlineCallFrame))) |
| m_createsArguments.add(inlineCallFrame); |
| } |
| |
| // Create data for variable access datas that we will want to analyze. |
| for (unsigned i = m_graph.m_variableAccessData.size(); i--;) { |
| VariableAccessData* variableAccessData = &m_graph.m_variableAccessData[i]; |
| if (!variableAccessData->isRoot()) |
| continue; |
| if (variableAccessData->isCaptured()) |
| continue; |
| m_argumentsAliasing.add(variableAccessData, ArgumentsAliasingData()); |
| } |
| |
| // Figure out which variables alias the arguments and nothing else, and are |
| // used only for GetByVal and GetArrayLength accesses. At the same time, |
| // identify uses of CreateArguments that are not consistent with the arguments |
| // being aliased only to variables that satisfy these constraints. |
| for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) { |
| BasicBlock* block = m_graph.m_blocks[blockIndex].get(); |
| if (!block) |
| continue; |
| for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) { |
| NodeIndex nodeIndex = block->at(indexInBlock); |
| Node& node = m_graph[nodeIndex]; |
| if (!node.shouldGenerate()) |
| continue; |
| switch (node.op()) { |
| case CreateArguments: { |
| // Ignore this op. If we see a lone CreateArguments then we want to |
| // completely ignore it because: |
| // 1) The default would be to see that the child is a GetLocal on the |
| // arguments register and conclude that we have an arguments escape. |
| // 2) The fact that a CreateArguments exists does not mean that it |
| // will continue to exist after we're done with this phase. As far |
| // as this phase is concerned, a CreateArguments only "exists" if it |
| // is used in a manner that necessitates its existance. |
| break; |
| } |
| |
| case TearOffArguments: { |
| // Ignore arguments tear off, because it's only relevant if we actually |
| // need to create the arguments. |
| break; |
| } |
| |
| case SetLocal: { |
| Node& source = m_graph[node.child1()]; |
| VariableAccessData* variableAccessData = node.variableAccessData(); |
| int argumentsRegister = |
| m_graph.uncheckedArgumentsRegisterFor(node.codeOrigin); |
| if (source.op() != CreateArguments && source.op() != PhantomArguments) { |
| // Make sure that the source of the SetLocal knows that if it's |
| // a variable that we think is aliased to the arguments, then it |
| // may escape at this point. In future, we could track transitive |
| // aliasing. But not yet. |
| observeBadArgumentsUse(node.child1()); |
| |
| // If this is an assignment to the arguments register, then |
| // pretend as if the arguments were created. We don't want to |
| // optimize code that explicitly assigns to the arguments, |
| // because that seems too ugly. |
| |
| // But, before getting rid of CreateArguments, we will have |
| // an assignment to the arguments registers with JSValue(). |
| // That's because CSE will refuse to get rid of the |
| // init_lazy_reg since it treats CreateArguments as reading |
| // local variables. That could be fixed, but it's easier to |
| // work around this here. |
| if (source.op() == JSConstant |
| && !source.valueOfJSConstant(codeBlock())) |
| break; |
| |
| if (argumentsRegister != InvalidVirtualRegister |
| && (variableAccessData->local() == argumentsRegister |
| || variableAccessData->local() == unmodifiedArgumentsRegister(argumentsRegister))) { |
| m_createsArguments.add(node.codeOrigin.inlineCallFrame); |
| break; |
| } |
| |
| if (variableAccessData->isCaptured()) |
| break; |
| |
| // Make sure that if it's a variable that we think is aliased to |
| // the arguments, that we know that it might actually not be. |
| ArgumentsAliasingData& data = |
| m_argumentsAliasing.find(variableAccessData)->value; |
| data.mergeNonArgumentsAssignment(); |
| data.mergeCallContext(node.codeOrigin.inlineCallFrame); |
| break; |
| } |
| if (argumentsRegister != InvalidVirtualRegister |
| && (variableAccessData->local() == argumentsRegister |
| || variableAccessData->local() == unmodifiedArgumentsRegister(argumentsRegister))) { |
| if (node.codeOrigin.inlineCallFrame == source.codeOrigin.inlineCallFrame) |
| break; |
| m_createsArguments.add(source.codeOrigin.inlineCallFrame); |
| break; |
| } |
| if (variableAccessData->isCaptured()) { |
| m_createsArguments.add(source.codeOrigin.inlineCallFrame); |
| break; |
| } |
| ArgumentsAliasingData& data = |
| m_argumentsAliasing.find(variableAccessData)->value; |
| data.mergeArgumentsAssignment(); |
| // This ensures that the variable's uses are in the same context as |
| // the arguments it is aliasing. |
| data.mergeCallContext(node.codeOrigin.inlineCallFrame); |
| data.mergeCallContext(source.codeOrigin.inlineCallFrame); |
| break; |
| } |
| |
| case GetLocal: |
| case Phi: { |
| VariableAccessData* variableAccessData = node.variableAccessData(); |
| if (variableAccessData->isCaptured()) |
| break; |
| ArgumentsAliasingData& data = |
| m_argumentsAliasing.find(variableAccessData)->value; |
| data.mergeCallContext(node.codeOrigin.inlineCallFrame); |
| break; |
| } |
| |
| case Flush: { |
| VariableAccessData* variableAccessData = node.variableAccessData(); |
| if (variableAccessData->isCaptured()) |
| break; |
| ArgumentsAliasingData& data = |
| m_argumentsAliasing.find(variableAccessData)->value; |
| data.mergeCallContext(node.codeOrigin.inlineCallFrame); |
| |
| // If a variable is used in a flush then by definition it escapes. |
| data.escapes = true; |
| break; |
| } |
| |
| case SetArgument: { |
| VariableAccessData* variableAccessData = node.variableAccessData(); |
| if (variableAccessData->isCaptured()) |
| break; |
| ArgumentsAliasingData& data = |
| m_argumentsAliasing.find(variableAccessData)->value; |
| data.mergeNonArgumentsAssignment(); |
| data.mergeCallContext(node.codeOrigin.inlineCallFrame); |
| break; |
| } |
| |
| case GetByVal: { |
| if (node.arrayMode().type() != Array::Arguments) { |
| observeBadArgumentsUses(node); |
| break; |
| } |
| |
| // That's so awful and pretty much impossible since it would |
| // imply that the arguments were predicted integer, but it's |
| // good to be defensive and thorough. |
| observeBadArgumentsUse(node.child2()); |
| observeProperArgumentsUse(node, node.child1()); |
| break; |
| } |
| |
| case GetArrayLength: { |
| if (node.arrayMode().type() != Array::Arguments) { |
| observeBadArgumentsUses(node); |
| break; |
| } |
| |
| observeProperArgumentsUse(node, node.child1()); |
| break; |
| } |
| |
| case Phantom: |
| // We don't care about phantom uses, since phantom uses are all about |
| // just keeping things alive for OSR exit. If something - like the |
| // CreateArguments - is just being kept alive, then this transformation |
| // will not break this, since the Phantom will now just keep alive a |
| // PhantomArguments and OSR exit will still do the right things. |
| break; |
| |
| case CheckStructure: |
| case ForwardCheckStructure: |
| case StructureTransitionWatchpoint: |
| case ForwardStructureTransitionWatchpoint: |
| case CheckArray: |
| // We don't care about these because if we get uses of the relevant |
| // variable then we can safely get rid of these, too. This of course |
| // relies on there not being any information transferred by the CFA |
| // from a CheckStructure on one variable to the information about the |
| // structures of another variable. |
| break; |
| |
| default: |
| observeBadArgumentsUses(node); |
| break; |
| } |
| } |
| } |
| |
| // Now we know which variables are aliased to arguments. But if any of them are |
| // found to have escaped, or were otherwise invalidated, then we need to mark |
| // the arguments as requiring creation. This is a property of SetLocals to |
| // variables that are neither the correct arguments register nor are marked as |
| // being arguments-aliased. |
| for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) { |
| BasicBlock* block = m_graph.m_blocks[blockIndex].get(); |
| if (!block) |
| continue; |
| for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) { |
| NodeIndex nodeIndex = block->at(indexInBlock); |
| Node& node = m_graph[nodeIndex]; |
| if (!node.shouldGenerate()) |
| continue; |
| if (node.op() != SetLocal) |
| continue; |
| Node& source = m_graph[node.child1()]; |
| if (source.op() != CreateArguments) |
| continue; |
| VariableAccessData* variableAccessData = node.variableAccessData(); |
| if (variableAccessData->isCaptured()) { |
| // The captured case would have already been taken care of in the |
| // previous pass. |
| continue; |
| } |
| |
| ArgumentsAliasingData& data = |
| m_argumentsAliasing.find(variableAccessData)->value; |
| if (data.isValid()) |
| continue; |
| |
| m_createsArguments.add(source.codeOrigin.inlineCallFrame); |
| } |
| } |
| |
| #if DFG_ENABLE(DEBUG_PROPAGATION_VERBOSE) |
| dataLogF("Arguments aliasing states:\n"); |
| for (unsigned i = 0; i < m_graph.m_variableAccessData.size(); ++i) { |
| VariableAccessData* variableAccessData = &m_graph.m_variableAccessData[i]; |
| if (!variableAccessData->isRoot()) |
| continue; |
| dataLog(" r", variableAccessData->local(), "(", VariableAccessDataDump(m_graph, variableAccessData), "): "); |
| if (variableAccessData->isCaptured()) |
| dataLogF("Captured"); |
| else { |
| ArgumentsAliasingData& data = |
| m_argumentsAliasing.find(variableAccessData)->value; |
| bool first = true; |
| if (data.callContextIsValid()) { |
| if (!first) |
| dataLogF(", "); |
| dataLogF("Have Call Context: %p", data.callContext); |
| first = false; |
| if (!m_createsArguments.contains(data.callContext)) |
| dataLogF(" (Does Not Create Arguments)"); |
| } |
| if (data.argumentsAssignmentIsValid()) { |
| if (!first) |
| dataLogF(", "); |
| dataLogF("Arguments Assignment Is Valid"); |
| first = false; |
| } |
| if (!data.escapes) { |
| if (!first) |
| dataLogF(", "); |
| dataLogF("Does Not Escape"); |
| first = false; |
| } |
| if (!first) |
| dataLogF(", "); |
| if (data.isValid()) { |
| if (m_createsArguments.contains(data.callContext)) |
| dataLogF("VALID"); |
| else |
| dataLogF("INVALID (due to argument creation)"); |
| } else |
| dataLogF("INVALID (due to bad variable use)"); |
| } |
| dataLogF("\n"); |
| } |
| #endif |
| |
| InsertionSet<NodeIndex> insertionSet; |
| |
| for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) { |
| BasicBlock* block = m_graph.m_blocks[blockIndex].get(); |
| if (!block) |
| continue; |
| for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) { |
| NodeIndex nodeIndex = block->at(indexInBlock); |
| Node& node = m_graph[nodeIndex]; |
| if (!node.shouldGenerate()) |
| continue; |
| |
| switch (node.op()) { |
| case SetLocal: { |
| Node& source = m_graph[node.child1()]; |
| if (source.op() != CreateArguments) |
| break; |
| |
| if (m_createsArguments.contains(source.codeOrigin.inlineCallFrame)) |
| break; |
| |
| VariableAccessData* variableAccessData = node.variableAccessData(); |
| |
| if (m_graph.argumentsRegisterFor(node.codeOrigin) == variableAccessData->local() |
| || unmodifiedArgumentsRegister(m_graph.argumentsRegisterFor(node.codeOrigin)) == variableAccessData->local()) |
| break; |
| |
| ASSERT(!variableAccessData->isCaptured()); |
| |
| // If this is a store into a VariableAccessData* that is marked as |
| // arguments aliasing for an InlineCallFrame* that does not create |
| // arguments, then flag the VariableAccessData as being an |
| // arguments-aliased. This'll let the OSR exit machinery do the right |
| // things. Note also that the SetLocal should become dead as soon as |
| // we replace all uses of this variable with GetMyArgumentsLength and |
| // GetMyArgumentByVal. |
| ASSERT(m_argumentsAliasing.find(variableAccessData)->value.isValid()); |
| changed |= variableAccessData->mergeIsArgumentsAlias(true); |
| break; |
| } |
| |
| case Phantom: { |
| // It's highly likely that we will have a Phantom referencing either |
| // CreateArguments, or a local op for the arguments register, or a |
| // local op for an arguments-aliased variable. In any of those cases, |
| // we should remove the phantom reference, since: |
| // 1) Phantoms only exist to aid OSR exit. But arguments simplification |
| // has its own OSR exit story, which is to inform OSR exit to reify |
| // the arguments as necessary. |
| // 2) The Phantom may keep the CreateArguments node alive, which is |
| // precisely what we don't want. |
| for (unsigned i = 0; i < AdjacencyList::Size; ++i) |
| removeArgumentsReferencingPhantomChild(node, i); |
| break; |
| } |
| |
| case CheckStructure: |
| case ForwardCheckStructure: |
| case StructureTransitionWatchpoint: |
| case ForwardStructureTransitionWatchpoint: |
| case CheckArray: { |
| // We can just get rid of this node, if it references a phantom argument. |
| if (!isOKToOptimize(m_graph[node.child1()])) |
| break; |
| m_graph.deref(node.child1()); |
| node.setOpAndDefaultFlags(Phantom); |
| node.children.setChild1(Edge()); |
| break; |
| } |
| |
| case GetByVal: { |
| if (node.arrayMode().type() != Array::Arguments) |
| break; |
| |
| // This can be simplified to GetMyArgumentByVal if we know that |
| // it satisfies either condition (1) or (2): |
| // 1) Its first child is a valid ArgumentsAliasingData and the |
| // InlineCallFrame* is not marked as creating arguments. |
| // 2) Its first child is CreateArguments and its InlineCallFrame* |
| // is not marked as creating arguments. |
| |
| if (!isOKToOptimize(m_graph[node.child1()])) |
| break; |
| |
| m_graph.deref(node.child1()); |
| node.children.child1() = node.children.child2(); |
| node.children.child2() = Edge(); |
| node.setOpAndDefaultFlags(GetMyArgumentByVal); |
| changed = true; |
| --indexInBlock; // Force reconsideration of this op now that it's a GetMyArgumentByVal. |
| break; |
| } |
| |
| case GetArrayLength: { |
| if (node.arrayMode().type() != Array::Arguments) |
| break; |
| |
| if (!isOKToOptimize(m_graph[node.child1()])) |
| break; |
| |
| m_graph.deref(node.child1()); |
| node.children.child1() = Edge(); |
| node.setOpAndDefaultFlags(GetMyArgumentsLength); |
| changed = true; |
| --indexInBlock; // Force reconsideration of this op noew that it's a GetMyArgumentsLength. |
| break; |
| } |
| |
| case GetMyArgumentsLength: |
| case GetMyArgumentsLengthSafe: { |
| if (m_createsArguments.contains(node.codeOrigin.inlineCallFrame)) { |
| ASSERT(node.op() == GetMyArgumentsLengthSafe); |
| break; |
| } |
| if (node.op() == GetMyArgumentsLengthSafe) { |
| node.setOp(GetMyArgumentsLength); |
| changed = true; |
| } |
| |
| CodeOrigin codeOrigin = node.codeOrigin; |
| if (!codeOrigin.inlineCallFrame) |
| break; |
| |
| // We know exactly what this will return. But only after we have checked |
| // that nobody has escaped our arguments. |
| Node check(CheckArgumentsNotCreated, codeOrigin); |
| check.ref(); |
| NodeIndex checkIndex = m_graph.size(); |
| m_graph.append(check); |
| insertionSet.append(indexInBlock, checkIndex); |
| |
| m_graph.convertToConstant( |
| nodeIndex, jsNumber(codeOrigin.inlineCallFrame->arguments.size() - 1)); |
| changed = true; |
| break; |
| } |
| |
| case GetMyArgumentByVal: |
| case GetMyArgumentByValSafe: { |
| if (m_createsArguments.contains(node.codeOrigin.inlineCallFrame)) { |
| ASSERT(node.op() == GetMyArgumentByValSafe); |
| break; |
| } |
| if (node.op() == GetMyArgumentByValSafe) { |
| node.setOp(GetMyArgumentByVal); |
| changed = true; |
| } |
| if (!node.codeOrigin.inlineCallFrame) |
| break; |
| if (!m_graph[node.child1()].hasConstant()) |
| break; |
| JSValue value = m_graph[node.child1()].valueOfJSConstant(codeBlock()); |
| if (!value.isInt32()) |
| break; |
| int32_t index = value.asInt32(); |
| if (index < 0 |
| || static_cast<size_t>(index + 1) >= |
| node.codeOrigin.inlineCallFrame->arguments.size()) |
| break; |
| |
| // We know which argument this is accessing. But only after we have checked |
| // that nobody has escaped our arguments. We also need to ensure that the |
| // index is kept alive. That's somewhat pointless since it's a constant, but |
| // it's important because this is one of those invariants that we like to |
| // have in the DFG. Note finally that we use the GetLocalUnlinked opcode |
| // here, since this is being done _after_ the prediction propagation phase |
| // has run - therefore it makes little sense to link the GetLocal operation |
| // into the VariableAccessData and Phi graphs. |
| |
| Node check(CheckArgumentsNotCreated, node.codeOrigin); |
| check.ref(); |
| |
| Node phantom(Phantom, node.codeOrigin); |
| phantom.ref(); |
| phantom.children = node.children; |
| |
| node.convertToGetLocalUnlinked( |
| static_cast<VirtualRegister>( |
| node.codeOrigin.inlineCallFrame->stackOffset + |
| m_graph.baselineCodeBlockFor(node.codeOrigin)->argumentIndexAfterCapture(index))); |
| |
| NodeIndex checkNodeIndex = m_graph.size(); |
| m_graph.append(check); |
| insertionSet.append(indexInBlock, checkNodeIndex); |
| NodeIndex phantomNodeIndex = m_graph.size(); |
| m_graph.append(phantom); |
| insertionSet.append(indexInBlock, phantomNodeIndex); |
| |
| changed = true; |
| break; |
| } |
| |
| case TearOffArguments: { |
| if (m_createsArguments.contains(node.codeOrigin.inlineCallFrame)) |
| continue; |
| |
| node.setOpAndDefaultFlags(Nop); |
| m_graph.clearAndDerefChild1(node); |
| m_graph.clearAndDerefChild2(node); |
| node.setRefCount(0); |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| insertionSet.execute(*block); |
| } |
| |
| for (BlockIndex blockIndex = 0; blockIndex < m_graph.m_blocks.size(); ++blockIndex) { |
| BasicBlock* block = m_graph.m_blocks[blockIndex].get(); |
| if (!block) |
| continue; |
| for (unsigned indexInBlock = 0; indexInBlock < block->size(); ++indexInBlock) { |
| NodeIndex nodeIndex = block->at(indexInBlock); |
| Node& node = m_graph[nodeIndex]; |
| if (node.op() != CreateArguments) |
| continue; |
| // If this is a CreateArguments for an InlineCallFrame* that does |
| // not create arguments, then replace it with a PhantomArguments. |
| // PhantomArguments is a non-executing node that just indicates |
| // that the node should be reified as an arguments object on OSR |
| // exit. |
| if (m_createsArguments.contains(node.codeOrigin.inlineCallFrame)) |
| continue; |
| if (node.shouldGenerate()) { |
| Node phantom(Phantom, node.codeOrigin); |
| phantom.children = node.children; |
| phantom.ref(); |
| NodeIndex phantomNodeIndex = m_graph.size(); |
| m_graph.append(phantom); |
| insertionSet.append(indexInBlock, phantomNodeIndex); |
| } |
| node.setOpAndDefaultFlags(PhantomArguments); |
| node.children.reset(); |
| changed = true; |
| } |
| insertionSet.execute(*block); |
| } |
| |
| if (changed) |
| m_graph.collectGarbage(); |
| |
| return changed; |
| } |
| |
| private: |
| HashSet<InlineCallFrame*, |
| DefaultHash<InlineCallFrame*>::Hash, |
| NullableHashTraits<InlineCallFrame*> > m_createsArguments; |
| HashMap<VariableAccessData*, ArgumentsAliasingData, |
| DefaultHash<VariableAccessData*>::Hash, |
| NullableHashTraits<VariableAccessData*> > m_argumentsAliasing; |
| |
| void observeBadArgumentsUse(Edge edge) |
| { |
| if (!edge) |
| return; |
| |
| Node& child = m_graph[edge]; |
| switch (child.op()) { |
| case CreateArguments: { |
| m_createsArguments.add(child.codeOrigin.inlineCallFrame); |
| break; |
| } |
| |
| case GetLocal: { |
| int argumentsRegister = m_graph.uncheckedArgumentsRegisterFor(child.codeOrigin); |
| if (argumentsRegister != InvalidVirtualRegister |
| && (child.local() == argumentsRegister |
| || child.local() == unmodifiedArgumentsRegister(argumentsRegister))) { |
| m_createsArguments.add(child.codeOrigin.inlineCallFrame); |
| break; |
| } |
| |
| VariableAccessData* variableAccessData = child.variableAccessData(); |
| if (variableAccessData->isCaptured()) |
| break; |
| |
| ArgumentsAliasingData& data = m_argumentsAliasing.find(variableAccessData)->value; |
| data.escapes = true; |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| |
| void observeBadArgumentsUses(Node& node) |
| { |
| for (unsigned i = m_graph.numChildren(node); i--;) |
| observeBadArgumentsUse(m_graph.child(node, i)); |
| } |
| |
| void observeProperArgumentsUse(Node& node, Edge edge) |
| { |
| Node& child = m_graph[edge]; |
| if (child.op() != GetLocal) { |
| // When can this happen? At least two cases that I can think |
| // of: |
| // |
| // 1) Aliased use of arguments in the same basic block, |
| // like: |
| // |
| // var a = arguments; |
| // var x = arguments[i]; |
| // |
| // 2) If we're accessing arguments we got from the heap! |
| |
| if (child.op() == CreateArguments |
| && node.codeOrigin.inlineCallFrame |
| != child.codeOrigin.inlineCallFrame) |
| m_createsArguments.add(child.codeOrigin.inlineCallFrame); |
| |
| return; |
| } |
| |
| VariableAccessData* variableAccessData = child.variableAccessData(); |
| if (child.local() == m_graph.uncheckedArgumentsRegisterFor(child.codeOrigin) |
| && node.codeOrigin.inlineCallFrame != child.codeOrigin.inlineCallFrame) { |
| m_createsArguments.add(child.codeOrigin.inlineCallFrame); |
| return; |
| } |
| |
| if (variableAccessData->isCaptured()) |
| return; |
| |
| ArgumentsAliasingData& data = m_argumentsAliasing.find(variableAccessData)->value; |
| data.mergeCallContext(node.codeOrigin.inlineCallFrame); |
| } |
| |
| bool isOKToOptimize(Node& source) |
| { |
| if (m_createsArguments.contains(source.codeOrigin.inlineCallFrame)) |
| return false; |
| |
| switch (source.op()) { |
| case GetLocal: { |
| VariableAccessData* variableAccessData = source.variableAccessData(); |
| int argumentsRegister = m_graph.uncheckedArgumentsRegisterFor(source.codeOrigin); |
| if (argumentsRegister == InvalidVirtualRegister) |
| break; |
| if (argumentsRegister == variableAccessData->local()) |
| return true; |
| if (unmodifiedArgumentsRegister(argumentsRegister) == variableAccessData->local()) |
| return true; |
| if (variableAccessData->isCaptured()) |
| break; |
| ArgumentsAliasingData& data = |
| m_argumentsAliasing.find(variableAccessData)->value; |
| if (!data.isValid()) |
| break; |
| |
| return true; |
| } |
| |
| case CreateArguments: { |
| return true; |
| } |
| |
| default: |
| break; |
| } |
| |
| return false; |
| } |
| |
| void removeArgumentsReferencingPhantomChild(Node& node, unsigned edgeIndex) |
| { |
| Edge edge = node.children.child(edgeIndex); |
| if (!edge) |
| return; |
| |
| Node& child = m_graph[edge]; |
| switch (child.op()) { |
| case Phi: // Arises if we had CSE on a GetLocal of the arguments register. |
| case GetLocal: // Arises if we had CSE on an arguments access to a variable aliased to the arguments. |
| case SetLocal: { // Arises if we had CSE on a GetLocal of the arguments register. |
| VariableAccessData* variableAccessData = child.variableAccessData(); |
| bool isDeadArgumentsRegister = |
| variableAccessData->local() == |
| m_graph.uncheckedArgumentsRegisterFor(child.codeOrigin) |
| && !m_createsArguments.contains(child.codeOrigin.inlineCallFrame); |
| bool isAliasedArgumentsRegister = |
| !variableAccessData->isCaptured() |
| && m_argumentsAliasing.find(variableAccessData)->value.isValid() |
| && !m_createsArguments.contains(child.codeOrigin.inlineCallFrame); |
| if (!isDeadArgumentsRegister && !isAliasedArgumentsRegister) |
| break; |
| m_graph.deref(edge); |
| node.children.removeEdgeFromBag(edgeIndex); |
| break; |
| } |
| |
| case CreateArguments: { // Arises if we CSE two GetLocals to the arguments register and then CSE the second use of the GetLocal to the first. |
| if (m_createsArguments.contains(child.codeOrigin.inlineCallFrame)) |
| break; |
| m_graph.deref(edge); |
| node.children.removeEdgeFromBag(edgeIndex); |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| }; |
| |
| bool performArgumentsSimplification(Graph& graph) |
| { |
| SamplingRegion samplingRegion("DFG Arguments Simplification Phase"); |
| return runPhase<ArgumentsSimplificationPhase>(graph); |
| } |
| |
| } } // namespace JSC::DFG |
| |
| #endif // ENABLE(DFG_JIT) |
| |
| |