Make empty shape initialization lazy (bug 639254, r=billm).
authorAndreas Gal <gal@mozilla.com>
Mon, 07 Mar 2011 18:42:04 -0800
changeset 64242 10fb605a55afa80876925bd9a9088b7089f6abdb
parent 64241 34c628db78cb3acff8a18fc3f09ab32f06e481a8
child 64243 441bc12e94e24d8d882cc32b9bd2479f83224179
push idunknown
push userunknown
push dateunknown
reviewersbillm
bugs639254
milestone2.0b13pre
Make empty shape initialization lazy (bug 639254, r=billm).
js/src/jsapi.cpp
js/src/jscompartment.cpp
js/src/jsemit.h
js/src/jsfun.cpp
js/src/jsiter.cpp
js/src/jsobj.cpp
js/src/jsparse.cpp
js/src/jsparse.h
js/src/jspropertytree.cpp
js/src/jspropertytree.h
js/src/jsproxy.cpp
js/src/jsscope.cpp
js/src/jsscope.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsscriptinlines.h
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4811,20 +4811,27 @@ CompileUCFunctionForPrincipalsCommon(JSC
         }
     }
 
     fun = js_NewFunction(cx, NULL, NULL, 0, JSFUN_INTERPRETED, obj, funAtom);
     if (!fun)
         goto out2;
 
     {
+        EmptyShape *emptyCallShape = EmptyShape::getEmptyCallShape(cx);
+        if (!emptyCallShape) {
+            fun = NULL;
+            goto out2;
+        }
+        AutoShapeRooter shapeRoot(cx, emptyCallShape);
+
         AutoObjectRooter tvr(cx, FUN_OBJECT(fun));
         MUST_FLOW_THROUGH("out");
 
-        Bindings bindings(cx);
+        Bindings bindings(cx, emptyCallShape);
         AutoBindingsRooter root(cx, bindings);
         for (i = 0; i < nargs; i++) {
             argAtom = js_Atomize(cx, argnames[i], strlen(argnames[i]), 0);
             if (!argAtom) {
                 fun = NULL;
                 goto out2;
             }
 
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -67,16 +67,22 @@ JSCompartment::JSCompartment(JSRuntime *
     gcTriggerBytes(0),
     gcLastBytes(0),
     data(NULL),
     active(false),
 #ifdef JS_METHODJIT
     jaegerCompartment(NULL),
 #endif
     propertyTree(thisForCtor()),
+    emptyArgumentsShape(NULL),
+    emptyBlockShape(NULL),
+    emptyCallShape(NULL),
+    emptyDeclEnvShape(NULL),
+    emptyEnumeratorShape(NULL),
+    emptyWithShape(NULL),
     debugMode(rt->debugMode),
 #if ENABLE_YARR_JIT
     regExpAllocator(NULL),
 #endif
     mathCache(NULL),
     marked(false)
 {
     JS_INIT_CLIST(&scripts);
@@ -86,17 +92,16 @@ JSCompartment::JSCompartment(JSRuntime *
     PodZero(&traceMonitor);
 #endif
 
     PodArrayZero(scriptsToGC);
 }
 
 JSCompartment::~JSCompartment()
 {
-    Shape::finishEmptyShapes(this);
     propertyTree.finish();
 
 #if ENABLE_YARR_JIT
     js_delete(regExpAllocator);
 #endif
 
 #if defined JS_TRACER
     FinishJIT(&traceMonitor);
@@ -133,19 +138,16 @@ JSCompartment::init()
 
 #ifdef DEBUG
     if (rt->meterEmptyShapes()) {
         if (!emptyShapes.init())
             return false;
     }
 #endif
 
-    if (!Shape::initEmptyShapes(this))
-        return false;
-
 #ifdef JS_TRACER
     if (!InitJIT(&traceMonitor))
         return false;
 #endif
 
 #if ENABLE_YARR_JIT
     regExpAllocator = JSC::ExecutableAllocator::create();
     if (!regExpAllocator)
@@ -463,29 +465,16 @@ JSCompartment::mark(JSTracer *trc)
 
         if (rt->gcCurrentCompartment && rt->gcCurrentCompartment != this)
             return;
 
         if (marked)
             return;
         marked = true;
     }
-
-    if (emptyArgumentsShape)
-        emptyArgumentsShape->trace(trc);
-    if (emptyBlockShape)
-        emptyBlockShape->trace(trc);
-    if (emptyCallShape)
-        emptyCallShape->trace(trc);
-    if (emptyDeclEnvShape)
-        emptyDeclEnvShape->trace(trc);
-    if (emptyEnumeratorShape)
-        emptyEnumeratorShape->trace(trc);
-    if (emptyWithShape)
-        emptyWithShape->trace(trc);
 }
 
 void
 JSCompartment::sweep(JSContext *cx, uint32 releaseInterval)
 {
     chunk = NULL;
     /* Remove dead wrappers from the table. */
     for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
@@ -493,16 +482,30 @@ JSCompartment::sweep(JSContext *cx, uint
                      !IsAboutToBeFinalized(cx, e.front().value.toGCThing()),
                      e.front().key.isString());
         if (IsAboutToBeFinalized(cx, e.front().key.toGCThing()) ||
             IsAboutToBeFinalized(cx, e.front().value.toGCThing())) {
             e.removeFront();
         }
     }
 
+    /* Remove dead empty shapes. */
+    if (emptyArgumentsShape && !emptyArgumentsShape->marked())
+        emptyArgumentsShape = NULL;
+    if (emptyBlockShape && !emptyBlockShape->marked())
+        emptyBlockShape = NULL;
+    if (emptyCallShape && !emptyCallShape->marked())
+        emptyCallShape = NULL;
+    if (emptyDeclEnvShape && !emptyDeclEnvShape->marked())
+        emptyDeclEnvShape = NULL;
+    if (emptyEnumeratorShape && !emptyEnumeratorShape->marked())
+        emptyEnumeratorShape = NULL;
+    if (emptyWithShape && !emptyWithShape->marked())
+        emptyWithShape = NULL;
+
 #ifdef JS_TRACER
     traceMonitor.sweep(cx);
 #endif
 
 #if defined JS_METHODJIT && defined JS_MONOIC
 
     /*
      * The release interval is the frequency with which we should try to destroy
--- a/js/src/jsemit.h
+++ b/js/src/jsemit.h
@@ -343,18 +343,19 @@ struct JSTreeContext {              /* t
     uint16          scopeDepth;     /* current lexical scope chain depth */
     uint16          maxScopeDepth;  /* maximum lexical scope chain depth */
 #endif
 
     void trace(JSTracer *trc);
 
     JSTreeContext(js::Parser *prs)
       : flags(0), bodyid(0), blockidGen(0), topStmt(NULL), topScopeStmt(NULL),
-        blockChainBox(NULL), blockNode(NULL), parser(prs), scopeChain_(NULL), parent(prs->tc),
-        staticLevel(0), funbox(NULL), functionList(NULL), innermostWith(NULL), bindings(prs->context),
+        blockChainBox(NULL), blockNode(NULL), parser(prs), scopeChain_(NULL),
+        parent(prs->tc), staticLevel(0), funbox(NULL), functionList(NULL),
+        innermostWith(NULL), bindings(prs->context, prs->emptyCallShape),
         sharpSlotBase(-1)
     {
         prs->tc = this;
         JS_SCOPE_DEPTH_METERING(scopeDepth = maxScopeDepth = 0);
     }
 
     /*
      * For functions the tree context is constructed and destructed a second
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -183,29 +183,34 @@ NewArguments(JSContext *cx, JSObject *pa
     if (!js_GetClassPrototype(cx, parent, JSProto_Object, &proto))
         return NULL;
 
     JS_STATIC_ASSERT(JSObject::ARGS_CLASS_RESERVED_SLOTS == 2);
     JSObject *argsobj = js_NewGCObject(cx, FINALIZE_OBJECT2);
     if (!argsobj)
         return NULL;
 
+    EmptyShape *emptyArgumentsShape = EmptyShape::getEmptyArgumentsShape(cx);
+    if (!emptyArgumentsShape)
+        return NULL;
+    AutoShapeRooter shapeRoot(cx, emptyArgumentsShape);
+
     ArgumentsData *data = (ArgumentsData *)
         cx->malloc(offsetof(ArgumentsData, slots) + argc * sizeof(Value));
     if (!data)
         return NULL;
     SetValueRangeToUndefined(data->slots, argc);
 
     /* Can't fail from here on, so initialize everything in argsobj. */
     argsobj->init(cx, callee.getFunctionPrivate()->inStrictMode()
                   ? &StrictArgumentsClass
                   : &js_ArgumentsClass,
                   proto, parent, NULL, false);
 
-    argsobj->setMap(cx->compartment->emptyArgumentsShape);
+    argsobj->setMap(emptyArgumentsShape);
 
     argsobj->setArgsLength(argc);
     argsobj->setArgsData(data);
     data->callee.setObject(callee);
 
     return argsobj;
 }
 
@@ -984,18 +989,22 @@ NewCallObject(JSContext *cx, Bindings *b
 
 static inline JSObject *
 NewDeclEnvObject(JSContext *cx, JSStackFrame *fp)
 {
     JSObject *envobj = js_NewGCObject(cx, FINALIZE_OBJECT2);
     if (!envobj)
         return NULL;
 
+    EmptyShape *emptyDeclEnvShape = EmptyShape::getEmptyDeclEnvShape(cx);
+    if (!emptyDeclEnvShape)
+        return NULL;
+
     envobj->init(cx, &js_DeclEnvClass, NULL, &fp->scopeChain(), fp, false);
-    envobj->setMap(cx->compartment->emptyDeclEnvShape);
+    envobj->setMap(emptyDeclEnvShape);
     return envobj;
 }
 
 JSObject *
 js_GetCallObject(JSContext *cx, JSStackFrame *fp)
 {
     /* Create a call object for fp only if it lacks one. */
     JS_ASSERT(fp->isFunctionFrame());
@@ -2466,17 +2475,22 @@ Function(JSContext *cx, uintN argc, Valu
      * Report errors via CSP is done in the script security manager.
      * js_CheckContentSecurityPolicy is defined in jsobj.cpp
      */
     if (!js_CheckContentSecurityPolicy(cx, parent)) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CSP_BLOCKED_FUNCTION);
         return JS_FALSE;
     }
 
-    Bindings bindings(cx);
+    EmptyShape *emptyCallShape = EmptyShape::getEmptyCallShape(cx);
+    if (!emptyCallShape)
+        return JS_FALSE;
+    AutoShapeRooter shapeRoot(cx, emptyCallShape);
+
+    Bindings bindings(cx, emptyCallShape);
     AutoBindingsRooter root(cx, bindings);
 
     Value *argv = vp + 2;
     uintN n = argc ? argc - 1 : 0;
     if (n > 0) {
         enum { OK, BAD, BAD_FORMAL } state;
 
         /*
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -419,18 +419,23 @@ NewIteratorObject(JSContext *cx, uintN f
          * native enumerator object via the stack and (as for all objects that
          * are not stillborn, with the exception of "NoSuchMethod" internal
          * helper objects) expect it to have a non-null map pointer, so we
          * share an empty Enumerator scope in the runtime.
          */
         JSObject *obj = js_NewGCObject(cx, FINALIZE_OBJECT0);
         if (!obj)
             return false;
+
+        EmptyShape *emptyEnumeratorShape = EmptyShape::getEmptyEnumeratorShape(cx);
+        if (!emptyEnumeratorShape)
+            return NULL;
+
         obj->init(cx, &js_IteratorClass, NULL, NULL, NULL, false);
-        obj->setMap(cx->compartment->emptyEnumeratorShape);
+        obj->setMap(emptyEnumeratorShape);
         return obj;
     }
 
     return NewBuiltinClassInstance(cx, &js_IteratorClass);
 }
 
 NativeIterator *
 NativeIterator::allocateIterator(JSContext *cx, uint32 slength, const AutoIdVector &props)
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3253,18 +3253,22 @@ js_NewWithObject(JSContext *cx, JSObject
     JSObject *obj;
 
     obj = js_NewGCObject(cx, FINALIZE_OBJECT2);
     if (!obj)
         return NULL;
 
     JSStackFrame *priv = js_FloatingFrameIfGenerator(cx, cx->fp());
 
+    EmptyShape *emptyWithShape = EmptyShape::getEmptyWithShape(cx);
+    if (!emptyWithShape)
+        return NULL;
+
     obj->init(cx, &js_WithClass, proto, parent, priv, false);
-    obj->setMap(cx->compartment->emptyWithShape);
+    obj->setMap(emptyWithShape);
     OBJ_SET_BLOCK_DEPTH(cx, obj, depth);
 
     AutoObjectRooter tvr(cx, obj);
     JSObject *thisp = proto->thisObject(cx);
     if (!thisp)
         return NULL;
 
     assertSameCompartment(cx, obj, thisp);
@@ -3279,18 +3283,22 @@ js_NewBlockObject(JSContext *cx)
     /*
      * Null obj's proto slot so that Object.prototype.* does not pollute block
      * scopes and to give the block object its own scope.
      */
     JSObject *blockObj = js_NewGCObject(cx, FINALIZE_OBJECT2);
     if (!blockObj)
         return NULL;
 
+    EmptyShape *emptyBlockShape = EmptyShape::getEmptyBlockShape(cx);
+    if (!emptyBlockShape)
+        return NULL;
+
     blockObj->init(cx, &js_BlockClass, NULL, NULL, NULL, false);
-    blockObj->setMap(cx->compartment->emptyBlockShape);
+    blockObj->setMap(emptyBlockShape);
     return blockObj;
 }
 
 JSObject *
 js_CloneBlockObject(JSContext *cx, JSObject *proto, JSStackFrame *fp)
 {
     JS_ASSERT(proto->isStaticBlock());
 
--- a/js/src/jsparse.cpp
+++ b/js/src/jsparse.cpp
@@ -190,28 +190,32 @@ Parser::Parser(JSContext *cx, JSPrincipa
     tokenStream(cx),
     principals(NULL),
     callerFrame(cfp),
     callerVarObj(cfp ? &cfp->varobj(cx->containingSegment(cfp)) : NULL),
     nodeList(NULL),
     functionCount(0),
     traceListHead(NULL),
     tc(NULL),
+    emptyCallShape(NULL),
     keepAtoms(cx->runtime)
 {
     js::PodArrayZero(tempFreeList);
     setPrincipals(prin);
     JS_ASSERT_IF(cfp, cfp->isScriptFrame());
 }
 
 bool
 Parser::init(const jschar *base, size_t length, const char *filename, uintN lineno,
              JSVersion version)
 {
     JSContext *cx = context;
+    emptyCallShape = EmptyShape::getEmptyCallShape(cx);
+    if (!emptyCallShape)
+        return false;
     tempPoolMark = JS_ARENA_MARK(&cx->tempPool);
     if (!tokenStream.init(base, length, filename, lineno, version)) {
         JS_ARENA_RELEASE(&cx->tempPool, tempPoolMark);
         return false;
     }
     return true;
 }
 
@@ -284,17 +288,17 @@ Parser::newFunctionBox(JSObject *obj, JS
     funbox->isFunctionBox = true;
     funbox->node = fn;
     funbox->siblings = tc->functionList;
     tc->functionList = funbox;
     ++tc->parser->functionCount;
     funbox->kids = NULL;
     funbox->parent = tc->funbox;
     funbox->methods = NULL;
-    new (&funbox->bindings) Bindings(context);
+    new (&funbox->bindings) Bindings(context, emptyCallShape);
     funbox->queued = false;
     funbox->inLoop = false;
     for (JSStmtInfo *stmt = tc->topStmt; stmt; stmt = stmt->down) {
         if (STMT_IS_LOOP(stmt)) {
             funbox->inLoop = true;
             break;
         }
     }
@@ -1816,24 +1820,24 @@ Compiler::compileFunctionBody(JSContext 
     JS_InitArenaPool(&notePool, "note", 1024, sizeof(jssrcnote),
                      &cx->scriptStackQuota);
 
     Parser &parser = compiler.parser;
     TokenStream &tokenStream = parser.tokenStream;
 
     JSCodeGenerator funcg(&parser, &codePool, &notePool, tokenStream.getLineno());
     if (!funcg.init())
-        return NULL;
+        return false;
 
     funcg.flags |= TCF_IN_FUNCTION;
     funcg.setFunction(fun);
     funcg.bindings.transfer(cx, bindings);
     fun->setArgCount(funcg.bindings.countArgs());
     if (!GenerateBlockId(&funcg, funcg.bodyid))
-        return NULL;
+        return false;
 
     /* FIXME: make Function format the source for a function definition. */
     tokenStream.mungeCurrentToken(TOK_NAME);
     JSParseNode *fn = FunctionNode::create(&funcg);
     if (fn) {
         fn->pn_body = NULL;
         fn->pn_cookie.makeFree();
 
--- a/js/src/jsparse.h
+++ b/js/src/jsparse.h
@@ -1047,16 +1047,17 @@ struct Parser : private js::AutoGCRooter
     void                *tempPoolMark;  /* initial JSContext.tempPool mark */
     JSPrincipals        *principals;    /* principals associated with source */
     JSStackFrame *const callerFrame;    /* scripted caller frame for eval and dbgapi */
     JSObject     *const callerVarObj;   /* callerFrame's varObj */
     JSParseNode         *nodeList;      /* list of recyclable parse-node structs */
     uint32              functionCount;  /* number of functions in current unit */
     JSObjectBox         *traceListHead; /* list of parsed object for GC tracing */
     JSTreeContext       *tc;            /* innermost tree context (stack-allocated) */
+    js::EmptyShape      *emptyCallShape;/* empty shape for Call objects */
 
     /* Root atoms and objects allocated for the parsed tree. */
     js::AutoKeepAtoms   keepAtoms;
 
     Parser(JSContext *cx, JSPrincipals *prin = NULL, JSStackFrame *cfp = NULL);
     ~Parser();
 
     friend void js::AutoGCRooter::trace(JSTracer *trc);
--- a/js/src/jspropertytree.cpp
+++ b/js/src/jspropertytree.cpp
@@ -74,49 +74,41 @@ PropertyTree::init()
 }
 
 void
 PropertyTree::finish()
 {
     JS_FinishArenaPool(&arenaPool);
 }
 
-/* On failure, returns NULL. Does not report out of memory. */
 Shape *
-PropertyTree::newShapeUnchecked()
+PropertyTree::newShape(JSContext *cx)
 {
     Shape *shape;
 
     shape = freeList;
     if (shape) {
         shape->removeFree();
     } else {
         JS_ARENA_ALLOCATE_CAST(shape, Shape *, &arenaPool, sizeof(Shape));
-        if (!shape)
+        if (!shape) {
+            JS_ReportOutOfMemory(cx);
             return NULL;
+        }
     }
 
 #ifdef DEBUG
     shape->compartment = compartment;
 #endif
 
     JS_COMPARTMENT_METER(compartment->livePropTreeNodes++);
     JS_COMPARTMENT_METER(compartment->totalPropTreeNodes++);
     return shape;
 }
 
-Shape *
-PropertyTree::newShape(JSContext *cx)
-{
-    Shape *shape = newShapeUnchecked();
-    if (!shape)
-        JS_ReportOutOfMemory(cx);
-    return shape;
-}
-
 static KidsHash *
 HashChildren(Shape *kid1, Shape *kid2)
 {
     void *mem = js_malloc(sizeof(KidsHash));
     if (!mem)
         return NULL;
 
     KidsHash *hash = new (mem) KidsHash();
--- a/js/src/jspropertytree.h
+++ b/js/src/jspropertytree.h
@@ -117,17 +117,16 @@ class PropertyTree
         : compartment(comp), freeList(NULL)
     {
         PodZero(&arenaPool);
     }
     
     bool init();
     void finish();
 
-    js::Shape *newShapeUnchecked();
     js::Shape *newShape(JSContext *cx);
     js::Shape *getChild(JSContext *cx, js::Shape *parent, const js::Shape &child);
 
     void orphanChildren(js::Shape *shape);
     void sweepShapes(JSContext *cx);
     void unmarkShapes(JSContext *cx);
 
     static void dumpShapes(JSContext *cx);
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -953,18 +953,16 @@ proxy_DeleteProperty(JSContext *cx, JSOb
         return false;
     rval->setBoolean(deleted);
     return true;
 }
 
 static void
 proxy_TraceObject(JSTracer *trc, JSObject *obj)
 {
-    JSContext *cx = trc->context;
-
     obj->getProxyHandler()->trace(trc, obj);
     MarkValue(trc, obj->getProxyPrivate(), "private");
     MarkValue(trc, obj->getProxyExtra(), "extra");
     if (obj->isFunctionProxy()) {
         MarkValue(trc, GetCall(obj), "call");
         MarkValue(trc, GetConstruct(obj), "construct");
     }
 }
--- a/js/src/jsscope.cpp
+++ b/js/src/jsscope.cpp
@@ -196,81 +196,16 @@ Shape::hashify(JSRuntime *rt)
 
 #ifdef DEBUG
 # include "jsprf.h"
 # define LIVE_SCOPE_METER(cx,expr) JS_LOCK_RUNTIME_VOID(cx->runtime,expr)
 #else
 # define LIVE_SCOPE_METER(cx,expr) /* nothing */
 #endif
 
-static inline bool
-InitField(JSCompartment *comp, EmptyShape *JSCompartment:: *field, Class *clasp)
-{
-    if (EmptyShape *emptyShape = EmptyShape::create(comp, clasp)) {
-        comp->*field = emptyShape;
-        return true;
-    }
-    return false;
-}
-
-/* static */
-bool
-Shape::initEmptyShapes(JSCompartment *comp)
-{
-    /*
-     * NewArguments allocates dslots to have enough room for the argc of the
-     * particular arguments object being created.
-     * never mutated, it's safe to pretend to have all the slots possible.
-     *
-     * Note how the fast paths in jsinterp.cpp for JSOP_LENGTH and JSOP_GETELEM
-     * 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.
-     */
-    if (!InitField(comp, &JSCompartment::emptyArgumentsShape, &js_ArgumentsClass))
-        return false;
-
-    if (!InitField(comp, &JSCompartment::emptyBlockShape, &js_BlockClass))
-        return false;
-
-    /*
-     * 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.
-     */
-    if (!InitField(comp, &JSCompartment::emptyCallShape, &js_CallClass))
-        return false;
-
-    /* A DeclEnv object holds the name binding for a named function expression. */
-    if (!InitField(comp, &JSCompartment::emptyDeclEnvShape, &js_DeclEnvClass))
-        return false;
-
-    /* Non-escaping native enumerator objects share this empty scope. */
-    if (!InitField(comp, &JSCompartment::emptyEnumeratorShape, &js_IteratorClass))
-        return false;
-
-    /* Same drill for With objects. */
-    if (!InitField(comp, &JSCompartment::emptyWithShape, &js_WithClass))
-        return false;
-
-    return true;
-}
-
-/* static */
-void
-Shape::finishEmptyShapes(JSCompartment *comp)
-{
-    comp->emptyArgumentsShape = NULL;
-    comp->emptyBlockShape = NULL;
-    comp->emptyCallShape = NULL;
-    comp->emptyDeclEnvShape = NULL;
-    comp->emptyEnumeratorShape = NULL;
-    comp->emptyWithShape = 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)(JSID_BITS(id)))
 #elif JS_BYTES_PER_WORD == 8
 # define HASH_ID(id) ((JSHashNumber)(JSID_BITS(id)) ^ (JSHashNumber)((JSID_BITS(id)) >> 32))
 #else
--- a/js/src/jsscope.h
+++ b/js/src/jsscope.h
@@ -47,16 +47,17 @@
 #ifdef DEBUG
 #include <stdio.h>
 #endif
 
 #include "jstypes.h"
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jshashtable.h"
+#include "jsiter.h"
 #include "jsobj.h"
 #include "jsprvtd.h"
 #include "jspubtd.h"
 #include "jspropertytree.h"
 #include "jsstrinlines.h"
 
 #ifdef _MSC_VER
 #pragma warning(push)
@@ -309,19 +310,16 @@ struct Shape : public JSObjectMap
     union {
         mutable size_t numLinearSearches;
         mutable js::PropertyTable *table;
     };
 
   public:
     inline void freeTable(JSContext *cx);
 
-    static bool initEmptyShapes(JSCompartment *comp);
-    static void finishEmptyShapes(JSCompartment *comp);
-
     jsid                id;
 
 #ifdef DEBUG
     JSCompartment       *compartment;
 #endif
 
   protected:
     union {
@@ -648,29 +646,56 @@ struct Shape : public JSObjectMap
 };
 
 struct EmptyShape : public js::Shape
 {
     EmptyShape(JSCompartment *comp, js::Class *aclasp);
 
     js::Class *getClass() const { return clasp; };
 
-    static EmptyShape *create(JSCompartment *comp, js::Class *clasp) {
-        js::Shape *eprop = comp->propertyTree.newShapeUnchecked();
-        if (!eprop)
-            return NULL;
-        return new (eprop) EmptyShape(comp, clasp);
-    }
-
     static EmptyShape *create(JSContext *cx, js::Class *clasp) {
         js::Shape *eprop = JS_PROPERTY_TREE(cx).newShape(cx);
         if (!eprop)
             return NULL;
         return new (eprop) EmptyShape(cx->compartment, clasp);
     }
+
+    static EmptyShape *ensure(JSContext *cx, js::Class *clasp, EmptyShape **shapep) {
+        EmptyShape *shape = *shapep;
+        if (!shape) {
+            if (!(shape = create(cx, clasp)))
+                return NULL;
+            return *shapep = shape;
+        }
+        return shape;
+    }
+
+    static EmptyShape *getEmptyArgumentsShape(JSContext *cx) {
+        return ensure(cx, &js_ArgumentsClass, &cx->compartment->emptyArgumentsShape);
+    }
+
+    static EmptyShape *getEmptyBlockShape(JSContext *cx) {
+        return ensure(cx, &js_BlockClass, &cx->compartment->emptyBlockShape);
+    }
+
+    static EmptyShape *getEmptyCallShape(JSContext *cx) {
+        return ensure(cx, &js_CallClass, &cx->compartment->emptyCallShape);
+    }
+
+    static EmptyShape *getEmptyDeclEnvShape(JSContext *cx) {
+        return ensure(cx, &js_DeclEnvClass, &cx->compartment->emptyDeclEnvShape);
+    }
+
+    static EmptyShape *getEmptyEnumeratorShape(JSContext *cx) {
+        return ensure(cx, &js_IteratorClass, &cx->compartment->emptyEnumeratorShape);
+    }
+
+    static EmptyShape *getEmptyWithShape(JSContext *cx) {
+        return ensure(cx, &js_WithClass, &cx->compartment->emptyWithShape);
+    }
 };
 
 } /* namespace js */
 
 /* js::Shape pointer tag bit indicating a collision. */
 #define SHAPE_COLLISION                 (jsuword(1))
 #define SHAPE_REMOVED                   ((js::Shape *) SHAPE_COLLISION)
 
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -371,17 +371,22 @@ js_XDRScript(JSXDRState *xdr, JSScript *
         nvars = argsVars & 0xFFFF;
         JS_ASSERT((paddingUpvars >> 16) == 0);
         nupvars = paddingUpvars & 0xFFFF;
     }
     JS_ASSERT(nargs != Bindings::BINDING_COUNT_LIMIT);
     JS_ASSERT(nvars != Bindings::BINDING_COUNT_LIMIT);
     JS_ASSERT(nupvars != Bindings::BINDING_COUNT_LIMIT);
 
-    Bindings bindings(cx);
+    EmptyShape *emptyCallShape = EmptyShape::getEmptyCallShape(cx);
+    if (!emptyCallShape)
+        return false;
+    AutoShapeRooter shapeRoot(cx, emptyCallShape);
+
+    Bindings bindings(cx, emptyCallShape);
     AutoBindingsRooter rooter(cx, bindings);
     uint32 nameCount = nargs + nvars + nupvars;
     if (nameCount > 0) {
         struct AutoMark {
           JSArenaPool * const pool;
           void * const mark;
           AutoMark(JSArenaPool *pool) : pool(pool), mark(JS_ARENA_MARK(pool)) { }
           ~AutoMark() {
@@ -1195,16 +1200,21 @@ JS_STATIC_ASSERT(sizeof(JSObjectArray) +
 JS_STATIC_ASSERT(JSScript::INVALID_OFFSET <= 255);
 
 JSScript *
 JSScript::NewScript(JSContext *cx, uint32 length, uint32 nsrcnotes, uint32 natoms,
                     uint32 nobjects, uint32 nupvars, uint32 nregexps,
                     uint32 ntrynotes, uint32 nconsts, uint32 nglobals,
                     uint16 nClosedArgs, uint16 nClosedVars, JSVersion version)
 {
+    EmptyShape *emptyCallShape = EmptyShape::getEmptyCallShape(cx);
+    if (!emptyCallShape)
+        return NULL;
+    AutoShapeRooter shapeRoot(cx, emptyCallShape);
+
     size_t size, vectorSize;
     JSScript *script;
     uint8 *cursor;
     unsigned constPadding = 0;
 
     uint32 totalClosed = nClosedArgs + nClosedVars;
 
     size = sizeof(JSScript) +
@@ -1238,17 +1248,17 @@ JSScript::NewScript(JSContext *cx, uint3
 
     script = (JSScript *) cx->malloc(size);
     if (!script)
         return NULL;
 
     PodZero(script);
     script->length = length;
     script->version = version;
-    new (&script->bindings) Bindings(cx);
+    new (&script->bindings) Bindings(cx, emptyCallShape);
 
     uint8 *scriptEnd = reinterpret_cast<uint8 *>(script + 1);
 
     cursor = scriptEnd;
     if (nobjects != 0) {
         script->objectsOffset = (uint8)(cursor - scriptEnd);
         cursor += sizeof(JSObjectArray);
     } else {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -170,17 +170,17 @@ enum BindingKind { NONE, ARGUMENT, VARIA
  */
 class Bindings {
     js::Shape *lastBinding;
     uint16 nargs;
     uint16 nvars;
     uint16 nupvars;
 
   public:
-    inline Bindings(JSContext *cx);
+    inline Bindings(JSContext *cx, EmptyShape *emptyCallShape);
 
     /*
      * Transfers ownership of bindings data from bindings into this fresh
      * Bindings instance. Once such a transfer occurs, the old bindings must
      * not be used again.
      */
     inline void transfer(JSContext *cx, Bindings *bindings);
 
--- a/js/src/jsscriptinlines.h
+++ b/js/src/jsscriptinlines.h
@@ -46,18 +46,18 @@
 #include "jsopcode.h"
 #include "jsregexp.h"
 #include "jsscript.h"
 #include "jsscope.h"
 
 namespace js {
 
 inline
-Bindings::Bindings(JSContext *cx)
-  : lastBinding(cx->compartment->emptyCallShape), nargs(0), nvars(0), nupvars(0)
+Bindings::Bindings(JSContext *cx, EmptyShape *emptyCallShape)
+  : lastBinding(emptyCallShape), nargs(0), nvars(0), nupvars(0)
 {
 }
 
 inline void
 Bindings::transfer(JSContext *cx, Bindings *bindings)
 {
     JS_ASSERT(lastBinding == cx->compartment->emptyCallShape);