Bug 676732 - Measure and/or avoid slop in important JS memory reporters. r=dmandelin.
authorNicholas Nethercote <nnethercote@mozilla.com>
Sun, 04 Sep 2011 18:32:50 -0700
changeset 77853 7fb15a645955ee2b808e639f2245b344833be6e1
parent 77852 6f99ff6c4c8655362961d4115428e5e581702bdc
child 77854 6d0812c1f610f972a724c9e1fc23bb600a596dd2
push id78
push userclegnitto@mozilla.com
push dateFri, 16 Dec 2011 17:32:24 +0000
treeherdermozilla-release@79d24e644fdd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdmandelin
bugs676732
milestone9.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 676732 - Measure and/or avoid slop in important JS memory reporters. r=dmandelin.
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsscope.h
js/src/jsscript.h
js/src/jstracer.cpp
js/src/methodjit/Compiler.cpp
js/src/methodjit/MethodJIT.cpp
js/src/methodjit/MethodJIT.h
js/src/shell/js.cpp
js/src/xpconnect/src/xpcjsruntime.cpp
js/src/xpconnect/src/xpcpublic.h
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2712,16 +2712,25 @@ obj_preventExtensions(JSContext *cx, uin
     vp->setObject(*obj);
     if (!obj->isExtensible())
         return true;
 
     AutoIdVector props(cx);
     return obj->preventExtensions(cx, &props);
 }
 
