Bug 1003149 - Part2: Implement JSOP_SPREAD* optimized stubs in the baseline compiler. r=jandem
authorTooru Fujisawa <arai_a@mac.com>
Fri, 16 May 2014 01:50:14 +0900
changeset 183847 bcf7062a8d7808987b61b117ddcf97ba58f701b8
parent 183846 4106f916a9b5a053da5423683fd3bcb78596749b
child 183848 3111b47cef975b3d286a1b2cf13abde7b1ba8d56
push id26804
push usercbook@mozilla.com
push dateTue, 20 May 2014 13:27:22 +0000
treeherdermozilla-central@c4142e3c7a6b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1003149
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1003149 - Part2: Implement JSOP_SPREAD* optimized stubs in the baseline compiler. r=jandem
js/src/jit-test/tests/basic/spread-call-maxarg.js
js/src/jit-test/tests/basic/spread-call-optimized.js
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineIC.h
js/src/jit/arm/MacroAssembler-arm.cpp
js/src/jit/arm/MacroAssembler-arm.h
js/src/jit/x64/MacroAssembler-x64.h
js/src/jit/x86/MacroAssembler-x86.h
js/src/jsinfer.cpp
js/src/vm/Interpreter.cpp
--- a/js/src/jit-test/tests/basic/spread-call-maxarg.js
+++ b/js/src/jit-test/tests/basic/spread-call-maxarg.js
@@ -1,30 +1,73 @@
-
 var config = getBuildConfiguration();
 
