Bug 773549 - Implemented inlined calls to specialized DOM methods. (r=sstangl)
authorEric Faust <efaust@mozilla.com>
Wed, 08 Aug 2012 17:02:46 -0700
changeset 103690 b2382c3c24cedc2fcd27d07a7a53ffc1fd0bdb3f
parent 103689 30894762f1fcdc36cfd64ccd1ce2c8473a8ebf4b
child 103691 7fcedafba16d88ea40f29f32549bda62831f47a4
push id1199
push userefaust@mozilla.com
push dateThu, 09 Aug 2012 00:02:55 +0000
reviewerssstangl
bugs773549
milestone17.0a1
Bug 773549 - Implemented inlined calls to specialized DOM methods. (r=sstangl)
js/src/ion/CodeGenerator.cpp
js/src/ion/CodeGenerator.h
js/src/ion/IonBuilder.cpp
js/src/ion/LIR-Common.h
js/src/ion/LOpcodes.h
js/src/ion/Lowering.cpp
js/src/ion/MIR.h
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -547,16 +547,111 @@ CodeGenerator::visitCallNative(LCallNati
     masm.adjustStack(IonNativeExitFrameLayout::Size() - unusedStack);
     JS_ASSERT(masm.framePushed() == initialStack);
 
     dropArguments(call->numStackArgs() + 1);
     return true;
 }
 
 bool