+size_t
+JSObject::sizeOfSlotsArray(size_t(*mus)(void *))
+{
+    if (!hasSlotsArray())
+        return 0;
+    size_t usable = mus((void *)slots);
+    return usable ? usable : numSlots() * sizeof(js::Value);
+}
+
 bool
 JSObject::sealOrFreeze(JSContext *cx, ImmutabilityType it)
 {
     assertSameCompartment(cx, this);
     JS_ASSERT(it == SEAL || it == FREEZE);
 
     AutoIdVector props(cx);
     if (isExtensible()) {
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -473,16 +473,18 @@ struct JSObject : js::gc::Cell {
     union {
         /* If prototype, type of values using this as their prototype. */
         js::types::TypeObject *newType;
 
         /* If dense array, the initialized length (see jsarray.cpp). */
         jsuword initializedLength;
     };
 
+    size_t sizeOfSlotsArray(size_t(*mus)(void *));
+
     JSObject    *parent;                    /* object's parent */
     void        *privateData;               /* private data */
     jsuword     capacity;                   /* total number of available slots */
 
   private:
     js::Value   *slots;                     /* dynamically allocated slots,
                                                or pointer to fixedSlots() for
                                                dense arrays. */
--- a/js/src/jsscope.h
+++ b/js/src/jsscope.h
@@ -246,17 +246,22 @@ struct PropertyTable {
     }
 
     /* By definition, hashShift = JS_DHASH_BITS - log2(capacity). */
     uint32 capacity() const { return JS_BIT(JS_DHASH_BITS - hashShift); }
 
     /* Computes the size of the entries array for a given capacity. */
     static size_t sizeOfEntries(size_t cap) { return cap * sizeof(Shape *); }
 
-    size_t sizeOf() const {
+    size_t sizeOf(size_t(*mus)(void *)) const {
+        if (mus) {
+            size_t usable = mus((void*)this) + mus(entries);
+            if (usable)
+                return usable;
+        }
         return sizeOfEntries(capacity()) + sizeof(PropertyTable);
     }
 
     /* Whether we need to grow.  We want to do this if the load factor is >= 0.75 */
     bool needsToGrow() const {
         uint32 size = capacity();
         return entryCount + removedCount >= size - (size >> 2);
     }
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -669,18 +669,19 @@ struct JSScript : public js::gc::Cell {
         void *addr = constructing ? jitArityCheckCtor : jitArityCheckNormal;
         if (addr == NULL)
             return JITScript_None;
         if (addr == JS_UNJITTABLE_SCRIPT)
             return JITScript_Invalid;
         return JITScript_Valid;
     }
 
-    // This method is implemented in MethodJIT.h.
-    JS_FRIEND_API(size_t) jitDataSize();/* Size of the JITScript and all sections */
+    /* Size of the JITScript and all sections.  (This method is implemented in MethodJIT.h.) */
+    JS_FRIEND_API(size_t) jitDataSize(size_t(*mus)(void *));
+    
 #endif
 
     jsbytecode *main() {
         return code + mainOffset;
     }
 
     JS_FRIEND_API(size_t) dataSize();   /* Size of all data sections */
     uint32 numNotes();                  /* Number of srcnote slots in the srcnotes section */
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -178,19 +178,19 @@ using namespace js::tjit;
  *   sundry small allocations:  18,048 bytes.
  *
  * The reserve sizes are chosen by exceeding this by a reasonable amount.
  * Reserves for 32-bits are slightly more than half, because most of the
  * allocated space is used to hold pointers.
  *
  * FIXME: Bug 624590 is open to get rid of all this.
  */
-static const size_t DataReserveSize  = 12500 * sizeof(uintptr_t);
-static const size_t TraceReserveSize =  5000 * sizeof(uintptr_t);
-static const size_t TempReserveSize  =  1000 * sizeof(uintptr_t);
+static const size_t DataReserveSize  =  8192 * sizeof(uintptr_t);
+static const size_t TraceReserveSize =   512 * sizeof(uintptr_t);
+static const size_t TempReserveSize  =  4096 * sizeof(uintptr_t);
 
 void*
 nanojit::Allocator::allocChunk(size_t nbytes, bool fallible)
 {
     VMAllocator *vma = (VMAllocator*)this;
     /*
      * Nb: it's conceivable that request 1 might fail (in which case
      * mOutOfMemory will be set) and then request 2 succeeds.  The subsequent
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -1323,17 +1323,18 @@ mjit::Compiler::finishThisUp(JITScript *
                 jitPics[i].u.get.typeCheckOffset = distance;
             }
         }
         stubCode.patch(pics[i].paramAddr, &jitPics[i]);
     }
 #endif
 
     JS_ASSERT(size_t(cursor - (uint8*)jit) == dataSize);
-    JS_ASSERT(jit->scriptDataSize() == dataSize);
+    /* Pass in NULL here -- we don't want slop bytes to be counted. */
+    JS_ASSERT(jit->scriptDataSize(NULL) == dataSize);
 
     /* Link fast and slow paths together. */
     stubcc.fixCrossJumps(result, masm.size(), masm.size() + stubcc.size());
 
     size_t doubleOffset = masm.size() + stubcc.size();
     double *inlineDoubles = (double *) (result + doubleOffset);
     double *oolDoubles = (double*) (result + doubleOffset +
                                     masm.numDoubles() * sizeof(double));
--- a/js/src/methodjit/MethodJIT.cpp
+++ b/js/src/methodjit/MethodJIT.cpp
@@ -1132,31 +1132,33 @@ mjit::JITScript::~JITScript()
         repatch.repatch(ic->funGuard, NULL);
         repatch.relink(ic->funJump, ic->slowPathStart);
         ic->purgeGuardedObject();
     }
 #endif
 }
 
 size_t
-JSScript::jitDataSize()
+JSScript::jitDataSize(size_t(*mus)(void *))
 {
     size_t n = 0;
     if (jitNormal)
-        n += jitNormal->scriptDataSize(); 
+        n += jitNormal->scriptDataSize(mus); 
     if (jitCtor)
-        n += jitCtor->scriptDataSize(); 
+        n += jitCtor->scriptDataSize(mus); 
     return n;
 }
 
 /* Please keep in sync with Compiler::finishThisUp! */
 size_t
-mjit::JITScript::scriptDataSize()
+mjit::JITScript::scriptDataSize(size_t(*mus)(void *))
 {
-    return sizeof(JITScript) +
+    size_t usable = mus ? mus(this) : 0;
+    return usable ? usable :
+        sizeof(JITScript) +
         sizeof(NativeMapEntry) * nNmapPairs +
         sizeof(InlineFrame) * nInlineFrames +
         sizeof(CallSite) * nCallSites +
         sizeof(JSObject *) * nRootedObjects +
 #if defined JS_MONOIC
         sizeof(ic::GetGlobalNameIC) * nGetGlobalNames +
         sizeof(ic::SetGlobalNameIC) * nSetGlobalNames +
         sizeof(ic::CallICInfo) * nCallICs +
--- a/js/src/methodjit/MethodJIT.h
+++ b/js/src/methodjit/MethodJIT.h
@@ -635,17 +635,17 @@ struct JITScript {
 
     void nukeScriptDependentICs();
     void sweepCallICs(JSContext *cx, bool purgeAll);
     void purgeMICs();
     void purgePICs();
 
     void trace(JSTracer *trc);
 
-    size_t scriptDataSize();
+    size_t scriptDataSize(size_t(*mus)(void *));
 
     jsbytecode *nativeToPC(void *returnAddress, CallSite **pinline) const;
 
   private:
     /* Helpers used to navigate the variable-length sections. */
     char *commonSectionLimit() const;
     char *monoICSectionsLimit() const;
     char *polyICSectionsLimit() const;
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4065,33 +4065,33 @@ MJitCodeStats(JSContext *cx, uintN argc,
     JS_SET_RVAL(cx, vp, JSVAL_VOID);
 #endif
     return true;
 }
 
 #ifdef JS_METHODJIT
 
 static void
-SumJitDataSizeCallabck(JSContext *cx, void *data, void *thing,
+SumJitDataSizeCallback(JSContext *cx, void *data, void *thing,
                        JSGCTraceKind traceKind, size_t thingSize)
 {
     size_t *sump = static_cast<size_t *>(data);
     JS_ASSERT(traceKind == JSTRACE_SCRIPT);
     JSScript *script = static_cast<JSScript *>(thing);
-    *sump += script->jitDataSize();
+    *sump += script->jitDataSize(NULL);
 }
 
 #endif
 
 JSBool
 MJitDataStats(JSContext *cx, uintN argc, jsval *vp)
 {
 #ifdef JS_METHODJIT
     size_t n = 0;
-    IterateCells(cx, NULL, gc::FINALIZE_TYPE_OBJECT, &n, SumJitDataSizeCallabck);
+    IterateCells(cx, NULL, gc::FINALIZE_TYPE_OBJECT, &n, SumJitDataSizeCallback);
     JS_SET_RVAL(cx, vp, INT_TO_JSVAL(n));
 #else
     JS_SET_RVAL(cx, vp, JSVAL_VOID);
 #endif
     return true;
 }
 
 JSBool
--- a/js/src/xpconnect/src/xpcjsruntime.cpp
+++ b/js/src/xpconnect/src/xpcjsruntime.cpp
@@ -1341,38 +1341,42 @@ CellCallback(JSContext *cx, void *vdata,
     IterateData *data = static_cast<IterateData *>(vdata);
     CompartmentStats *curr = data->currCompartmentStats;
     curr->gcHeapKinds[traceKind] += thingSize;
     switch (traceKind)
     {
         case JSTRACE_OBJECT:
         {
             JSObject *obj = static_cast<JSObject *>(thing);
-            curr->objectSlots += JS_ObjectCountDynamicSlots(obj) * sizeof(js::Value);
+            curr->objectSlots += obj->sizeOfSlotsArray(moz_malloc_usable_size);
             break;
         }
         case JSTRACE_STRING:
         {
             JSString *str = static_cast<JSString *>(thing);
             curr->stringChars += str->charsHeapSize();
             break;
         }
         case JSTRACE_SHAPE:
         {
             js::Shape *shape = static_cast<js::Shape *>(thing);
             if(shape->hasTable())
-                curr->propertyTables += shape->getTable()->sizeOf();
+                curr->propertyTables +=
+                    shape->getTable()->sizeOf(moz_malloc_usable_size);
             break;
         }
         case JSTRACE_SCRIPT:
         {
             JSScript *script = static_cast<JSScript *>(thing);
-            curr->scriptData += script->dataSize();
+            if (script->data != script->inlineData) {
+                size_t usable = moz_malloc_usable_size(script->data);
+                curr->scriptData += usable ? usable : script->dataSize();
+            }
 #ifdef JS_METHODJIT
-            curr->mjitData += script->jitDataSize();
+            curr->mjitData += script->jitDataSize(moz_malloc_usable_size);
 #endif
             break;
         }
         case JSTRACE_TYPE_OBJECT:
         {
             js::types::TypeObject *obj = static_cast<js::types::TypeObject *>(thing);
             JS_GetTypeInferenceObjectStats(obj, &curr->typeInferenceMemory);
             break;
@@ -1623,16 +1627,18 @@ CollectCompartmentStatsForRuntime(JSRunt
             js::GC_CHUNK_SIZE;
 
         js::IterateCompartmentsArenasCells(cx, data, CompartmentCallback,
                                            ArenaCallback, CellCallback);
 
         for(js::ThreadDataIter i(rt); !i.empty(); i.popFront())
             data->stackSize += i.threadData()->stackSpace.committedSize();
 
+        size_t usable = moz_malloc_usable_size(rt);
+        data->runtimeObjectSize += usable ? usable : sizeof(JSRuntime);
         data->atomsTableSize += rt->atomState.atoms.tableSize();
     }
 
     JS_DestroyContextNoGC(cx);
 
     // This is initialized to all bytes stored in used chunks, and then we
     // subtract used space from it each time around the loop.
     data->gcHeapChunkDirtyUnused = data->gcHeapChunkTotal -
@@ -1669,16 +1675,19 @@ CollectCompartmentStatsForRuntime(JSRunt
     data->gcHeapUnusedPercentage = (data->gcHeapChunkCleanUnused +
                                     data->gcHeapChunkDirtyUnused +
                                     data->gcHeapArenaUnused) * 10000 /
                                    data->gcHeapChunkTotal;
 
     return true;
 }
 
+#define SLOP_BYTES_STRING \
+    " The measurement includes slop bytes caused by the heap allocator rounding up request sizes."
+
 static void
 ReportCompartmentStats(const CompartmentStats &stats,
                        const nsACString &pathPrefix,
                        nsIMemoryMultiReporterCallback *callback,
                        nsISupports *closure)
 {
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
                                               "gc-heap/arena-headers"),
@@ -1731,17 +1740,17 @@ ReportCompartmentStats(const Compartment
                                               "gc-heap/shapes"),
                        JS_GC_HEAP_KIND, stats.gcHeapKinds[JSTRACE_SHAPE],
     "Memory on the compartment's garbage-collected JavaScript heap that holds "
     "shapes. A shape is an internal data structure that makes JavaScript "
     "property accesses fast.",
                        callback, closure);
 
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
-                                              "gc-heap/shapes"),
+                                              "gc-heap/type-objects"),
                        JS_GC_HEAP_KIND, stats.gcHeapKinds[JSTRACE_TYPE_OBJECT],
     "Memory on the compartment's garbage-collected JavaScript heap that holds "
     "type inference information.",
                        callback, closure);
 
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
                                               "gc-heap/xml"),
                        JS_GC_HEAP_KIND, stats.gcHeapKinds[JSTRACE_XML],
@@ -1751,17 +1760,17 @@ ReportCompartmentStats(const Compartment
 
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
                                               "object-slots"),
                        nsIMemoryReporter::KIND_HEAP, stats.objectSlots,
     "Memory allocated for the compartment's non-fixed object slot arrays, "
     "which are used to represent object properties.  Some objects also "
     "contain a fixed number of slots which are stored on the compartment's "
     "JavaScript heap; those slots are not counted here, but in "
-    "'gc-heap/objects' instead.",
+    "'gc-heap/objects' instead." SLOP_BYTES_STRING,
                        callback, closure);
 
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
                                               "string-chars"),
                        nsIMemoryReporter::KIND_HEAP, stats.stringChars,
     "Memory allocated to hold the compartment's string characters.  Sometimes "
     "more memory is allocated than necessary, to simplify string "
     "concatenation.  Each string also includes a header which is stored on the "
@@ -1769,40 +1778,31 @@ ReportCompartmentStats(const Compartment
     "'gc-heap/strings' instead.",
                        callback, closure);
 
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
                                               "property-tables"),
                        nsIMemoryReporter::KIND_HEAP, stats.propertyTables,
     "Memory allocated for the compartment's property tables.  A property "
     "table is an internal data structure that makes JavaScript property "
-    "accesses fast.",
+    "accesses fast." SLOP_BYTES_STRING,
                        callback, closure);
 
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
                                               "object-empty-shapes"),
                        nsIMemoryReporter::KIND_HEAP,
                        stats.typeInferenceMemory.emptyShapes,
     "Arrays attached to prototype JS objects managing shape information.",
                        callback, closure);
 
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
-                                              "scripts"),
-                       nsIMemoryReporter::KIND_HEAP,
-                       stats.gcHeapKinds[JSTRACE_SCRIPT],
-    "Memory allocated for the compartment's JSScripts.  A JSScript is created "
-    "for each user-defined function in a script.  One is also created for "
-    "the top-level code in a script.",
-                       callback, closure);
-
-    ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
                                               "script-data"),
                        nsIMemoryReporter::KIND_HEAP, stats.scriptData,
     "Memory allocated for JSScript bytecode and various variable-length "