-// FIXME: ASAN and debug builds run this too slowly for now.  Re-enable
-// after bug 919948 lands.
+// FIXME: ASAN and debug builds run this too slowly for now.
 if (!config.debug && !config.asan) {
-    let a = [];
-    a.length = getMaxArgs() + 1;
+    let longArray = [];
+    longArray.length = getMaxArgs() + 1;
+    let shortArray = [];
+    let a;
 
     let f = function() {
     };
 
-    try {
-        f(...a);
-    } catch (e) {
-        assertEq(e.message, "too many function arguments");
+    // Call_Scripted
+    //   Optimized stub is used after some calls.
+    a = shortArray;
+    for (let i = 0; i < 4; i++) {
+        if (i == 3) {
+            a = longArray;
+        }
+        try {
+            f(...a);
+        } catch (e) {
+            assertEq(e.message, "too many function arguments");
+        }
+    }
+
+    // Call_Scripted (constructing)
+    a = shortArray;
+    for (let i = 0; i < 4; i++) {
+        if (i == 3) {
+            a = longArray;
+        }
+        try {
+            new f(...a);
+        } catch (e) {
+            assertEq(e.message, "too many constructor arguments");
+        }
     }
 
-    try {
-        new f(...a);
-    } catch (e) {
-        assertEq(e.message, "too many constructor arguments");
+    // Call_Native
+    a = shortArray;
+    for (let i = 0; i < 4; i++) {
+        if (i == 3) {
+            a = longArray;
+        }
+        try {
+            Math.max(...a);
+        } catch (e) {
+            assertEq(e.message, "too many function arguments");
+        }
     }
 
+    // Call_Native (constructing)
+    a = shortArray;
+    for (let i = 0; i < 4; i++) {
+        if (i == 3) {
+            a = longArray;
+        }
+        try {
+            new Date(...a);
+        } catch (e) {
+            assertEq(e.message, "too many constructor arguments");
+        }
+    }
+
+    // No optimized stub for eval.
+    a = longArray;
     try {
         eval(...a);
     } catch (e) {
         assertEq(e.message, "too many function arguments");
     }
 }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-optimized.js
@@ -0,0 +1,26 @@
+// Call_Scripted
+let f = function(a, b, c, d, e) {
+    return a * 10000 + b * 1000 + c * 100 + d * 10 + e;
+};
+for (let i = 0; i < 4; i++) {
+    assertEq(f(...[1, 2, 3, 4, 5]), 12345);
+}
+
+// Call_Scripted (constructing)
+let A = function(a, b, c, d, e) {
+    this.v = a * 10000 + b * 1000 + c * 100 + d * 10 + e;
+};
+for (let i = 0; i < 4; i++) {
+    assertEq(new A(...[1, 2, 3, 4, 5]).v, 12345);
+}
+
+// Call_Native
+for (let i = 0; i < 4; i++) {
+    assertEq(Math.max(...[1, 2, 3, 4, 5]), 5);
+}
+
+// Call_Native (constructing)
+for (let i = 0; i < 4; i++) {
+    assertEq(new Date(...[2014, 4, 28, 8, 16, 1]).getTime(),
+             new Date(2014, 4, 28, 8, 16, 1).getTime());
+}
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -7957,17 +7957,18 @@ GetTemplateObjectForNative(JSContext *cx
         return true;
     }
 
     return true;
 }
 
 static bool
 TryAttachCallStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, jsbytecode *pc,
-                  JSOp op, uint32_t argc, Value *vp, bool constructing, bool useNewType)
+                  JSOp op, uint32_t argc, Value *vp, bool constructing, bool isSpread,
+                  bool useNewType)
 {
     if (useNewType || op == JSOP_EVAL)
         return true;
 
     if (stub->numOptimizedStubs() >= ICCall_Fallback::MAX_OPTIMIZED_STUBS) {
         // TODO: Discard all stubs in this IC and replace with inert megamorphic stub.
         // But for now we just bail.
         return true;
@@ -8005,21 +8006,21 @@ TryAttachCallStub(JSContext *cx, ICCall_
         // Check if this stub chain has already generalized scripted calls.
         if (stub->scriptedStubsAreGeneralized()) {
             IonSpew(IonSpew_BaselineIC, "  Chain already has generalized scripted call stub!");
             return true;
         }
 
         if (stub->scriptedStubCount() >= ICCall_Fallback::MAX_SCRIPTED_STUBS) {
             // Create a Call_AnyScripted stub.
-            IonSpew(IonSpew_BaselineIC, "  Generating Call_AnyScripted stub (cons=%s)",
-                    constructing ? "yes" : "no");
+            IonSpew(IonSpew_BaselineIC, "  Generating Call_AnyScripted stub (cons=%s, spread=%s)",
+                    constructing ? "yes" : "no", isSpread ? "yes" : "no");
 
             ICCallScriptedCompiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
-                                            constructing, script->pcToOffset(pc));
+                                            constructing, isSpread, script->pcToOffset(pc));
             ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
             if (!newStub)
                 return false;
 
             // Before adding new stub, unlink all previous Call_Scripted.
             stub->unlinkStubsWithKind(cx, ICStub::Call_Scripted);
 
             // Add new generalized stub.
@@ -8037,22 +8038,22 @@ TryAttachCallStub(JSContext *cx, ICCall_
         RootedObject templateObject(cx);
         if (constructing) {
             templateObject = CreateThisForFunction(cx, fun, MaybeSingletonObject);
             if (!templateObject)
                 return false;
         }
 
         IonSpew(IonSpew_BaselineIC,
-                "  Generating Call_Scripted stub (fun=%p, %s:%d, cons=%s)",
+                "  Generating Call_Scripted stub (fun=%p, %s:%d, cons=%s, spread=%s)",
                 fun.get(), fun->nonLazyScript()->filename(), fun->nonLazyScript()->lineno(),
-                constructing ? "yes" : "no");
+                constructing ? "yes" : "no", isSpread ? "yes" : "no");
         ICCallScriptedCompiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
                                         calleeScript, templateObject,
-                                        constructing, script->pcToOffset(pc));
+                                        constructing, isSpread, script->pcToOffset(pc));
         ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
         if (!newStub)
             return false;
 
         stub->addNewStub(newStub);
         return true;
     }
 
@@ -8079,25 +8080,28 @@ TryAttachCallStub(JSContext *cx, ICCall_
         }
 
         if (stub->nativeStubCount() >= ICCall_Fallback::MAX_NATIVE_STUBS) {
             IonSpew(IonSpew_BaselineIC,
                     "  Too many Call_Native stubs. TODO: add Call_AnyNative!");
             return true;
         }
 
-        CallArgs args = CallArgsFromVp(argc, vp);
         RootedObject templateObject(cx);
-        if (!GetTemplateObjectForNative(cx, script, pc, fun->native(), args, &templateObject))
-            return false;
-
-        IonSpew(IonSpew_BaselineIC, "  Generating Call_Native stub (fun=%p, cons=%s)",
-                fun.get(), constructing ? "yes" : "no");
+        if (MOZ_LIKELY(!isSpread)) {
+            CallArgs args = CallArgsFromVp(argc, vp);
+            if (!GetTemplateObjectForNative(cx, script, pc, fun->native(), args, &templateObject))
+                return false;
+        }
+
+        IonSpew(IonSpew_BaselineIC, "  Generating Call_Native stub (fun=%p, cons=%s, spread=%s)",
+                fun.get(), constructing ? "yes" : "no", isSpread ? "yes" : "no");
         ICCall_Native::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