+CodeGenerator::visitCallDOMNative(LCallDOMNative *call)
+{
+    JSFunction *target = call->func();
+    JS_ASSERT(target);
+    JS_ASSERT(target->isNative());
+    JS_ASSERT(target->jitInfo());
+    JS_ASSERT(call->mir()->isDOMFunction());
+
+    int callargslot = call->argslot();
+    int unusedStack = StackOffsetOfPassedArg(callargslot);
+
+    // Registers used for callWithABI() argument-passing.
+    const Register argJSContext = ToRegister(call->getArgJSContext());
+    const Register argObj       = ToRegister(call->getArgObj());
+    const Register argPrivate   = ToRegister(call->getArgPrivate());
+    const Register argArgc      = ToRegister(call->getArgArgc());
+    const Register argVp        = ToRegister(call->getArgVp());
+
+    DebugOnly<uint32> initialStack = masm.framePushed();
+
+    masm.checkStackAlignment();
+
+    // DOM methods have the signature:
+    //  bool (*)(JSContext *, HandleObject, void *private, unsigned argc, Value *vp)
+    // Where vp[0] is space for an outparam and the callee, vp[1] is |this|, and vp[2] onward
+    // are the function arguments.
+
+    // Nestle the stack up against the pushed arguments, leaving StackPointer at
+    // &vp[1]
+    masm.adjustStack(unusedStack);
+    masm.movePtr(StackPointer, argObj);
+
+    // Push a Value containing the callee object: natives are allowed to access their callee before
+    // setitng the return value. The StackPointer is moved to &vp[0].
+    masm.Push(ObjectValue(*target));
+    masm.movePtr(StackPointer, argVp);
+
+    // Use argArgc as scratch.
+    Register obj = masm.extractObject(Address(argObj, 0), argArgc);
+    // GetReservedSlot(obj, DOM_PROTO_INSTANCE_CLASS_SLOT).toPrivate()
+    masm.loadPrivate(Address(obj, JSObject::getFixedSlotOffset(0)), argPrivate);
+
+    // Load argc from the call instruction.
+    masm.move32(Imm32(call->numStackArgs()), argArgc);
+    // Push argument into what will become the IonExitFrame
+    masm.Push(argArgc);
+
+    // Construct native exit frame.
+    uint32 safepointOffset;
+    if (!masm.buildFakeExitFrame(argJSContext, &safepointOffset))
+        return false;
+    masm.enterFakeExitFrame();
+
+    if (!markSafepointAt(safepointOffset, call))
+        return false;
+
+    // Construct and execute call.
+    masm.setupUnalignedABICall(5, argJSContext);
+
+    masm.loadJSContext(argJSContext);
+
+    masm.passABIArg(argJSContext);
+    masm.passABIArg(argObj);
+    masm.passABIArg(argPrivate);
+    masm.passABIArg(argArgc);
+    masm.passABIArg(argVp);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target->jitInfo()->op));
+
+    // Test for failure.
+    Label success, exception;
+    masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &exception);
+
+    // Load the outparam vp[0] into output register(s).
+    masm.loadValue(Address(StackPointer, IonNativeExitFrameLayout::offsetOfResult()), JSReturnOperand);
+    masm.jump(&success);
+
+    // Handle exception case.
+    {
+        masm.bind(&exception);
+        masm.handleException();
+    }
+    masm.bind(&success);
+
+    // The next instruction is removing the footer of the exit frame, so there
+    // is no need for leaveFakeExitFrame.
+
+    // Move the StackPointer back to its original location, unwinding the native exit frame.
+    masm.adjustStack(IonNativeExitFrameLayout::Size() - unusedStack);
+    JS_ASSERT(masm.framePushed() == initialStack);
+
+    return true;
+}
+
+
+bool
 CodeGenerator::emitCallInvokeFunction(LCallGeneric *call, uint32 unusedStack)
 {
     typedef bool (*pf)(JSContext *, JSFunction *, uint32, Value *, Value *);
     static const VMFunction InvokeFunctionInfo = FunctionInfo<pf>(InvokeFunction);
 
     // Nestle %esp up to the argument vector.
     // Each path must account for framePushed_ separately, for callVM to be valid.
     masm.freeStack(unusedStack);
--- a/js/src/ion/CodeGenerator.h
+++ b/js/src/ion/CodeGenerator.h
@@ -159,16 +159,17 @@ class CodeGenerator : public CodeGenerat
     bool visitBitOpV(LBitOpV *lir);
     bool emitInstanceOf(LInstruction *ins, Register rhs);
     bool visitInstanceOfO(LInstanceOfO *ins);
     bool visitInstanceOfV(LInstanceOfV *ins);
     bool visitProfilingEnter(LProfilingEnter *lir);
     bool visitProfilingExit(LProfilingExit *lir);
     bool visitGetDOMProperty(LGetDOMProperty *lir);
     bool visitSetDOMProperty(LSetDOMProperty *lir);
+    bool visitCallDOMNative(LCallDOMNative *lir);
 
     bool visitCheckOverRecursed(LCheckOverRecursed *lir);
     bool visitCheckOverRecursedFailure(CheckOverRecursedFailure *ool);
 
     bool visitUnboxDouble(LUnboxDouble *lir);
     bool visitOutOfLineUnboxDouble(OutOfLineUnboxDouble *ool);
     bool visitOutOfLineStoreElementHole(OutOfLineStoreElementHole *ool);
 
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -3732,17 +3732,21 @@ IonBuilder::makeCallBarrier(HandleFuncti
 
         thisArg->block()->discard(thisArg);
         current->add(newThis);
         thisArg = newThis;
     }
 
     // Pass |this| and function.
     call->addArg(0, thisArg);
-    call->initFunction(current->pop());
+
+    MDefinition *fun = current->pop();
+    if (fun->isDOMFunction())
+        call->setDOMFunction();
+    call->initFunction(fun);
 
     current->add(call);
     current->push(call);
     if (!resumeAfter(call))
         return false;
 
     return pushTypeBarrier(call, types, barrier);
 }
