Bug 671150 - Add memory reporter for the JS PropertyTable. r=brendan.
authorNicholas Nethercote <nnethercote@mozilla.com>
Sun, 24 Jul 2011 17:00:56 -0700
changeset 73472 2d63d7fd35a010e9c4e63762eee4fd9d51921a4e
parent 73471 5e57dacf6ff172a3c157a79b26788c34f9e0f69e
child 73473 39fef23838930b8ceda359fd618e686a9e53d521
push idunknown
push userunknown
push dateunknown
reviewersbrendan
bugs671150
milestone8.0a1
Bug 671150 - Add memory reporter for the JS PropertyTable. r=brendan.
js/src/jsscope.cpp
js/src/jsscope.h
js/src/xpconnect/src/xpcjsruntime.cpp
--- a/js/src/jsscope.cpp
+++ b/js/src/jsscope.cpp
@@ -127,18 +127,16 @@ JSObject::ensureClassReservedSlotsForEmp
      */
     uint32 nfixed = JSSLOT_FREE(getClass());
     if (nfixed > numSlots() && !allocSlots(cx, nfixed))
         return false;
 
     return true;
 }
 
-#define PROPERTY_TABLE_NBYTES(n) ((n) * sizeof(Shape *))
-
 bool
 PropertyTable::init(JSRuntime *rt, Shape *lastProp)
 {
     /*
      * Either we're creating a table for a large scope that was populated
      * via property cache hit logic under JSOP_INITPROP, JSOP_SETNAME, or
      * JSOP_SETPROP; or else calloc failed at least once already. In any
      * event, let's try to grow, overallocating to hold at least twice the
@@ -147,17 +145,17 @@ PropertyTable::init(JSRuntime *rt, Shape
     uint32 sizeLog2 = JS_CeilingLog2(2 * entryCount);
     if (sizeLog2 < MIN_SIZE_LOG2)
         sizeLog2 = MIN_SIZE_LOG2;
 
     /*
      * Use rt->calloc_ for memory accounting and overpressure handling
      * without OOM reporting. See PropertyTable::change.
      */
-    entries = (Shape **) rt->calloc_(JS_BIT(sizeLog2) * sizeof(Shape *));
+    entries = (Shape **) rt->calloc_(sizeOfEntries(JS_BIT(sizeLog2)));
     if (!entries)
         return false;
 
     hashShift = JS_DHASH_BITS - sizeLog2;
     for (Shape::Range r = lastProp->all(); !r.empty(); r.popFront()) {
         const Shape &shape = r.front();
         Shape **spp = search(shape.propid, true);
 
@@ -285,45 +283,40 @@ PropertyTable::search(jsid id, bool addi
 
     /* NOTREACHED */
     return NULL;
 }
 
 bool
 PropertyTable::change(int log2Delta, JSContext *cx)
 {
-    int oldlog2, newlog2;
-    uint32 oldsize, newsize, nbytes;
-    Shape **newTable, **oldTable, **spp, **oldspp, *shape;
-
     JS_ASSERT(entries);
 
     /*
      * Grow, shrink, or compress by changing this->entries.
      */
-    oldlog2 = JS_DHASH_BITS - hashShift;
-    newlog2 = oldlog2 + log2Delta;
-    oldsize = JS_BIT(oldlog2);
-    newsize = JS_BIT(newlog2);
-    nbytes = PROPERTY_TABLE_NBYTES(newsize);
-    newTable = (Shape **) cx->calloc_(nbytes);
+    int oldlog2 = JS_DHASH_BITS - hashShift;
+    int newlog2 = oldlog2 + log2Delta;
+    uint32 oldsize = JS_BIT(oldlog2);
+    uint32 newsize = JS_BIT(newlog2);
+    Shape **newTable = (Shape **) cx->calloc_(sizeOfEntries(newsize));
     if (!newTable)
         return false;
 
     /* Now that we have newTable allocated, update members. */
     hashShift = JS_DHASH_BITS - newlog2;
     removedCount = 0;
-    oldTable = entries;
+    Shape **oldTable = entries;
     entries = newTable;
 
     /* Copy only live entries, leaving removed and free ones behind. */
-    for (oldspp = oldTable; oldsize != 0; oldspp++) {
-        shape = SHAPE_FETCH(oldspp);
+    for (Shape **oldspp = oldTable; oldsize != 0; oldspp++) {
+        Shape *shape = SHAPE_FETCH(oldspp);
         if (shape) {
-            spp = search(shape->propid, true);
+            Shape **spp = search(shape->propid, true);
             JS_ASSERT(SHAPE_IS_FREE(*spp));
             *spp = shape;
         }
         oldsize--;
     }
 
     /* Finally, free the old entries storage. */
     cx->free_(oldTable);
--- a/js/src/jsscope.h
+++ b/js/src/jsscope.h
@@ -243,16 +243,23 @@ struct PropertyTable {
 
     ~PropertyTable() {
         js::UnwantedForeground::free_(entries);
     }
 
     /* 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 {
+        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);
     }
 
     /*
      * Try to grow the table.  On failure, reports out of memory on cx
@@ -361,26 +368,16 @@ struct Shape : public js::gc::Cell
 
     inline void removeFromDictionary(JSObject *obj) const;
     inline void insertIntoDictionary(js::Shape **dictp);
 
     js::Shape *getChild(JSContext *cx, const js::Shape &child, js::Shape **listp);
 
     bool hashify(JSRuntime *rt);
 
-    bool hasTable() const {
-        /* A valid pointer should be much bigger than MAX_LINEAR_SEARCHES. */
-        return numLinearSearches > PropertyTable::MAX_LINEAR_SEARCHES;
-    }
-
-    js::PropertyTable *getTable() const {
-        JS_ASSERT(hasTable());
-        return table;
-    }
-
     void setTable(js::PropertyTable *t) const {
         JS_ASSERT_IF(t && t->freelist != SHAPE_INVALID_SLOT, t->freelist < slotSpan);
         table = t;
     }
 
     /*
      * Setter for parent. The challenge is to maintain JSObjectMap::slotSpan in
      * the face of arbitrary slot order.
@@ -428,16 +425,26 @@ struct Shape : public js::gc::Cell
             slotSpan = JS_MAX(p->slotSpan, slot + 1);
         JS_ASSERT(slotSpan < JSObject::NSLOTS_LIMIT);
         parent = p;
     }
 
   public:
     static JS_FRIEND_DATA(Shape) sharedNonNative;
 
+    bool hasTable() const {
+        /* A valid pointer should be much bigger than MAX_LINEAR_SEARCHES. */
+        return numLinearSearches > PropertyTable::MAX_LINEAR_SEARCHES;
+    }
+
+    js::PropertyTable *getTable() const {
+        JS_ASSERT(hasTable());
+        return table;
+    }
+
     bool isNative() const { return this != &sharedNonNative; }
 
     const js::Shape *previous() const {
         return parent;
     }
 
     class Range {
       protected:
--- a/js/src/xpconnect/src/xpcjsruntime.cpp
+++ b/js/src/xpconnect/src/xpcjsruntime.cpp
@@ -41,16 +41,17 @@
 
 /* Per JSRuntime object */
 
 #include "xpcprivate.h"
 #include "WrapperFactory.h"
 #include "dom_quickstubs.h"
 
 #include "jsgcchunk.h"
+#include "jsscope.h"
 #include "nsIMemoryReporter.h"
 #include "nsPrintfCString.h"
 #include "mozilla/FunctionTimer.h"
 #include "prsystem.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
@@ -1399,16 +1400,17 @@ private:
 
         PRInt64 gcHeapObjects;
         PRInt64 gcHeapStrings;
         PRInt64 gcHeapShapes;
         PRInt64 gcHeapXml;
 
         PRInt64 objectSlots;
         PRInt64 stringChars;
+        PRInt64 propertyTables;
 
         PRInt64 scripts;
 #ifdef JS_METHODJIT
         PRInt64 mjitCode;
         PRInt64 mjitData;
 #endif
 #ifdef JS_TRACER
         PRInt64 tjitCode;
@@ -1538,27 +1540,31 @@ private:
 
     static void
     CellCallback(JSContext *cx, void *vdata, void *thing, size_t traceKind,
                  size_t thingSize)
     {
         IterateData *data = static_cast<IterateData *>(vdata);
         CompartmentStats *curr = data->currCompartmentStats;
         if (traceKind == JSTRACE_OBJECT) {
+            curr->gcHeapObjects += thingSize;
             JSObject *obj = static_cast<JSObject *>(thing);
-            curr->gcHeapObjects += thingSize;
             if (obj->hasSlotsArray()) {
                 curr->objectSlots += obj->numSlots() * sizeof(js::Value);
             }
         } else if (traceKind == JSTRACE_STRING) {
+            curr->gcHeapStrings += thingSize;
             JSString *str = static_cast<JSString *>(thing);
-            curr->gcHeapStrings += thingSize;
             curr->stringChars += str->charsHeapSize();
         } else if (traceKind == JSTRACE_SHAPE) {
             curr->gcHeapShapes += thingSize;
+            js::Shape *shape = static_cast<js::Shape *>(thing);
+            if (shape->hasTable()) {
+                curr->propertyTables += shape->getTable()->sizeOf();
+            }
         } else {
             JS_ASSERT(traceKind == JSTRACE_XML);
             curr->gcHeapXml += thingSize;
         }
         // Yes, this is a subtraction:  see ArenaCallback() for details.
         curr->gcHeapArenaUnused -= thingSize;
     }
 
@@ -1691,16 +1697,22 @@ public:
             BYTES0(mkPath(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 "
     "compartment's JavaScript heap;  that header is not counted here, but in "
     "'gc-heap/strings' instead.");
 
+            BYTES0(mkPath(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.");
+
             BYTES0(mkPath(name, "scripts"),
                nsIMemoryReporter::KIND_HEAP, stats->scripts,
     "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.  Each JSScript includes byte-code and "
     "various other things.");
 
 #ifdef JS_METHODJIT