[JSC] Use JSFixedArray directly when using call_varargs
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 25 Apr 2017 05:09:10 +0000 (05:09 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 25 Apr 2017 05:09:10 +0000 (05:09 +0000)
https://bugs.webkit.org/show_bug.cgi?id=171057

Reviewed by Saam Barati.

JSTests:

* stress/spread-capture-rest.js: Added.
(shouldBe):
(capture):
(a):
(b):
* stress/spread-multi-layers.js: Added.
(shouldBe):
(a):
(b):
(c):
(d):
* stress/spread-non-varargs.js: Added.
(shouldBe):
(a):
(b):

Source/JavaScriptCore:

Previously we always emit new_array_with_spread when calling call(...args).
But this array is unnecessary if varargs operation can handle Spread directly.

This patch implements a peep-hole optimization in the bytecode compiler layer
to omit new_array_with_spread. This is very simple and effective because this
peep-hole optimization is quite common when using (...args) style calls and
this optimization works all the tiers. While we can implement the phase to
omit this NewArrayWithSpread in argument elimination phase, it only works
for FTL. While such an optimization can work with complex data flow, this
peep-hole optimization can optimize a common case easily.

For now, Spread and PhantomSpread can be directly drained by CallVarargs
and LoadVarargs related operations. We modify DFG and FTL to handle this correctly.

This shows six-speed improvement.

    spread.es6                 89.4300+-2.0236     ^     69.6015+-1.7278        ^ definitely 1.2849x faster
    spread-generator.es6      344.7879+-5.9147     ^    331.2712+-6.8610        ^ definitely 1.0408x faster

* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::emitCall):
(JSC::BytecodeGenerator::emitConstruct):
* dfg/DFGArgumentsEliminationPhase.cpp:
* dfg/DFGPreciseLocalClobberize.h:
(JSC::DFG::PreciseLocalClobberizeAdaptor::readTop):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileSpread):
(JSC::FTL::DFG::LowerDFGToB3::compileCallOrConstructVarargsSpread):
(JSC::FTL::DFG::LowerDFGToB3::compileCallOrConstructVarargs):
(JSC::FTL::DFG::LowerDFGToB3::compileForwardVarargs):
(JSC::FTL::DFG::LowerDFGToB3::compileForwardVarargsWithSpread):
* interpreter/Interpreter.cpp:
(JSC::sizeOfVarargs):
(JSC::loadVarargs):
* parser/Nodes.h:
(JSC::ArrayNode::elements):
* runtime/JSFixedArray.cpp:
(JSC::JSFixedArray::copyToArguments):
* runtime/JSFixedArray.h:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@215720 268f45cc-cd09-0410-ab3c-d52691b4dbfc

13 files changed:
JSTests/ChangeLog
JSTests/stress/spread-capture-rest.js [new file with mode: 0644]
JSTests/stress/spread-multi-layers.js [new file with mode: 0644]
JSTests/stress/spread-non-varargs.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp
Source/JavaScriptCore/dfg/DFGPreciseLocalClobberize.h
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Source/JavaScriptCore/interpreter/Interpreter.cpp
Source/JavaScriptCore/parser/Nodes.h
Source/JavaScriptCore/runtime/JSFixedArray.cpp
Source/JavaScriptCore/runtime/JSFixedArray.h

index 8d8ec41..7a7c18c 100644 (file)
@@ -1,3 +1,26 @@
+2017-04-24  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [JSC] Use JSFixedArray directly when using call_varargs
+        https://bugs.webkit.org/show_bug.cgi?id=171057
+
+        Reviewed by Saam Barati.
+
+        * stress/spread-capture-rest.js: Added.
+        (shouldBe):
+        (capture):
+        (a):
+        (b):
+        * stress/spread-multi-layers.js: Added.
+        (shouldBe):
+        (a):
+        (b):
+        (c):
+        (d):
+        * stress/spread-non-varargs.js: Added.
+        (shouldBe):
+        (a):
+        (b):
+
 2017-04-24  Joseph Pecoraro  <pecoraro@apple.com>
 
         test262: test262/test/language/computed-property-names/class/static/getter-prototype.js
