[INFER] Use new type objects when the result of 'new' is assigned to a .prototype, bug 641714.
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 15 Mar 2011 00:17:07 -0700
changeset 74772 4cdb8e6b4391273f2ea0ecc2a7943b3e820b9e2c
parent 74771 ce31f0090eb00b73ba494b620ea2c0c3b9153b40
child 74773 484164b6d6ca4783d747056450e1545d038effc8
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
bugs641714
milestone2.0b13pre
[INFER] Use new type objects when the result of 'new' is assigned to a .prototype, bug 641714.
js/src/jsinfer.cpp
js/src/jsinferinlines.h
js/src/jsinterp.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/InvokeHelpers.cpp
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -1708,16 +1708,50 @@ GetScriptObject(JSContext *cx, JSScript 
 
 static inline const Value &
 GetScriptConst(JSContext *cx, JSScript *script, const jsbytecode *pc)
 {
     unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, 0);
     return script->getConst(index);
 }
 
+bool
+UseNewType(JSContext *cx, JSScript *script, jsbytecode *pc)
+{
+    JS_ASSERT(cx->typeInferenceEnabled());
+
+    /*
+     * Make a heuristic guess at a use of JSOP_NEW that the constructed object
+     * should have a fresh type object. We do this when the NEW is immediately
+     * followed by a simple assignment to an object's .prototype field.
+     * This is designed to catch common patterns for subclassing in JS:
+     *
+     * function Super() { ... }
+     * function Sub1() { ... }
+     * function Sub2() { ... }
+     *
+     * 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)
+        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;
+}
+
 void
 TypeCompartment::growPendingArray(JSContext *cx)
 {
     unsigned newCapacity = js::Max(unsigned(100), pendingCapacity * 2);
     PendingWork *newArray = (PendingWork *) js_calloc(newCapacity * sizeof(PendingWork));
     if (!newArray) {
         cx->compartment->types.setPendingNukeTypes(cx);
         return;
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -182,16 +182,19 @@ TypeCompartment::checkPendingRecompiles(
     inferenceStartTime = 0;
     if (pendingNukeTypes)
         return nukeTypes(cx);
     else if (pendingRecompiles && !processPendingRecompiles(cx))
         return false;
     return true;
 }
 
+bool
+UseNewType(JSContext *cx, JSScript *script, jsbytecode *pc);
+
 } } /* namespace js::types */
 
 /////////////////////////////////////////////////////////////////////
 // JSContext
 /////////////////////////////////////////////////////////////////////
 
 inline bool
 JSContext::typeInferenceEnabled()
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -707,17 +707,19 @@ Invoke(JSContext *cx, const CallArgs &ar
     JS_ASSERT_IF(flags & JSINVOKE_CONSTRUCT, !fun->isConstructor());
     if (fun->isNative())
         return CallJSNative(cx, fun->u.n.native, args.argc(), args.base());
 
     /* Handle the empty-script special case. */
     JSScript *script = fun->script();
     if (JS_UNLIKELY(script->isEmpty())) {
         if (flags & JSINVOKE_CONSTRUCT) {
-            JSObject *obj = js_CreateThisForFunction(cx, &callee);
+            bool newType = cx->typeInferenceEnabled() &&
+                UseNewType(cx, cx->fp()->script(), cx->regs->pc);
+            JSObject *obj = js_CreateThisForFunction(cx, &callee, newType);
             if (!obj)
                 return false;
             args.rval().setObject(*obj);
         } else {
             args.rval().setUndefined();
         }
         return true;
     }
@@ -2203,20 +2205,20 @@ IteratorNext(JSContext *cx, JSObject *it
             }
             /* Take the slow path if we have to stringify a numeric property name. */
         }
     }
     return js_IteratorNext(cx, iterobj, rval);
 }
 
 static inline bool
