Bug 826148 - Part 1: JSFunction flag and interpreter changes (r=luke)
☠☠ backed out by 6cda85d6e4f6 ☠ ☠
authorShu-yu Guo <shu@rfrn.org>
Thu, 10 Jan 2013 13:04:04 -0800
changeset 118502 b659dc17b1646aa6ecb3275afab6829b6080c7f1
parent 118501 cf76976f7316d13a00ed9a5c3ce038cc6f758069
child 118503 57bf735f3e186a1501fe2fe01b46bc1ef41d0ca2
push id24166
push userMs2ger@gmail.com
push dateFri, 11 Jan 2013 13:57:41 +0000
treeherdermozilla-central@63c4b0f66a0c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs826148
milestone21.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 826148 - Part 1: JSFunction flag and interpreter changes (r=luke)
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsinterp.cpp
js/src/jsscript.cpp
js/src/jsscript.h
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -237,16 +237,68 @@ JSRuntime::createJaegerRuntime(JSContext
         return NULL;
     }
 
     jaegerRuntime_ = jr;
     return jaegerRuntime_;
 }
 #endif
 
+void
+JSCompartment::sweepCallsiteClones()
+{
+    if (callsiteClones.initialized()) {
+        for (CallsiteCloneTable::Enum e(callsiteClones); !e.empty(); e.popFront()) {
+            CallsiteCloneKey key = e.front().key;
+            JSFunction *fun = e.front().value;
+            if (!key.script->isMarked() || !fun->isMarked())
+                e.removeFront();
+        }
+    }
+}
+
+RawFunction
+js::CloneFunctionAtCallsite(JSContext *cx, HandleFunction fun, HandleScript script, jsbytecode *pc)
+{
+    JS_ASSERT(cx->typeInferenceEnabled());
+    JS_ASSERT(fun->isCloneAtCallsite());
+    JS_ASSERT(types::UseNewTypeForClone(fun));
+    JS_ASSERT(!fun->nonLazyScript()->enclosingStaticScope());
+
+    typedef CallsiteCloneKey Key;
+    typedef CallsiteCloneTable Table;
+
+    Table &table = cx->compartment->callsiteClones;
+    if (!table.initialized() && !table.init())
+        return NULL;
+
+    Key key;
+    key.script = script;
+    key.offset = pc - script->code;
+    key.original = fun;
+
+    Table::AddPtr p = table.lookupForAdd(key);
+    if (p)
+        return p->value;
+
+    RootedObject parent(cx, fun->environment());
+    RootedFunction clone(cx, CloneFunctionObject(cx, fun, parent,
+                                                 JSFunction::ExtendedFinalizeKind));
+    if (!clone)
+        return NULL;
+
+    // Store a link back to the original for function.caller.
+    clone->setExtendedSlot(0, ObjectValue(*fun));
+
+    if (!table.add(p, key, clone.get()))
+        return NULL;
+
+    return clone;
+}
+
 JSContext *
 js::NewContext(JSRuntime *rt, size_t stackChunkSize)
 {
     JS_AbortIfWrongThread(rt);
 
     JSContext *cx = js_new<JSContext>(rt);
     if (!cx)
         return NULL;
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -53,16 +53,49 @@ js_ReportOutOfMemory(JSContext *cx);
 
 extern void
 js_ReportAllocationOverflow(JSContext *cx);
 
 namespace js {
 
 typedef HashSet<JSObject *> ObjectSet;
 
+struct CallsiteCloneKey {
+    /* The original function that we are cloning. */
+    JSFunction *original;
+
+    /* The script of the call. */
+    JSScript *script;
+
+    /* The offset of the call. */
+    uint32_t offset;
+
+    CallsiteCloneKey() { PodZero(this); }
+
+    typedef CallsiteCloneKey Lookup;
+
+    static inline uint32_t hash(CallsiteCloneKey key) {
+        return uint32_t(size_t(key.script->code + key.offset) ^ size_t(key.original));
+    }
+
+    static inline bool match(const CallsiteCloneKey &a, const CallsiteCloneKey &b) {
+        return a.script == b.script && a.offset == b.offset && a.original == b.original;
+    }
+};
+
+typedef HashMap<CallsiteCloneKey,
+                ReadBarriered<JSFunction>,
+                CallsiteCloneKey,
+                SystemAllocPolicy> CallsiteCloneTable;
+
+RawFunction CloneFunctionAtCallsite(JSContext *cx, HandleFunction fun,
+                                    HandleScript script, jsbytecode *pc);
+
+typedef HashSet<JSObject *> ObjectSet;
+
 /* Detects cycles when traversing an object graph. */
 class AutoCycleDetector
 {
     JSContext *cx;
     JSObject *obj;
     bool cyclic;
     uint32_t hashsetGenerationAtInit;
     ObjectSet::AddPtr hashsetAddPointer;
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -645,16 +645,17 @@ JSCompartment::sweep(FreeOp *fop, bool r
 
         /* Remove dead references held weakly by the compartment. */
 
         sweepBaseShapeTable();
         sweepInitialShapeTable();
         sweepNewTypeObjectTable(newTypeObjects);
         sweepNewTypeObjectTable(lazyTypeObjects);
         sweepBreakpoints(fop);
+        sweepCallsiteClones();
 
         if (global_ && IsObjectAboutToBeFinalized(global_.unsafeGet()))
             global_ = NULL;
 
 #ifdef JS_ION
         if (ionCompartment_)
             ionCompartment_->sweep(fop);
 #endif
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -345,16 +345,24 @@ struct JSCompartment : private JS::shado
     void sweepNewTypeObjectTable(js::types::TypeObjectSet &table);
 
     js::types::TypeObject *getNewType(JSContext *cx, js::TaggedProto proto,
                                       JSFunction *fun = NULL, bool isDOM = false);
 
     js::types::TypeObject *getLazyType(JSContext *cx, js::Handle<js::TaggedProto> proto);
 
     /*
+     * Hash table of all manually call site-cloned functions from within
+     * self-hosted code. Cloning according to call site provides extra
+     * sensitivity for type specialization and inlining.
+     */
+    js::CallsiteCloneTable callsiteClones;
+    void sweepCallsiteClones();
+
+    /*
      * Keeps track of the total number of malloc bytes connected to a
      * compartment's GC things. This counter should be used in preference to
      * gcMallocBytes. These counters affect collection in the same way as
      * gcBytes and gcTriggerBytes.
      */
     size_t                       gcMallocAndFreeBytes;
     size_t                       gcTriggerMallocAndFreeBytes;
 
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -80,16 +80,23 @@ fun_getProperty(JSContext *cx, HandleObj
         if (!JSObject::getProto(cx, obj, &obj))
             return false;
         if (!obj)
             return true;
     }
     RootedFunction fun(cx, obj->toFunction());
 
     /*
+     * Callsite clones should never escape to script, so get the original
+     * function.
+     */
+    if (fun->isCallsiteClone())
+        fun = fun->getExtendedSlot(0).toObject().toFunction();
+
+    /*
      * Mark the function's script as uninlineable, to expand any of its
      * frames on the stack before we go looking for them. This allows the
      * below walk to only check each explicit frame rather than needing to
      * check any calls that were inlined.
      */
     if (fun->isInterpreted()) {
         JSFunction::getOrCreateScript(cx, fun)->uninlineable = true;
         MarkTypeObjectFlags(cx, fun, OBJECT_FLAG_UNINLINEABLE);
@@ -1509,39 +1516,18 @@ js_CloneFunctionObject(JSContext *cx, Ha
             return NULL;
 
         /*
          * Across compartments we have to clone the script for interpreted
          * functions. Cross-compartment cloning only happens via JSAPI
          * (JS_CloneFunctionObject) which dynamically ensures that 'script' has
          * no enclosing lexical scope (only the global scope).
          */
-        if (clone->isInterpreted()) {
-            RootedScript script(cx, clone->nonLazyScript());
-            JS_ASSERT(script->compartment() == fun->compartment());
-            JS_ASSERT_IF(script->compartment() != cx->compartment,
-                         !script->enclosingStaticScope());
-
-            RootedObject scope(cx, script->enclosingStaticScope());
-
-            clone->mutableScript().init(NULL);
-
-            RootedScript cscript(cx, CloneScript(cx, scope, clone, script));
-            if (!cscript)
-                return NULL;
-
-            clone->setScript(cscript);
-            cscript->setFunction(clone);
-
-            GlobalObject *global = script->compileAndGo ? &script->global() : NULL;
-
-            script = clone->nonLazyScript();
-            CallNewScriptHook(cx, script, clone);
-            Debugger::onNewScript(cx, script, global);
-        }
+        if (clone->isInterpreted() && !CloneFunctionScript(cx, fun, clone))
+            return NULL;
     }
     return clone;
 }
 
 JSFunction *
 js_DefineFunction(JSContext *cx, HandleObject obj, HandleId id, Native native,
                   unsigned nargs, unsigned flags, AllocKind kind)
 {
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -39,16 +39,23 @@ class JSFunction : public JSObject
         SELF_HOSTED      = 0x0100,  /* function is self-hosted builtin and must not be
                                        decompilable nor constructible. */
         SELF_HOSTED_CTOR = 0x0200,  /* function is self-hosted builtin constructor and
                                        must be constructible but not decompilable. */
         HAS_REST         = 0x0400,  /* function has a rest (...) parameter */
         HAS_DEFAULTS     = 0x0800,  /* function has at least one default parameter */
         INTERPRETED_LAZY = 0x1000,  /* function is interpreted but doesn't have a script yet */
 
+        /*
+         * Function is cloned anew at each callsite. This is temporarily
+         * needed for ParallelArray selfhosted code until type information can
+         * be made context sensitive. See discussion in bug 826148.
+         */
+        CALLSITE_CLONE   = 0x2000,
+
         /* Derived Flags values for convenience: */
         NATIVE_FUN = 0,
         INTERPRETED_LAMBDA = INTERPRETED | LAMBDA
     };
 
     static void staticAsserts() {
         JS_STATIC_ASSERT(INTERPRETED == JS_FUNCTION_INTERPRETED_BIT);
         MOZ_STATIC_ASSERT(sizeof(JSFunction) == sizeof(js::shadow::Function),
@@ -95,16 +102,21 @@ class JSFunction : public JSObject
     bool isExprClosure()            const { return flags & EXPR_CLOSURE; }
     bool hasGuessedAtom()           const { return flags & HAS_GUESSED_ATOM; }
     bool isLambda()                 const { return flags & LAMBDA; }
     bool isSelfHostedBuiltin()      const { return flags & SELF_HOSTED; }
     bool isSelfHostedConstructor()  const { return flags & SELF_HOSTED_CTOR; }
     bool hasRest()                  const { return flags & HAS_REST; }
     bool hasDefaults()              const { return flags & HAS_DEFAULTS; }
 
+    /* Original functions that should be cloned are not extended. */
+    bool isCloneAtCallsite()        const { return (flags & CALLSITE_CLONE) && !isExtended(); }
+    /* Cloned functions keep a backlink to the original in extended slot 0. */
+    bool isCallsiteClone()          const { return (flags & CALLSITE_CLONE) && isExtended(); }
+
     /* Compound attributes: */
     bool isBuiltin() const {
         return isNative() || isSelfHostedBuiltin();
     }
     bool isInterpretedConstructor() const {
         return isInterpreted() && !isFunctionPrototype() &&
                (!isSelfHostedBuiltin() || isSelfHostedConstructor());
     }
@@ -136,16 +148,20 @@ class JSFunction : public JSObject
         flags |= SELF_HOSTED;
     }
 
     void setIsSelfHostedConstructor() {
         JS_ASSERT(!isSelfHostedConstructor());
         flags |= SELF_HOSTED_CTOR;
     }
 
+    void setIsCloneAtCallsite() {
+        flags |= CALLSITE_CLONE;
+    }
+
     void setIsFunctionPrototype() {
         JS_ASSERT(!isFunctionPrototype());
         flags |= IS_FUN_PROTO;
     }
 
     void setIsHeavyweight() {
         flags |= HEAVYWEIGHT;
     }
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -2354,18 +2354,35 @@ BEGIN_CASE(JSOP_FUNCALL)
     if (regs.fp()->hasPushedSPSFrame())
         cx->runtime->spsProfiler.updatePC(script, regs.pc);
     JS_ASSERT(regs.stackDepth() >= 2 + GET_ARGC(regs.pc));
     CallArgs args = CallArgsFromSp(GET_ARGC(regs.pc), regs.sp);
 
     bool construct = (*regs.pc == JSOP_NEW);
 
     RootedFunction &fun = rootFunction0;
+    bool isFunction = IsFunctionObject(args.calleev(), fun.address());
+
+    /*
+     * Some builtins are marked as clone-at-callsite to increase precision of
+     * TI and JITs.
+     */
+    if (isFunction) {
+        if (fun->isInterpretedLazy() && !JSFunction::getOrCreateScript(cx, fun))
+            goto error;
+        if (cx->typeInferenceEnabled() && fun->isCloneAtCallsite()) {
+            fun = CloneFunctionAtCallsite(cx, fun, script, regs.pc);
+            if (!fun)
+                goto error;
+            args.setCallee(ObjectValue(*fun));
+        }
+    }
+
     /* Don't bother trying to fast-path calls to scripted non-constructors. */
-    if (!IsFunctionObject(args.calleev(), fun.address()) || !fun->isInterpretedConstructor()) {
+    if (!isFunction || !fun->isInterpretedConstructor()) {
         if (construct) {
             if (!InvokeConstructorKernel(cx, args))
                 goto error;
         } else {
             if (!InvokeKernel(cx, args))
                 goto error;
         }
         Value *newsp = args.spAfterCall();
@@ -2375,19 +2392,17 @@ BEGIN_CASE(JSOP_FUNCALL)
         DO_NEXT_OP(len);
     }
 
     if (!TypeMonitorCall(cx, args, construct))
         goto error;
 
     InitialFrameFlags initial = construct ? INITIAL_CONSTRUCT : INITIAL_NONE;
     bool newType = cx->typeInferenceEnabled() && UseNewType(cx, script, regs.pc);
-    RootedScript funScript(cx, JSFunction::getOrCreateScript(cx, fun));
-    if (!funScript)
-        goto error;
+    RootedScript funScript(cx, fun->nonLazyScript());
     if (!cx->stack.pushInlineFrame(cx, regs, args, *fun, funScript, initial))
         goto error;
 
     SET_SCRIPT(regs.fp()->script());
 #ifdef JS_METHODJIT
     script->resetLoopCount();
 #endif
 
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -2338,16 +2338,47 @@ js::CloneScript(JSContext *cx, HandleObj
             vector[i].init(regexps[i]);
     }
     if (ntrynotes != 0)
         dst->trynotes()->vector = Rebase<JSTryNote>(dst, src, src->trynotes()->vector);
 
     return dst;
 }
 
+bool
+js::CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction clone)
+{
+    JS_ASSERT(clone->isInterpreted());
+
+    RootedScript script(cx, clone->nonLazyScript());
+    JS_ASSERT(script);
+    JS_ASSERT(script->compartment() == original->compartment());
+    JS_ASSERT_IF(script->compartment() != cx->compartment,
+                 !script->enclosingStaticScope());
+
+    RootedObject scope(cx, script->enclosingStaticScope());
+
+    clone->mutableScript().init(NULL);
+
+    RawScript cscript = CloneScript(cx, scope, clone, script);
+    if (!cscript)
+        return false;
+
+    clone->setScript(cscript);
+    cscript->setFunction(clone);
+
+    GlobalObject *global = script->compileAndGo ? &script->global() : NULL;
+
+    script = clone->nonLazyScript();
+    CallNewScriptHook(cx, script, clone);
+    Debugger::onNewScript(cx, script, global);
+
+    return true;
+}
+
 DebugScript *
 JSScript::debugScript()
 {
     JS_ASSERT(hasDebugScript);
     DebugScriptMap *map = compartment()->debugScriptMap;
     JS_ASSERT(map);
     DebugScriptMap::Ptr p = map->lookup(this);
     JS_ASSERT(p);
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -1295,16 +1295,19 @@ enum LineOption {
 };
 
 inline void
 CurrentScriptFileLineOrigin(JSContext *cx, unsigned *linenop, LineOption = NOT_CALLED_FROM_JSOP_EVAL);
 
 extern UnrootedScript
 CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, HandleScript script);
 
+bool
+CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction clone);
+
 /*
  * NB: after a successful XDR_DECODE, XDRScript callers must do any required
  * subsequent set-up of owning function or script object and then call
  * js_CallNewScriptHook.
  */
 template<XDRMode mode>
 bool
 XDRScript(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript enclosingScript,