diff --git a/JSTests/stress/spread-capture-rest.js b/JSTests/stress/spread-capture-rest.js
new file mode 100644 (file)
index 0000000..6ad7dbe
--- /dev/null
@@ -0,0 +1,80 @@
+(function () {
+    "use strict";
+
+    function shouldBe(actual, expected)
+    {
+        if (actual !== expected)
+            throw new Error('bad value: ' + actual);
+    }
+    noInline(shouldBe);
+
+    function capture(arg)
+    {
+    }
+    noInline(capture);
+
+    var flag = false;
+
+    function a(...args)
+    {
+        // This makes args and Spread non-phantom.
+        capture(args);
+        if (flag) {
+            OSRExit();
+            return args[0];
+        }
+        return b(...args);
+    }
+
+    function b(...args)
+    {
+        return Math.max(...args);
+    }
+
+    for (var i = 0; i < 1e6; ++i) {
+        flag = i > (1e6 - 100);
+        var ret = a(0, 1, 2, 3, 4);
+        if (!flag)
+            shouldBe(ret, 4);
+        else
+            shouldBe(ret, 0);
+    }
+}());
+
+(function () {
+    "use strict";
+
+    function shouldBe(actual, expected)
+    {
+        if (actual !== expected)
+            throw new Error('bad value: ' + actual);
+    }
+    noInline(shouldBe);
+
+    function capture(arg)
+    {
+    }
+    noInline(capture);
+
+    var flag = false;
+
+    function a(...args)
+    {
+        // This makes args and Spread non-phantom.
+        capture(args);
+        if (flag) {
+            OSRExit();
+            return args[0];
+        }
+        return Math.max(...args);
+    }
+
+    for (var i = 0; i < 1e6; ++i) {
+        flag = i > (1e6 - 100);
+        var ret = a(0, 1, 2, 3, 4);
+        if (!flag)
+            shouldBe(ret, 4);
+        else
+            shouldBe(ret, 0);
+    }
+}());
diff --git a/JSTests/stress/spread-multi-layers.js b/JSTests/stress/spread-multi-layers.js
new file mode 100644 (file)
index 0000000..60ae69e
--- /dev/null
@@ -0,0 +1,47 @@
+(function () {
+    "use strict";
+
+    function shouldBe(actual, expected)
+    {
+        if (actual !== expected)
+            throw new Error('bad value: ' + actual);
+    }
+    noInline(shouldBe);
+
+    var flag = false;
+
+    function a(...args)
+    {
+        return b(...args);
+    }
+
+    function b(...args)
+    {
+        if (flag) {
+            OSRExit();
+            return args[0];
+        }
+        return c(...args);
+    }
+
+    function c(...args)
+    {
+        return d(...args);
+    }
+
+    function d(...args)
+    {
+        return Math.max(...args);
+    }
+    noInline(d);
+
+    var array = [0, 1, 2, 3, 4, 5];
+    for (var i = 0; i < 1e6; ++i) {
+        flag = i > (1e6 - 100);
+        var ret = a(...array);
+        if (!flag)
+            shouldBe(ret, 5);
+        else
+            shouldBe(ret, 0);
+    }
+}());
diff --git a/JSTests/stress/spread-non-varargs.js b/JSTests/stress/spread-non-varargs.js
new file mode 100644 (file)
index 0000000..dc26af7
--- /dev/null
@@ -0,0 +1,35 @@
+(function () {
+    "use strict";
+
+    function shouldBe(actual, expected)
+    {
+        if (actual !== expected)
+            throw new Error('bad value: ' + actual);
+    }
+    noInline(shouldBe);
+
+    var flag = false;
+
+    function a(...args)
+    {
+        if (flag) {
+            OSRExit();
+            return args[0];
+        }
+        return b(...args);
+    }
+
+    function b(...args)
+    {
+        return Math.max(...args);
+    }
+
+    for (var i = 0; i < 1e6; ++i) {
+        flag = i > (1e6 - 100);
+        var ret = a(0, 1, 2, 3, 4);
+        if (!flag)
+            shouldBe(ret, 4);
+        else
+            shouldBe(ret, 0);
+    }
+}());
index d6d52df..116c9ed 100644 (file)
@@ -1,5 +1,52 @@
 2017-04-24  Yusuke Suzuki  <utatane.tea@gmail.com>
 
+        [JSC] Use JSFixedArray directly when using call_varargs
+        https://bugs.webkit.org/show_bug.cgi?id=171057
+
+        Reviewed by Saam Barati.
+
+        Previously we always emit new_array_with_spread when calling call(...args).
+        But this array is unnecessary if varargs operation can handle Spread directly.
+
+        This patch implements a peep-hole optimization in the bytecode compiler layer
+        to omit new_array_with_spread. This is very simple and effective because this
+        peep-hole optimization is quite common when using (...args) style calls and
+        this optimization works all the tiers. While we can implement the phase to
+        omit this NewArrayWithSpread in argument elimination phase, it only works
+        for FTL. While such an optimization can work with complex data flow, this
+        peep-hole optimization can optimize a common case easily.
+
+        For now, Spread and PhantomSpread can be directly drained by CallVarargs
+        and LoadVarargs related operations. We modify DFG and FTL to handle this correctly.
+
+        This shows six-speed improvement.
+
+            spread.es6                 89.4300+-2.0236     ^     69.6015+-1.7278        ^ definitely 1.2849x faster
+            spread-generator.es6      344.7879+-5.9147     ^    331.2712+-6.8610        ^ definitely 1.0408x faster
+
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::emitCall):
+        (JSC::BytecodeGenerator::emitConstruct):
+        * dfg/DFGArgumentsEliminationPhase.cpp:
+        * dfg/DFGPreciseLocalClobberize.h:
+        (JSC::DFG::PreciseLocalClobberizeAdaptor::readTop):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileSpread):
+        (JSC::FTL::DFG::LowerDFGToB3::compileCallOrConstructVarargsSpread):
+        (JSC::FTL::DFG::LowerDFGToB3::compileCallOrConstructVarargs):
+        (JSC::FTL::DFG::LowerDFGToB3::compileForwardVarargs):
+        (JSC::FTL::DFG::LowerDFGToB3::compileForwardVarargsWithSpread):
+        * interpreter/Interpreter.cpp:
+        (JSC::sizeOfVarargs):
+        (JSC::loadVarargs):
+        * parser/Nodes.h:
+        (JSC::ArrayNode::elements):
+        * runtime/JSFixedArray.cpp:
+        (JSC::JSFixedArray::copyToArguments):
+        * runtime/JSFixedArray.h:
+
+2017-04-24  Yusuke Suzuki  <utatane.tea@gmail.com>
+
         [WTF] Move JSC tools/StackTrace to WTF and unify stack trace dump code
         https://bugs.webkit.org/show_bug.cgi?id=171199
 