-                                         fun, templateObject, constructing, script->pcToOffset(pc));
+                                         fun, templateObject, constructing, isSpread,
+                                         script->pcToOffset(pc));
         ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
         if (!newStub)
             return false;
 
         stub->addNewStub(newStub);
         return true;
     }
 
@@ -8151,17 +8155,17 @@ DoCallFallback(JSContext *cx, BaselineFr
             return false;
     }
 
     // Compute construcing and useNewType flags.
     bool constructing = (op == JSOP_NEW);
     bool newType = types::UseNewType(cx, script, pc);
 
     // Try attaching a call stub.
-    if (!TryAttachCallStub(cx, stub, script, pc, op, argc, vp, constructing, newType))
+    if (!TryAttachCallStub(cx, stub, script, pc, op, argc, vp, constructing, false, newType))
         return false;
 
     // Maybe update PC in profiler entry before leaving this script by call.
     if (cx->runtime()->spsProfiler.enabled() && frame->hasPushedSPSFrame())
         cx->runtime()->spsProfiler.updatePC(script, pc);
 
     if (!MaybeCloneFunctionAtCallsite(cx, &callee, script, pc))
         return false;
@@ -8203,22 +8207,32 @@ DoSpreadCallFallback(JSContext *cx, Base
     // This fallback stub may trigger debug mode toggling.
     DebugModeOSRVolatileStub<ICCall_Fallback *> stub(frame, stub_);
 
     // Ensure vp array is rooted - we may GC in here.
     AutoArrayRooter vpRoot(cx, 3, vp);
 
     RootedScript script(cx, frame->script());
     jsbytecode *pc = stub->icEntry()->pc(script);
-    FallbackICSpew(cx, stub, "SpreadCall(%s)", js_CodeName[JSOp(*pc)]);
+    JSOp op = JSOp(*pc);
+    FallbackICSpew(cx, stub, "SpreadCall(%s)", js_CodeName[op]);
 
     RootedValue callee(cx, vp[0]);
     RootedValue thisv(cx, vp[1]);
     RootedValue arr(cx, vp[2]);
 
+    bool constructing = (op == JSOP_SPREADNEW);
+
+    // Try attaching a call stub.
+    if (op != JSOP_SPREADEVAL &&
+        !TryAttachCallStub(cx, stub, script, pc, op, 1, vp, constructing, true, false))
+    {
+        return false;
+    }
+
     // Maybe update PC in profiler entry before leaving this script by call.
     if (cx->runtime()->spsProfiler.enabled() && frame->hasPushedSPSFrame())
         cx->runtime()->spsProfiler.updatePC(script, pc);
 
     if (!MaybeCloneFunctionAtCallsite(cx, &callee, script, pc))
         return false;
 
     if (!SpreadCallOperation(cx, script, pc, thisv, callee, arr, res))
@@ -8266,16 +8280,59 @@ ICCallStubCompiler::pushCallArguments(Ma
         masm.addPtr(Imm32(sizeof(Value)), argPtr);
 
         masm.sub32(Imm32(1), count);
         masm.jump(&loop);
     }
     masm.bind(&done);
 }
 
