Call functions with insufficient arguments. (Bug 685099, r=dvander)
authorSean Stangl <sstangl@mozilla.com>
Wed, 07 Sep 2011 20:17:50 -0700
changeset 77312 31641ffb0e9d5550b58fea50d32c8abb942add38
parent 77311 af4ea34da9df9fa81e2c026d9076fd2512db0a7c
child 77313 23edb5d4dea53215c5bc46015e237cb3a9f468c5
push id201
push usersean.stangl@gmail.com
push dateThu, 15 Sep 2011 00:47:07 +0000
reviewersdvander
bugs685099
milestone9.0a1
Call functions with insufficient arguments. (Bug 685099, r=dvander)
js/src/ion/Ion.cpp
js/src/ion/IonCompartment.h
js/src/ion/IonFrames.h
js/src/ion/MIRGenerator.h
js/src/ion/shared/CodeGenerator-x86-shared.cpp
js/src/ion/x64/Assembler-x64.h
js/src/ion/x64/CodeGenerator-x64.cpp
js/src/ion/x64/MacroAssembler-x64.h
js/src/ion/x64/Trampoline-x64.cpp
js/src/ion/x86/Assembler-x86.h
js/src/ion/x86/MacroAssembler-x86.h
js/src/ion/x86/Trampoline-x86.cpp
--- a/js/src/ion/Ion.cpp
+++ b/js/src/ion/Ion.cpp
@@ -134,17 +134,18 @@ ion::SetIonContext(IonContext *ctx)
     return true;
 }
 #endif
 
 IonCompartment::IonCompartment()
   : execAlloc_(NULL),
     enterJIT_(NULL),
     bailoutHandler_(NULL),
-    returnError_(NULL)
+    returnError_(NULL),
+    argumentsRectifier_(NULL)
 {
 }
 
 bool
 IonCompartment::initialize(JSContext *cx)
 {
     execAlloc_ = js::OffTheBooks::new_<JSC::ExecutableAllocator>();
     if (!execAlloc_)
@@ -179,16 +180,18 @@ void
 IonCompartment::sweep(JSContext *cx)
 {
     if (enterJIT_ && IsAboutToBeFinalized(cx, enterJIT_))
         enterJIT_ = NULL;
     if (bailoutHandler_ && IsAboutToBeFinalized(cx, bailoutHandler_))
         bailoutHandler_ = NULL;
     if (returnError_ && IsAboutToBeFinalized(cx, returnError_))
         returnError_ = NULL;
+    if (argumentsRectifier_ && IsAboutToBeFinalized(cx, argumentsRectifier_))
+        argumentsRectifier_ = NULL;
 
     for (size_t i = 0; i < bailoutTables_.length(); i++) {
         if (bailoutTables_[i] && IsAboutToBeFinalized(cx, bailoutTables_[i]))
             bailoutTables_[i] = NULL;
     }
 }
 
 IonCode *
--- a/js/src/ion/IonCompartment.h
+++ b/js/src/ion/IonCompartment.h
@@ -74,18 +74,23 @@ class IonCompartment
     js::Vector<IonCode *, 4, SystemAllocPolicy> bailoutTables_;
 
     // Generic bailout table; used if the bailout table overflows.
     IonCode *bailoutHandler_;
 
     // Error-returning thunk.
     IonCode *returnError_;
 
+    // Argument-rectifying thunk, in the case of insufficient arguments passed
+    // to a function call site. Pads with |undefined|.
+    IonCode *argumentsRectifier_;
+
     IonCode *generateEnterJIT(JSContext *cx);
     IonCode *generateReturnError(JSContext *cx);
+    IonCode *generateArgumentsRectifier(JSContext *cx);
     IonCode *generateBailoutTable(JSContext *cx, uint32 frameClass);
     IonCode *generateBailoutHandler(JSContext *cx);
 
   public:
     bool initialize(JSContext *cx);
     IonCompartment();
     ~IonCompartment();
 
@@ -104,16 +109,26 @@ class IonCompartment
                 return NULL;
         }
         return bailoutHandler_;
     }
 
     // Infallible; does not generate a table.
     IonCode *getBailoutTable(const FrameSizeClass &frameClass);
 