@@ -4337,38 +4341,55 @@ TestSingletonPropertyTypes(JSContext *cx
         key = JSProto_Number;
         break;
 
       case JSVAL_TYPE_BOOLEAN:
         key = JSProto_Boolean;
         break;
 
       case JSVAL_TYPE_OBJECT:
-      case JSVAL_TYPE_UNKNOWN:
+      case JSVAL_TYPE_UNKNOWN: {
         // For property accesses which may be on many objects, we just need to
         // find a prototype common to all the objects; if that prototype
-        // has the property, the access will not be on a missing property.
-        if (types->getObjectCount() == 1) {
-            types::TypeObject *object = types->getTypeObject(0);
-            if (!object)
-                return true;
-            if (object && object->proto) {
-                if (!TestSingletonProperty(cx, object->proto, id, isKnownConstant))
+        // has the singleton property, the access will not be on a missing property.
+        bool thoughtConstant = true;
+        for (unsigned i = 0; i < types->getObjectCount(); i++) {
+            types::TypeObject *object = types->getTypeObject(i);
+            if (!object) {
+                // Try to get it through the singleton.
+                JSObject *curObj = types->getSingleObject(i);
+                // As per the comment in jsinfer.h, there can be holes in
+                // TypeSets, so just skip over them.
+                if (!curObj)
+                    continue;
+                object = curObj->getType(cx);
+            }
+
+            if (object->proto) {
+                // Test this type.
+                if (!TestSingletonProperty(cx, object->proto, id, &thoughtConstant))
                     return false;
-                if (*isKnownConstant) {
-                    types->addFreeze(cx);
-
-                    // If this is not a known object, a test will be needed.
-                    *testObject = (type != JSVAL_TYPE_OBJECT);
-                }
-                return true;
+                // Short circuit
+                if (!thoughtConstant)
+                    break;
+            } else {
+                // Can't be on the prototype chain with no prototypes...
+                thoughtConstant = false;
+                break;
             }
         }
+        if (thoughtConstant) {
+                types->addFreeze(cx);
+
+                // If this is not a known object, a test will be needed.
+                *testObject = (type != JSVAL_TYPE_OBJECT);
+        }
+        *isKnownConstant = thoughtConstant;
         return true;
-
+      }
       default:
         return true;
     }
 
     RootedObject proto(cx);
     if (!js_GetClassPrototype(cx, globalObj, key, &proto, NULL))
         return false;
 
@@ -5430,16 +5451,78 @@ TestShouldDOMCall(JSContext *cx, types::
         RootedObject proto(cx, typeProto);
         if (!instanceChecker(proto, jinfo->protoID, jinfo->depth))
             return false;
     }
 
     return true;
 }
 
+static bool
+TestAreKnownDOMTypes(JSContext *cx, types::TypeSet *inTypes)
+{
+    if (inTypes->unknown())
+        return false;
+
+    // First iterate to make sure they all are DOM objects, then freeze all of
+    // them as such if they are.
+    for (unsigned i = 0; i < inTypes->getObjectCount(); i++) {
+        types::TypeObject *curType = inTypes->getTypeObject(i);
+
+        if (!curType) {
+            JSObject *curObj = inTypes->getSingleObject(i);
+
+            // Skip holes in TypeSets.
+            if (!curObj)
+                continue;
+
+            curType = curObj->getType(cx);
+        }
+
+        if (curType->unknownProperties())
+            return false;
+
+        // Unlike TypeSet::HasObjectFlags, TypeObject::hasAnyFlags doesn't add a
+        // freeze.
+        if (curType->hasAnyFlags(types::OBJECT_FLAG_NON_DOM))
+            return false;
+    }
+
+    // If we didn't check anything, no reason to say yes.
+    if (inTypes->getObjectCount() > 0)
+        return true;
+
+    return false;
+}
+
+static void
+FreezeDOMTypes(JSContext *cx, types::TypeSet *inTypes)
+{
+    for (unsigned i = 0; i < inTypes->getObjectCount(); i++) {
+        types::TypeObject *curType = inTypes->getTypeObject(i);
+
+        if (!curType) {
+            JSObject *curObj = inTypes->getSingleObject(i);
+
+            // Skip holes in TypeSets.
+            if (!curObj)
+                continue;
+
+            curType = curObj->getType(cx);
+        }
+
+        // Add freeze by asking the question.
+        DebugOnly<bool> wasntDOM = types::TypeSet::HasObjectFlags(cx, curType,
+                                                                  types::OBJECT_FLAG_NON_DOM);
+        JS_ASSERT(!wasntDOM);
+    }
+
+    inTypes->addFreeze(cx);
+}
+
 bool
 IonBuilder::annotateGetPropertyCache(JSContext *cx, MDefinition *obj, MGetPropertyCache *getPropCache,
                                     types::TypeSet *objTypes, types::TypeSet *pushedTypes)
 {
     RootedId id(cx, NameToId(getPropCache->name()));
 
     // Ensure every pushed value is a singleton.
     if (pushedTypes->unknownObject() || pushedTypes->baseFlags() != 0)
@@ -5636,17 +5719,29 @@ IonBuilder::jsop_getprop(HandlePropertyN
             return false;
         }
 
         if (isKnownConstant) {
             if (testObject) {
                 MGuardObject *guard = MGuardObject::New(obj);
                 current->add(guard);
             }
-            return pushConstant(ObjectValue(*singleton));
+            MConstant *known = MConstant::New(ObjectValue(*singleton));
+            current->add(known);
+            current->push(known);
+            if (singleton->isFunction()) {
+                RootedFunction singletonFunc(cx, singleton->toFunction());
+                if (TestAreKnownDOMTypes(cx, unaryTypes.inTypes) &&
+                    TestShouldDOMCall(cx, unaryTypes.inTypes, singletonFunc))
+                {
+                    FreezeDOMTypes(cx, unaryTypes.inTypes);
+                    known->setDOMFunction();
+                }
+            }
+            return true;
         }
     }
 
     if (types::TypeSet *propTypes = GetDefiniteSlot(cx, unaryTypes.inTypes, name)) {
         MDefinition *useObj = obj;
         if (unaryTypes.inTypes && unaryTypes.inTypes->baseFlags()) {
             MGuardObject *guard = MGuardObject::New(obj);
             current->add(guard);
--- a/js/src/ion/LIR-Common.h
+++ b/js/src/ion/LIR-Common.h
@@ -623,16 +623,69 @@ class LCallNative : public LCallInstruct
         return getTemp(2)->output();
     }
 
     const LAllocation *getTempReg() {
         return getTemp(3)->output();
     }
 };
 
+class LCallDOMNative : public LCallInstructionHelper<BOX_PIECES, 0, 5>
+{
+    uint32 argslot_;
+
+  public:
+    LIR_HEADER(CallDOMNative);
+
+    LCallDOMNative(uint32 argslot,
+                   const LDefinition &argJSContext, const LDefinition &argObj,
+                   const LDefinition &argPrivate, const LDefinition &argArgc,
+                   const LDefinition &argVp)
+      : argslot_(argslot)
+    {
+        setTemp(0, argJSContext);
+        setTemp(1, argObj);
+        setTemp(2, argPrivate);
+        setTemp(3, argArgc);
+        setTemp(4, argVp);
+    }
+
+    JSFunction *func() const {
+        return mir()->getSingleTarget();
+    }
+    uint32 argslot() const {
+        return argslot_;
+    }
+    MCall *mir() const {
+        return mir_->toCall();
+    }
+
+    // Named for consistency with LCallNative. Actually should be argc().
+    uint32 numStackArgs() const {
+        JS_ASSERT(mir()->numStackArgs() >= 1);
+        return mir()->numStackArgs() - 1;
+    }
+
+    const LAllocation *getArgJSContext() {
+        return getTemp(0)->output();
+    }
+    const LAllocation *getArgObj() {
+        return getTemp(1)->output();
+    }
+    const LAllocation *getArgPrivate() {
+        return getTemp(2)->output();
+    }
+    const LAllocation *getArgArgc() {
+        return getTemp(3)->output();
+    }
+    const LAllocation *getArgVp() {
+        return getTemp(4)->output();
+    }
+};
+
 // Generates a polymorphic callsite for |new|, where |this| has not been
 // pre-allocated by the caller.
 class LCallConstructor : public LCallInstructionHelper<BOX_PIECES, 1, 0>
 {
     uint32 argslot_;
 
   public:
     LIR_HEADER(CallConstructor);
--- a/js/src/ion/LOpcodes.h
+++ b/js/src/ion/LOpcodes.h
@@ -156,17 +156,18 @@
     _(Floor)                        \
     _(Round)                        \
     _(InstanceOfO)                  \
     _(InstanceOfV)                  \
     _(InterruptCheck)               \
     _(ProfilingEnter)               \
     _(ProfilingExit)                \
     _(GetDOMProperty)               \
-    _(SetDOMProperty)
+    _(SetDOMProperty)               \
+    _(CallDOMNative)
 
 #if defined(JS_CPU_X86)
 # include "x86/LOpcodes-x86.h"
 #elif defined(JS_CPU_X64)
 # include "x64/LOpcodes-x64.h"
 #elif defined(JS_CPU_ARM)
 # include "arm/LOpcodes-arm.h"
 #endif
--- a/js/src/ion/Lowering.cpp
+++ b/js/src/ion/Lowering.cpp
@@ -221,23 +221,36 @@ LIRGenerator::visitCall(MCall *call)
     JS_ASSERT(CallTempReg1 != ArgumentsRectifierReg);
     JS_ASSERT(call->getFunction()->type() == MIRType_Object);
 
     // Height of the current argument vector.
     uint32 argslot = getArgumentSlotForCall();
     JSFunction *target = call->getSingleTarget();
 
     if (target && target->isNative()) {
-        LCallNative *lir = new LCallNative(argslot, tempFixed(CallTempReg0),
-                tempFixed(CallTempReg1), tempFixed(CallTempReg2), tempFixed(CallTempReg3));
-
-        if (!defineReturn(lir, call))
-            return false;
-        if (!assignSafepoint(lir, call))
-            return false;
+        if (call->isDOMFunction()) {
+            LCallDOMNative *lir = new LCallDOMNative(argslot,
+                                                     tempFixed(CallTempReg0),
+                                                     tempFixed(CallTempReg1),
+                                                     tempFixed(CallTempReg2),
+                                                     tempFixed(CallTempReg3),
+                                                     tempFixed(CallTempReg4));
+            if (!defineReturn(lir, call))
+                return false;
+            if (!assignSafepoint(lir, call))
+                return false;
+        } else {
+            LCallNative *lir = new LCallNative(argslot, tempFixed(CallTempReg0),
+                                               tempFixed(CallTempReg1), tempFixed(CallTempReg2),
+                                               tempFixed(CallTempReg3));
+            if (!defineReturn(lir, call))
+                return false;
+            if (!assignSafepoint(lir, call))
+                return false;
+        }
     } else if (!target && call->isConstructing()) {
         LCallConstructor *lir = new LCallConstructor(useFixed(call->getFunction(), CallTempReg0),
                                                        argslot);
         if (!defineVMReturn(lir, call))
             return false;
         if (!assignSafepoint(lir, call))
             return false;
     } else {
--- a/js/src/ion/MIR.h
+++ b/js/src/ion/MIR.h
@@ -45,17 +45,18 @@ MIRType MIRTypeFromValue(const js::Value
     _(Commutative)                                                              \
     _(Movable)       /* Allow LICM and GVN to move this instruction */          \
     _(Lowered)       /* (Debug only) has a virtual register */                  \
     _(Guard)         /* Not removable if uses == 0 */                           \
                                                                                 \
     /* The instruction has been marked dead for lazy removal from resume
      * points.
      */                                                                         \
-    _(Unused)
+    _(Unused)                                                                   \
+    _(DOMFunction)   /* Contains or uses a common DOM method function */
 
 class MDefinition;
 class MInstruction;
 class MBasicBlock;
 class MNode;
 class MUse;
 class MIRGraph;
 class MResumePoint;