Call instances uniquely shaped at birth due to lack of runtime-shared empty scope (569391, r=jorendorff).
authorBrendan Eich <brendan@mozilla.org>
Tue, 01 Jun 2010 15:01:11 -0700
changeset 43221 6b5e3d5422d9499e6af500d4118d5e2b542b6968
parent 43220 cc6d850f4028b9a03b5ff6183ffbc1c6d8d924ae
child 43222 66cee22c27063b833215cafdf3e936dac325249b
push idunknown
push userunknown
push dateunknown
reviewersjorendorff
bugs569391
milestone1.9.3a5pre
Call instances uniquely shaped at birth due to lack of runtime-shared empty scope (569391, r=jorendorff).
js/src/jscntxt.h
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsobj.cpp
js/src/jsobjinlines.h
js/src/jsscope.cpp
js/src/jsscope.h
js/src/jsscopeinlines.h
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1431,16 +1431,17 @@ struct JSRuntime {
      */
     volatile uint32     shapeGen;
 
     /* Literal table maintained by jsatom.c functions. */
     JSAtomState         atomState;
 
     JSEmptyScope          *emptyArgumentsScope;
     JSEmptyScope          *emptyBlockScope;
+    JSEmptyScope          *emptyCallScope;
 
     /*
      * Various metering fields are defined at the end of JSRuntime. In this
      * way there is no need to recompile all the code that refers to other
      * fields of JSRuntime after enabling the corresponding metering macro.
      */
 #ifdef JS_DUMP_ENUM_CACHE_STATS
     int32               nativeEnumProbes;
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -179,18 +179,17 @@ NewArguments(JSContext *cx, JSObject *pa
     if (!argsobj)
         return NULL;
 
     /* Init immediately to avoid GC seeing a half-init'ed object. */
     argsobj->init(&js_ArgumentsClass, proto, parent, JSVAL_NULL);
     argsobj->setArgsCallee(OBJECT_TO_JSVAL(callee));
     argsobj->setArgsLength(argc);
 
-    argsobj->map = cx->runtime->emptyArgumentsScope;
-    cx->runtime->emptyArgumentsScope->hold();
+    argsobj->map = cx->runtime->emptyArgumentsScope->hold();
 
     /* This must come after argsobj->map has been set. */
     if (!js_EnsureReservedSlots(cx, argsobj, argc))
         return NULL;
     return argsobj;
 }
 
 static void
@@ -765,21 +764,28 @@ static JSBool
 CalleeGetter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
 {
     return CheckForEscapingClosure(cx, obj, vp);
 }
 
 static JSObject *
 NewCallObject(JSContext *cx, JSFunction *fun, JSObject *scopeChain)
 {
-    JSObject *callobj = NewObjectWithGivenProto(cx, &js_CallClass, NULL, scopeChain);
-    if (!callobj ||
-        !js_EnsureReservedSlots(cx, callobj, fun->countArgsAndVars())) {
+    JSObject *callobj = js_NewGCObject(cx);
+    if (!callobj)
         return NULL;
-    }
+
+    /* Init immediately to avoid GC seeing a half-init'ed object. */
+    callobj->init(&js_CallClass, NULL, scopeChain, JSVAL_NULL);
+
+    callobj->map = cx->runtime->emptyCallScope->hold();
+
+    /* This must come after callobj->map has been set. */
+    if (!js_EnsureReservedSlots(cx, callobj, fun->countArgsAndVars()))
+        return NULL;
     return callobj;
 }
 
 JSObject *
 js_GetCallObject(JSContext *cx, JSStackFrame *fp)
 {
     JSObject *callobj;
 
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -168,16 +168,19 @@ struct JSFunction : public JSObject
     bool isHeavyweight()    const { return JSFUN_HEAVYWEIGHT_TEST(flags); }
     unsigned minArgs()      const { return FUN_MINARGS(this); }
 
     uintN countVars() const {
         JS_ASSERT(FUN_INTERPRETED(this));
         return u.i.nvars;
     }
 
+    /* uint16 representation bounds number of call object dynamic slots. */
+    enum { MAX_ARGS_AND_VARS = 2 * UINT16_MAX };
+
     uintN countArgsAndVars() const {
         JS_ASSERT(FUN_INTERPRETED(this));
         return nargs + u.i.nvars;
     }
 
     uintN countLocalNames() const {
         JS_ASSERT(FUN_INTERPRETED(this));
         return countArgsAndVars() + u.i.nupvars;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3064,18 +3064,17 @@ js_CloneBlockObject(JSContext *cx, JSObj
         return NULL;
 
     /* The caller sets parent on its own. */
     jsval priv = (jsval)js_FloatingFrameIfGenerator(cx, fp);
     clone->init(&js_BlockClass, proto, NULL, priv);
     clone->fslots[JSSLOT_BLOCK_DEPTH] = proto->fslots[JSSLOT_BLOCK_DEPTH];
 
     JS_ASSERT(cx->runtime->emptyBlockScope->freeslot == JSSLOT_BLOCK_DEPTH + 1);
-    clone->map = cx->runtime->emptyBlockScope;
-    cx->runtime->emptyBlockScope->hold();
+    clone->map = cx->runtime->emptyBlockScope->hold();
     JS_ASSERT(OBJ_IS_CLONED_BLOCK(clone));
     return clone;
 }
 
 JS_REQUIRES_STACK JSBool
 js_PutBlockObject(JSContext *cx, JSBool normalUnwind)
 {
     /* Blocks have one fixed slot available for the first local.*/
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -464,18 +464,17 @@ JSObject::setQNameLocalName(jsval name)
 inline void
 JSObject::initSharingEmptyScope(JSClass *clasp, JSObject *proto, JSObject *parent,
                                 jsval privateSlotValue)
 {
     init(clasp, proto, parent, privateSlotValue);
 
     JSEmptyScope *emptyScope = proto->scope()->emptyScope;
     JS_ASSERT(emptyScope->clasp == clasp);
-    emptyScope->hold();
-    map = emptyScope;
+    map = emptyScope->hold();
 }
 
 inline void
 JSObject::freeSlotsArray(JSContext *cx)
 {
     JS_ASSERT(hasSlotsArray());
     JS_ASSERT(size_t(dslots[-1]) > JS_INITIAL_NSLOTS);
     cx->free(dslots - 1);
--- a/js/src/jsscope.cpp
+++ b/js/src/jsscope.cpp
@@ -307,39 +307,61 @@ JSScope::initRuntimeState(JSContext *cx)
      * bypass resolution of scope properties for length and element indices on
      * arguments objects. This helps ensure that any arguments object needing
      * its own mutable scope (with unique shape) is a rare event.
      */
     rt->emptyArgumentsScope->freeslot = JS_INITIAL_NSLOTS + JS_ARGS_LENGTH_MAX;
 
     rt->emptyBlockScope = cx->create<JSEmptyScope>(cx, &js_ObjectOps, &js_BlockClass);
     if (!rt->emptyBlockScope) {
-        rt->emptyArgumentsScope->drop(cx);
-        rt->emptyArgumentsScope = NULL;
+        JSScope::finishRuntimeState(cx);
         return false;
     }
     JS_ASSERT(rt->emptyBlockScope->shape == JSScope::EMPTY_BLOCK_SHAPE);
     JS_ASSERT(rt->emptyBlockScope->nrefs == 2);
     rt->emptyBlockScope->nrefs = 1;
+
+    rt->emptyCallScope = cx->create<JSEmptyScope>(cx, &js_ObjectOps, &js_CallClass);
+    if (!rt->emptyCallScope) {
+        JSScope::finishRuntimeState(cx);
+        return false;
+    }
+    JS_ASSERT(rt->emptyCallScope->shape == JSScope::EMPTY_CALL_SHAPE);
+    JS_ASSERT(rt->emptyCallScope->nrefs == 2);
+    rt->emptyCallScope->nrefs = 1;
+
+    /*
+     * Initialize the shared scope for all empty Call objects so gets for args
+     * and vars do not force the creation of a mutable scope for the particular
+     * call object being accessed.
+     *
+     * See comment above for rt->emptyArgumentsScope->freeslot initialization.
+     */
+    rt->emptyCallScope->freeslot = JS_INITIAL_NSLOTS + JSFunction::MAX_ARGS_AND_VARS;
+
     return true;
 }
 
 /* static */
 void
 JSScope::finishRuntimeState(JSContext *cx)
 {
     JSRuntime *rt = cx->runtime;
     if (rt->emptyArgumentsScope) {
         rt->emptyArgumentsScope->drop(cx);
         rt->emptyArgumentsScope = NULL;
     }
     if (rt->emptyBlockScope) {
         rt->emptyBlockScope->drop(cx);
         rt->emptyBlockScope = NULL;
     }
+    if (rt->emptyCallScope) {
+        rt->emptyCallScope->drop(cx);
+        rt->emptyCallScope = NULL;
+    }
 }
 
 JS_STATIC_ASSERT(sizeof(JSHashNumber) == 4);
 JS_STATIC_ASSERT(sizeof(jsid) == JS_BYTES_PER_WORD);
 
 #if JS_BYTES_PER_WORD == 4
 # define HASH_ID(id) ((JSHashNumber)(id))
 #elif JS_BYTES_PER_WORD == 8
--- a/js/src/jsscope.h
+++ b/js/src/jsscope.h
@@ -499,31 +499,33 @@ struct JSScope : public JSObjectMap
     bool isSharedEmpty() const  { return !object; }
 
     static bool initRuntimeState(JSContext *cx);
     static void finishRuntimeState(JSContext *cx);
 
     enum {
         EMPTY_ARGUMENTS_SHAPE = 1,
         EMPTY_BLOCK_SHAPE     = 2,
-        LAST_RESERVED_SHAPE   = 2
+        EMPTY_CALL_SHAPE      = 3,
+        LAST_RESERVED_SHAPE   = 3
     };
 };
 
 struct JSEmptyScope : public JSScope
 {
     JSClass * const clasp;
     jsrefcount      nrefs;              /* count of all referencing objects */
 
     JSEmptyScope(JSContext *cx, const JSObjectOps *ops, JSClass *clasp);
 
-    void hold() {
+    JSEmptyScope *hold() {
         /* The method is only called for already held objects. */
         JS_ASSERT(nrefs >= 1);
         JS_ATOMIC_INCREMENT(&nrefs);
+        return this;
     }
 
     void drop(JSContext *cx) {
         JS_ASSERT(nrefs >= 1);
         JS_ATOMIC_DECREMENT(&nrefs);
         if (nrefs == 0)
             destroy(cx);
     }
--- a/js/src/jsscopeinlines.h
+++ b/js/src/jsscopeinlines.h
@@ -56,18 +56,17 @@ JSScope::createEmptyScope(JSContext *cx,
     return emptyScope;
 }
 
 inline JSEmptyScope *
 JSScope::getEmptyScope(JSContext *cx, JSClass *clasp)
 {
     if (emptyScope) {
         JS_ASSERT(clasp == emptyScope->clasp);
-        emptyScope->hold();
-        return emptyScope;
+        return emptyScope->hold();
     }
     return createEmptyScope(cx, clasp);
 }
 
 inline bool
 JSScope::ensureEmptyScope(JSContext *cx, JSClass *clasp)
 {
     if (emptyScope) {