--- /dev/null
+/*
+ * Copyright (C) 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 "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>
+
+#if PLATFORM(IOS)
+#include "Operations.h"
+#endif
+
+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 are live, using a conservative approximation of
+ // liveness.
+ 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) {
+ Node* node = block->at(indexInBlock);
+ switch (node->op()) {
+ case GetLocal:
+ case Flush:
+ case PhantomLocal:
+ m_isLive.add(node->variableAccessData());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // 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) {
+ Node* node = block->at(indexInBlock);
+ 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 = node->child1().node();
+ 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(source);
+
+ // 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 the variable is totally dead, then ignore it.
+ if (!m_isLive.contains(variableAccessData))
+ 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: /* FIXME: https://bugs.webkit.org/show_bug.cgi?id=108555 */ {
+ 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().node());
+ 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) {
+ Node* node = block->at(indexInBlock);
+ if (node->op() != SetLocal)
+ continue;
+ Node* source = node->child1().node();
+ 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 insertionSet(m_graph);
+
+ 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++) {
+ Node* node = block->at(indexInBlock);
+ switch (node->op()) {
+ case SetLocal: {
+ Node* source = node->child1().node();
+ 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());
+ if (variableAccessData->mergeIsArgumentsAlias(true)) {
+ changed = true;
+
+ // Make sure that the variable knows, that it may now hold non-cell values.
+ variableAccessData->predict(SpecEmpty);
+ }
+
+ // Make sure that the SetLocal doesn't check that the input is a Cell.
+ if (node->child1().useKind() != UntypedUse) {
+ node->child1().setUseKind(UntypedUse);
+ changed = true;
+ }
+ break;
+ }
+
+ case PhantomLocal: {
+ VariableAccessData* variableAccessData = node->variableAccessData();
+
+ if (variableAccessData->isCaptured()
+ || !m_argumentsAliasing.find(variableAccessData)->value.isValid()
+ || m_createsArguments.contains(node->codeOrigin.inlineCallFrame))
+ break;
+
+ // Turn PhantomLocals into just GetLocals. This will preserve the threading
+ // of the local through to this point, but will allow it to die, causing
+ // only OSR to know about it.
+
+ node->setOpAndDefaultFlags(GetLocal);
+ break;
+ }
+
+ case Flush: {
+ VariableAccessData* variableAccessData = node->variableAccessData();
+
+ if (variableAccessData->isCaptured()
+ || !m_argumentsAliasing.find(variableAccessData)->value.isValid()
+ || m_createsArguments.contains(node->codeOrigin.inlineCallFrame))
+ break;
+
+ RELEASE_ASSERT_NOT_REACHED();
+ 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(node->child1().node()))
+ break;
+ node->convertToPhantom();
+ 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(node->child1().node()))
+ break;
+
+ 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(node->child1().node()))
+ break;
+
+ 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.
+ insertionSet.insertNode(
+ indexInBlock, SpecNone, CheckArgumentsNotCreated, codeOrigin);
+
+ m_graph.convertToConstant(
+ node, 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 (!node->child1()->hasConstant())
+ break;
+ JSValue value = 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.
+
+ CodeOrigin codeOrigin = node->codeOrigin;
+ AdjacencyList children = node->children;
+
+ node->convertToGetLocalUnlinked(
+ static_cast<VirtualRegister>(
+ node->codeOrigin.inlineCallFrame->stackOffset +
+ m_graph.baselineCodeBlockFor(node->codeOrigin)->argumentIndexAfterCapture(index)));
+
+ insertionSet.insertNode(
+ indexInBlock, SpecNone, CheckArgumentsNotCreated,
+ codeOrigin);
+ insertionSet.insertNode(
+ indexInBlock, SpecNone, Phantom, codeOrigin,
+ children);
+
+ changed = true;
+ break;
+ }
+
+ case TearOffArguments: {
+ if (m_createsArguments.contains(node->codeOrigin.inlineCallFrame))
+ continue;
+
+ node->setOpAndDefaultFlags(Nop);
+ m_graph.clearAndDerefChild1(node);
+ m_graph.clearAndDerefChild2(node);
+ 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) {
+ Node* node = block->at(indexInBlock);
+ 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;
+ insertionSet.insertNode(
+ indexInBlock, SpecNone, Phantom, node->codeOrigin, node->children);
+ node->setOpAndDefaultFlags(PhantomArguments);
+ node->children.reset();
+ changed = true;
+ }
+ insertionSet.execute(block);
+ }
+
+ if (changed) {
+ m_graph.dethread();
+ m_graph.m_form = LoadStore;
+ }
+
+ return changed;
+ }
+
+private:
+ HashSet<InlineCallFrame*,
+ DefaultHash<InlineCallFrame*>::Hash,
+ NullableHashTraits<InlineCallFrame*> > m_createsArguments;
+ HashMap<VariableAccessData*, ArgumentsAliasingData,
+ DefaultHash<VariableAccessData*>::Hash,
+ NullableHashTraits<VariableAccessData*> > m_argumentsAliasing;
+ HashSet<VariableAccessData*> m_isLive;
+
+ void observeBadArgumentsUse(Node* node)
+ {
+ if (!node)
+ return;
+
+ switch (node->op()) {
+ case CreateArguments: {
+ m_createsArguments.add(node->codeOrigin.inlineCallFrame);
+ break;
+ }
+
+ case GetLocal: {
+ int argumentsRegister = m_graph.uncheckedArgumentsRegisterFor(node->codeOrigin);
+ if (argumentsRegister != InvalidVirtualRegister
+ && (node->local() == argumentsRegister
+ || node->local() == unmodifiedArgumentsRegister(argumentsRegister))) {
+ m_createsArguments.add(node->codeOrigin.inlineCallFrame);
+ break;
+ }
+
+ VariableAccessData* variableAccessData = node->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).node());
+ }
+
+ void observeProperArgumentsUse(Node* node, Edge edge)
+ {
+ if (edge->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 (edge->op() == CreateArguments
+ && node->codeOrigin.inlineCallFrame
+ != edge->codeOrigin.inlineCallFrame)
+ m_createsArguments.add(edge->codeOrigin.inlineCallFrame);
+
+ return;
+ }
+
+ VariableAccessData* variableAccessData = edge->variableAccessData();
+ if (edge->local() == m_graph.uncheckedArgumentsRegisterFor(edge->codeOrigin)
+ && node->codeOrigin.inlineCallFrame != edge->codeOrigin.inlineCallFrame) {
+ m_createsArguments.add(edge->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;
+
+ switch (edge->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 = edge->variableAccessData();
+ bool isDeadArgumentsRegister =
+ variableAccessData->local() ==
+ m_graph.uncheckedArgumentsRegisterFor(edge->codeOrigin)
+ && !m_createsArguments.contains(edge->codeOrigin.inlineCallFrame);
+ bool isAliasedArgumentsRegister =
+ !variableAccessData->isCaptured()
+ && m_argumentsAliasing.find(variableAccessData)->value.isValid()
+ && !m_createsArguments.contains(edge->codeOrigin.inlineCallFrame);
+ if (!isDeadArgumentsRegister && !isAliasedArgumentsRegister)
+ break;
+ node->children.removeEdge(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(edge->codeOrigin.inlineCallFrame))
+ break;
+ node->children.removeEdge(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)
+
+