diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp')
-rw-r--r-- | Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp | 1087 |
1 files changed, 1087 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp b/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp new file mode 100644 index 000000000..b93be30cd --- /dev/null +++ b/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp @@ -0,0 +1,1087 @@ +/* + * Copyright (C) 2015-2016 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 "DFGArgumentsEliminationPhase.h" + +#if ENABLE(DFG_JIT) + +#include "ArrayPrototype.h" +#include "BytecodeLivenessAnalysisInlines.h" +#include "ClonedArguments.h" +#include "DFGArgumentsUtilities.h" +#include "DFGBasicBlockInlines.h" +#include "DFGBlockMapInlines.h" +#include "DFGClobberize.h" +#include "DFGCombinedLiveness.h" +#include "DFGForAllKills.h" +#include "DFGGraph.h" +#include "DFGInsertionSet.h" +#include "DFGLivenessAnalysisPhase.h" +#include "DFGOSRAvailabilityAnalysisPhase.h" +#include "DFGPhase.h" +#include "JSCInlines.h" +#include <wtf/HashSet.h> +#include <wtf/ListDump.h> + +namespace JSC { namespace DFG { + +namespace { + +bool verbose = false; + +class ArgumentsEliminationPhase : public Phase { +public: + ArgumentsEliminationPhase(Graph& graph) + : Phase(graph, "arguments elimination") + { + } + + bool run() + { + // For now this phase only works on SSA. This could be changed; we could have a block-local + // version over LoadStore. + DFG_ASSERT(m_graph, nullptr, m_graph.m_form == SSA); + + if (verbose) { + dataLog("Graph before arguments elimination:\n"); + m_graph.dump(); + } + + identifyCandidates(); + if (m_candidates.isEmpty()) + return false; + + eliminateCandidatesThatEscape(); + if (m_candidates.isEmpty()) + return false; + + eliminateCandidatesThatInterfere(); + if (m_candidates.isEmpty()) + return false; + + transform(); + + return true; + } + +private: + // Just finds nodes that we know how to work with. + void identifyCandidates() + { + for (BasicBlock* block : m_graph.blocksInPreOrder()) { + for (Node* node : *block) { + switch (node->op()) { + case CreateDirectArguments: + case CreateClonedArguments: + m_candidates.add(node); + break; + + case CreateRest: + if (m_graph.isWatchingHavingABadTimeWatchpoint(node)) { + // If we're watching the HavingABadTime watchpoint it means that we will be invalidated + // when it fires (it may or may not have actually fired yet). We don't try to eliminate + // this allocation when we're not watching the watchpoint because it could entail calling + // indexed accessors (and probably more crazy things) on out of bound accesses to the + // rest parameter. It's also much easier to reason about this way. + m_candidates.add(node); + } + break; + + case Spread: + if (m_graph.isWatchingHavingABadTimeWatchpoint(node)) { + // We check ArrayUse here because ArrayUse indicates that the iterator + // protocol for Arrays is non-observable by user code (e.g, it hasn't + // been changed). + if (node->child1().useKind() == ArrayUse && node->child1()->op() == CreateRest && m_candidates.contains(node->child1().node())) + m_candidates.add(node); + } + break; + + case NewArrayWithSpread: { + if (m_graph.isWatchingHavingABadTimeWatchpoint(node)) { + BitVector* bitVector = node->bitVector(); + // We only allow for Spreads to be of rest nodes for now. + bool isOK = true; + for (unsigned i = 0; i < node->numChildren(); i++) { + if (bitVector->get(i)) { + Node* child = m_graph.varArgChild(node, i).node(); + isOK = child->op() == Spread && child->child1()->op() == CreateRest && m_candidates.contains(child); + if (!isOK) + break; + } + } + + if (!isOK) + break; + + m_candidates.add(node); + } + break; + } + + case CreateScopedArguments: + // FIXME: We could handle this if it wasn't for the fact that scoped arguments are + // always stored into the activation. + // https://bugs.webkit.org/show_bug.cgi?id=143072 and + // https://bugs.webkit.org/show_bug.cgi?id=143073 + break; + + default: + break; + } + } + } + + if (verbose) + dataLog("Candidates: ", listDump(m_candidates), "\n"); + } + + bool isStillValidCandidate(Node* candidate) + { + switch (candidate->op()) { + case Spread: + return m_candidates.contains(candidate->child1().node()); + + case NewArrayWithSpread: { + BitVector* bitVector = candidate->bitVector(); + for (unsigned i = 0; i < candidate->numChildren(); i++) { + if (bitVector->get(i)) { + if (!m_candidates.contains(m_graph.varArgChild(candidate, i).node())) + return false; + } + } + return true; + } + + default: + return true; + } + + RELEASE_ASSERT_NOT_REACHED(); + return false; + } + + void removeInvalidCandidates() + { + bool changed; + do { + changed = false; + Vector<Node*, 1> toRemove; + + for (Node* candidate : m_candidates) { + if (!isStillValidCandidate(candidate)) + toRemove.append(candidate); + } + + if (toRemove.size()) { + changed = true; + for (Node* node : toRemove) + m_candidates.remove(node); + } + + } while (changed); + } + + void transitivelyRemoveCandidate(Node* node, Node* source = nullptr) + { + bool removed = m_candidates.remove(node); + if (removed && verbose && source) + dataLog("eliminating candidate: ", node, " because it escapes from: ", source, "\n"); + + if (removed) + removeInvalidCandidates(); + } + + // Look for escaping sites, and remove from the candidates set if we see an escape. + void eliminateCandidatesThatEscape() + { + auto escape = [&] (Edge edge, Node* source) { + if (!edge) + return; + transitivelyRemoveCandidate(edge.node(), source); + }; + + auto escapeBasedOnArrayMode = [&] (ArrayMode mode, Edge edge, Node* source) { + switch (mode.type()) { + case Array::DirectArguments: + if (edge->op() != CreateDirectArguments) + escape(edge, source); + break; + + case Array::Contiguous: { + if (edge->op() != CreateClonedArguments && edge->op() != CreateRest) { + escape(edge, source); + return; + } + + // Everything is fine if we're doing an in-bounds access. + if (mode.isInBounds()) + break; + + // If we're out-of-bounds then we proceed only if the prototype chain + // for the allocation is sane (i.e. doesn't have indexed properties). + JSGlobalObject* globalObject = m_graph.globalObjectFor(edge->origin.semantic); + InlineWatchpointSet& objectPrototypeTransition = globalObject->objectPrototype()->structure()->transitionWatchpointSet(); + if (edge->op() == CreateRest) { + InlineWatchpointSet& arrayPrototypeTransition = globalObject->arrayPrototype()->structure()->transitionWatchpointSet(); + if (arrayPrototypeTransition.isStillValid() + && objectPrototypeTransition.isStillValid() + && globalObject->arrayPrototypeChainIsSane()) { + m_graph.watchpoints().addLazily(arrayPrototypeTransition); + m_graph.watchpoints().addLazily(objectPrototypeTransition); + break; + } + } else { + if (objectPrototypeTransition.isStillValid() + && globalObject->objectPrototypeIsSane()) { + m_graph.watchpoints().addLazily(objectPrototypeTransition); + break; + } + } + escape(edge, source); + break; + } + + case Array::ForceExit: + break; + + default: + escape(edge, source); + break; + } + }; + + removeInvalidCandidates(); + + for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { + for (Node* node : *block) { + switch (node->op()) { + case GetFromArguments: + break; + + case GetByVal: + escapeBasedOnArrayMode(node->arrayMode(), node->child1(), node); + escape(node->child2(), node); + escape(node->child3(), node); + break; + + case GetArrayLength: + // FIXME: It would not be hard to support NewArrayWithSpread here if it is only over Spread(CreateRest) nodes. + escape(node->child2(), node); + break; + + case NewArrayWithSpread: { + BitVector* bitVector = node->bitVector(); + bool isWatchingHavingABadTimeWatchpoint = m_graph.isWatchingHavingABadTimeWatchpoint(node); + for (unsigned i = 0; i < node->numChildren(); i++) { + Edge child = m_graph.varArgChild(node, i); + bool dontEscape; + if (bitVector->get(i)) { + dontEscape = child->op() == Spread + && child->child1().useKind() == ArrayUse + && child->child1()->op() == CreateRest + && isWatchingHavingABadTimeWatchpoint; + } else + dontEscape = false; + + if (!dontEscape) + escape(child, node); + } + + break; + } + + case Spread: { + bool isOK = node->child1().useKind() == ArrayUse && node->child1()->op() == CreateRest; + if (!isOK) + escape(node->child1(), node); + break; + } + + + case LoadVarargs: + if (node->loadVarargsData()->offset && node->child1()->op() == NewArrayWithSpread) + escape(node->child1(), node); + break; + + case CallVarargs: + case ConstructVarargs: + case TailCallVarargs: + case TailCallVarargsInlinedCaller: + escape(node->child1(), node); + escape(node->child2(), node); + if (node->callVarargsData()->firstVarArgOffset && node->child3()->op() == NewArrayWithSpread) + escape(node->child3(), node); + break; + + case Check: + m_graph.doToChildren( + node, + [&] (Edge edge) { + if (edge.willNotHaveCheck()) + return; + + if (alreadyChecked(edge.useKind(), SpecObject)) + return; + + escape(edge, node); + }); + break; + + case MovHint: + case PutHint: + break; + + case GetButterfly: + // This barely works. The danger is that the GetButterfly is used by something that + // does something escaping to a candidate. Fortunately, the only butterfly-using ops + // that we exempt here also use the candidate directly. If there ever was a + // butterfly-using op that we wanted to exempt, then we'd have to look at the + // butterfly's child and check if it's a candidate. + break; + + case CheckArray: + escapeBasedOnArrayMode(node->arrayMode(), node->child1(), node); + break; + + case CheckStructure: { + if (!m_candidates.contains(node->child1().node())) + break; + + Structure* structure = nullptr; + JSGlobalObject* globalObject = m_graph.globalObjectFor(node->child1().node()->origin.semantic); + switch (node->child1().node()->op()) { + case CreateDirectArguments: + structure = globalObject->directArgumentsStructure(); + break; + case CreateClonedArguments: + structure = globalObject->clonedArgumentsStructure(); + break; + case CreateRest: + ASSERT(m_graph.isWatchingHavingABadTimeWatchpoint(node)); + structure = globalObject->restParameterStructure(); + break; + case NewArrayWithSpread: + ASSERT(m_graph.isWatchingHavingABadTimeWatchpoint(node)); + structure = globalObject->originalArrayStructureForIndexingType(ArrayWithContiguous); + break; + default: + RELEASE_ASSERT_NOT_REACHED(); + } + ASSERT(structure); + + if (!node->structureSet().contains(m_graph.registerStructure(structure))) + escape(node->child1(), node); + break; + } + + // FIXME: We should be able to handle GetById/GetByOffset on callee. + // https://bugs.webkit.org/show_bug.cgi?id=143075 + + case GetByOffset: + if (node->child2()->op() == CreateClonedArguments && node->storageAccessData().offset == clonedArgumentsLengthPropertyOffset) + break; + FALLTHROUGH; + default: + m_graph.doToChildren(node, [&] (Edge edge) { return escape(edge, node); }); + break; + } + } + } + + if (verbose) + dataLog("After escape analysis: ", listDump(m_candidates), "\n"); + } + + // Anywhere that a candidate is live (in bytecode or in DFG), check if there is a chance of + // interference between the stack area that the arguments object copies from and the arguments + // object's payload. Conservatively this means that the stack region doesn't get stored to. + void eliminateCandidatesThatInterfere() + { + performLivenessAnalysis(m_graph); + performOSRAvailabilityAnalysis(m_graph); + m_graph.initializeNodeOwners(); + CombinedLiveness combinedLiveness(m_graph); + + BlockMap<Operands<bool>> clobberedByBlock(m_graph); + for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { + Operands<bool>& clobberedByThisBlock = clobberedByBlock[block]; + clobberedByThisBlock = Operands<bool>(OperandsLike, m_graph.block(0)->variablesAtHead); + for (Node* node : *block) { + clobberize( + m_graph, node, NoOpClobberize(), + [&] (AbstractHeap heap) { + if (heap.kind() != Stack) { + ASSERT(!heap.overlaps(Stack)); + return; + } + ASSERT(!heap.payload().isTop()); + VirtualRegister reg(heap.payload().value32()); + clobberedByThisBlock.operand(reg) = true; + }, + NoOpClobberize()); + } + } + + for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { + // Stop if we've already removed all candidates. + if (m_candidates.isEmpty()) + return; + + // Ignore blocks that don't write to the stack. + bool writesToStack = false; + for (unsigned i = clobberedByBlock[block].size(); i--;) { + if (clobberedByBlock[block][i]) { + writesToStack = true; + break; + } + } + if (!writesToStack) + continue; + + forAllKillsInBlock( + m_graph, combinedLiveness, block, + [&] (unsigned nodeIndex, Node* candidate) { + if (!m_candidates.contains(candidate)) + return; + + // Check if this block has any clobbers that affect this candidate. This is a fairly + // fast check. + bool isClobberedByBlock = false; + Operands<bool>& clobberedByThisBlock = clobberedByBlock[block]; + + if (InlineCallFrame* inlineCallFrame = candidate->origin.semantic.inlineCallFrame) { + if (inlineCallFrame->isVarargs()) { + isClobberedByBlock |= clobberedByThisBlock.operand( + inlineCallFrame->stackOffset + CallFrameSlot::argumentCount); + } + + if (!isClobberedByBlock || inlineCallFrame->isClosureCall) { + isClobberedByBlock |= clobberedByThisBlock.operand( + inlineCallFrame->stackOffset + CallFrameSlot::callee); + } + + if (!isClobberedByBlock) { + for (unsigned i = 0; i < inlineCallFrame->arguments.size() - 1; ++i) { + VirtualRegister reg = + VirtualRegister(inlineCallFrame->stackOffset) + + CallFrame::argumentOffset(i); + if (clobberedByThisBlock.operand(reg)) { + isClobberedByBlock = true; + break; + } + } + } + } else { + // We don't include the ArgumentCount or Callee in this case because we can be + // damn sure that this won't be clobbered. + for (unsigned i = 1; i < static_cast<unsigned>(codeBlock()->numParameters()); ++i) { + if (clobberedByThisBlock.argument(i)) { + isClobberedByBlock = true; + break; + } + } + } + + if (!isClobberedByBlock) + return; + + // Check if we can immediately eliminate this candidate. If the block has a clobber + // for this arguments allocation, and we'd have to examine every node in the block, + // then we can just eliminate the candidate. + if (nodeIndex == block->size() && candidate->owner != block) { + if (verbose) + dataLog("eliminating candidate: ", candidate, " because it is clobbered by: ", block->at(nodeIndex), "\n"); + transitivelyRemoveCandidate(candidate); + return; + } + + // This loop considers all nodes up to the nodeIndex, excluding the nodeIndex. + while (nodeIndex--) { + Node* node = block->at(nodeIndex); + if (node == candidate) + break; + + bool found = false; + clobberize( + m_graph, node, NoOpClobberize(), + [&] (AbstractHeap heap) { + if (heap.kind() == Stack && !heap.payload().isTop()) { + if (argumentsInvolveStackSlot(candidate, VirtualRegister(heap.payload().value32()))) + found = true; + return; + } + if (heap.overlaps(Stack)) + found = true; + }, + NoOpClobberize()); + + if (found) { + if (verbose) + dataLog("eliminating candidate: ", candidate, " because it is clobbered by ", block->at(nodeIndex), "\n"); + transitivelyRemoveCandidate(candidate); + return; + } + } + }); + } + + // Q: How do we handle OSR exit with a live PhantomArguments at a point where the inline call + // frame is dead? A: Naively we could say that PhantomArguments must escape the stack slots. But + // that would break PutStack sinking, which in turn would break object allocation sinking, in + // cases where we have a varargs call to an otherwise pure method. So, we need something smarter. + // For the outermost arguments, we just have a PhantomArguments that magically knows that it + // should load the arguments from the call frame. For the inline arguments, we have the heap map + // in the availabiltiy map track each possible inline argument as a promoted heap location. If the + // PutStacks for those arguments aren't sunk, those heap locations will map to very trivial + // availabilities (they will be flush availabilities). But if sinking happens then those + // availabilities may become whatever. OSR exit should be able to handle this quite naturally, + // since those availabilities speak of the stack before the optimizing compiler stack frame is + // torn down. + + if (verbose) + dataLog("After interference analysis: ", listDump(m_candidates), "\n"); + } + + void transform() + { + InsertionSet insertionSet(m_graph); + + for (BasicBlock* block : m_graph.blocksInPreOrder()) { + for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) { + Node* node = block->at(nodeIndex); + + auto getArrayLength = [&] (Node* candidate) -> Node* { + return emitCodeToGetArgumentsArrayLength( + insertionSet, candidate, nodeIndex, node->origin); + }; + + auto isEliminatedAllocation = [&] (Node* candidate) -> bool { + if (!m_candidates.contains(candidate)) + return false; + // We traverse in such a way that we are guaranteed to see a def before a use. + // Therefore, we should have already transformed the allocation before the use + // of an allocation. + ASSERT(candidate->op() == PhantomCreateRest || candidate->op() == PhantomDirectArguments || candidate->op() == PhantomClonedArguments + || candidate->op() == PhantomSpread || candidate->op() == PhantomNewArrayWithSpread); + return true; + }; + + switch (node->op()) { + case CreateDirectArguments: + if (!m_candidates.contains(node)) + break; + + node->setOpAndDefaultFlags(PhantomDirectArguments); + break; + + case CreateRest: + if (!m_candidates.contains(node)) + break; + + node->setOpAndDefaultFlags(PhantomCreateRest); + // We don't need this parameter for OSR exit, we can find out all the information + // we need via the static parameter count and the dynamic argument count. + node->child1() = Edge(); + break; + + case CreateClonedArguments: + if (!m_candidates.contains(node)) + break; + + node->setOpAndDefaultFlags(PhantomClonedArguments); + break; + + case Spread: + if (!m_candidates.contains(node)) + break; + + node->setOpAndDefaultFlags(PhantomSpread); + break; + + case NewArrayWithSpread: + if (!m_candidates.contains(node)) + break; + + node->setOpAndDefaultFlags(PhantomNewArrayWithSpread); + break; + + case GetFromArguments: { + Node* candidate = node->child1().node(); + if (!isEliminatedAllocation(candidate)) + break; + + DFG_ASSERT( + m_graph, node, node->child1()->op() == PhantomDirectArguments); + VirtualRegister reg = + virtualRegisterForArgument(node->capturedArgumentsOffset().offset() + 1) + + node->origin.semantic.stackOffset(); + StackAccessData* data = m_graph.m_stackAccessData.add(reg, FlushedJSValue); + node->convertToGetStack(data); + break; + } + + case GetByOffset: { + Node* candidate = node->child2().node(); + if (!isEliminatedAllocation(candidate)) + break; + + if (node->child2()->op() != PhantomClonedArguments) + break; + + ASSERT(node->storageAccessData().offset == clonedArgumentsLengthPropertyOffset); + + // Meh, this is kind of hackish - we use an Identity so that we can reuse the + // getArrayLength() helper. + node->convertToIdentityOn(getArrayLength(candidate)); + break; + } + + case GetArrayLength: { + Node* candidate = node->child1().node(); + if (!isEliminatedAllocation(candidate)) + break; + + // Meh, this is kind of hackish - we use an Identity so that we can reuse the + // getArrayLength() helper. + node->convertToIdentityOn(getArrayLength(candidate)); + break; + } + + case GetByVal: { + // FIXME: For ClonedArguments, we would have already done a separate bounds check. + // This code will cause us to have two bounds checks - the original one that we + // already factored out in SSALoweringPhase, and the new one we insert here, which is + // often implicitly part of GetMyArgumentByVal. B3 will probably eliminate the + // second bounds check, but still - that's just silly. + // https://bugs.webkit.org/show_bug.cgi?id=143076 + + Node* candidate = node->child1().node(); + if (!isEliminatedAllocation(candidate)) + break; + + unsigned numberOfArgumentsToSkip = 0; + if (candidate->op() == PhantomCreateRest) + numberOfArgumentsToSkip = candidate->numberOfArgumentsToSkip(); + + Node* result = nullptr; + if (node->child2()->isInt32Constant()) { + unsigned index = node->child2()->asUInt32(); + InlineCallFrame* inlineCallFrame = candidate->origin.semantic.inlineCallFrame; + index += numberOfArgumentsToSkip; + + bool safeToGetStack; + if (inlineCallFrame) + safeToGetStack = index < inlineCallFrame->arguments.size() - 1; + else { + safeToGetStack = + index < static_cast<unsigned>(codeBlock()->numParameters()) - 1; + } + if (safeToGetStack) { + StackAccessData* data; + VirtualRegister arg = virtualRegisterForArgument(index + 1); + if (inlineCallFrame) + arg += inlineCallFrame->stackOffset; + data = m_graph.m_stackAccessData.add(arg, FlushedJSValue); + + if (!inlineCallFrame || inlineCallFrame->isVarargs()) { + insertionSet.insertNode( + nodeIndex, SpecNone, CheckInBounds, node->origin, + node->child2(), Edge(getArrayLength(candidate), Int32Use)); + } + + result = insertionSet.insertNode( + nodeIndex, node->prediction(), GetStack, node->origin, OpInfo(data)); + } + } + + if (!result) { + NodeType op; + if (node->arrayMode().isInBounds()) + op = GetMyArgumentByVal; + else + op = GetMyArgumentByValOutOfBounds; + result = insertionSet.insertNode( + nodeIndex, node->prediction(), op, node->origin, OpInfo(numberOfArgumentsToSkip), + node->child1(), node->child2()); + } + + // Need to do this because we may have a data format conversion here. + node->convertToIdentityOn(result); + break; + } + + case LoadVarargs: { + Node* candidate = node->child1().node(); + if (!isEliminatedAllocation(candidate)) + break; + + // LoadVarargs can exit, so it better be exitOK. + DFG_ASSERT(m_graph, node, node->origin.exitOK); + bool canExit = true; + LoadVarargsData* varargsData = node->loadVarargsData(); + + auto storeArgumentCountIncludingThis = [&] (unsigned argumentCountIncludingThis) { + Node* argumentCountIncludingThisNode = insertionSet.insertConstant( + nodeIndex, node->origin.withExitOK(canExit), + jsNumber(argumentCountIncludingThis)); + insertionSet.insertNode( + nodeIndex, SpecNone, MovHint, node->origin.takeValidExit(canExit), + OpInfo(varargsData->count.offset()), Edge(argumentCountIncludingThisNode)); + insertionSet.insertNode( + nodeIndex, SpecNone, PutStack, node->origin.withExitOK(canExit), + OpInfo(m_graph.m_stackAccessData.add(varargsData->count, FlushedInt32)), + Edge(argumentCountIncludingThisNode, KnownInt32Use)); + }; + + auto storeValue = [&] (Node* value, unsigned storeIndex) { + VirtualRegister reg = varargsData->start + storeIndex; + StackAccessData* data = + m_graph.m_stackAccessData.add(reg, FlushedJSValue); + + insertionSet.insertNode( + nodeIndex, SpecNone, MovHint, node->origin.takeValidExit(canExit), + OpInfo(reg.offset()), Edge(value)); + insertionSet.insertNode( + nodeIndex, SpecNone, PutStack, node->origin.withExitOK(canExit), + OpInfo(data), Edge(value)); + }; + + if (candidate->op() == PhantomNewArrayWithSpread) { + bool canConvertToStaticLoadStores = true; + BitVector* bitVector = candidate->bitVector(); + + for (unsigned i = 0; i < candidate->numChildren(); i++) { + if (bitVector->get(i)) { + Node* child = m_graph.varArgChild(candidate, i).node(); + ASSERT(child->op() == PhantomSpread && child->child1()->op() == PhantomCreateRest); + InlineCallFrame* inlineCallFrame = child->child1()->origin.semantic.inlineCallFrame; + if (!inlineCallFrame || inlineCallFrame->isVarargs()) { + canConvertToStaticLoadStores = false; + break; + } + } + } + + if (canConvertToStaticLoadStores) { + unsigned argumentCountIncludingThis = 1; // |this| + for (unsigned i = 0; i < candidate->numChildren(); i++) { + if (bitVector->get(i)) { + Node* child = m_graph.varArgChild(candidate, i).node(); + ASSERT(child->op() == PhantomSpread && child->child1()->op() == PhantomCreateRest); + unsigned numberOfArgumentsToSkip = child->child1()->numberOfArgumentsToSkip(); + InlineCallFrame* inlineCallFrame = child->child1()->origin.semantic.inlineCallFrame; + unsigned numberOfSpreadArguments; + unsigned frameArgumentCount = inlineCallFrame->arguments.size() - 1; + if (frameArgumentCount >= numberOfArgumentsToSkip) + numberOfSpreadArguments = frameArgumentCount - numberOfArgumentsToSkip; + else + numberOfSpreadArguments = 0; + + argumentCountIncludingThis += numberOfSpreadArguments; + } else + ++argumentCountIncludingThis; + } + + if (argumentCountIncludingThis <= varargsData->limit) { + storeArgumentCountIncludingThis(argumentCountIncludingThis); + + DFG_ASSERT(m_graph, node, varargsData->limit - 1 >= varargsData->mandatoryMinimum); + // Define our limit to exclude "this", since that's a bit easier to reason about. + unsigned limit = varargsData->limit - 1; + unsigned storeIndex = 0; + for (unsigned i = 0; i < candidate->numChildren(); i++) { + if (bitVector->get(i)) { + Node* child = m_graph.varArgChild(candidate, i).node(); + ASSERT(child->op() == PhantomSpread && child->child1()->op() == PhantomCreateRest); + unsigned numberOfArgumentsToSkip = child->child1()->numberOfArgumentsToSkip(); + InlineCallFrame* inlineCallFrame = child->child1()->origin.semantic.inlineCallFrame; + unsigned frameArgumentCount = inlineCallFrame->arguments.size() - 1; + for (unsigned loadIndex = numberOfArgumentsToSkip; loadIndex < frameArgumentCount; ++loadIndex) { + VirtualRegister reg = virtualRegisterForArgument(loadIndex + 1) + inlineCallFrame->stackOffset; + StackAccessData* data = m_graph.m_stackAccessData.add(reg, FlushedJSValue); + Node* value = insertionSet.insertNode( + nodeIndex, SpecNone, GetStack, node->origin.withExitOK(canExit), + OpInfo(data)); + storeValue(value, storeIndex); + ++storeIndex; + } + } else { + Node* value = m_graph.varArgChild(candidate, i).node(); + storeValue(value, storeIndex); + ++storeIndex; + } + } + + RELEASE_ASSERT(storeIndex <= limit); + Node* undefined = nullptr; + for (; storeIndex < limit; ++storeIndex) { + if (!undefined) { + undefined = insertionSet.insertConstant( + nodeIndex, node->origin.withExitOK(canExit), jsUndefined()); + } + storeValue(undefined, storeIndex); + } + } + + node->remove(); + node->origin.exitOK = canExit; + break; + } + } else { + unsigned numberOfArgumentsToSkip = 0; + if (candidate->op() == PhantomCreateRest) + numberOfArgumentsToSkip = candidate->numberOfArgumentsToSkip(); + varargsData->offset += numberOfArgumentsToSkip; + + InlineCallFrame* inlineCallFrame = candidate->origin.semantic.inlineCallFrame; + + if (inlineCallFrame + && !inlineCallFrame->isVarargs()) { + + unsigned argumentCountIncludingThis = inlineCallFrame->arguments.size(); + if (argumentCountIncludingThis > varargsData->offset) + argumentCountIncludingThis -= varargsData->offset; + else + argumentCountIncludingThis = 1; + RELEASE_ASSERT(argumentCountIncludingThis >= 1); + + if (argumentCountIncludingThis <= varargsData->limit) { + + storeArgumentCountIncludingThis(argumentCountIncludingThis); + + DFG_ASSERT(m_graph, node, varargsData->limit - 1 >= varargsData->mandatoryMinimum); + // Define our limit to exclude "this", since that's a bit easier to reason about. + unsigned limit = varargsData->limit - 1; + Node* undefined = nullptr; + for (unsigned storeIndex = 0; storeIndex < limit; ++storeIndex) { + // First determine if we have an element we can load, and load it if + // possible. + + Node* value = nullptr; + unsigned loadIndex = storeIndex + varargsData->offset; + + if (loadIndex + 1 < inlineCallFrame->arguments.size()) { + VirtualRegister reg = virtualRegisterForArgument(loadIndex + 1) + inlineCallFrame->stackOffset; + StackAccessData* data = m_graph.m_stackAccessData.add( + reg, FlushedJSValue); + + value = insertionSet.insertNode( + nodeIndex, SpecNone, GetStack, node->origin.withExitOK(canExit), + OpInfo(data)); + } else { + // FIXME: We shouldn't have to store anything if + // storeIndex >= varargsData->mandatoryMinimum, but we will still + // have GetStacks in that range. So if we don't do the stores, we'll + // have degenerate IR: we'll have GetStacks of something that didn't + // have PutStacks. + // https://bugs.webkit.org/show_bug.cgi?id=147434 + + if (!undefined) { + undefined = insertionSet.insertConstant( + nodeIndex, node->origin.withExitOK(canExit), jsUndefined()); + } + value = undefined; + } + + // Now that we have a value, store it. + storeValue(value, storeIndex); + } + + node->remove(); + node->origin.exitOK = canExit; + break; + } + } + } + + node->setOpAndDefaultFlags(ForwardVarargs); + break; + } + + case CallVarargs: + case ConstructVarargs: + case TailCallVarargs: + case TailCallVarargsInlinedCaller: { + Node* candidate = node->child3().node(); + if (!isEliminatedAllocation(candidate)) + break; + + auto convertToStaticArgumentCountCall = [&] (const Vector<Node*>& arguments) { + unsigned firstChild = m_graph.m_varArgChildren.size(); + m_graph.m_varArgChildren.append(node->child1()); + m_graph.m_varArgChildren.append(node->child2()); + for (Node* argument : arguments) + m_graph.m_varArgChildren.append(Edge(argument)); + switch (node->op()) { + case CallVarargs: + node->setOpAndDefaultFlags(Call); + break; + case ConstructVarargs: + node->setOpAndDefaultFlags(Construct); + break; + case TailCallVarargs: + node->setOpAndDefaultFlags(TailCall); + break; + case TailCallVarargsInlinedCaller: + node->setOpAndDefaultFlags(TailCallInlinedCaller); + break; + default: + RELEASE_ASSERT_NOT_REACHED(); + } + node->children = AdjacencyList( + AdjacencyList::Variable, + firstChild, m_graph.m_varArgChildren.size() - firstChild); + }; + + auto convertToForwardsCall = [&] () { + switch (node->op()) { + case CallVarargs: + node->setOpAndDefaultFlags(CallForwardVarargs); + break; + case ConstructVarargs: + node->setOpAndDefaultFlags(ConstructForwardVarargs); + break; + case TailCallVarargs: + node->setOpAndDefaultFlags(TailCallForwardVarargs); + break; + case TailCallVarargsInlinedCaller: + node->setOpAndDefaultFlags(TailCallForwardVarargsInlinedCaller); + break; + default: + RELEASE_ASSERT_NOT_REACHED(); + } + }; + + if (candidate->op() == PhantomNewArrayWithSpread) { + bool canTransformToStaticArgumentCountCall = true; + BitVector* bitVector = candidate->bitVector(); + for (unsigned i = 0; i < candidate->numChildren(); i++) { + if (bitVector->get(i)) { + Node* node = m_graph.varArgChild(candidate, i).node(); + ASSERT(node->op() == PhantomSpread); + ASSERT(node->child1()->op() == PhantomCreateRest); + InlineCallFrame* inlineCallFrame = node->child1()->origin.semantic.inlineCallFrame; + if (!inlineCallFrame || inlineCallFrame->isVarargs()) { + canTransformToStaticArgumentCountCall = false; + break; + } + } + } + + if (canTransformToStaticArgumentCountCall) { + Vector<Node*> arguments; + for (unsigned i = 0; i < candidate->numChildren(); i++) { + Node* child = m_graph.varArgChild(candidate, i).node(); + if (bitVector->get(i)) { + ASSERT(child->op() == PhantomSpread); + ASSERT(child->child1()->op() == PhantomCreateRest); + InlineCallFrame* inlineCallFrame = child->child1()->origin.semantic.inlineCallFrame; + unsigned numberOfArgumentsToSkip = child->child1()->numberOfArgumentsToSkip(); + for (unsigned i = 1 + numberOfArgumentsToSkip; i < inlineCallFrame->arguments.size(); ++i) { + StackAccessData* data = m_graph.m_stackAccessData.add( + virtualRegisterForArgument(i) + inlineCallFrame->stackOffset, + FlushedJSValue); + + Node* value = insertionSet.insertNode( + nodeIndex, SpecNone, GetStack, node->origin, OpInfo(data)); + + arguments.append(value); + } + } else + arguments.append(child); + } + + convertToStaticArgumentCountCall(arguments); + } else + convertToForwardsCall(); + } else { + unsigned numberOfArgumentsToSkip = 0; + if (candidate->op() == PhantomCreateRest) + numberOfArgumentsToSkip = candidate->numberOfArgumentsToSkip(); + CallVarargsData* varargsData = node->callVarargsData(); + varargsData->firstVarArgOffset += numberOfArgumentsToSkip; + + InlineCallFrame* inlineCallFrame = candidate->origin.semantic.inlineCallFrame; + if (inlineCallFrame && !inlineCallFrame->isVarargs()) { + Vector<Node*> arguments; + for (unsigned i = 1 + varargsData->firstVarArgOffset; i < inlineCallFrame->arguments.size(); ++i) { + StackAccessData* data = m_graph.m_stackAccessData.add( + virtualRegisterForArgument(i) + inlineCallFrame->stackOffset, + FlushedJSValue); + + Node* value = insertionSet.insertNode( + nodeIndex, SpecNone, GetStack, node->origin, OpInfo(data)); + + arguments.append(value); + } + + convertToStaticArgumentCountCall(arguments); + } else + convertToForwardsCall(); + } + + break; + } + + case CheckArray: + case GetButterfly: { + if (!isEliminatedAllocation(node->child1().node())) + break; + node->remove(); + break; + } + + case CheckStructure: + if (!isEliminatedAllocation(node->child1().node())) + break; + node->child1() = Edge(); // Remove the cell check since we've proven it's not needed and FTL lowering might botch this. + node->remove(); + break; + + default: + break; + } + } + + insertionSet.execute(block); + } + } + + HashSet<Node*> m_candidates; +}; + +} // anonymous namespace + +bool performArgumentsElimination(Graph& graph) +{ + return runPhase<ArgumentsEliminationPhase>(graph); +} + +} } // namespace JSC::DFG + +#endif // ENABLE(DFG_JIT) + |