Bug 739512: Patch 3: shrink the representation of optional arrays in JSScript. r=luke.
authorNicholas Nethercote <nnethercote@mozilla.com>
Wed, 11 Apr 2012 17:19:00 -0700
changeset 93331 25d54e0cdf317284b4339f62f800965381477273
parent 93330 632af18d86bfe582b1fa59cb8eadf54816433cf5
child 93332 a12879dc977621a89a5f273efdec22efe0f2aafc
push id22628
push usereakhgari@mozilla.com
push dateMon, 07 May 2012 23:03:04 +0000
treeherdermozilla-central@333626f688a4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs739512
milestone15.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 739512: Patch 3: shrink the representation of optional arrays in JSScript. r=luke.
js/src/jsscript.cpp
js/src/jsscript.h
js/src/vm/ScopeObject.cpp
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1014,29 +1014,30 @@ js::FreeScriptFilenames(JSRuntime *rt)
     table.clear();
 }
 
 /*
  * JSScript::data has a complex, manually-controlled, memory layout.
  *
  * First are some optional array headers.  They are optional because they
  * often aren't needed, i.e. the corresponding arrays often have zero elements.
- * Each header has an offset in JSScript that indicates its location within
- * |data|; that offset is INVALID_OFFSET if the array header is not present.
- * Each header also has an accessor function in JSScript.
+ * Each header has a bit in JSScript::hasArrayBits that indicates if it's
+ * present within |data|;  from this the offset of each present array header
+ * can be computed.  Each header has an accessor function in JSScript that
+ * encapsulates this offset computation.
  *
- * Array type       Array elements  Offset            Accessor
- * ----------       --------------  ------            --------
- * ConstArray       Consts          constsOffset      consts()
- * ObjectArray      Objects         objectsOffset     objects()
- * ObjectArray      Regexps         regexpsOffset     regexps()
- * TryNoteArray     Try notes       tryNotesOffset    trynotes()
- * GlobalSlotArray  Globals         globalsOffset     globals()
- * ClosedSlotArray  ClosedArgs      closedArgsOffset  closedArgs()
- * ClosedSlotArray  ClosedVars      closedVarsOffset  closedVars()
+ * Array type       Array elements  Accessor
+ * ----------       --------------  --------
+ * ConstArray       Consts          consts()
+ * ObjectArray      Objects         objects()
+ * ObjectArray      Regexps         regexps()
+ * TryNoteArray     Try notes       trynotes()
+ * GlobalSlotArray  Globals         globals()
+ * ClosedSlotArray  ClosedArgs      closedArgs()
+ * ClosedSlotArray  ClosedVars      closedVars()
  *
  * Then are the elements of several arrays.  
  * - Most of these arrays have headers listed above (if present).  For each of
  *   these, the array pointer and the array length is stored in the header.  
  * - The remaining arrays have pointers and lengths that are stored directly in
  *   JSScript.  This is because, unlike the others, they are nearly always
  *   non-zero length and so the optional-header space optimization isn't
  *   worthwhile.
@@ -1094,31 +1095,16 @@ JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTR
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapPtrObject, HeapPtrObject));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapPtrObject, JSTryNote));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(JSTryNote, GlobalSlotArray::Entry));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(GlobalSlotArray::Entry, uint32_t));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(uint32_t, uint32_t));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(uint32_t, jsbytecode));
 JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(jsbytecode, jssrcnote));
 
-/*
- * Check that uint8_t offsets is enough to reach any optional array allocated
- * within |data|. For that we check that the maximum possible offset for the
- * closedVars array -- the last optional array -- still fits in 1 byte and does
- * not coincide with INVALID_OFFSET.
- */
-JS_STATIC_ASSERT(sizeof(ConstArray) +
-                 sizeof(ObjectArray) +
-                 sizeof(ObjectArray) +
-                 sizeof(TryNoteArray) +
-                 sizeof(GlobalSlotArray) +
-                 sizeof(ClosedSlotArray)
-                 < JSScript::INVALID_OFFSET);
-JS_STATIC_ASSERT(JSScript::INVALID_OFFSET <= 255);
-
 static inline size_t
 ScriptDataSize(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t natoms,
                uint32_t nobjects, uint32_t nregexps, uint32_t ntrynotes, uint32_t nconsts,
                uint32_t nglobals, uint16_t nClosedArgs, uint16_t nClosedVars)
 {
     size_t size = 0;
 
     if (nconsts != 0)
@@ -1175,57 +1161,42 @@ JSScript::NewScript(JSContext *cx, uint3
     PodZero(script);
     script->data  = data;
     script->length = length;
     script->version = version;
     new (&script->bindings) Bindings(cx);
 
     uint8_t *cursor = data;
     if (nconsts != 0) {
-        script->constsOffset = uint8_t(cursor - data);
+        script->setHasArray(CONSTS);
         cursor += sizeof(ConstArray);
-    } else {
-        script->constsOffset = JSScript::INVALID_OFFSET;
     }
     if (nobjects != 0) {
-        script->objectsOffset = uint8_t(cursor - data);
+        script->setHasArray(OBJECTS);
         cursor += sizeof(ObjectArray);
-    } else {
-        script->objectsOffset = JSScript::INVALID_OFFSET;
     }
     if (nregexps != 0) {
-        script->regexpsOffset = uint8_t(cursor - data);
+        script->setHasArray(REGEXPS);
         cursor += sizeof(ObjectArray);
-    } else {
-        script->regexpsOffset = JSScript::INVALID_OFFSET;
     }
     if (ntrynotes != 0) {
-        script->trynotesOffset = uint8_t(cursor - data);
+        script->setHasArray(TRYNOTES);
         cursor += sizeof(TryNoteArray);
-    } else {
-        script->trynotesOffset = JSScript::INVALID_OFFSET;
     }
     if (nglobals != 0) {
-        script->globalsOffset = uint8_t(cursor - data);
+        script->setHasArray(GLOBALS);
         cursor += sizeof(GlobalSlotArray);
-    } else {
-        script->globalsOffset = JSScript::INVALID_OFFSET;
     }
     if (nClosedArgs != 0) {
-        script->closedArgsOffset = uint8_t(cursor - data);
+        script->setHasArray(CLOSED_ARGS);
         cursor += sizeof(ClosedSlotArray);
-    } else {
-        script->closedArgsOffset = JSScript::INVALID_OFFSET;
     }
-    JS_ASSERT(cursor - data < 0xFF);
     if (nClosedVars != 0) {
-        script->closedVarsOffset = uint8_t(cursor - data);
+        script->setHasArray(CLOSED_VARS);
         cursor += sizeof(ClosedSlotArray);
-    } else {
-        script->closedVarsOffset = JSScript::INVALID_OFFSET;
     }
 
     if (nconsts != 0) {
         JS_ASSERT(reinterpret_cast<uintptr_t>(cursor) % sizeof(jsval) == 0);
         script->consts()->length = nconsts;
         script->consts()->vector = (HeapValue *)cursor;
         cursor += nconsts * sizeof(script->consts()->vector[0]);
     }
@@ -1760,23 +1731,23 @@ Rebase(JSScript *dst, JSScript *src, T *
     return reinterpret_cast<T *>(dst->data + off);
 }
 
 JSScript *
 js::CloneScript(JSContext *cx, JSScript *src)
 {
     /* NB: Keep this in sync with XDRScript. */
 
-    uint32_t nconsts = JSScript::isValidOffset(src->constsOffset) ? src->consts()->length : 0;
-    uint32_t nobjects = JSScript::isValidOffset(src->objectsOffset) ? src->objects()->length : 0;
-    uint32_t nregexps = JSScript::isValidOffset(src->regexpsOffset) ? src->regexps()->length : 0;
-    uint32_t ntrynotes = JSScript::isValidOffset(src->trynotesOffset) ? src->trynotes()->length : 0;
+    uint32_t nconsts   = src->hasConsts()   ? src->consts()->length   : 0;
+    uint32_t nobjects  = src->hasObjects()  ? src->objects()->length  : 0;
+    uint32_t nregexps  = src->hasRegexps()  ? src->regexps()->length  : 0;
+    uint32_t ntrynotes = src->hasTrynotes() ? src->trynotes()->length : 0;
     uint32_t nClosedArgs = src->numClosedArgs();
     uint32_t nClosedVars = src->numClosedVars();
-    JS_ASSERT(!JSScript::isValidOffset(src->globalsOffset));
+    JS_ASSERT(!src->hasGlobals());
     uint32_t nglobals = 0;
 
     /* Script data */
 
     size_t size = ScriptDataSize(cx, src->length, src->numNotes(), src->natoms,
                                  nobjects, nregexps, ntrynotes, nconsts, nglobals,
                                  nClosedArgs, nClosedVars);
 
@@ -1878,23 +1849,17 @@ js::CloneScript(JSContext *cx, JSScript 
     dst->nTypeSets = src->nTypeSets;
     dst->nslots = src->nslots;
     dst->staticLevel = src->staticLevel;
     if (src->argumentsHasLocalBinding()) {
         dst->setArgumentsHasLocalBinding(src->argumentsLocalSlot());
         if (src->analyzedArgsUsage())
             dst->setNeedsArgsObj(src->needsArgsObj());
     }
-    dst->constsOffset = src->constsOffset;
-    dst->objectsOffset = src->objectsOffset;
-    dst->regexpsOffset = src->regexpsOffset;
-    dst->trynotesOffset = src->trynotesOffset;
-    dst->globalsOffset = src->globalsOffset;
-    dst->closedArgsOffset = src->closedArgsOffset;
-    dst->closedVarsOffset = src->closedVarsOffset;
+    dst->cloneHasArray(src);
     dst->noScriptRval = src->noScriptRval;
     dst->savedCallerFun = src->savedCallerFun;
     dst->strictModeCode = src->strictModeCode;
     dst->compileAndGo = src->compileAndGo;
     dst->bindingsAccessedDynamically = src->bindingsAccessedDynamically;
     dst->hasSingletons = src->hasSingletons;
     dst->isGenerator = src->isGenerator;
 
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -483,21 +483,16 @@ struct JSScript : public js::gc::Cell
 #ifdef DEBUG
     // Unique identifier within the compartment for this script, used for
     // printing analysis information.
     uint32_t        id_;
   private:
     uint32_t        idpad;
 #endif
 
-#if JS_BITS_PER_WORD == 32
-  private:
-    uint32_t        pad32;
-#endif
-
     // 16-bit fields.
 
   private:
     uint16_t        version;    /* JS version under which script was compiled */
 
   public:
     uint16_t        nfixed;     /* number of slots besides stack operands in
                                    slot array */
@@ -508,29 +503,35 @@ struct JSScript : public js::gc::Cell
     uint16_t        nslots;     /* vars plus maximum stack depth */
     uint16_t        staticLevel;/* static level for display maintenance */
 
   private:
     uint16_t        argsSlot_;  /* slot holding 'arguments' (if argumentsHasLocalBindings) */
 
     // 8-bit fields.
 
+  private:
+    // The bits in this field indicate the presence/non-presence of several
+    // optional arrays in |data|.  See the comments above NewScript() for
+    // details.
+    uint8_t         hasArrayBits;
+
+    // The kinds of the optional arrays.
   public:
-    // Offsets to various array structures from the end of this script, or
-    // JSScript::INVALID_OFFSET if the array has length 0.
-    uint8_t         constsOffset;   /* offset to the array of constants */
-    uint8_t         objectsOffset;  /* offset to the array of nested function,
-                                       block, scope, xml and one-time regexps
-                                       objects */
-    uint8_t         regexpsOffset;  /* offset to the array of to-be-cloned
-                                       regexps  */
-    uint8_t         trynotesOffset; /* offset to the array of try notes */
-    uint8_t         globalsOffset;  /* offset to the array of global slots */
-    uint8_t         closedArgsOffset; /* offset to the array of closed args */
-    uint8_t         closedVarsOffset; /* offset to the array of closed vars */
+    enum ArrayKind {
+        CONSTS,
+        OBJECTS,
+        REGEXPS,
+        TRYNOTES,
+        GLOBALS,
+        CLOSED_ARGS,
+        CLOSED_VARS,
+        LIMIT
+    };
+    JS_STATIC_ASSERT(sizeof(hasArrayBits) * 8 >= LIMIT);
 
     // 1-bit fields.
 
   public:
     bool            noScriptRval:1; /* no need for result value of last
                                        expression statement */
     bool            savedCallerFun:1; /* can call getCallerFunction() */
     bool            strictModeCode:1; /* code is in strict mode */
@@ -738,60 +739,71 @@ struct JSScript : public js::gc::Cell
     size_t computedSizeOfData();
     size_t sizeOfData(JSMallocSizeOfFun mallocSizeOf);
 
     uint32_t numNotes();  /* Number of srcnote slots in the srcnotes section */
 
     /* Script notes are allocated right after the code. */
     jssrcnote *notes() { return (jssrcnote *)(code + length); }
 
-    static const uint8_t INVALID_OFFSET = 0xFF;
-    static bool isValidOffset(uint8_t offset) { return offset != INVALID_OFFSET; }
+    bool hasArray(ArrayKind kind)           { return (hasArrayBits & (1 << kind)); }
+    void setHasArray(ArrayKind kind)        { hasArrayBits |= (1 << kind); }
+    void cloneHasArray(JSScript *script)    { hasArrayBits = script->hasArrayBits; }
 
-    bool hasConsts()        { return isValidOffset(constsOffset);     }
-    bool hasObjects()       { return isValidOffset(objectsOffset);    }
-    bool hasRegexps()       { return isValidOffset(regexpsOffset);    }
-    bool hasTrynotes()      { return isValidOffset(trynotesOffset);   }
-    bool hasGlobals()       { return isValidOffset(globalsOffset);    }
-    bool hasClosedArgs()    { return isValidOffset(closedArgsOffset); }
-    bool hasClosedVars()    { return isValidOffset(closedVarsOffset); }
+    bool hasConsts()        { return hasArray(CONSTS);      }
+    bool hasObjects()       { return hasArray(OBJECTS);     }
+    bool hasRegexps()       { return hasArray(REGEXPS);     }
+    bool hasTrynotes()      { return hasArray(TRYNOTES);    }
+    bool hasGlobals()       { return hasArray(GLOBALS);     }
+    bool hasClosedArgs()    { return hasArray(CLOSED_ARGS); }
+    bool hasClosedVars()    { return hasArray(CLOSED_VARS); }
+
+    #define OFF(fooOff, hasFoo, t)   (fooOff() + (hasFoo() ? sizeof(t) : 0))
+
+    size_t constsOffset()     { return 0; }
+    size_t objectsOffset()    { return OFF(constsOffset,     hasConsts,     js::ConstArray);      }
+    size_t regexpsOffset()    { return OFF(objectsOffset,    hasObjects,    js::ObjectArray);     }
+    size_t trynotesOffset()   { return OFF(regexpsOffset,    hasRegexps,    js::ObjectArray);     }
+    size_t globalsOffset()    { return OFF(trynotesOffset,   hasTrynotes,   js::TryNoteArray);    }
+    size_t closedArgsOffset() { return OFF(globalsOffset,    hasGlobals,    js::GlobalSlotArray); }
+    size_t closedVarsOffset() { return OFF(closedArgsOffset, hasClosedArgs, js::ClosedSlotArray); }
 
     js::ConstArray *consts() {
         JS_ASSERT(hasConsts());
-        return reinterpret_cast<js::ConstArray *>(data + constsOffset);
+        return reinterpret_cast<js::ConstArray *>(data + constsOffset());
     }
 
     js::ObjectArray *objects() {
         JS_ASSERT(hasObjects());
-        return reinterpret_cast<js::ObjectArray *>(data + objectsOffset);
+        return reinterpret_cast<js::ObjectArray *>(data + objectsOffset());
     }
 
     js::ObjectArray *regexps() {
         JS_ASSERT(hasRegexps());
-        return reinterpret_cast<js::ObjectArray *>(data + regexpsOffset);
+        return reinterpret_cast<js::ObjectArray *>(data + regexpsOffset());
     }
 
     js::TryNoteArray *trynotes() {
         JS_ASSERT(hasTrynotes());
-        return reinterpret_cast<js::TryNoteArray *>(data + trynotesOffset);
+        return reinterpret_cast<js::TryNoteArray *>(data + trynotesOffset());
     }
 
     js::GlobalSlotArray *globals() {
         JS_ASSERT(hasGlobals());
-        return reinterpret_cast<js::GlobalSlotArray *>(data + globalsOffset);
+        return reinterpret_cast<js::GlobalSlotArray *>(data + globalsOffset());
     }
 
     js::ClosedSlotArray *closedArgs() {
         JS_ASSERT(hasClosedArgs());
-        return reinterpret_cast<js::ClosedSlotArray *>(data + closedArgsOffset);
+        return reinterpret_cast<js::ClosedSlotArray *>(data + closedArgsOffset());
     }
 
     js::ClosedSlotArray *closedVars() {
         JS_ASSERT(hasClosedVars());
-        return reinterpret_cast<js::ClosedSlotArray *>(data + closedVarsOffset);
+        return reinterpret_cast<js::ClosedSlotArray *>(data + closedVarsOffset());
     }
 
     uint32_t numClosedArgs() {
         return hasClosedArgs() ? closedArgs()->length : 0;
     }
 
     uint32_t numClosedVars() {
         return hasClosedVars() ? closedVars()->length : 0;
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -880,17 +880,17 @@ Class js::BlockClass = {
 /*
  * If there's a parent id, then get the parent out of our script's object
  * array. We know that we clone block objects in outer-to-inner order, which
  * means that getting the parent now will work.
  */
 static uint32_t
 FindObjectIndex(JSScript *script, StaticBlockObject *maybeBlock)
 {
-    if (!maybeBlock || !JSScript::isValidOffset(script->objectsOffset))
+    if (!maybeBlock || !script->hasObjects())
         return NO_PARENT_INDEX;
 
     ObjectArray *objects = script->objects();
     HeapPtrObject *vector = objects->vector;
     unsigned length = objects->length;
     for (unsigned i = 0; i < length; ++i) {
         if (vector[i] == maybeBlock)
             return i;