-ScriptPrologue(JSContext *cx, JSStackFrame *fp)
+ScriptPrologue(JSContext *cx, JSStackFrame *fp, bool newType)
 {
     if (fp->isConstructing()) {
-        JSObject *obj = js_CreateThisForFunction(cx, &fp->callee());
+        JSObject *obj = js_CreateThisForFunction(cx, &fp->callee(), newType);
         if (!obj)
             return false;
         fp->functionThis().setObject(*obj);
     }
     if (fp->isExecuteFrame()) {
         if (JSInterpreterHook hook = cx->debugHooks->executeHook)
             fp->setHookData(hook(cx, fp, JS_TRUE, 0, cx->debugHooks->executeHookData));
     } else {
@@ -2635,17 +2637,19 @@ Interpret(JSContext *cx, JSStackFrame *e
 
     if (regs.fp->hasImacropc())
         atoms = COMMON_ATOMS_START(&rt->atomState);
 #endif
 
     /* Don't call the script prologue if executing between Method and Trace JIT. */
     if (interpMode == JSINTERP_NORMAL) {
         JS_ASSERT_IF(!regs.fp->isGeneratorFrame(), regs.pc == script->code);
-        if (!ScriptPrologue(cx, regs.fp))
+        bool newType = regs.fp->isConstructing() && cx->typeInferenceEnabled() &&
+            regs.fp->prev() && UseNewType(cx, regs.fp->prev()->script(), regs.fp->prev()->pc(cx));
+        if (!ScriptPrologue(cx, regs.fp, newType))
             goto error;
     }
 
     CHECK_INTERRUPT_HANDLER();
 
     RESET_USE_METHODJIT();
 
     /* State communicated between non-local jumps: */
@@ -4783,18 +4787,20 @@ BEGIN_CASE(JSOP_NEW)
 
     /*
      * Assign lval, callee, and newfun exactly as the code at inline_call: expects to
      * find them, to avoid nesting a js_Interpret call via js_InvokeConstructor.
      */
     if (IsFunctionObject(vp[0], &callee)) {
         newfun = callee->getFunctionPrivate();
         if (newfun->isInterpreted()) {
+            bool newType = cx->typeInferenceEnabled() && UseNewType(cx, script, regs.pc);
+
             if (newfun->u.i.script->isEmpty()) {
-                JSObject *obj2 = js_CreateThisForFunction(cx, callee);
+                JSObject *obj2 = js_CreateThisForFunction(cx, callee, newType);
                 if (!obj2)
                     goto error;
                 vp[0].setObject(*obj2);
                 regs.sp = vp + 1;
                 goto end_new;
             }
 
             flags = JSFRAME_CONSTRUCTING;
@@ -4851,16 +4857,19 @@ BEGIN_CASE(JSOP_FUNCALL)
             if (JS_UNLIKELY(inlineCallCount >= JS_MAX_INLINE_CALL_COUNT)) {
                 js_ReportOverRecursed(cx);
                 goto error;
             }
 
             if (!cx->typeMonitorCall(CallArgs(vp + 2, argc), flags & JSFRAME_CONSTRUCTING))
                 goto error;
 
+            bool newType = (flags & JSFRAME_CONSTRUCTING) &&
+                cx->typeInferenceEnabled() && UseNewType(cx, script, regs.pc);
+
             /* Get pointer to new frame/slots, prepare arguments. */
             StackSpace &stack = cx->stack();
             JSStackFrame *newfp = stack.getInlineFrame(cx, regs.sp, argc, newfun,
                                                        newscript, &flags);
             if (JS_UNLIKELY(!newfp))
                 goto error;
 
             /* Initialize frame, locals. */
@@ -4891,24 +4900,24 @@ BEGIN_CASE(JSOP_FUNCALL)
 #ifdef JS_METHODJIT
             /* Try to ensure methods are method JIT'd.  */
             mjit::CompileRequest request = (interpMode == JSINTERP_NORMAL)
                                            ? mjit::CompileRequest_Interpreter
                                            : mjit::CompileRequest_JIT;
             mjit::CompileStatus status = mjit::CanMethodJIT(cx, script, regs.fp, request);
             if (status == mjit::Compile_Error)
                 goto error;
-            if (!TRACE_RECORDER(cx) && !TRACE_PROFILER(cx) && status == mjit::Compile_Okay) {
+            if (!TRACE_RECORDER(cx) && !TRACE_PROFILER(cx) && status == mjit::Compile_Okay && !newType) {
                 interpReturnOK = mjit::JaegerShot(cx);
                 CHECK_INTERRUPT_HANDLER();
                 goto jit_return;
             }
 #endif
 
-            if (!ScriptPrologue(cx, regs.fp))
+            if (!ScriptPrologue(cx, regs.fp, newType))
                 goto error;
 
             CHECK_INTERRUPT_HANDLER();
 
             /* Load first op and dispatch it (safe since JSOP_STOP). */
             op = (JSOp) *regs.pc;
             DO_OP();
         }
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2940,34 +2940,44 @@ JSObject *
 js_CreateThisForFunctionWithProto(JSContext *cx, JSObject *callee, JSObject *proto)
 {
     /* Caller must ensure that proto's new type is not marked as an array. */
     gc::FinalizeKind kind = NewObjectGCKind(cx, &js_ObjectClass);
     return NewNonFunction<WithProto::Class>(cx, &js_ObjectClass, proto, callee->getParent(), kind);
 }
 
 JSObject *
-js_CreateThisForFunction(JSContext *cx, JSObject *callee)
+js_CreateThisForFunction(JSContext *cx, JSObject *callee, bool newType)
 {
     Value protov;
     if (!callee->getProperty(cx,
                              ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom),
                              &protov)) {
         return NULL;
     }
     JSObject *proto;
     if (protov.isObject()) {
         proto = &protov.toObject();
         TypeObject *type = proto->getNewType(cx);
         if (!type || !cx->markTypeArrayNotPacked(type, true))
             return NULL;
     } else {
         proto = NULL;
     }
-    return js_CreateThisForFunctionWithProto(cx, callee, proto);
+    JSObject *obj = js_CreateThisForFunctionWithProto(cx, callee, proto);
+    if (obj && newType) {
+        JS_ASSERT(cx->typeInferenceEnabled());
+        types::TypeObject *type = cx->newTypeObject("SpecializedThis", obj->getProto());
+        if (!type)
+            return NULL;
+        obj->setType(type);
+        if (!callee->getFunctionPrivate()->script()->typeSetThis(cx, (types::jstype) type))
+            return NULL;
+    }
+    return obj;
 }
 
 #ifdef JS_TRACER
 
 static JS_ALWAYS_INLINE JSObject*
 NewObjectWithClassProto(JSContext *cx, Class *clasp, JSObject *proto,
                         /*gc::FinalizeKind*/ unsigned _kind)
 {
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1578,17 +1578,17 @@ js_ConstructObject(JSContext *cx, js::Cl
 
 // Specialized call for constructing |this| with a known function callee,
 // and a known prototype.
 extern JSObject *
 js_CreateThisForFunctionWithProto(JSContext *cx, JSObject *callee, JSObject *proto);
 
 // Specialized call for constructing |this| with a known function callee.
 extern JSObject *
-js_CreateThisForFunction(JSContext *cx, JSObject *callee);
+js_CreateThisForFunction(JSContext *cx, JSObject *callee, bool newType);
 
 // Generic call for constructing |this|.
 extern JSObject *
 js_CreateThis(JSContext *cx, JSObject *callee);
 
 extern jsid
 js_CheckForStringIndex(jsid id);
 
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -2900,22 +2900,24 @@ mjit::Compiler::inlineCallHelper(uint32 
      * From the presence of JSOP_FUN{CALL,APPLY}, we speculate that we are
      * going to call js_fun_{call,apply}. Normally, this call would go through
      * js::Invoke to ultimately call 'this'. We can do much better by having
      * the callIC cache and call 'this' directly. However, if it turns out that
      * we are not actually calling js_fun_call, the callIC must act as normal.
      */
     bool lowerFunCallOrApply = IsLowerableFunCallOrApply(PC);
 
+    bool newType = callingNew && cx->typeInferenceEnabled() && types::UseNewType(cx, script, PC);
+
     /*
      * Currently, constant values are not functions, so don't even try to
      * optimize. This lets us assume that callee/this have regs below.
      */
 #ifdef JS_MONOIC
-    if (debugMode() ||
+    if (debugMode() || newType ||
         origCallee->isConstant() || origCallee->isNotType(JSVAL_TYPE_OBJECT) ||
         (lowerFunCallOrApply &&
          (origThis->isConstant() || origThis->isNotType(JSVAL_TYPE_OBJECT)))) {
 #endif
         if (applyTricks == LazyArgsObj) {
             /* frame.pop() above reset us to pre-JSOP_ARGUMENTS state */
             jsop_arguments();
             frame.pushSynced(JSVAL_TYPE_UNKNOWN, NULL);
--- a/js/src/methodjit/InvokeHelpers.cpp
+++ b/js/src/methodjit/InvokeHelpers.cpp
@@ -346,16 +346,19 @@ static inline bool
 UncachedInlineCall(VMFrame &f, uint32 flags, void **pret, bool *unjittable, uint32 argc, types::jstype *argTypes)
 {
     JSContext *cx = f.cx;
     Value *vp = f.regs.sp - (argc + 2);
     JSObject &callee = vp->toObject();
     JSFunction *newfun = callee.getFunctionPrivate();
     JSScript *newscript = newfun->script();
 
+    bool newType = (flags & JSFRAME_CONSTRUCTING) && cx->typeInferenceEnabled() &&
+        types::UseNewType(cx, f.regs.fp->script(), f.regs.pc);
+
     if (argTypes && argc == newfun->nargs) {
         /*
          * Use the space of all possible types being passed at this callsite if there
          * is a match between argc and nargs, so that the fastEntry can be subsequently
          * used without further type checking. If there is an argument count mismatch,
          * the callee's args will end up getting marked as unknown.
          */
         if (flags & JSFRAME_CONSTRUCTING) {
@@ -402,20 +405,25 @@ UncachedInlineCall(VMFrame &f, uint32 fl
         if (status == Compile_Abort)
             *unjittable = true;
     }
 
     /* Create call object now that we can't fail entering callee. */
     if (newfun->isHeavyweight() && !js_GetCallObject(cx, newfp))
         return false;
 
-    /* If newscript was successfully compiled, run it. */
-    if (JITScript *jit = newscript->getJIT(newfp->isConstructing())) {
-        *pret = jit->invokeEntry;
-        return true;
+    /*
+     * If newscript was successfully compiled, run it. Skip for calls which
+     * will be constructing a new type object for 'this'.
+     */
+    if (!newType) {
+        if (JITScript *jit = newscript->getJIT(newfp->isConstructing())) {
+            *pret = jit->invokeEntry;
+            return true;
+        }
     }
 
     /* Otherwise, run newscript in the interpreter. */
     bool ok = !!Interpret(cx, cx->fp());
     InlineReturn(f);
 
     *pret = NULL;
     return ok;