-    "tables.",
+    "tables." SLOP_BYTES_STRING,
                        callback, closure);
 
 #ifdef JS_METHODJIT
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
                                               "mjit-code/method"),
                        nsIMemoryReporter::KIND_NONHEAP, stats.mjitCodeMethod,
     "Memory used by the method JIT to hold the compartment's generated code.",
                        callback, closure);
@@ -1819,17 +1819,17 @@ ReportCompartmentStats(const Compartment
     "Memory allocated by the method and/or regexp JIT to hold the "
     "compartment's code, but which is currently unused.",
                        callback, closure);
 
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
                                               "mjit-data"),
                        nsIMemoryReporter::KIND_HEAP, stats.mjitData,
     "Memory used by the method JIT for the compartment's compilation data: "
-    "JITScripts, native maps, and inline cache structs.",
+    "JITScripts, native maps, and inline cache structs." SLOP_BYTES_STRING,
                        callback, closure);
 #endif
 #ifdef JS_TRACER
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
                                               "tjit-code"),
                        nsIMemoryReporter::KIND_NONHEAP, stats.tjitCode,
     "Memory used by the trace JIT to hold the compartment's generated code.",
                        callback, closure);
@@ -1879,21 +1879,21 @@ ReportCompartmentStats(const Compartment
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
                                               "type-inference/tables"),
                        nsIMemoryReporter::KIND_HEAP,
                        stats.typeInferenceMemory.tables,
     "Memory used during type inference for compartment-wide tables.",
                        callback, closure);
 
     ReportMemoryBytes0(MakeMemoryReporterPath(pathPrefix, stats.name,
-                                              "type-inference-temporary"),
+                                              "analysis-temporary"),
                        nsIMemoryReporter::KIND_HEAP,
                        stats.typeInferenceMemory.temporary,