index ad0f417..048664d 100644 (file)
@@ -3421,6 +3421,19 @@ RegisterID* BytecodeGenerator::emitCall(OpcodeID opcodeID, RegisterID* dst, Regi
         if (n && n->m_expr->isSpreadExpression()) {
             RELEASE_ASSERT(!n->m_next);
             auto expression = static_cast<SpreadExpressionNode*>(n->m_expr)->expression();
+            if (expression->isArrayLiteral()) {
+                auto* elements = static_cast<ArrayNode*>(expression)->elements();
+                if (elements && !elements->next() && elements->value()->isSpreadExpression()) {
+                    ExpressionNode* expression = static_cast<SpreadExpressionNode*>(elements->value())->expression();
+                    RefPtr<RegisterID> argumentRegister = emitNode(callArguments.argumentRegister(0), expression);
+                    emitOpcode(op_spread);
+                    instructions().append(argumentRegister.get()->index());
+                    instructions().append(argumentRegister.get()->index());
+
+                    RefPtr<RegisterID> thisRegister = emitMove(newTemporary(), callArguments.thisRegister());
+                    return emitCallVarargs(opcodeID == op_tail_call ? op_tail_call_varargs : op_call_varargs, dst, func, callArguments.thisRegister(), argumentRegister.get(), newTemporary(), 0, divot, divotStart, divotEnd, debuggableCall);
+                }
+            }
             RefPtr<RegisterID> argumentRegister;
             argumentRegister = expression->emitBytecode(*this, callArguments.argumentRegister(0));
             RefPtr<RegisterID> thisRegister = emitMove(newTemporary(), callArguments.thisRegister());
@@ -3639,6 +3652,19 @@ RegisterID* BytecodeGenerator::emitConstruct(RegisterID* dst, RegisterID* func,
         if (n && n->m_expr->isSpreadExpression()) {
             RELEASE_ASSERT(!n->m_next);
             auto expression = static_cast<SpreadExpressionNode*>(n->m_expr)->expression();
+            if (expression->isArrayLiteral()) {
+                auto* elements = static_cast<ArrayNode*>(expression)->elements();
+                if (elements && !elements->next() && elements->value()->isSpreadExpression()) {
+                    ExpressionNode* expression = static_cast<SpreadExpressionNode*>(elements->value())->expression();
+                    RefPtr<RegisterID> argumentRegister = emitNode(callArguments.argumentRegister(0), expression);
+                    emitOpcode(op_spread);
+                    instructions().append(argumentRegister.get()->index());
+                    instructions().append(argumentRegister.get()->index());
+
+                    RefPtr<RegisterID> thisRegister = emitMove(newTemporary(), callArguments.thisRegister());
+                    return emitConstructVarargs(dst, func, callArguments.thisRegister(), argumentRegister.get(), newTemporary(), 0, divot, divotStart, divotEnd, DebuggableCall::No);
+                }
+            }
             RefPtr<RegisterID> argumentRegister;
             argumentRegister = expression->emitBytecode(*this, callArguments.argumentRegister(0));
             return emitConstructVarargs(dst, func, callArguments.thisRegister(), argumentRegister.get(), newTemporary(), 0, divot, divotStart, divotEnd, DebuggableCall::No);
index b93be30..4663080 100644 (file)
@@ -323,7 +323,7 @@ private:
 
                     
                 case LoadVarargs:
-                    if (node->loadVarargsData()->offset && node->child1()->op() == NewArrayWithSpread)
+                    if (node->loadVarargsData()->offset && (node->child1()->op() == NewArrayWithSpread || node->child1()->op() == Spread))
                         escape(node->child1(), node);
                     break;
                     
@@ -333,7 +333,7 @@ private:
                 case TailCallVarargsInlinedCaller:
                     escape(node->child1(), node);
                     escape(node->child2(), node);
-                    if (node->callVarargsData()->firstVarArgOffset && node->child3()->op() == NewArrayWithSpread)
+                    if (node->callVarargsData()->firstVarArgOffset && (node->child3()->op() == NewArrayWithSpread || node->child3()->op() == Spread))
                         escape(node->child3(), node);
                     break;
 
@@ -855,6 +855,10 @@ private:
                         unsigned numberOfArgumentsToSkip = 0;
                         if (candidate->op() == PhantomCreateRest)
                             numberOfArgumentsToSkip = candidate->numberOfArgumentsToSkip();
+                        else if (candidate->op() == PhantomSpread) {
+                            ASSERT(candidate->child1()->op() == PhantomCreateRest);
+                            numberOfArgumentsToSkip = candidate->child1()->numberOfArgumentsToSkip();
+                        }
                         varargsData->offset += numberOfArgumentsToSkip;
 
                         InlineCallFrame* inlineCallFrame = candidate->origin.semantic.inlineCallFrame;
@@ -976,44 +980,60 @@ private:
                         }
                     };
                     
-                    if (candidate->op() == PhantomNewArrayWithSpread) {
+                    if (candidate->op() == PhantomNewArrayWithSpread || candidate->op() == PhantomSpread) {
                         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;
+                        auto canTransformToStaticArgumentCountCallForSpread = [] (Node* spread) {
+                            ASSERT(spread->op() == PhantomSpread);
+                            ASSERT(spread->child1()->op() == PhantomCreateRest);
+                            InlineCallFrame* inlineCallFrame = spread->child1()->origin.semantic.inlineCallFrame;
+                            return inlineCallFrame && !inlineCallFrame->isVarargs();
+                        };
+
+                        if (candidate->op() == PhantomNewArrayWithSpread) {
+                            BitVector* bitVector = candidate->bitVector();
                             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);
+                                    Node* spread = m_graph.varArgChild(candidate, i).node();
+                                    if (!canTransformToStaticArgumentCountCallForSpread(spread)) {
+                                        canTransformToStaticArgumentCountCall = false;
+                                        break;
                                     }
-                                } else
-                                    arguments.append(child);
+                                }
                             }
+                        } else
+                            canTransformToStaticArgumentCountCall = canTransformToStaticArgumentCountCallForSpread(candidate);
+
+                        if (canTransformToStaticArgumentCountCall) {
+                            Vector<Node*> arguments;
+                            auto appendSpread = [&] (Node* spread) {
+                                ASSERT(spread->op() == PhantomSpread);
+                                ASSERT(spread->child1()->op() == PhantomCreateRest);
+                                InlineCallFrame* inlineCallFrame = spread->child1()->origin.semantic.inlineCallFrame;
+                                unsigned numberOfArgumentsToSkip = spread->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);
+                                }
+                            };
+
+                            if (candidate->op() == PhantomNewArrayWithSpread) {
+                                BitVector* bitVector = candidate->bitVector();
+                                for (unsigned i = 0; i < candidate->numChildren(); i++) {
+                                    Node* child = m_graph.varArgChild(candidate, i).node();
+                                    if (bitVector->get(i))
+                                        appendSpread(child);
+                                    else
+                                        arguments.append(child);
+                                }
+                            } else
+                                appendSpread(candidate);
 
                             convertToStaticArgumentCountCall(arguments);
                         } else