+void
+ICCallStubCompiler::guardSpreadCall(MacroAssembler &masm, Register argcReg, Label *failure)
+{
+    masm.unboxObject(Address(BaselineStackReg, ICStackValueOffset), argcReg);
+    masm.loadPtr(Address(argcReg, JSObject::offsetOfElements()), argcReg);
+    masm.load32(Address(argcReg, ObjectElements::offsetOfLength()), argcReg);
+
+    // Ensure actual argc <= ARGS_LENGTH_MAX
+    masm.branch32(Assembler::Above, argcReg, Imm32(ARGS_LENGTH_MAX), failure);
+}
+
+void
+ICCallStubCompiler::pushSpreadCallArguments(MacroAssembler &masm, GeneralRegisterSet regs,
+                                            Register argcReg)
+{
+    // Push arguments
+    Register startReg = regs.takeAny();
+    Register endReg = regs.takeAny();
+    masm.unboxObject(Address(BaselineStackReg, STUB_FRAME_SIZE), startReg);
+    masm.loadPtr(Address(startReg, JSObject::offsetOfElements()), startReg);
+    masm.mov(argcReg, endReg);
+    static_assert(sizeof(Value) == 8, "Value must be 8 bytes");
+    masm.lshiftPtr(Imm32(3), endReg);
+    masm.addPtr(startReg, endReg);
+
+    // Copying pre-decrements endReg by 8 until startReg is reached
+    Label copyDone;
+    Label copyStart;
+    masm.bind(&copyStart);
+    masm.branchPtr(Assembler::Equal, endReg, startReg, &copyDone);
+    masm.subPtr(Imm32(sizeof(Value)), endReg);
+    masm.pushValue(Address(endReg, 0));
+    masm.jump(&copyStart);
+    masm.bind(&copyDone);
+
+    regs.add(startReg);
+    regs.add(endReg);
+
+    // Push the callee and |this|.
+    masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE + 1 * sizeof(Value)));
+    masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE + 2 * sizeof(Value)));
+}
+
 Register
 ICCallStubCompiler::guardFunApply(MacroAssembler &masm, GeneralRegisterSet regs, Register argcReg,
                                   bool checkNative, FunApplyThing applyThing, Label *failure)
 {
     // Ensure argc == 2
     masm.branch32(Assembler::NotEqual, argcReg, Imm32(2), failure);
 
     // Stack looks like:
@@ -8569,20 +8626,28 @@ ICCallScriptedCompiler::generateStubCode
 
     Register argcReg = R0.scratchReg();
     JS_ASSERT(argcReg != ArgumentsRectifierReg);
 
     regs.take(argcReg);
     regs.take(ArgumentsRectifierReg);
     regs.takeUnchecked(BaselineTailCallReg);
 
+    if (isSpread_)
+        guardSpreadCall(masm, argcReg, &failure);
+
     // Load the callee in R1.
     // Stack Layout: [ ..., CalleeVal, ThisVal, Arg0Val, ..., ArgNVal, +ICStackValueOffset+ ]
-    BaseIndex calleeSlot(BaselineStackReg, argcReg, TimesEight, ICStackValueOffset + sizeof(Value));
-    masm.loadValue(calleeSlot, R1);
+    if (isSpread_) {
+        masm.loadValue(Address(BaselineStackReg, 2 * sizeof(Value) + ICStackValueOffset), R1);
+    } else {
+        BaseIndex calleeSlot(BaselineStackReg, argcReg, TimesEight,
+                             ICStackValueOffset + sizeof(Value));
+        masm.loadValue(calleeSlot, R1);
+    }
     regs.take(R1);
 
     // Ensure callee is an object.
     masm.branchTestObject(Assembler::NotEqual, R1, &failure);
 
     // Ensure callee is a function.
     Register callee = masm.extractObject(R1, ExtractTemp0);
     masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_,
@@ -8626,19 +8691,24 @@ ICCallScriptedCompiler::generateStubCode
     Label failureLeaveStubFrame;
 
     if (isConstructing_) {
         // Save argc before call.
         masm.push(argcReg);
 
         // Stack now looks like:
         //      [..., Callee, ThisV, Arg0V, ..., ArgNV, StubFrameHeader, ArgC ]
-        BaseIndex calleeSlot2(BaselineStackReg, argcReg, TimesEight,
-                               sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t));
-        masm.loadValue(calleeSlot2, R1);
+        if (isSpread_) {
+            masm.loadValue(Address(BaselineStackReg,
+                                   2 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t)), R1);
+        } else {
+            BaseIndex calleeSlot2(BaselineStackReg, argcReg, TimesEight,
+                                  sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t));
+            masm.loadValue(calleeSlot2, R1);
+        }
         masm.push(masm.extractObject(R1, ExtractTemp0));
         if (!callVM(CreateThisInfoBaseline, masm))
             return false;
 
         // Return of CreateThis must be an object.
 #ifdef DEBUG
         Label createdThisIsObject;
         masm.branchTestObject(Assembler::Equal, JSReturnOperand, &createdThisIsObject);
@@ -8655,31 +8725,39 @@ ICCallScriptedCompiler::generateStubCode
 
         // Restore saved argc so we can use it to calculate the address to save
         // the resulting this object to.
         masm.pop(argcReg);
 
         // Save "this" value back into pushed arguments on stack.  R0 can be clobbered after that.
         // Stack now looks like:
         //      [..., Callee, ThisV, Arg0V, ..., ArgNV, StubFrameHeader ]