-    "Memory used during type inference to hold transient analysis "
-    "information.  Cleared on GC.",
+    "Memory used during type inference and compilation to hold transient "
+    "analysis information.  Cleared on GC.",
                        callback, closure);
 }
 
 void
 ReportJSRuntimeStats(const IterateData &data, const nsACString &pathPrefix,
                      nsIMemoryMultiReporterCallback *callback,
                      nsISupports *closure)
 {
@@ -1901,18 +1901,18 @@ ReportJSRuntimeStats(const IterateData &
         index < data.compartmentStatsVector.Length();
         index++)
     {
         ReportCompartmentStats(data.compartmentStatsVector[index], pathPrefix,
                                callback, closure);
     }
 
     ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("runtime/runtime-object"),
-                      nsIMemoryReporter::KIND_NONHEAP, sizeof(JSRuntime),
-    "Memory used by the JSRuntime object.",
+                      nsIMemoryReporter::KIND_NONHEAP, data.runtimeObjectSize,
+    "Memory used by the JSRuntime object." SLOP_BYTES_STRING,
                       callback, closure);
 
     ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("runtime/atoms-table"),
                       nsIMemoryReporter::KIND_NONHEAP, data.atomsTableSize,
     "Memory used by the atoms table.",
                       callback, closure);
 
     ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("stack"),