index 6fd8ae6..d79d2d1 100644 (file)
@@ -121,18 +121,22 @@ private:
                 m_read(VirtualRegister(inlineCallFrame->stackOffset + CallFrameSlot::argumentCount));
         };
 
+        auto readPhantomSpreadNode = [&] (Node* spread) {
+            ASSERT(spread->op() == PhantomSpread);
+            ASSERT(spread->child1()->op() == PhantomCreateRest);
+            InlineCallFrame* inlineCallFrame = spread->child1()->origin.semantic.inlineCallFrame;
+            unsigned numberOfArgumentsToSkip = spread->child1()->numberOfArgumentsToSkip();
+            readFrame(inlineCallFrame, numberOfArgumentsToSkip);
+        };
+
         auto readNewArrayWithSpreadNode = [&] (Node* arrayWithSpread) {
             ASSERT(arrayWithSpread->op() == NewArrayWithSpread || arrayWithSpread->op() == PhantomNewArrayWithSpread);
             BitVector* bitVector = arrayWithSpread->bitVector();
             for (unsigned i = 0; i < arrayWithSpread->numChildren(); i++) {
                 if (bitVector->get(i)) {
                     Node* child = m_graph.varArgChild(arrayWithSpread, i).node();
-                    if (child->op() == PhantomSpread) {
-                        ASSERT(child->child1()->op() == PhantomCreateRest);
-                        InlineCallFrame* inlineCallFrame = child->child1()->origin.semantic.inlineCallFrame;
-                        unsigned numberOfArgumentsToSkip = child->child1()->numberOfArgumentsToSkip();
-                        readFrame(inlineCallFrame, numberOfArgumentsToSkip);
-                    }
+                    if (child->op() == PhantomSpread)
+                        readPhantomSpreadNode(child);
                 }
             }
         };
@@ -173,9 +177,12 @@ private:
             if (isPhantomNode && isFTL(m_graph.m_plan.mode))
                 break;
             