-        BaseIndex thisSlot(BaselineStackReg, argcReg, TimesEight, STUB_FRAME_SIZE);
-        masm.storeValue(R0, thisSlot);
+        if (isSpread_) {
+            masm.storeValue(R0, Address(BaselineStackReg, sizeof(Value) + STUB_FRAME_SIZE));
+        } else {
+            BaseIndex thisSlot(BaselineStackReg, argcReg, TimesEight, STUB_FRAME_SIZE);
+            masm.storeValue(R0, thisSlot);
+        }
 
         // Restore the stub register from the baseline stub frame.
         masm.loadPtr(Address(BaselineStackReg, STUB_FRAME_SAVED_STUB_OFFSET), BaselineStubReg);
 
         // Reload callee script. Note that a GC triggered by CreateThis may
         // have destroyed the callee BaselineScript and IonScript. CreateThis is
         // safely repeatable though, so in this case we just leave the stub frame
         // and jump to the next stub.
 
         // Just need to load the script now.
-        BaseIndex calleeSlot3(BaselineStackReg, argcReg, TimesEight,
-                               sizeof(Value) + STUB_FRAME_SIZE);
-        masm.loadValue(calleeSlot3, R0);
+        if (isSpread_) {
+            masm.loadValue(Address(BaselineStackReg, 2 * sizeof(Value) + STUB_FRAME_SIZE), R0);
+        } else {
+            BaseIndex calleeSlot3(BaselineStackReg, argcReg, TimesEight,
+                                  sizeof(Value) + STUB_FRAME_SIZE);
+            masm.loadValue(calleeSlot3, R0);
+        }
         callee = masm.extractObject(R0, ExtractTemp0);
         regs.add(R0);
         regs.takeUnchecked(callee);
         masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee);
 
         code = regs.takeAny();
         masm.loadBaselineOrIonRaw(callee, code, SequentialExecution, &failureLeaveStubFrame);
 
@@ -8692,17 +8770,20 @@ ICCallScriptedCompiler::generateStubCode
         if (canUseTailCallReg)
             regs.addUnchecked(BaselineTailCallReg);
     }
     Register scratch = regs.takeAny();
 
     // Values are on the stack left-to-right. Calling convention wants them
     // right-to-left so duplicate them on the stack in reverse order.
     // |this| and callee are pushed last.
-    pushCallArguments(masm, regs, argcReg);
+    if (isSpread_)
+        pushSpreadCallArguments(masm, regs, argcReg);
+    else
+        pushCallArguments(masm, regs, argcReg);
 
     // The callee is on top of the stack. Pop and unbox it.
     ValueOperand val = regs.takeAnyValue();
     masm.popValue(val);
     callee = masm.extractObject(val, ExtractTemp0);
 
     EmitCreateStubFrameDescriptor(masm, scratch);
 
@@ -8755,20 +8836,24 @@ ICCallScriptedCompiler::generateStubCode
         // Current stack: [ ARGVALS..., ThisVal, ActualArgc, Callee, Descriptor ]
         // However, we can't use this ThisVal, because it hasn't been traced.  We need to use
         // The ThisVal higher up the stack:
         // Current stack: [ ThisVal, ARGVALS..., ...STUB FRAME...,
         //                  ARGVALS..., ThisVal, ActualArgc, Callee, Descriptor ]
         masm.loadPtr(Address(BaselineStackReg, 2*sizeof(size_t)), scratchReg);
 
         // scratchReg now contains actualArgCount.  Double it to account for skipping past two
-        // pushed copies of argument values.  Additionally, we need to add:
+        // pushed copies of argument values for non-spread, increment it to account for skipping
+        // actual argument values and array object for spread.  Additionally, we need to add:
         // STUB_FRAME_SIZE + sizeof(ThisVal) + sizeof(size_t) + sizeof(void *) + sizoef(size_t)
         // for: stub frame, this value, actual argc, callee, and descriptor
-        masm.lshiftPtr(Imm32(1), scratchReg);
+        if (isSpread_)
+            masm.add32(Imm32(1), scratchReg);
+        else
+            masm.lshiftPtr(Imm32(1), scratchReg);
         BaseIndex reloadThisSlot(BaselineStackReg, scratchReg, TimesEight,
                                  STUB_FRAME_SIZE + sizeof(Value) + 3*sizeof(size_t));
         masm.loadValue(reloadThisSlot, JSReturnOperand);
 #ifdef DEBUG
         masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace);
         masm.assumeUnreachable("Return of constructing call should be an object.");
 #endif
         masm.bind(&skipThisReplace);