--- a/js/src/xpconnect/src/xpcpublic.h
+++ b/js/src/xpconnect/src/xpcpublic.h
@@ -222,27 +222,29 @@ struct CompartmentStats
     PRInt64 tjitDataNonAllocators;
 #endif
     TypeInferenceMemoryStats typeInferenceMemory;
 };
 
 struct IterateData
 {
     IterateData()
-      : atomsTableSize(0),
+      : runtimeObjectSize(0),
+        atomsTableSize(0),
         stackSize(0),
         gcHeapChunkTotal(0),
         gcHeapChunkCleanUnused(0),
         gcHeapChunkDirtyUnused(0),
         gcHeapArenaUnused(0),
         gcHeapChunkAdmin(0),
         gcHeapUnusedPercentage(0),
         compartmentStatsVector(),
         currCompartmentStats(NULL) { }
 
+    PRInt64 runtimeObjectSize;
     PRInt64 atomsTableSize;
     PRInt64 stackSize;
     PRInt64 gcHeapChunkTotal;
     PRInt64 gcHeapChunkCleanUnused;
     PRInt64 gcHeapChunkDirtyUnused;
     PRInt64 gcHeapArenaUnused;
     PRInt64 gcHeapChunkAdmin;
     PRInt64 gcHeapUnusedPercentage;