+    // Fallible; generates a thunk and returns the target.
+    IonCode *getArgumentsRectifier(JSContext *cx) {
+        if (!argumentsRectifier_) {
+            argumentsRectifier_ = generateArgumentsRectifier(cx);
+            if (!argumentsRectifier_)
+                return NULL;
+        }
+        return argumentsRectifier_;
+    }
+
     EnterIonCode enterJIT(JSContext *cx) {
         if (!enterJIT_) {
             enterJIT_ = generateEnterJIT(cx);
             if (!enterJIT_)
                 return NULL;
         }
         if (!returnError_) {
             returnError_ = generateReturnError(cx);
@@ -164,13 +179,13 @@ class IonActivation
         JS_ASSERT(bailout_);
         return maybeTakeBailout();
     }
     FrameRegs &oldFrameRegs() {
         return oldFrameRegs_;
     }
 };
 
+} // namespace ion
 } // namespace js
-} // namespace ion
 
 #endif // jsion_ion_compartment_h__
 
--- a/js/src/ion/IonFrames.h
+++ b/js/src/ion/IonFrames.h
@@ -58,25 +58,24 @@ namespace ion {
 //   this    _/
 //   calleeToken - Encodes script or JSFunction
 //   descriptor  - Size of the parent frame 
 //   returnAddr - Return address, entering into the next call.
 //   .. locals ..
 
 // Layout of the frame prefix. This assumes the stack architecture grows down.
 // If this is ever not the case, we'll have to refactor.
-class IonFrameData
+struct IonFrameData
 {
-  protected:
     void *returnAddress_;
     uintptr_t sizeDescriptor_;
     void *calleeToken_;
 };
 
-class IonFramePrefix : public IonFrameData
+class IonFramePrefix : protected IonFrameData
 {
   public:
     // True if this is the frame passed into EnterIonCode.
     bool isEntryFrame() const {
         return !(sizeDescriptor_ & 1);
     }
     // The depth of the parent frame.
     size_t prevFrameDepth() const {
--- a/js/src/ion/MIRGenerator.h
+++ b/js/src/ion/MIRGenerator.h
@@ -40,17 +40,20 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef jsion_mirgen_h__
 #define jsion_mirgen_h__
 
 // This file declares the data structures used to build a control-flow graph
 // containing MIR.
 
+#include "jscntxt.h"
+#include "jscompartment.h"
 #include "IonAllocPolicy.h"
+#include "IonCompartment.h"
 
 namespace js {
 namespace ion {
 
 class MBasicBlock;
 class MIRGraph;
 class MStart;
 
@@ -105,16 +108,19 @@ class MIRGenerator
         return firstStackSlot() + i;
     }
     MIRGraph &graph() {
         return graph_;
     }
     bool ensureBallast() {
         return temp().ensureBallast();
     }
+    IonCompartment *ionCompartment() const {
+        return cx->compartment->ionCompartment();
+    }
 
     template <typename T>
     T * allocate(size_t count = 1)
     {
         return reinterpret_cast<T *>(temp().allocate(sizeof(T) * count));
     }
 
     // Set an error state and prints a message. Returns false so errors can be
--- a/js/src/ion/shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/ion/shared/CodeGenerator-x86-shared.cpp
@@ -726,35 +726,53 @@ CodeGeneratorX86Shared::visitCallGeneric
     if (!bailoutIf(Assembler::Zero, call->snapshot()))
         return false;
 
     // Remember the size of the frame above this point, in case of bailout.
     uint32 stack_size = masm.framePushed() - unused_stack;
     // Mark !IonFramePrefix::isEntryFrame().
     uint32 size_descriptor = stack_size << 1;
 
-    // If insufficient arguments are passed, bail.
-    // Bug 685099: Instead of bailing, create a new frame with |undefined| padding.
-    masm.load16(Operand(tokreg, offsetof(JSFunction, nargs)), nargsreg);
-    masm.cmpl(nargsreg, Imm32(call->nargs()));
-    if (!bailoutIf(Assembler::NotEqual, call->snapshot()))
-        return false;
-
     // Nestle %esp up to the argument vector.
     if (unused_stack)
         masm.addPtr(Imm32(unused_stack), StackPointer);
 
     // Construct the IonFramePrefix.
     masm.push(tokreg);
     masm.push(Imm32(size_descriptor));
 
-    // Finally, call.
-    masm.movePtr(Operand(objreg, offsetof(IonScript, method_)), objreg);
-    masm.movePtr(Operand(objreg, IonCode::OffsetOfCode()), objreg);
-    masm.call(objreg);
+    // Call the function, padding with |undefined| in case of insufficient args.
+    {
+        Label thunk, rejoin;
+
+        // Get the address of the argumentsRectifier code.
+        IonCompartment *ion = gen->ionCompartment();
+        IonCode *argumentsRectifier = ion->getArgumentsRectifier(gen->cx);
+        if (!argumentsRectifier)
+            return false;
+
+        // Check whether the provided arguments satisfy target argc.
+        masm.load16(Operand(tokreg, offsetof(JSFunction, nargs)), nargsreg);
+        masm.cmpl(nargsreg, Imm32(call->nargs()));
+        masm.j(Assembler::Above, &thunk);
+
+        // No argument fixup needed. Call the function normally.
+        masm.movePtr(Operand(objreg, offsetof(IonScript, method_)), objreg);
+        masm.movePtr(Operand(objreg, IonCode::OffsetOfCode()), objreg);
+        masm.call(objreg);
+        masm.jump(&rejoin);
+
+        // Argument fixup needed. Create a frame with correct |nargs| and then call.
+        masm.bind(&thunk);
+        masm.mov(Imm32(call->nargs()), ArgumentsRectifierReg);
+        masm.movePtr(ImmWord(argumentsRectifier->raw()), ecx); // safe to take: return reg.
+        masm.call(ecx);
+
+        masm.bind(&rejoin);
+    }
 
     // Increment to remove IonFramePrefix; decrement to fill FrameSizeClass.
     int prefix_garbage = 2 * sizeof(void *);
     int restore_diff = prefix_garbage - unused_stack;
     
     if (restore_diff > 0)
         masm.addPtr(Imm32(restore_diff), StackPointer);
     else if (restore_diff < 0)
--- a/js/src/ion/x64/Assembler-x64.h
+++ b/js/src/ion/x64/Assembler-x64.h
@@ -45,45 +45,53 @@
 #include "ion/shared/Assembler-shared.h"
 #include "ion/CompactBuffer.h"
 #include "ion/IonCode.h"
 
 namespace js {
 namespace ion {
 
 static const Register rax = { JSC::X86Registers::eax };
+static const Register rbx = { JSC::X86Registers::ebx };
 static const Register rcx = { JSC::X86Registers::ecx };
 static const Register rdx = { JSC::X86Registers::edx };
+static const Register rsi = { JSC::X86Registers::esi };
+static const Register rdi = { JSC::X86Registers::edi };
+static const Register rbp = { JSC::X86Registers::ebp };
 static const Register r8  = { JSC::X86Registers::r8  };
 static const Register r9  = { JSC::X86Registers::r9  };
 static const Register r10 = { JSC::X86Registers::r10 };
 static const Register r11 = { JSC::X86Registers::r11 };
 static const Register r12 = { JSC::X86Registers::r12 };
 static const Register r13 = { JSC::X86Registers::r13 };
 static const Register r14 = { JSC::X86Registers::r14 };
 static const Register r15 = { JSC::X86Registers::r15 };
-static const Register rdi = { JSC::X86Registers::edi };
-static const Register rsi = { JSC::X86Registers::esi };
-static const Register rbx = { JSC::X86Registers::ebx };
-static const Register rbp = { JSC::X86Registers::ebp };
 static const Register rsp = { JSC::X86Registers::esp };
 
-// Aliased x86 registers for common assertions.
+// X86-common synonyms.
 static const Register eax = rax;
+static const Register ebx = rbx;
+static const Register ecx = rcx;
 static const Register edx = rdx;
+static const Register esi = rsi;
+static const Register edi = rdi;
+static const Register ebp = rbp;
+static const Register esp = rsp;
 
 static const Register InvalidReg = { JSC::X86Registers::invalid_reg };
 static const FloatRegister InvalidFloatReg = { JSC::X86Registers::invalid_xmm };
 
 static const Register StackPointer = rsp;
 static const Register JSReturnReg = rcx;
 static const Register ReturnReg = rax;
 static const Register ScratchReg = r11;
 static const FloatRegister ScratchFloatReg = { JSC::X86Registers::xmm15 };
 
+static const Register ArgumentsRectifierReg = { JSC::X86Registers::r8 };
+
 // Different argument registers for WIN64
 #if defined(_WIN64)
 static const Register ArgReg0 = rcx;
 static const Register ArgReg1 = rdx;
 static const Register ArgReg2 = r8;
 static const Register ArgReg3 = r9;
 #else
 static const Register ArgReg0 = rdi;
--- a/js/src/ion/x64/CodeGenerator-x64.cpp
+++ b/js/src/ion/x64/CodeGenerator-x64.cpp
@@ -77,25 +77,18 @@ FrameSizeClass::frameSize() const
 {
     JS_NOT_REACHED("x64 does not use frame size classes");
     return 0;
 }
 
 bool
 CodeGeneratorX64::visitValue(LValue *value)
 {
-    jsval_layout jv;
-    jv.asBits = JSVAL_BITS(Jsvalify(value->value()));
-
     LDefinition *reg = value->getDef(0);
-
-    if (value->value().isMarkable())
-        masm.movq(ImmGCPtr(jv.asPtr), ToRegister(reg));
-    else
-        masm.movq(ImmWord(jv.asBits), ToRegister(reg));
+    masm.moveValue(value->value(), ToRegister(reg));
     return true;
 }
 
 static inline JSValueShiftedTag
 MIRTypeToShiftedTag(MIRType type)
 {
     switch (type) {
       case MIRType_Int32:
--- a/js/src/ion/x64/MacroAssembler-x64.h
+++ b/js/src/ion/x64/MacroAssembler-x64.h
@@ -98,16 +98,19 @@ class MacroAssemblerX64 : public MacroAs
     // X86/X64-common interface.
     /////////////////////////////////////////////////////////////////
     void storeValue(ValueOperand val, Operand dest) {
         movq(val.valueReg(), dest);
     }
     void movePtr(Operand op, const Register &dest) {
         movq(op, dest);
     }
+    void moveValue(const Value &val, const Register &dest) {
+        movq(ImmWord((void *)val.asRawBits()), dest);
+    }
 
     /////////////////////////////////////////////////////////////////
     // Common interface.
     /////////////////////////////////////////////////////////////////
     void reserveStack(uint32 amount) {
         if (amount)
             subq(Imm32(amount), StackPointer);
         framePushed_ += amount;
--- a/js/src/ion/x64/Trampoline-x64.cpp
+++ b/js/src/ion/x64/Trampoline-x64.cpp
@@ -189,16 +189,99 @@ IonCompartment::generateReturnError(JSCo
     masm.pop(r11);
 
     GenerateReturn(masm, JS_FALSE);
     
     Linker linker(masm);
     return linker.newCode(cx);
 }
 
+IonCode *
+IonCompartment::generateArgumentsRectifier(JSContext *cx)
+{
+    MacroAssembler masm(cx);
+
+    // ArgumentsRectifierReg contains the |nargs| pushed onto the current frame.
+    // Including |this|, there are (|nargs| + 1) arguments to copy.
+    JS_ASSERT(ArgumentsRectifierReg == r8);
+
+    // Load the number of |undefined|s to push into %rcx.
+    masm.movq(Operand(rsp, offsetof(IonFrameData, calleeToken_)), rax);
+    masm.load16(Operand(rax, offsetof(JSFunction, nargs)), rcx);
+    masm.subq(r8, rcx);
+
+    masm.moveValue(UndefinedValue(), r10);
+
+    masm.movq(rsp, rbp); // Save %rsp.
+
+    // Push undefined.
+    {
+        Label undefLoopTop;
+        masm.bind(&undefLoopTop);
+
+        masm.push(r10);
+        masm.subl(Imm32(1), rcx);
+
+        masm.testl(rcx, rcx);
+        masm.j(Assembler::NonZero, &undefLoopTop);
+    }
+
+    // Get the topmost argument.
+    masm.movq(r8, r9);
+    masm.shlq(Imm32(3), r9); // r9 <- (nargs) * sizeof(Value)
+
+    masm.movq(rbp, rcx);
+    masm.addq(Imm32(sizeof(IonFrameData)), rcx);
+    masm.addq(r9, rcx);
+
+    // Push arguments, |nargs| + 1 times (to include |this|).
+    {
+        Label copyLoopTop, initialSkip;
+
+        masm.jump(&initialSkip);
+
+        masm.bind(&copyLoopTop);
+        masm.subq(Imm32(sizeof(Value)), rcx);
+        masm.subl(Imm32(1), r8);
+        masm.bind(&initialSkip);
+
+        masm.mov(Operand(rcx, 0x0), rdx);
+        masm.push(rdx);
+
+        masm.testl(r8, r8);
+        masm.j(Assembler::NonZero, &copyLoopTop);
+    }
+
+    masm.subq(rsp, rbp);
+    masm.shll(Imm32(1), rbp); // construct sizeDescriptor.
+
+    // Construct IonFrameData.
+    masm.push(rax); // calleeToken.
+    masm.push(rbp); // sizeDescriptor.
+
+    // Call the target function.
+    // Note that this code assumes the function is JITted.
+    masm.movq(Operand(rax, offsetof(JSFunction, u.i.script)), rax);
+    masm.movq(Operand(rax, offsetof(JSScript, ion)), rax);
+    masm.movq(Operand(rax, offsetof(IonScript, method_)), rax);
+    masm.movq(Operand(rax, IonCode::OffsetOfCode()), rax);
+    masm.call(rax);
+
+    // Remove the rectifier frame.
+    masm.pop(rbx);            // rbx <- sizeDescriptor_
+    masm.shrl(Imm32(1), rbx); // rbx <- size of pushed arguments
+    masm.pop(r11);            // Discard calleeToken_
+    masm.addq(rbx, rsp);      // Discard pushed arguments.
+
+    masm.ret();
+
+    Linker linker(masm);
+    return linker.newCode(cx);
+}
+
 static void
 GenerateBailoutThunk(MacroAssembler &masm, uint32 frameClass)
 {
     // Push registers such that we can access them from [base + code].
     masm.reserveStack(Registers::Total * sizeof(void *));
     for (uint32 i = 0; i < Registers::Total; i++)
         masm.movq(Register::FromCode(i), Operand(rsp, i * sizeof(void *)));
 
--- a/js/src/ion/x86/Assembler-x86.h
+++ b/js/src/ion/x86/Assembler-x86.h
@@ -63,16 +63,18 @@ static const Register InvalidReg = { JSC
 static const FloatRegister InvalidFloatReg = { JSC::X86Registers::invalid_xmm };
 
 static const Register JSReturnReg_Type = ecx;
 static const Register JSReturnReg_Data = edx;
 static const Register StackPointer = esp;
 static const Register ReturnReg = eax;
 static const FloatRegister ScratchFloatReg = { JSC::X86Registers::xmm7 };
 
+static const Register ArgumentsRectifierReg = { JSC::X86Registers::esi };
+
 struct ImmTag : public Imm32
 {
     ImmTag(JSValueTag mask)
       : Imm32(int32(mask))
     { }
 };
 
 struct ImmType : public ImmTag
--- a/js/src/ion/x86/MacroAssembler-x86.h
+++ b/js/src/ion/x86/MacroAssembler-x86.h
@@ -79,16 +79,22 @@ class MacroAssemblerX86 : public MacroAs
     /////////////////////////////////////////////////////////////////
     Operand ToPayload(Operand base) {
         return base;
     }
     Operand ToType(Operand base) {
         return Operand(Register::FromCode(base.base()),
                        base.disp() + sizeof(void *));
     }
+    void moveValue(const Value &val, const Register &type, const Register &data) {
+        jsval_layout jv;
+        jv.asBits = JSVAL_BITS(Jsvalify(val));
+        movl(Imm32(jv.s.tag), type);
+        movl(Imm32(jv.s.payload.i32), data);
+    }
 
     /////////////////////////////////////////////////////////////////
     // X86/X64-common interface.
     /////////////////////////////////////////////////////////////////
     void storeValue(ValueOperand val, Operand dest) {
         movl(val.payloadReg(), ToPayload(dest));
         movl(val.typeReg(), ToType(dest));
     }
--- a/js/src/ion/x86/Trampoline-x86.cpp
+++ b/js/src/ion/x86/Trampoline-x86.cpp
@@ -191,16 +191,102 @@ IonCompartment::generateReturnError(JSCo
     masm.addl(eax, esp);
 
     GenerateReturn(masm, JS_FALSE);
     
     Linker linker(masm);
     return linker.newCode(cx);
 }
 
+IonCode *
+IonCompartment::generateArgumentsRectifier(JSContext *cx)
+{
+    MacroAssembler masm(cx);
+
+    // ArgumentsRectifierReg contains the |nargs| pushed onto the current frame.
+    // Including |this|, there are (|nargs| + 1) arguments to copy.
+    JS_ASSERT(ArgumentsRectifierReg == esi);
+
+    // Load the number of |undefined|s to push into %ecx.
+    masm.movl(Operand(esp, offsetof(IonFrameData, calleeToken_)), eax);
+    masm.load16(Operand(eax, offsetof(JSFunction, nargs)), ecx);
+    masm.subl(esi, ecx);
+
+    masm.moveValue(UndefinedValue(), ebx, edi);
+
+    masm.movl(esp, ebp); // Save %esp.
+
+    // Push undefined.
+    {
+        Label undefLoopTop;
+        masm.bind(&undefLoopTop);
+
+        masm.push(ebx); // type(undefined);
+        masm.push(edi); // payload(undefined);
+        masm.subl(Imm32(1), ecx);
+
+        masm.testl(ecx, ecx);
+        masm.j(Assembler::NonZero, &undefLoopTop);
+    }
+
+    // Get the topmost argument.
+    masm.movl(esi, edi);
+    masm.shll(Imm32(3), edi); // edi <- nargs * sizeof(Value);
+
+    masm.movl(ebp, ecx);
+    masm.addl(Imm32(sizeof(IonFrameData)), ecx);
+    masm.addl(edi, ecx);
+
+    // Push arguments, |nargs| + 1 times (to include |this|).
+    {
+        Label copyLoopTop, initialSkip;
+
+        masm.jump(&initialSkip);
+
+        masm.bind(&copyLoopTop);
+        masm.subl(Imm32(sizeof(Value)), ecx);
+        masm.subl(Imm32(1), esi);
+        masm.bind(&initialSkip);
+
+        masm.mov(Operand(ecx, sizeof(Value)/2), edx);
+        masm.push(edx);
+        masm.mov(Operand(ecx, 0x0), edx);
+        masm.push(edx);
+
+        masm.testl(esi, esi);
+        masm.j(Assembler::NonZero, &copyLoopTop);
+    }
+
+    masm.subl(esp, ebp);
+    masm.shll(Imm32(1), ebp); // construct sizeDescriptor.
+
+    // Construct IonFrameData.
+    masm.push(eax); // calleeToken
+    masm.push(ebp); // sizeDescriptor
+
+    // Call the target function.
+    // Note that this assumes the function is JITted.
+    masm.movl(Operand(eax, offsetof(JSFunction, u.i.script)), eax);
+    masm.movl(Operand(eax, offsetof(JSScript, ion)), eax);
+    masm.movl(Operand(eax, offsetof(IonScript, method_)), eax);
+    masm.movl(Operand(eax, IonCode::OffsetOfCode()), eax);
+    masm.call(eax);
+
+    // Remove the rectifier frame.
+    masm.pop(ebx);            // ebx <- sizeDescriptor
+    masm.shrl(Imm32(1), ebx); // ebx <- size of pushed arguments
+    masm.pop(edi);            // Discard calleeToken
+    masm.addl(ebx, esp);      // Discard pushed arguments.
+
+    masm.ret();
+
+    Linker linker(masm);
+    return linker.newCode(cx);
+}
+
 static void
 GenerateBailoutThunk(MacroAssembler &masm, uint32 frameClass)
 {
     // Push registers such that we can access them from [base + code].
     masm.reserveStack(Registers::Total * sizeof(void *));
     for (uint32 i = 0; i < Registers::Total; i++)
         masm.movl(Register::FromCode(i), Operand(esp, i * sizeof(void *)));