@@ -8795,19 +8880,26 @@ ICCall_Native::Compiler::generateStubCod
 {
     Label failure;
     GeneralRegisterSet regs(availableGeneralRegs(0));
 
     Register argcReg = R0.scratchReg();
     regs.take(argcReg);
     regs.takeUnchecked(BaselineTailCallReg);
 
+    if (isSpread_)
+        guardSpreadCall(masm, argcReg, &failure);
+
     // Load the callee in R1.
-    BaseIndex calleeSlot(BaselineStackReg, argcReg, TimesEight, ICStackValueOffset + sizeof(Value));
-    masm.loadValue(calleeSlot, R1);
+    if (isSpread_) {
+        masm.loadValue(Address(BaselineStackReg, ICStackValueOffset + 2 * sizeof(Value)), R1);
+    } else {
+        BaseIndex calleeSlot(BaselineStackReg, argcReg, TimesEight, ICStackValueOffset + sizeof(Value));
+        masm.loadValue(calleeSlot, R1);
+    }
     regs.take(R1);
 
     masm.branchTestObject(Assembler::NotEqual, R1, &failure);
 
     // Ensure callee matches this stub's callee.
     Register callee = masm.extractObject(R1, ExtractTemp0);
     Address expectedCallee(BaselineStubReg, ICCall_Native::offsetOfCallee());
     masm.branchPtr(Assembler::NotEqual, expectedCallee, callee, &failure);
@@ -8817,17 +8909,20 @@ ICCall_Native::Compiler::generateStubCod
 
     // Push a stub frame so that we can perform a non-tail call.
     // Note that this leaves the return address in TailCallReg.
     enterStubFrame(masm, regs.getAny());
 
     // Values are on the stack left-to-right. Calling convention wants them
     // right-to-left so duplicate them on the stack in reverse order.
     // |this| and callee are pushed last.
-    pushCallArguments(masm, regs, argcReg);
+    if (isSpread_)
+        pushSpreadCallArguments(masm, regs, argcReg);
+    else
+        pushCallArguments(masm, regs, argcReg);
 
     if (isConstructing_) {
         // Stack looks like: [ ..., Arg0Val, ThisVal, CalleeVal ]
         // Replace ThisVal with MagicValue(JS_IS_CONSTRUCTING)
         masm.storeValue(MagicValue(JS_IS_CONSTRUCTING), Address(BaselineStackReg, sizeof(Value)));
     }
 
     masm.checkStackAlignment();
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -5405,16 +5405,18 @@ class ICCallStubCompiler : public ICStub
     { }
 
     enum FunApplyThing {
         FunApply_MagicArgs,
         FunApply_Array
     };
 
     void pushCallArguments(MacroAssembler &masm, GeneralRegisterSet regs, Register argcReg);
+    void pushSpreadCallArguments(MacroAssembler &masm, GeneralRegisterSet regs, Register argcReg);
+    void guardSpreadCall(MacroAssembler &masm, Register argcReg, Label *failure);
     Register guardFunApply(MacroAssembler &masm, GeneralRegisterSet regs, Register argcReg,
                            bool checkNative, FunApplyThing applyThing, Label *failure);
     void pushCallerArguments(MacroAssembler &masm, GeneralRegisterSet regs);
     void pushArrayArguments(MacroAssembler &masm, Address arrayVal, GeneralRegisterSet regs);
 };
 
 class ICCall_Fallback : public ICMonitoredFallbackStub
 {
@@ -5564,42 +5566,46 @@ class ICCall_AnyScripted : public ICMoni
     }
 };
 
 // Compiler for Call_Scripted and Call_AnyScripted stubs.
 class ICCallScriptedCompiler : public ICCallStubCompiler {
   protected:
     ICStub *firstMonitorStub_;
     bool isConstructing_;
+    bool isSpread_;
     RootedScript calleeScript_;
     RootedObject templateObject_;
     uint32_t pcOffset_;
     bool generateStubCode(MacroAssembler &masm);
 
     virtual int32_t getKey() const {
-        return static_cast<int32_t>(kind) | (static_cast<int32_t>(isConstructing_) << 16);
+        return static_cast<int32_t>(kind) | (static_cast<int32_t>(isConstructing_) << 16) |
+               (static_cast<int32_t>(isSpread_) << 17);
     }
 
   public:
     ICCallScriptedCompiler(JSContext *cx, ICStub *firstMonitorStub,
                            HandleScript calleeScript, HandleObject templateObject,
-                           bool isConstructing, uint32_t pcOffset)
+                           bool isConstructing, bool isSpread, uint32_t pcOffset)
       : ICCallStubCompiler(cx, ICStub::Call_Scripted),
         firstMonitorStub_(firstMonitorStub),
         isConstructing_(isConstructing),
