Ignore:
Timestamp:
Nov 29, 2016, 10:24:44 PM (9 years ago)
Author:
[email protected]
Message:

We should be able optimize the pattern where we spread a function's rest parameter to another call
https://bugs.webkit.org/show_bug.cgi?id=163865

Reviewed by Filip Pizlo.

JSTests:

  • microbenchmarks/default-derived-constructor.js: Added.

(createClassHierarchy.let.currentClass):
(createClassHierarchy):

  • stress/call-varargs-spread.js: Added.

(assert):
(bar):
(foo):

  • stress/load-varargs-on-new-array-with-spread-convert-to-static-loads.js: Added.

(assert):
(baz):
(bar):
(foo):

  • stress/new-array-with-spread-with-normal-spread-and-phantom-spread.js: Added.

(assert):
(foo):
(escape):
(bar):

  • stress/phantom-new-array-with-spread-osr-exit.js: Added.

(assert):
(baz):
(bar):
(effects):
(foo):

  • stress/phantom-spread-forward-varargs.js: Added.

(assert):
(test1.bar):
(test1.foo):
(test1):
(test2.bar):
(test2.foo):
(test3.baz):
(test3.bar):
(test3.foo):
(test4.baz):
(test4.bar):
(test4.foo):
(test5.baz):
(test5.bar):
(test5.foo):

  • stress/phantom-spread-osr-exit.js: Added.

(assert):
(baz):
(bar):
(effects):
(foo):

  • stress/spread-call-convert-to-static-call.js: Added.

(assert):
(baz):
(bar):
(foo):

  • stress/spread-forward-call-varargs-stack-overflow.js: Added.

(assert):
(identity):
(bar):
(foo):

  • stress/spread-forward-varargs-rest-parameter-change-iterator-protocol-2.js: Added.

(assert):
(baz.Array.prototype.Symbol.iterator):
(baz):
(bar):
(foo):
(test):

  • stress/spread-forward-varargs-rest-parameter-change-iterator-protocol.js: Added.

(assert):
(baz.Array.prototype.Symbol.iterator):
(baz):
(bar):
(foo):

  • stress/spread-forward-varargs-stack-overflow.js: Added.

(assert):
(bar):
(foo):

Source/JavaScriptCore:

This patch optimizes the following patterns to prevent both the allocation
of the rest parameter, and the execution of the iterator protocol:

`
function foo(...args) {

let arr = [...args];

}

and

function foo(...args) {

bar(...args);

}
`

To do this, I've extended the arguments elimination phase to reason
about Spread and NewArrayWithSpread. I've added two new nodes, PhantomSpread
and PhantomNewArrayWithSpread. PhantomSpread is only allowed over rest
parameters that don't escape. If the rest parameter *does* escape, we can't
convert the spread into a phantom because it would not be sound w.r.t JS
semantics because we would be reading from the call frame even though
the rest array may have changed.

Note that NewArrayWithSpread also understands what to do when one of its
arguments is PhantomSpread(@PhantomCreateRest) even if it itself is escaped.

PhantomNewArrayWithSpread is only allowed over a series of
PhantomSpread(@PhantomCreateRest) nodes. Like with PhantomSpread, PhantomNewArrayWithSpread
is only allowed if none of its arguments that are being spread are escaped
and if it itself is not escaped.

Because there is a dependency between a node being a candidate and
the escaped state of the node's children, I've extended the notion
of escaping a node inside the arguments elimination phase. Now, when
any node is escaped, we must consider all other candidates that are may
now no longer be valid.

For example:

`
function foo(...args) {

escape(args);
bar(...args);

}
`

In the above program, we don't know if the function call to escape()
modifies args, therefore, the spread can not become phantom because
the execution of the spread may not be as simple as reading the
arguments from the call frame.

Unfortunately, the arguments elimination phase does not consider control
flow when doing its escape analysis. It would be good to integrate this
phase with the object allocation sinking phase. To see why, consider
an example where we don't eliminate the spread and allocation of the rest
parameter even though we could:

`
function foo(rareCondition, ...args) {

bar(...args);
if (rareCondition)

baz(args);

}
`

There are only a few users of the PhantomSpread and PhantomNewArrayWithSpread
nodes. PhantomSpread is only used by PhantomNewArrayWithSpread and NewArrayWithSpread.
PhantomNewArrayWithSpread is only used by ForwardVarargs and the various
*Call*ForwardVarargs nodes. The users of these phantoms know how to produce
what the phantom node would have produced. For example, NewArrayWithSpread
knows how to produce the values that would have been produced by PhantomSpread(@PhantomCreateRest)
by directly reading from the call frame.

