Bug 869056 - Added optimized baseline stubs for JSOP_FUNAPPLY with MagicValue(Arguments) as second arg. r=evilpie
authorKannan Vijayan <kvijayan@mozilla.com>
Tue, 07 May 2013 15:17:54 -0400
changeset 142114 7cf0f7203cdaa4dc88388fe3c83dc355e056f78b
parent 142113 d26ed846519e356595e75ce9f906813fe414bdb2
child 142115 03b869365923287fb0d852d8631547cba5fcc080
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersevilpie
bugs869056
milestone23.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 869056 - Added optimized baseline stubs for JSOP_FUNAPPLY with MagicValue(Arguments) as second arg. r=evilpie
js/src/ion/BaselineIC.cpp
js/src/ion/BaselineIC.h
--- a/js/src/ion/BaselineIC.cpp
+++ b/js/src/ion/BaselineIC.cpp
@@ -607,16 +607,17 @@ ICStubCompiler::leaveStubFrame(MacroAsse
 
 void
 ICStubCompiler::guardProfilingEnabled(MacroAssembler &masm, Register scratch, Label *skip)
 {
     // This should only be called from the following stubs.
     JS_ASSERT(kind == ICStub::Call_Scripted      || kind == ICStub::Call_AnyScripted     ||
               kind == ICStub::Call_Native        || kind == ICStub::GetProp_CallScripted ||
               kind == ICStub::GetProp_CallNative || kind == ICStub::GetProp_CallListBaseNative ||
+              kind == ICStub::Call_ScriptedApplyArguments ||
               kind == ICStub::GetProp_CallListBaseWithGenerationNative ||
               kind == ICStub::SetProp_CallScripted || kind == ICStub::SetProp_CallNative);
 
     // Guard on bit in frame that indicates if the SPS frame was pushed in the first
     // place.  This code is expected to be called from within a stub that has already
     // entered a stub frame.
     JS_ASSERT(entersStubFrame_);
     masm.loadPtr(Address(BaselineFrameReg, 0), scratch);
@@ -6523,16 +6524,51 @@ ICSetProp_CallNative::Compiler::generate
     return true;
 }
 
 //
 // Call_Fallback
 //
 
 static bool
+TryAttachFunApplyStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, jsbytecode *pc,
+                      HandleValue thisv, uint32_t argc, Value *argv)
+{
+    if (argc != 2)
+        return true;
+
+    if (!thisv.isObject() || !thisv.toObject().isFunction())
+        return true;
+    RootedFunction target(cx, thisv.toObject().toFunction());
+
+    // right now, only handle situation where second argument is |arguments|
+    if (argv[1].isMagic(JS_OPTIMIZED_ARGUMENTS) && !script->needsArgsObj()) {
+        if (target->hasScript() &&
+            (target->nonLazyScript()->hasBaselineScript() ||
+             target->nonLazyScript()->hasIonScript()) &&
+            !stub->hasStub(ICStub::Call_ScriptedApplyArguments))
+        {
+            IonSpew(IonSpew_BaselineIC, "  Generating Call_ScriptedApplyArguments stub");
+
+            ICCall_ScriptedApplyArguments::Compiler compiler(
+                cx, stub->fallbackMonitorStub()->firstMonitorStub(), pc - script->code);
+            ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
+            if (!newStub)
+                return false;
+
+            stub->addNewStub(newStub);
+            return true;
+        }
+
+        // TODO: handle FUNAPPLY for native targets.
+    }
+    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)
 {
     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.
@@ -6594,35 +6630,41 @@ TryAttachCallStub(JSContext *cx, ICCall_
         if (!newStub)
             return false;
 
         stub->addNewStub(newStub);
         return true;
     }
 
     if (fun->isNative() && (!constructing || (constructing && fun->isNativeConstructor()))) {
-
         // Generalied native call stubs are not here yet!
         JS_ASSERT(!stub->nativeStubsAreGeneralized());
 
-        if (stub->nativeStubCount() >= ICCall_Fallback::MAX_NATIVE_STUBS) {
-            IonSpew(IonSpew_BaselineIC, "  Too many Call_Native stubs. TODO: add Call_AnyNative!");
+        // Check for JSOP_FUNAPPLY
+        if (op == JSOP_FUNAPPLY && fun->maybeNative() == js_fun_apply) {
+            if (!TryAttachFunApplyStub(cx, stub, script, pc, thisv, argc, vp + 2))
+                return false;
+        } else {
+            if (stub->nativeStubCount() >= ICCall_Fallback::MAX_NATIVE_STUBS) {
+                IonSpew(IonSpew_BaselineIC,
+                        "  Too many Call_Native stubs. TODO: add Call_AnyNative!");
+                return true;
+            }
+
+            IonSpew(IonSpew_BaselineIC, "  Generating Call_Native stub (fun=%p, cons=%s)",
+                    fun.get(), constructing ? "yes" : "no");
+            ICCall_Native::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
+                                             fun, constructing, pc - script->code);
+            ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
+            if (!newStub)
+                return false;
+
+            stub->addNewStub(newStub);
             return true;
         }
-
-        IonSpew(IonSpew_BaselineIC, "  Generating Call_Native stub (fun=%p, cons=%s)", fun.get(),
-                constructing ? "yes" : "no");
-        ICCall_Native::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
-                                         fun, constructing, pc - script->code);
-        ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
-        if (!newStub)
-            return false;
-
-        stub->addNewStub(newStub);
-        return true;
     }
 
     return true;
 }
 
 static bool
 MaybeCloneFunctionAtCallsite(JSContext *cx, MutableHandleValue callee, HandleScript script,
                              jsbytecode *pc)