+        isSpread_(isSpread),
         calleeScript_(cx, calleeScript),
         templateObject_(cx, templateObject),
         pcOffset_(pcOffset)
     { }
 
     ICCallScriptedCompiler(JSContext *cx, ICStub *firstMonitorStub, bool isConstructing,
-                           uint32_t pcOffset)
+                           bool isSpread, uint32_t pcOffset)
       : ICCallStubCompiler(cx, ICStub::Call_AnyScripted),
         firstMonitorStub_(firstMonitorStub),
         isConstructing_(isConstructing),
+        isSpread_(isSpread),
         calleeScript_(cx, nullptr),
         templateObject_(cx, nullptr),
         pcOffset_(pcOffset)
     { }
 
     ICStub *getStub(ICStubSpace *space) {
         if (calleeScript_) {
             return ICCall_Scripted::New(space, getStubCode(), firstMonitorStub_,
@@ -5661,32 +5667,35 @@ class ICCall_Native : public ICMonitored
     }
 #endif
 
     // Compiler for this stub kind.
     class Compiler : public ICCallStubCompiler {
       protected:
         ICStub *firstMonitorStub_;
         bool isConstructing_;
+        bool isSpread_;
         RootedFunction callee_;
         RootedObject templateObject_;
         uint32_t pcOffset_;
         bool generateStubCode(MacroAssembler &masm);
 
         virtual int32_t getKey() const {
-            return static_cast<int32_t>(kind) | (static_cast<int32_t>(isConstructing_) << 16);
+            return static_cast<int32_t>(kind) | (static_cast<int32_t>(isConstructing_) << 16) |
+                   (static_cast<int32_t>(isSpread_) << 17);
         }
 
       public:
         Compiler(JSContext *cx, ICStub *firstMonitorStub,
                  HandleFunction callee, HandleObject templateObject,
-                 bool isConstructing, uint32_t pcOffset)
+                 bool isConstructing, bool isSpread, uint32_t pcOffset)
           : ICCallStubCompiler(cx, ICStub::Call_Native),
             firstMonitorStub_(firstMonitorStub),
             isConstructing_(isConstructing),
+            isSpread_(isSpread),
             callee_(cx, callee),
             templateObject_(cx, templateObject),
             pcOffset_(pcOffset)
         { }
 
         ICStub *getStub(ICStubSpace *space) {
             return ICCall_Native::New(space, getStubCode(), firstMonitorStub_,
                                       callee_, templateObject_, pcOffset_);
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -3092,16 +3092,22 @@ MacroAssemblerARMCompat::unboxString(con
 
 void
 MacroAssemblerARMCompat::unboxObject(const ValueOperand &src, Register dest)
 {
     ma_mov(src.payloadReg(), dest);
 }
 
 void
+MacroAssemblerARMCompat::unboxObject(const Address &src, Register dest)
+{
+    ma_ldr(payloadOf(src), dest);
+}
+
+void
 MacroAssemblerARMCompat::unboxValue(const ValueOperand &src, AnyRegister dest)
 {
     if (dest.isFloat()) {
         Label notInt32, end;
         branchTestInt32(Assembler::NotEqual, src, &notInt32);
         convertInt32ToDouble(src.payloadReg(), dest.fpu());
         ma_b(&end);
         bind(&notInt32);
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -809,16 +809,17 @@ class MacroAssemblerARMCompat : public M
     void unboxInt32(const Address &src, Register dest);
     void unboxBoolean(const ValueOperand &operand, Register dest);
     void unboxBoolean(const Address &src, Register dest);
     void unboxDouble(const ValueOperand &operand, FloatRegister dest);
     void unboxDouble(const Address &src, FloatRegister dest);
     void unboxString(const ValueOperand &operand, Register dest);
     void unboxString(const Address &src, Register dest);
     void unboxObject(const ValueOperand &src, Register dest);
+    void unboxObject(const Address &src, Register dest);
     void unboxValue(const ValueOperand &src, AnyRegister dest);
     void unboxPrivate(const ValueOperand &src, Register dest);
 
     void notBoolean(const ValueOperand &val) {
         ma_eor(Imm32(1), val.payloadReg());
     }
 
     // boxing code
--- a/js/src/jit/x64/MacroAssembler-x64.h
+++ b/js/src/jit/x64/MacroAssembler-x64.h
@@ -1081,16 +1081,17 @@ class MacroAssemblerX64 : public MacroAs
         andq(ScratchReg, dest);
     }
 
     void unboxString(const ValueOperand &src, Register dest) { unboxNonDouble(src, dest); }
     void unboxString(const Operand &src, Register dest) { unboxNonDouble(src, dest); }
 
     void unboxObject(const ValueOperand &src, Register dest) { unboxNonDouble(src, dest); }
     void unboxObject(const Operand &src, Register dest) { unboxNonDouble(src, dest); }
+    void unboxObject(const Address &src, Register dest) { unboxNonDouble(Operand(src), dest); }
 
     // Extended unboxing API. If the payload is already in a register, returns
     // that register. Otherwise, provides a move to the given scratch register,
     // and returns that.
     Register extractObject(const Address &address, Register scratch) {
         JS_ASSERT(scratch != ScratchReg);
         loadPtr(address, ScratchReg);
         // We have a special coupling with unboxObject. As long as the registers
--- a/js/src/jit/x86/MacroAssembler-x86.h
+++ b/js/src/jit/x86/MacroAssembler-x86.h
@@ -817,16 +817,19 @@ class MacroAssemblerX86 : public MacroAs
     }
     void unboxBoolean(const Address &src, Register dest) {
         movl(payloadOf(src), dest);
     }
     void unboxObject(const ValueOperand &src, Register dest) {
         if (src.payloadReg() != dest)
             movl(src.payloadReg(), dest);
     }
+    void unboxObject(const Address &src, Register dest) {
+        movl(payloadOf(src), dest);
+    }
     void unboxDouble(const ValueOperand &src, FloatRegister dest) {
         JS_ASSERT(dest != ScratchFloatReg);
         if (Assembler::HasSSE41()) {
             movd(src.payloadReg(), dest);
             pinsrd(src.typeReg(), dest);
         } else {
             movd(src.payloadReg(), dest);
             movd(src.typeReg(), ScratchFloatReg);
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -2115,22 +2115,19 @@ types::UseNewType(JSContext *cx, JSScrip
      *
      * Sub1.prototype = new Super();
      * Sub2.prototype = new Super();
      *
      * Using distinct type objects for the particular prototypes of Sub1 and
      * Sub2 lets us continue to distinguish the two subclasses and any extra
      * properties added to those prototype objects.
      */
-    if (JSOp(*pc) == JSOP_NEW)
-        pc += JSOP_NEW_LENGTH;
-    else if (JSOp(*pc) == JSOP_SPREADNEW)
-        pc += JSOP_SPREADNEW_LENGTH;
-    else
+    if (JSOp(*pc) != JSOP_NEW)
         return false;
+    pc += JSOP_NEW_LENGTH;
     if (JSOp(*pc) == JSOP_SETPROP) {
         jsid id = GetAtomId(cx, script, pc, 0);
         if (id == id_prototype(cx))
             return true;
     }
 
     return false;
 }
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -3947,16 +3947,25 @@ js::SpreadCallOperation(JSContext *cx, H
 
     if (length > ARGS_LENGTH_MAX) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                              op == JSOP_SPREADNEW ? JSMSG_TOO_MANY_CON_SPREADARGS
                                                   : JSMSG_TOO_MANY_FUN_SPREADARGS);
         return false;
     }
 
+#ifdef DEBUG
+    // The object must be an array with dense elements and no holes. Baseline's
+    // optimized spread call stubs rely on this.
+    JS_ASSERT(aobj->getDenseInitializedLength() == length);
+    JS_ASSERT(!aobj->isIndexed());
+    for (uint32_t i = 0; i < length; i++)
+        JS_ASSERT(!aobj->getDenseElement(i).isMagic());
+#endif
+
     InvokeArgs args(cx);
 
     if (!args.init(length))
         return false;
 
     args.setCallee(callee);
     args.setThis(thisv);