-            if (isForwardingNode && m_node->hasArgumentsChild() && m_node->argumentsChild() && m_node->argumentsChild()->op() == PhantomNewArrayWithSpread) {
-                Node* arrayWithSpread = m_node->argumentsChild().node();
-                readNewArrayWithSpreadNode(arrayWithSpread);
+            if (isForwardingNode && m_node->hasArgumentsChild() && m_node->argumentsChild()
+                && (m_node->argumentsChild()->op() == PhantomNewArrayWithSpread || m_node->argumentsChild()->op() == PhantomSpread)) {
+                if (m_node->argumentsChild()->op() == PhantomNewArrayWithSpread)
+                    readNewArrayWithSpreadNode(m_node->argumentsChild().node());
+                else
+                    readPhantomSpreadNode(m_node->argumentsChild().node());
             } else {
                 InlineCallFrame* inlineCallFrame;
                 if (m_node->hasArgumentsChild() && m_node->argumentsChild())
index 9d0189c..20e04c4 100644 (file)
@@ -4668,8 +4668,9 @@ private:
     {
         // It would be trivial to support this, but for now, we never create
         // IR that would necessitate this. The reason is that Spread is only
-        // consumed by NewArrayWithSpread and never anything else. Also, any
-        // Spread(PhantomCreateRest) will turn into PhantomSpread(PhantomCreateRest).
+        // consumed by NewArrayWithSpread and Varargs operations. And it is
+        // never anything else. Also, any Spread(PhantomCreateRest) will turn
+        // into PhantomSpread(PhantomCreateRest).
         RELEASE_ASSERT(m_node->child1()->op() != PhantomCreateRest); 
 
         LValue argument = lowCell(m_node->child1());
@@ -6404,34 +6405,45 @@ private:
     void compileCallOrConstructVarargsSpread()
     {
         Node* node = m_node;
+        Node* arguments = node->child3().node();
+
         LValue jsCallee = lowJSValue(m_node->child1());
         LValue thisArg = lowJSValue(m_node->child2());
 
-        RELEASE_ASSERT(node->child3()->op() == PhantomNewArrayWithSpread);
-        Node* arrayWithSpread = node->child3().node();
-        BitVector* bitVector = arrayWithSpread->bitVector();
+        RELEASE_ASSERT(arguments->op() == PhantomNewArrayWithSpread || arguments->op() == PhantomSpread);
+
         unsigned numNonSpreadParameters = 0;
         Vector<LValue, 2> spreadLengths;
         Vector<LValue, 8> patchpointArguments;
         HashMap<InlineCallFrame*, LValue, WTF::DefaultHash<InlineCallFrame*>::Hash, WTF::NullableHashTraits<InlineCallFrame*>> cachedSpreadLengths;
 
-        for (unsigned i = 0; i < arrayWithSpread->numChildren(); i++) {
-            if (bitVector->get(i)) {
-                Node* spread = m_graph.varArgChild(arrayWithSpread, i).node();
-                RELEASE_ASSERT(spread->op() == PhantomSpread);
-                RELEASE_ASSERT(spread->child1()->op() == PhantomCreateRest);
-                InlineCallFrame* inlineCallFrame = spread->child1()->origin.semantic.inlineCallFrame;
-                unsigned numberOfArgumentsToSkip = spread->child1()->numberOfArgumentsToSkip();
-                LValue length = cachedSpreadLengths.ensure(inlineCallFrame, [&] () {
-                    return m_out.zeroExtPtr(getSpreadLengthFromInlineCallFrame(inlineCallFrame, numberOfArgumentsToSkip));
-                }).iterator->value;
-                patchpointArguments.append(length);
-                spreadLengths.append(length);
-            } else {
-                ++numNonSpreadParameters;
-                LValue argument = lowJSValue(m_graph.varArgChild(arrayWithSpread, i));
-                patchpointArguments.append(argument);
+        auto loadSpreadLength = [this, &cachedSpreadLengths] (Node* spread) -> LValue {
+            RELEASE_ASSERT(spread->op() == PhantomSpread);
+            RELEASE_ASSERT(spread->child1()->op() == PhantomCreateRest);
+            InlineCallFrame* inlineCallFrame = spread->child1()->origin.semantic.inlineCallFrame;
+            unsigned numberOfArgumentsToSkip = spread->child1()->numberOfArgumentsToSkip();
+            return cachedSpreadLengths.ensure(inlineCallFrame, [&] () {
+                return m_out.zeroExtPtr(getSpreadLengthFromInlineCallFrame(inlineCallFrame, numberOfArgumentsToSkip));
+            }).iterator->value;
+        };
+
+        if (arguments->op() == PhantomNewArrayWithSpread) {
+            BitVector* bitVector = arguments->bitVector();
+            for (unsigned i = 0; i < arguments->numChildren(); i++) {
+                if (bitVector->get(i)) {
+                    LValue length = loadSpreadLength(m_graph.varArgChild(arguments, i).node());
+                    patchpointArguments.append(length);
+                    spreadLengths.append(length);
+                } else {
+                    ++numNonSpreadParameters;
+                    LValue argument = lowJSValue(m_graph.varArgChild(arguments, i));
+                    patchpointArguments.append(argument);
+                }
             }
+        } else {
+            LValue length = loadSpreadLength(arguments);
+            patchpointArguments.append(length);
+            spreadLengths.append(length);
         }
 
         LValue argumentCountIncludingThis = m_out.constIntPtr(numNonSpreadParameters + 1);
@@ -6547,37 +6559,43 @@ private:
 
                     int storeOffset = CallFrame::thisArgumentOffset() * static_cast<int>(sizeof(Register));
 
-                    for (unsigned i = arrayWithSpread->numChildren(); i--; ) {
-                        unsigned paramsOffset = 4;
-
-                        if (bitVector->get(i)) {
-                            Node* spread = state->graph.varArgChild(arrayWithSpread, i).node();
-                            RELEASE_ASSERT(spread->op() == PhantomSpread);
-                            RELEASE_ASSERT(spread->child1()->op() == PhantomCreateRest);
-                            InlineCallFrame* inlineCallFrame = spread->child1()->origin.semantic.inlineCallFrame;
-
-                            unsigned numberOfArgumentsToSkip = spread->child1()->numberOfArgumentsToSkip();
-
-                            B3::ValueRep numArgumentsToCopy = params[paramsOffset + i];
-                            getValueFromRep(numArgumentsToCopy, scratchGPR3);
-                            int loadOffset = (AssemblyHelpers::argumentsStart(inlineCallFrame).offset() + numberOfArgumentsToSkip) * static_cast<int>(sizeof(Register));
-
-                            auto done = jit.branchTestPtr(MacroAssembler::Zero, scratchGPR3);
-                            auto loopStart = jit.label();
-                            jit.subPtr(CCallHelpers::TrustedImmPtr(static_cast<size_t>(1)), scratchGPR3);
-                            jit.subPtr(CCallHelpers::TrustedImmPtr(static_cast<size_t>(1)), scratchGPR2);
-                            jit.load64(CCallHelpers::BaseIndex(GPRInfo::callFrameRegister, scratchGPR3, CCallHelpers::TimesEight, loadOffset), scratchGPR4);
-                            jit.store64(scratchGPR4,
-                                CCallHelpers::BaseIndex(scratchGPR1, scratchGPR2, CCallHelpers::TimesEight, storeOffset));
-                            jit.branchTestPtr(CCallHelpers::NonZero, scratchGPR3).linkTo(loopStart, &jit);
-                            done.link(&jit);
-                        } else {
-                            jit.subPtr(CCallHelpers::TrustedImmPtr(static_cast<size_t>(1)), scratchGPR2);
-                            getValueFromRep(params[paramsOffset + i], scratchGPR3);
-                            jit.store64(scratchGPR3,
-                                CCallHelpers::BaseIndex(scratchGPR1, scratchGPR2, CCallHelpers::TimesEight, storeOffset));
+                    unsigned paramsOffset = 4;
+                    auto emitSpread = [&] (Node* spread, unsigned index) {
+                        RELEASE_ASSERT(spread->op() == PhantomSpread);
+                        RELEASE_ASSERT(spread->child1()->op() == PhantomCreateRest);
+                        InlineCallFrame* inlineCallFrame = spread->child1()->origin.semantic.inlineCallFrame;
+
+                        unsigned numberOfArgumentsToSkip = spread->child1()->numberOfArgumentsToSkip();
+
+                        B3::ValueRep numArgumentsToCopy = params[paramsOffset + index];
+                        getValueFromRep(numArgumentsToCopy, scratchGPR3);
+                        int loadOffset = (AssemblyHelpers::argumentsStart(inlineCallFrame).offset() + numberOfArgumentsToSkip) * static_cast<int>(sizeof(Register));
+
+                        auto done = jit.branchTestPtr(MacroAssembler::Zero, scratchGPR3);
+                        auto loopStart = jit.label();
+                        jit.subPtr(CCallHelpers::TrustedImmPtr(static_cast<size_t>(1)), scratchGPR3);
+                        jit.subPtr(CCallHelpers::TrustedImmPtr(static_cast<size_t>(1)), scratchGPR2);
+                        jit.load64(CCallHelpers::BaseIndex(GPRInfo::callFrameRegister, scratchGPR3, CCallHelpers::TimesEight, loadOffset), scratchGPR4);
+                        jit.store64(scratchGPR4,
+                            CCallHelpers::BaseIndex(scratchGPR1, scratchGPR2, CCallHelpers::TimesEight, storeOffset));
+                        jit.branchTestPtr(CCallHelpers::NonZero, scratchGPR3).linkTo(loopStart, &jit);
+                        done.link(&jit);
+                    };
+
+                    if (arguments->op() == PhantomNewArrayWithSpread) {
+                        BitVector* bitVector = arguments->bitVector();
+                        for (unsigned i = arguments->numChildren(); i--; ) {
+                            if (bitVector->get(i))
+                                emitSpread(state->graph.varArgChild(arguments, i).node(), i);
+                            else {
+                                jit.subPtr(CCallHelpers::TrustedImmPtr(static_cast<size_t>(1)), scratchGPR2);
+                                getValueFromRep(params[paramsOffset + i], scratchGPR3);
+                                jit.store64(scratchGPR3,
+                                    CCallHelpers::BaseIndex(scratchGPR1, scratchGPR2, CCallHelpers::TimesEight, storeOffset));
+                            }
                         }
-                    }
+                    } else
+                        emitSpread(arguments, 0);
                 }
 
                 {
@@ -6692,9 +6710,12 @@ private:
             break;
         }
 
-        if (forwarding && m_node->child3() && m_node->child3()->op() == PhantomNewArrayWithSpread) {
-            compileCallOrConstructVarargsSpread();
-            return;
+        if (forwarding && m_node->child3()) {
+            Node* arguments = m_node->child3().node();
+            if (arguments->op() == PhantomNewArrayWithSpread || arguments->op() == PhantomSpread) {
+                compileCallOrConstructVarargsSpread();
+                return;
+            }
         }
 
         
@@ -7068,9 +7089,12 @@ private:
     
     void compileForwardVarargs()
     {
-        if (m_node->child1() && m_node->child1()->op() == PhantomNewArrayWithSpread) {
-            compileForwardVarargsWithSpread();
-            return;
+        if (m_node->child1()) {
+            Node* arguments = m_node->child1().node();
+            if (arguments->op() == PhantomNewArrayWithSpread || arguments->op() == PhantomSpread) {
+                compileForwardVarargsWithSpread();
+                return;
+            }
         }
 
         LoadVarargsData* data = m_node->loadVarargsData();
@@ -7196,25 +7220,32 @@ private:
     {
         HashMap<InlineCallFrame*, LValue, WTF::DefaultHash<InlineCallFrame*>::Hash, WTF::NullableHashTraits<InlineCallFrame*>> cachedSpreadLengths;
 
-        Node* arrayWithSpread = m_node->child1().node();
-        RELEASE_ASSERT(arrayWithSpread->op() == PhantomNewArrayWithSpread);
-        BitVector* bitVector = arrayWithSpread->bitVector();
+        Node* arguments = m_node->child1().node();
+        RELEASE_ASSERT(arguments->op() == PhantomNewArrayWithSpread || arguments->op() == PhantomSpread);
 
         unsigned numberOfStaticArguments = 0;
         Vector<LValue, 2> spreadLengths;
-        for (unsigned i = 0; i < arrayWithSpread->numChildren(); i++) {
-            if (bitVector->get(i)) {
-                Node* child = m_graph.varArgChild(arrayWithSpread, i).node();
-                ASSERT(child->op() == PhantomSpread);
-                ASSERT(child->child1()->op() == PhantomCreateRest);
-                InlineCallFrame* inlineCallFrame = child->child1()->origin.semantic.inlineCallFrame;
-                LValue length = cachedSpreadLengths.ensure(inlineCallFrame, [&] () {
-                    return getSpreadLengthFromInlineCallFrame(inlineCallFrame, child->child1()->numberOfArgumentsToSkip());
-                }).iterator->value;
-                spreadLengths.append(length);
-            } else
-                ++numberOfStaticArguments;
-        }
+
+        auto loadSpreadLength = [this, &cachedSpreadLengths] (Node* spread) -> LValue {
+            ASSERT(spread->op() == PhantomSpread);
+            ASSERT(spread->child1()->op() == PhantomCreateRest);
+            InlineCallFrame* inlineCallFrame = spread->child1()->origin.semantic.inlineCallFrame;
+            unsigned numberOfArgumentsToSkip = spread->child1()->numberOfArgumentsToSkip();
+            return cachedSpreadLengths.ensure(inlineCallFrame, [&] () {
+                return getSpreadLengthFromInlineCallFrame(inlineCallFrame, numberOfArgumentsToSkip);
+            }).iterator->value;
+        };
+
+        if (arguments->op() == PhantomNewArrayWithSpread) {
+            BitVector* bitVector = arguments->bitVector();
+            for (unsigned i = 0; i < arguments->numChildren(); i++) {
+                if (bitVector->get(i))
+                    spreadLengths.append(loadSpreadLength(m_graph.varArgChild(arguments, i).node()));
+                else
+                    ++numberOfStaticArguments;
+            }
+        } else
+            spreadLengths.append(loadSpreadLength(arguments));
 
         LValue lengthIncludingThis = m_out.constInt32(1 + numberOfStaticArguments);
         for (LValue length : spreadLengths)
@@ -7229,45 +7260,53 @@ private:
 
         LValue targetStart = addressFor(data->machineStart).value();
         LValue storeIndex = m_out.constIntPtr(0);
-        for (unsigned i = 0; i < arrayWithSpread->numChildren(); i++) {
-            if (bitVector->get(i)) {
-                Node* child = m_graph.varArgChild(arrayWithSpread, i).node();
-                RELEASE_ASSERT(child->op() == PhantomSpread);
-                RELEASE_ASSERT(child->child1()->op() == PhantomCreateRest);
-                InlineCallFrame* inlineCallFrame = child->child1()->origin.semantic.inlineCallFrame;
 
-                LValue sourceStart = getArgumentsStart(inlineCallFrame, child->child1()->numberOfArgumentsToSkip());
-                LValue spreadLength = m_out.zeroExtPtr(cachedSpreadLengths.get(inlineCallFrame));
+        auto forwardSpread = [this, &cachedSpreadLengths, &targetStart] (Node* spread, LValue storeIndex) -> LValue {
+            RELEASE_ASSERT(spread->op() == PhantomSpread);
+            RELEASE_ASSERT(spread->child1()->op() == PhantomCreateRest);
+            InlineCallFrame* inlineCallFrame = spread->child1()->origin.semantic.inlineCallFrame;
 
-                LBasicBlock loop = m_out.newBlock();
-                LBasicBlock continuation = m_out.newBlock();
-                ValueFromBlock startLoadIndex = m_out.anchor(m_out.constIntPtr(0));
-                ValueFromBlock startStoreIndex = m_out.anchor(storeIndex);
-                ValueFromBlock startStoreIndexForEnd = m_out.anchor(storeIndex);
-
-                m_out.branch(m_out.isZero64(spreadLength), unsure(continuation), unsure(loop));
-
-                LBasicBlock lastNext = m_out.appendTo(loop, continuation);
-                LValue loopStoreIndex = m_out.phi(Int64, startStoreIndex);
-                LValue loadIndex = m_out.phi(Int64, startLoadIndex);
-                LValue value = m_out.load64(
-                    m_out.baseIndex(m_heaps.variables, sourceStart, loadIndex));
-                m_out.store64(value, m_out.baseIndex(m_heaps.variables, targetStart, loopStoreIndex));
-                LValue nextLoadIndex = m_out.add(m_out.constIntPtr(1), loadIndex);
-                m_out.addIncomingToPhi(loadIndex, m_out.anchor(nextLoadIndex));
-                LValue nextStoreIndex = m_out.add(m_out.constIntPtr(1), loopStoreIndex);
-                m_out.addIncomingToPhi(loopStoreIndex, m_out.anchor(nextStoreIndex));
-                ValueFromBlock loopStoreIndexForEnd = m_out.anchor(nextStoreIndex);
-                m_out.branch(m_out.below(nextLoadIndex, spreadLength), unsure(loop), unsure(continuation));
+            LValue sourceStart = getArgumentsStart(inlineCallFrame, spread->child1()->numberOfArgumentsToSkip());
+            LValue spreadLength = m_out.zeroExtPtr(cachedSpreadLengths.get(inlineCallFrame));
 
-                m_out.appendTo(continuation, lastNext);
-                storeIndex = m_out.phi(Int64, startStoreIndexForEnd, loopStoreIndexForEnd);
-            } else {
-                LValue value = lowJSValue(m_graph.varArgChild(arrayWithSpread, i));
-                m_out.store64(value, m_out.baseIndex(m_heaps.variables, targetStart, storeIndex));
-                storeIndex = m_out.add(m_out.constIntPtr(1), storeIndex);
+            LBasicBlock loop = m_out.newBlock();
+            LBasicBlock continuation = m_out.newBlock();
+            ValueFromBlock startLoadIndex = m_out.anchor(m_out.constIntPtr(0));
+            ValueFromBlock startStoreIndex = m_out.anchor(storeIndex);
+            ValueFromBlock startStoreIndexForEnd = m_out.anchor(storeIndex);
+
+            m_out.branch(m_out.isZero64(spreadLength), unsure(continuation), unsure(loop));
+
+            LBasicBlock lastNext = m_out.appendTo(loop, continuation);
+            LValue loopStoreIndex = m_out.phi(Int64, startStoreIndex);
+            LValue loadIndex = m_out.phi(Int64, startLoadIndex);
+            LValue value = m_out.load64(
+                m_out.baseIndex(m_heaps.variables, sourceStart, loadIndex));
+            m_out.store64(value, m_out.baseIndex(m_heaps.variables, targetStart, loopStoreIndex));
+            LValue nextLoadIndex = m_out.add(m_out.constIntPtr(1), loadIndex);
+            m_out.addIncomingToPhi(loadIndex, m_out.anchor(nextLoadIndex));
+            LValue nextStoreIndex = m_out.add(m_out.constIntPtr(1), loopStoreIndex);
+            m_out.addIncomingToPhi(loopStoreIndex, m_out.anchor(nextStoreIndex));
+            ValueFromBlock loopStoreIndexForEnd = m_out.anchor(nextStoreIndex);
+            m_out.branch(m_out.below(nextLoadIndex, spreadLength), unsure(loop), unsure(continuation));
+
+            m_out.appendTo(continuation, lastNext);
+            return m_out.phi(Int64, startStoreIndexForEnd, loopStoreIndexForEnd);
+        };
+
+        if (arguments->op() == PhantomNewArrayWithSpread) {
+            BitVector* bitVector = arguments->bitVector();
+            for (unsigned i = 0; i < arguments->numChildren(); i++) {
+                if (bitVector->get(i))
+                    storeIndex = forwardSpread(m_graph.varArgChild(arguments, i).node(), storeIndex);
+                else {
+                    LValue value = lowJSValue(m_graph.varArgChild(arguments, i));
+                    m_out.store64(value, m_out.baseIndex(m_heaps.variables, targetStart, storeIndex));
+                    storeIndex = m_out.add(m_out.constIntPtr(1), storeIndex);
+                }
             }
-        }
+        } else
+            storeIndex = forwardSpread(arguments, storeIndex);
 
         LBasicBlock undefinedLoop = m_out.newBlock();
         LBasicBlock continuation = m_out.newBlock();
index fef8324..bb5e523 100644 (file)
@@ -46,6 +46,7 @@
 #include "JSArrayInlines.h"
 #include "JSBoundFunction.h"
 #include "JSCInlines.h"
+#include "JSFixedArray.h"
 #include "JSLexicalEnvironment.h"
 #include "JSModuleEnvironment.h"
 #include "JSString.h"
@@ -191,6 +192,9 @@ unsigned sizeOfVarargs(CallFrame* callFrame, JSValue arguments, uint32_t firstVa
     case ScopedArgumentsType:
         length = jsCast<ScopedArguments*>(cell)->length(callFrame);
         break;
+    case JSFixedArrayType:
+        length = jsCast<JSFixedArray*>(cell)->size();
+        break;
     case StringType:
     case SymbolType:
         throwException(callFrame, scope, createInvalidFunctionApplyParameterError(callFrame,  arguments));
@@ -253,6 +257,9 @@ void loadVarargs(CallFrame* callFrame, VirtualRegister firstElementDest, JSValue
     case ScopedArgumentsType:
         jsCast<ScopedArguments*>(cell)->copyToArguments(callFrame, firstElementDest, offset, length);
         return;
+    case JSFixedArrayType:
+        jsCast<JSFixedArray*>(cell)->copyToArguments(callFrame, firstElementDest, offset, length);
+        return;
     default: {
         ASSERT(arguments.isObject());
         JSObject* object = jsCast<JSObject*>(cell);
index bccef5e..6a4f92e 100644 (file)
@@ -633,7 +633,7 @@ namespace JSC {
 
         ArgumentListNode* toArgumentList(ParserArena&, int, int) const;
 
-        ElementNode* elements() const { ASSERT(isSimpleArray()); return m_element; }
+        ElementNode* elements() const { return m_element; }
     private:
         RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
 
index 15d3e23..2ec7ca8 100644 (file)
@@ -40,4 +40,14 @@ void JSFixedArray::visitChildren(JSCell* cell, SlotVisitor& visitor)
     visitor.appendValuesHidden(thisObject->buffer(), thisObject->size());
 }
 
+void JSFixedArray::copyToArguments(ExecState* exec, VirtualRegister firstElementDest, unsigned offset, unsigned length)
+{
+    for (unsigned i = 0; i < length; ++i) {
+        if ((i + offset) < m_size)
+            exec->r(firstElementDest + i) = get(i + offset);
+        else
+            exec->r(firstElementDest + i) = jsUndefined();
+    }
+}
+
 } // namespace JSC
index ba88278..c0c7349 100644 (file)
@@ -111,6 +111,8 @@ public:
         return WTF::roundUpToMultipleOf<sizeof(WriteBarrier<Unknown>)>(sizeof(JSFixedArray));
     }
 
+    void copyToArguments(ExecState*, VirtualRegister firstElementDest, unsigned offset, unsigned length);
+
 private:
     unsigned m_size;