@@ -6740,16 +6782,101 @@ ICCallStubCompiler::pushCallArguments(Ma
         masm.addPtr(Imm32(sizeof(Value)), argPtr);
 
         masm.sub32(Imm32(1), count);
         masm.jump(&loop);
     }
     masm.bind(&done);
 }
 
+Register
+ICCallStubCompiler::guardFunApply(MacroAssembler &masm, GeneralRegisterSet regs, Register argcReg,
+                                  bool checkNative, Label *failure)
+{
+    // Ensure argc == 2
+    masm.branch32(Assembler::NotEqual, argcReg, Imm32(2), failure);
+
+    // Stack looks like:
+    //      [..., CalleeV, ThisV, Arg0V, Arg1V <MaybeReturnReg>]
+
+    // Ensure that the second arg is magic arguments.
+    Address secondArgSlot(BaselineStackReg, ICStackValueOffset);
+    masm.branchTestMagic(Assembler::NotEqual, secondArgSlot, failure);
+
+    // Ensure that this frame doesn't have an arguments object.
+    masm.branchTest32(Assembler::NonZero,
+                      Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()),
+                      Imm32(BaselineFrame::HAS_ARGS_OBJ),
+                      failure);
+
+    // Stack now confirmed to be like:
+    //      [..., CalleeV, ThisV, Arg0V, MagicValue(Arguments), <MaybeReturnAddr>]
+
+    // Load the callee, ensure that it's js_fun_apply
+    ValueOperand val = regs.takeAnyValue();
+    Address calleeSlot(BaselineStackReg, ICStackValueOffset + (3 * sizeof(Value)));
+    masm.loadValue(calleeSlot, val);
+
+    masm.branchTestObject(Assembler::NotEqual, val, failure);
+    Register callee = masm.extractObject(val, ExtractTemp1);
+
+    masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &FunctionClass, failure);
+    masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee);
+
+    masm.branchPtr(Assembler::NotEqual, callee, ImmWord((void*) js_fun_apply), failure);
+
+    // Load the |thisv|, ensure that it's a scripted function with a valid baseline or ion
+    // script, or a native function.
+    Address thisSlot(BaselineStackReg, ICStackValueOffset + (2 * sizeof(Value)));
+    masm.loadValue(thisSlot, val);
+
+    masm.branchTestObject(Assembler::NotEqual, val, failure);
+    Register target = masm.extractObject(val, ExtractTemp1);
+    regs.add(val);
+    regs.takeUnchecked(target);
+
+    masm.branchTestObjClass(Assembler::NotEqual, target, regs.getAny(), &FunctionClass, failure);
+
+    if (checkNative) {
+        masm.branchIfInterpreted(target, failure);
+    } else {
+        masm.branchIfFunctionHasNoScript(target, failure);
+        Register temp = regs.takeAny();
+        masm.loadPtr(Address(target, JSFunction::offsetOfNativeOrScript()), temp);
+        masm.loadBaselineOrIonRaw(temp, temp, SequentialExecution, failure);
+        regs.add(temp);
+    }
+    return target;
+}
+
+void
+ICCallStubCompiler::pushCallerArguments(MacroAssembler &masm, GeneralRegisterSet regs)
+{
+    // Initialize copyReg to point to start caller arguments vector.
+    // Initialize argcReg to poitn to the end of it.
+    Register startReg = regs.takeAny();
+    Register endReg = regs.takeAny();
+    masm.loadPtr(Address(BaselineFrameReg, 0), startReg);
+    masm.loadPtr(Address(startReg, BaselineFrame::offsetOfNumActualArgs()), endReg);
+    masm.addPtr(Imm32(BaselineFrame::offsetOfArg(0)), startReg);
+    JS_ASSERT(sizeof(Value) == 8);
+    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);
+}
+
 typedef bool (*DoCallFallbackFn)(JSContext *, BaselineFrame *, ICCall_Fallback *,
                                  uint32_t, Value *, MutableHandleValue);
 static const VMFunction DoCallFallbackInfo = FunctionInfo<DoCallFallbackFn>(DoCallFallback);
 
 bool
 ICCall_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
 {
     JS_ASSERT(R0 == JSReturnOperand);
@@ -7158,16 +7285,126 @@ ICCall_Native::Compiler::generateStubCod
     masm.bind(&exception);
     masm.handleException();
 
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
+bool
+ICCall_ScriptedApplyArguments::Compiler::generateStubCode(MacroAssembler &masm)
+{
+    Label failure;
+    GeneralRegisterSet regs(availableGeneralRegs(0));
+
+    Register argcReg = R0.scratchReg();
+    regs.take(argcReg);
+    regs.takeUnchecked(BaselineTailCallReg);
+    regs.takeUnchecked(ArgumentsRectifierReg);
+
+    //
+    // Validate inputs
+    //
+
+    Register target = guardFunApply(masm, regs, argcReg, /*checkNative=*/false, &failure);
+    if (regs.has(target)) {
+        regs.take(target);
+    } else {
+        // If target is already a reserved reg, take another register for it, because it's
+        // probably currently an ExtractTemp, which might get clobbered later.
+        Register targetTemp = regs.takeAny();
+        masm.movePtr(target, targetTemp);
+        target = targetTemp;
+    }
+
+    // Push a stub frame so that we can perform a non-tail call.
+    enterStubFrame(masm, regs.getAny());
+
+    //
+    // Push arguments
+    //
+
+    // Stack now looks like:
+    //      [..., js_fun_apply, TargetV, TargetThisV, MagicArgsV, StubFrameHeader]
+
+    // Push all arguments supplied to caller function onto the stack.
+    pushCallerArguments(masm, regs);
+
+    // Stack now looks like:
+    //                                      BaselineFrameReg -------------------.
+    //                                                                          v
+    //      [..., js_fun_apply, TargetV, TargetThisV, MagicArgsV, StubFrameHeader,
+    //       PushedArgN, ..., PushedArg0]
+    // Can't fail after this, so it's ok to release argcReg back.
+
+    // Push actual argument 0 as |thisv| for call.
+    masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE + sizeof(Value)));
+
+    // All pushes after this use Push instead of push to make sure ARM can align
+    // stack properly for call.
+    Register scratch = regs.takeAny();
+    EmitCreateStubFrameDescriptor(masm, scratch);
+
+    masm.loadPtr(Address(BaselineFrameReg, 0), argcReg);
+    masm.loadPtr(Address(argcReg, BaselineFrame::offsetOfNumActualArgs()), argcReg);
+    masm.Push(argcReg);
+    masm.Push(target);
+    masm.Push(scratch);
+
+    // Load nargs into scratch for underflow check, and then load jitcode pointer into target.
+    masm.load16ZeroExtend(Address(target, offsetof(JSFunction, nargs)), scratch);
+    masm.loadPtr(Address(target, JSFunction::offsetOfNativeOrScript()), target);
+    masm.loadBaselineOrIonRaw(target, target, SequentialExecution, NULL);
+
+    // Handle arguments underflow.
+    Label noUnderflow;
+    masm.branch32(Assembler::AboveOrEqual, argcReg, scratch, &noUnderflow);
+    {
+        // Call the arguments rectifier.
+        JS_ASSERT(ArgumentsRectifierReg != target);
+        JS_ASSERT(ArgumentsRectifierReg != argcReg);
+
+        IonCode *argumentsRectifier =
+            cx->compartment->ionCompartment()->getArgumentsRectifier(SequentialExecution);
+
+        masm.movePtr(ImmGCPtr(argumentsRectifier), target);
+        masm.loadPtr(Address(target, IonCode::offsetOfCode()), target);
+        masm.mov(argcReg, ArgumentsRectifierReg);
+    }
+    masm.bind(&noUnderflow);
+    regs.add(argcReg);
+
+    // If needed, update SPS Profiler frame entry.  At this point, BaselineTailCallReg
+    // and scratch can be clobbered.
+    {
+        Label skipProfilerUpdate;
+        Register pcIdx = regs.getAny();
+        JS_ASSERT(pcIdx != ArgumentsRectifierReg);
+        JS_ASSERT(pcIdx != target);
+        guardProfilingEnabled(masm, scratch, &skipProfilerUpdate);
+
+        masm.load32(Address(BaselineStubReg, ICCall_ScriptedApplyArguments::offsetOfPCOffset()),
+                    pcIdx);
+        masm.spsUpdatePCIdx(&cx->runtime->spsProfiler, pcIdx, scratch);
+
+        masm.bind(&skipProfilerUpdate);
+    }
+    // Do call
+    masm.callIon(target);
+    leaveStubFrame(masm, true);
+
+    // Enter type monitor IC to type-check result.
+    EmitEnterTypeMonitorIC(masm);
+
+    masm.bind(&failure);
+    EmitStubGuardFailure(masm);
+    return true;
+}
+
 static JSBool
 DoubleValueToInt32ForSwitch(Value *v)
 {
     double d = v->toDouble();
     int32_t truncated = int32_t(d);
     if (d != double(truncated))
         return false;
 
--- a/js/src/ion/BaselineIC.h
+++ b/js/src/ion/BaselineIC.h
@@ -319,16 +319,17 @@ class ICEntry
     _(UnaryArith_Fallback)      \
     _(UnaryArith_Int32)         \
     _(UnaryArith_Double)        \
                                 \
     _(Call_Fallback)            \
     _(Call_Scripted)            \
     _(Call_AnyScripted)         \
     _(Call_Native)              \
+    _(Call_ScriptedApplyArguments) \
                                 \
     _(GetElem_Fallback)         \
     _(GetElem_Native)           \
     _(GetElem_NativePrototype)  \
     _(GetElem_String)           \
     _(GetElem_Dense)            \
     _(GetElem_TypedArray)       \
     _(GetElem_Arguments)        \
@@ -716,20 +717,21 @@ class ICStub
 
     static inline size_t offsetOfExtra() {
         return offsetof(ICStub, extra_);
     }
 
     static bool CanMakeCalls(ICStub::Kind kind) {
         JS_ASSERT(IsValidKind(kind));
         switch (kind) {
+          case Call_Fallback:
           case Call_Scripted:
           case Call_AnyScripted:
           case Call_Native:
-          case Call_Fallback:
+          case Call_ScriptedApplyArguments:
           case UseCount_Fallback:
           case GetProp_CallScripted:
           case GetProp_CallNative:
           case GetProp_CallListBaseNative:
           case GetProp_CallListBaseWithGenerationNative:
           case SetProp_CallScripted:
           case SetProp_CallNative:
             return true;
@@ -4765,16 +4767,19 @@ class ICSetProp_CallNative : public ICSe
 class ICCallStubCompiler : public ICStubCompiler
 {
   protected:
     ICCallStubCompiler(JSContext *cx, ICStub::Kind kind)
       : ICStubCompiler(cx, kind)
     { }
 
     void pushCallArguments(MacroAssembler &masm, GeneralRegisterSet regs, Register argcReg);
+    Register guardFunApply(MacroAssembler &masm, GeneralRegisterSet regs, Register argcReg,
+                           bool checkNative, Label *failure);
+    void pushCallerArguments(MacroAssembler &masm, GeneralRegisterSet regs);
 };
 
 class ICCall_Fallback : public ICMonitoredFallbackStub
 {
     friend class ICStubSpace;
   public:
     static const unsigned CONSTRUCTING_FLAG = 0x0001;
 
@@ -4997,16 +5002,66 @@ class ICCall_Native : public ICMonitored
         { }
 
         ICStub *getStub(ICStubSpace *space) {
             return ICCall_Native::New(space, getStubCode(), firstMonitorStub_, callee_, pcOffset_);
         }
     };
 };
 
+class ICCall_ScriptedApplyArguments : public ICMonitoredStub
+{
+    friend class ICStubSpace;
+
+  protected:
+    uint32_t pcOffset_;
+
+    ICCall_ScriptedApplyArguments(IonCode *stubCode, ICStub *firstMonitorStub, uint32_t pcOffset)
+      : ICMonitoredStub(ICStub::Call_ScriptedApplyArguments, stubCode, firstMonitorStub),
+        pcOffset_(pcOffset)
+    {}
+
+  public:
+    static inline ICCall_ScriptedApplyArguments *New(ICStubSpace *space, IonCode *code,
+                                                     ICStub *firstMonitorStub, uint32_t pcOffset)
+    {
+        if (!code)
+            return NULL;
+        return space->allocate<ICCall_ScriptedApplyArguments>(code, firstMonitorStub, pcOffset);
+    }
+
+    static size_t offsetOfPCOffset() {
+        return offsetof(ICCall_ScriptedApplyArguments, pcOffset_);
+    }
+
+    // Compiler for this stub kind.
+    class Compiler : public ICCallStubCompiler {
+      protected:
+        ICStub *firstMonitorStub_;
+        uint32_t pcOffset_;
+        bool generateStubCode(MacroAssembler &masm);
+
+        virtual int32_t getKey() const {
+            return static_cast<int32_t>(kind);
+        }
+
+      public:
+        Compiler(JSContext *cx, ICStub *firstMonitorStub, uint32_t pcOffset)
+          : ICCallStubCompiler(cx, ICStub::Call_ScriptedApplyArguments),
+            firstMonitorStub_(firstMonitorStub),
+            pcOffset_(pcOffset)
+        { }
+
+        ICStub *getStub(ICStubSpace *space) {
+            return ICCall_ScriptedApplyArguments::New(space, getStubCode(), firstMonitorStub_,
+                                                      pcOffset_);
+        }
+    };
+};
+
 // Stub for performing a TableSwitch, updating the IC's return address to jump
 // to whatever point the switch is branching to.
 class ICTableSwitch : public ICStub
 {
     friend class ICStubSpace;
 
   protected: // Protected to silence Clang warning.
     void **table_;