This patch is a 6% speedup on my MBP on ES6SampleBench.

  • b3/B3LowerToAir.cpp:

(JSC::B3::Air::LowerToAir::tryAppendLea):

  • b3/B3ValueRep.h:
  • builtins/BuiltinExecutables.cpp:

(JSC::BuiltinExecutables::createDefaultConstructor):

  • dfg/DFGAbstractInterpreterInlines.h:

(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):

  • dfg/DFGArgumentsEliminationPhase.cpp:
  • dfg/DFGClobberize.h:

(JSC::DFG::clobberize):

  • dfg/DFGDoesGC.cpp:

(JSC::DFG::doesGC):

  • dfg/DFGFixupPhase.cpp:

(JSC::DFG::FixupPhase::fixupNode):

  • dfg/DFGForAllKills.h:

(JSC::DFG::forAllKillsInBlock):

  • dfg/DFGNode.h:

(JSC::DFG::Node::hasConstant):
(JSC::DFG::Node::constant):
(JSC::DFG::Node::bitVector):
(JSC::DFG::Node::isPhantomAllocation):

  • dfg/DFGNodeType.h:
  • dfg/DFGOSRAvailabilityAnalysisPhase.cpp:

(JSC::DFG::OSRAvailabilityAnalysisPhase::run):
(JSC::DFG::LocalOSRAvailabilityCalculator::LocalOSRAvailabilityCalculator):
(JSC::DFG::LocalOSRAvailabilityCalculator::executeNode):

  • dfg/DFGOSRAvailabilityAnalysisPhase.h:
  • dfg/DFGObjectAllocationSinkingPhase.cpp:
  • dfg/DFGPreciseLocalClobberize.h:

(JSC::DFG::PreciseLocalClobberizeAdaptor::readTop):

  • dfg/DFGPredictionPropagationPhase.cpp:
  • dfg/DFGPromotedHeapLocation.cpp:

(WTF::printInternal):

  • dfg/DFGPromotedHeapLocation.h:
  • dfg/DFGSafeToExecute.h:

(JSC::DFG::safeToExecute):

  • dfg/DFGSpeculativeJIT32_64.cpp:

(JSC::DFG::SpeculativeJIT::compile):

  • dfg/DFGSpeculativeJIT64.cpp:

(JSC::DFG::SpeculativeJIT::compile):

  • dfg/DFGValidate.cpp:
  • ftl/FTLCapabilities.cpp:

(JSC::FTL::canCompile):

  • ftl/FTLLowerDFGToB3.cpp:

(JSC::FTL::DFG::LowerDFGToB3::LowerDFGToB3):
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileNewArrayWithSpread):
(JSC::FTL::DFG::LowerDFGToB3::compileSpread):
(JSC::FTL::DFG::LowerDFGToB3::compileCallOrConstructVarargsSpread):
(JSC::FTL::DFG::LowerDFGToB3::compileCallOrConstructVarargs):
(JSC::FTL::DFG::LowerDFGToB3::compileForwardVarargs):
(JSC::FTL::DFG::LowerDFGToB3::getSpreadLengthFromInlineCallFrame):
(JSC::FTL::DFG::LowerDFGToB3::compileForwardVarargsWithSpread):

  • ftl/FTLOperations.cpp:

(JSC::FTL::operationPopulateObjectInOSR):
(JSC::FTL::operationMaterializeObjectInOSR):

  • jit/SetupVarargsFrame.cpp:

(JSC::emitSetupVarargsFrameFastCase):

  • jsc.cpp:

(GlobalObject::finishCreation):
(functionMaxArguments):

  • runtime/JSFixedArray.h:

(JSC::JSFixedArray::createFromArray):

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/dfg/DFGPreciseLocalClobberize.h

    r208524 r209121  
    107107    void readTop()
    108108    {
    109         switch (m_node->op()) {
    110         case GetMyArgumentByVal:
    111         case GetMyArgumentByValOutOfBounds:
    112         case ForwardVarargs:
    113         case CallForwardVarargs:
    114         case ConstructForwardVarargs:
    115         case TailCallForwardVarargs:
    116         case TailCallForwardVarargsInlinedCaller: {
    117 
    118             InlineCallFrame* inlineCallFrame;
    119             if (m_node->hasArgumentsChild() && m_node->argumentsChild())
    120                 inlineCallFrame = m_node->argumentsChild()->origin.semantic.inlineCallFrame;
    121             else
    122                 inlineCallFrame = m_node->origin.semantic.inlineCallFrame;
    123 
    124             unsigned numberOfArgumentsToSkip = 0;
    125             if (m_node->op() == GetMyArgumentByVal || m_node->op() == GetMyArgumentByValOutOfBounds) {
    126                 // The value of numberOfArgumentsToSkip guarantees that GetMyArgumentByVal* will never
    127                 // read any arguments below the number of arguments to skip. For example, if numberOfArgumentsToSkip is 2,
    128                 // we will never read argument 0 or argument 1.
    129                 numberOfArgumentsToSkip = m_node->numberOfArgumentsToSkip();
    130             }
    131 
     109        auto readFrame = [&] (InlineCallFrame* inlineCallFrame, unsigned numberOfArgumentsToSkip) {
    132110            if (!inlineCallFrame) {
    133111                // Read the outermost arguments and argument count.
     
    135113                    m_read(virtualRegisterForArgument(i));
    136114                m_read(VirtualRegister(CallFrameSlot::argumentCount));
    137                 break;
     115                return;
    138116            }
    139117           
     
    142120            if (inlineCallFrame->isVarargs())
    143121                m_read(VirtualRegister(inlineCallFrame->stackOffset + CallFrameSlot::argumentCount));
     122        };
     123
     124        auto readNewArrayWithSpreadNode = [&] (Node* arrayWithSpread) {
     125            ASSERT(arrayWithSpread->op() == NewArrayWithSpread || arrayWithSpread->op() == PhantomNewArrayWithSpread);
     126            BitVector* bitVector = arrayWithSpread->bitVector();
     127            for (unsigned i = 0; i < arrayWithSpread->numChildren(); i++) {
     128                if (bitVector->get(i)) {
     129                    Node* child = m_graph.varArgChild(arrayWithSpread, i).node();
     130                    if (child->op() == PhantomSpread) {
     131                        ASSERT(child->child1()->op() == PhantomCreateRest);
     132                        InlineCallFrame* inlineCallFrame = child->child1()->origin.semantic.inlineCallFrame;
     133                        unsigned numberOfArgumentsToSkip = child->child1()->numberOfArgumentsToSkip();
     134                        readFrame(inlineCallFrame, numberOfArgumentsToSkip);
     135                    }
     136                }
     137            }
     138        };
     139
     140        bool isForwardingNode = false;
     141        switch (m_node->op()) {
     142        case ForwardVarargs:
     143        case CallForwardVarargs:
     144        case ConstructForwardVarargs:
     145        case TailCallForwardVarargs:
     146        case TailCallForwardVarargsInlinedCaller:
     147            isForwardingNode = true;
     148            FALLTHROUGH;
     149        case GetMyArgumentByVal:
     150        case GetMyArgumentByValOutOfBounds: {
     151
     152            if (isForwardingNode && m_node->hasArgumentsChild() && m_node->argumentsChild() && m_node->argumentsChild()->op() == PhantomNewArrayWithSpread) {
     153                Node* arrayWithSpread = m_node->argumentsChild().node();
     154                readNewArrayWithSpreadNode(arrayWithSpread);
     155            } else {
     156                InlineCallFrame* inlineCallFrame;
     157                if (m_node->hasArgumentsChild() && m_node->argumentsChild())
     158                    inlineCallFrame = m_node->argumentsChild()->origin.semantic.inlineCallFrame;
     159                else
     160                    inlineCallFrame = m_node->origin.semantic.inlineCallFrame;
     161
     162                unsigned numberOfArgumentsToSkip = 0;
     163                if (m_node->op() == GetMyArgumentByVal || m_node->op() == GetMyArgumentByValOutOfBounds) {
     164                    // The value of numberOfArgumentsToSkip guarantees that GetMyArgumentByVal* will never
     165                    // read any arguments below the number of arguments to skip. For example, if numberOfArgumentsToSkip is 2,
     166                    // we will never read argument 0 or argument 1.
     167                    numberOfArgumentsToSkip = m_node->numberOfArgumentsToSkip();
     168                }
     169
     170                readFrame(inlineCallFrame, numberOfArgumentsToSkip);
     171            }
     172
     173            break;
     174        }
     175       
     176        case NewArrayWithSpread: {
     177            readNewArrayWithSpreadNode(m_node);
    144178            break;
    145179        }
Note: See TracChangeset for help on using the changeset viewer.