Bug 666058 - Don't share chunks for system compartments. r=gal,igor.
authorGregor Wagner <anygregor@gmail.com>
Tue, 05 Jul 2011 14:14:33 +1000
changeset 72292 0c94f01f53af
parent 72291 c3e051b9da9c
child 72293 b552fdfe62a6
push id20691
push usernnethercote@mozilla.com
push date2011-07-05 04:15 +0000
treeherdermozilla-central@0c94f01f53af [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgal, igor
bugs666058
milestone7.0a1
Bug 666058 - Don't share chunks for system compartments. r=gal,igor.
caps/src/nsSystemPrincipal.cpp
js/src/jsapi-tests/testGCChunkAlloc.cpp
js/src/jsapi.cpp
js/src/jscntxt.h
js/src/jscompartment.h
js/src/jsgc.cpp
--- a/caps/src/nsSystemPrincipal.cpp
+++ b/caps/src/nsSystemPrincipal.cpp
@@ -303,27 +303,30 @@ nsSystemPrincipal::Write(nsIObjectOutput
 /////////////////////////////////////////////
 // Constructor, Destructor, initialization //
 /////////////////////////////////////////////
 
 nsSystemPrincipal::nsSystemPrincipal()
 {
 }
 
+// Don't rename the system principal!
+// The JS engine (NewCompartment) relies on this name. 
+// XXX: bug 669123 will fix this hack.
 #define SYSTEM_PRINCIPAL_SPEC "[System Principal]"
 
 nsresult
 nsSystemPrincipal::Init()
 {
     // Use an nsCString so we only do the allocation once here and then
     // share with nsJSPrincipals
     nsCString str(SYSTEM_PRINCIPAL_SPEC);
     if (!str.EqualsLiteral(SYSTEM_PRINCIPAL_SPEC)) {
         NS_WARNING("Out of memory initializing system principal");
         return NS_ERROR_OUT_OF_MEMORY;
     }
-    
+
     return mJSPrincipals.Init(this, str);
 }
 
 nsSystemPrincipal::~nsSystemPrincipal(void)
 {
 }
--- a/js/src/jsapi-tests/testGCChunkAlloc.cpp
+++ b/js/src/jsapi-tests/testGCChunkAlloc.cpp
@@ -5,36 +5,48 @@
  * http://creativecommons.org/licenses/publicdomain/
  * Contributor: Igor Bukanov
  */
 
 #include "tests.h"
 #include "jsgcchunk.h"
 #include "jscntxt.h"
 
-/* We allow to allocate only single chunk. */
+/* We allow to allocate 2 (system/user) chunks. */
+
+/* XXX: using pool[0] and pool[1] is a hack;  bug 669123 will fix this. */
 
 class CustomGCChunkAllocator: public js::GCChunkAllocator {
   public:
-    CustomGCChunkAllocator() : pool(NULL) {}
-    void *pool;
+    CustomGCChunkAllocator() { pool[0] = NULL; pool[1] = NULL; }
+    void *pool[2];
     
   private:
 
     virtual void *doAlloc() {
-        if (!pool)
+        if (!pool[0] && !pool[1])
             return NULL;
-        void *chunk = pool;
-        pool = NULL;
+        void *chunk = NULL;
+        if (pool[0]) {
+            chunk = pool[0];
+            pool[0] = NULL;
+        } else {
+            chunk = pool[1];
+            pool[1] = NULL;
+        }
         return chunk;
     }
         
     virtual void doFree(void *chunk) {
-        JS_ASSERT(!pool);
-        pool = chunk;
+        JS_ASSERT(!pool[0] || !pool[1]);
+        if (!pool[0]) {
+            pool[0] = chunk;
+        } else {
+            pool[1] = chunk;
+        }
     }
 };
 
 static CustomGCChunkAllocator customGCChunkAllocator;
 
 static unsigned errorCount = 0;
 
 static void
@@ -64,17 +76,18 @@ BEGIN_TEST(testGCChunkAlloc)
         "})();";
     JSBool ok = JS_EvaluateScript(cx, global, source, strlen(source), "", 1,
                                   root.addr());
 
     /* Check that we get OOM. */
     CHECK(!ok);
     CHECK(!JS_IsExceptionPending(cx));
     CHECK(errorCount == 1);
-    CHECK(!customGCChunkAllocator.pool);
+    CHECK(!customGCChunkAllocator.pool[0]);
+    CHECK(!customGCChunkAllocator.pool[1]);
     JS_GC(cx);
     JS_ToggleOptions(cx, JSOPTION_JIT);
     EVAL("(function() {"
          "    var array = [];"
          "    for (var i = max >> 1; i != 0;) {"
          "        --i;"
          "        array.push({});"
          "    }"
@@ -87,25 +100,30 @@ virtual JSRuntime * createRuntime() {
     /*
      * To test failure of chunk allocation allow to use GC twice the memory
      * the single chunk contains.
      */
     JSRuntime *rt = JS_NewRuntime(2 * js::GC_CHUNK_SIZE);
     if (!rt)
         return NULL;
 
-    customGCChunkAllocator.pool = js::AllocGCChunk();
-    JS_ASSERT(customGCChunkAllocator.pool);
+    customGCChunkAllocator.pool[0] = js::AllocGCChunk();
+    customGCChunkAllocator.pool[1] = js::AllocGCChunk();
+    JS_ASSERT(customGCChunkAllocator.pool[0]);
+    JS_ASSERT(customGCChunkAllocator.pool[1]);
 
     rt->setCustomGCChunkAllocator(&customGCChunkAllocator);
     return rt;
 }
 
 virtual void destroyRuntime() {
     JS_DestroyRuntime(rt);
 
     /* We should get the initial chunk back at this point. */
-    JS_ASSERT(customGCChunkAllocator.pool);
-    js::FreeGCChunk(customGCChunkAllocator.pool);
-    customGCChunkAllocator.pool = NULL;
+    JS_ASSERT(customGCChunkAllocator.pool[0]);
+    JS_ASSERT(customGCChunkAllocator.pool[1]);
+    js::FreeGCChunk(customGCChunkAllocator.pool[0]);
+    js::FreeGCChunk(customGCChunkAllocator.pool[1]);
+    customGCChunkAllocator.pool[0] = NULL;
+    customGCChunkAllocator.pool[1] = NULL;
 }
 
 END_TEST(testGCChunkAlloc)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -661,16 +661,17 @@ JSRuntime::init(uint32 maxbytes)
 
     if (!(atomsCompartment = this->new_<JSCompartment>(this)) ||
         !atomsCompartment->init() ||
         !compartments.append(atomsCompartment)) {
         Foreground::delete_(atomsCompartment);
         return false;
     }
 
+    atomsCompartment->systemGCChunks = true;
     atomsCompartment->setGCLastBytes(8192, GC_NORMAL);
 
     if (!js_InitAtomState(this))
         return false;
 
     wrapObjectCallback = js::TransparentObjectWrapper;
 
 #ifdef JS_THREADSAFE
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -371,16 +371,17 @@ struct JSRuntime {
      *
      * This comes early in JSRuntime to minimize the immediate format used by
      * trace-JITted code that reads it.
      */
     uint32              protoHazardShape;
 
     /* Garbage collector state, used by jsgc.c. */
     js::GCChunkSet      gcChunkSet;
+    js::GCChunkSet      gcSystemChunkSet;
 
     js::RootedValueMap  gcRootsHash;
     js::GCLocks         gcLocksHash;
     jsrefcount          gcKeepAtoms;
     uint32              gcBytes;
     uint32              gcTriggerBytes;
     size_t              gcLastBytes;
     size_t              gcMaxBytes;
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -373,16 +373,17 @@ struct JS_FRIEND_API(JSCompartment) {
     js::gc::ArenaList            arenas[js::gc::FINALIZE_LIMIT];
     js::gc::FreeLists            freeLists;
 
     uint32                       gcBytes;
     uint32                       gcTriggerBytes;
     size_t                       gcLastBytes;
 
     bool                         hold;
+    bool                         systemGCChunks;
 
 #ifdef JS_TRACER
   private:
     /*
      * Trace-tree JIT recorder/interpreter state.  It's created lazily because
      * many compartments don't end up needing it.
      */
     js::TraceMonitor             *traceMonitor_;
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -457,42 +457,66 @@ ReleaseGCChunk(JSRuntime *rt, Chunk *p)
 
 inline Chunk *
 PickChunk(JSContext *cx)
 {
     Chunk *chunk = cx->compartment->chunk;
     if (chunk && chunk->hasAvailableArenas())
         return chunk;
 
+    JSRuntime *rt = cx->runtime;
+    bool systemGCChunks = cx->compartment->systemGCChunks;
+
     /*
      * The chunk used for the last allocation is full, search all chunks for
      * free arenas.
      */
-    JSRuntime *rt = cx->runtime;
-    for (GCChunkSet::Range r(rt->gcChunkSet.all()); !r.empty(); r.popFront()) {
+    GCChunkSet::Range r(systemGCChunks ? rt->gcSystemChunkSet.all() : rt->gcChunkSet.all());
+    for (; !r.empty(); r.popFront()) {
         chunk = r.front();
         if (chunk->hasAvailableArenas()) {
             cx->compartment->chunk = chunk;
             return chunk;
         }
     }
 
     chunk = AllocateGCChunk(rt);
-    if (!chunk)
-        return NULL;
+    if (!chunk) {
+        /* Our last chance is to find an empty chunk in the other chunk set. */
+        GCChunkSet::Enum e(systemGCChunks ? rt->gcChunkSet : rt->gcSystemChunkSet);
+        for (; !e.empty(); e.popFront()) {
+            if (e.front()->info.numFree == ArenasPerChunk) {
+                chunk = e.front();
+                e.removeFront();
+                break;
+            }
+        }
+
+        if (!chunk)
+            return NULL;
+    }
 
     /*
      * FIXME bug 583732 - chunk is newly allocated and cannot be present in
      * the table so using ordinary lookupForAdd is suboptimal here.
      */
-    GCChunkSet::AddPtr p = rt->gcChunkSet.lookupForAdd(chunk);
+    GCChunkSet::AddPtr p = systemGCChunks ?
+                           rt->gcSystemChunkSet.lookupForAdd(chunk) :
+                           rt->gcChunkSet.lookupForAdd(chunk);
     JS_ASSERT(!p);
-    if (!rt->gcChunkSet.add(p, chunk)) {
-        ReleaseGCChunk(rt, chunk);
-        return NULL;
+    if (systemGCChunks) {
+        if (!rt->gcSystemChunkSet.add(p, chunk)) {
+            ReleaseGCChunk(rt, chunk);
+            return NULL;
+        }
+    } else {
+        if (!rt->gcChunkSet.add(p, chunk)) {
+            ReleaseGCChunk(rt, chunk);
+            return NULL;
+        }
     }
 
     chunk->init(rt);
     cx->compartment->chunk = chunk;
     rt->gcChunkAllocationSinceLastGC = true;
     return chunk;
 }
 
@@ -512,16 +536,28 @@ ExpireGCChunks(JSRuntime *rt, JSGCInvoca
             if (gckind == GC_SHRINK || chunk->info.age++ > MaxAge) {
                 e.removeFront();
                 ReleaseGCChunk(rt, chunk);
                 continue;
             }
             rt->gcChunksWaitingToExpire++;
         }
     }
+    for (GCChunkSet::Enum e(rt->gcSystemChunkSet); !e.empty(); e.popFront()) {
+        Chunk *chunk = e.front();
+        JS_ASSERT(chunk->info.runtime == rt);
+        if (chunk->unused()) {
+            if (gckind == GC_SHRINK || chunk->info.age++ > MaxAge) {
+                e.removeFront();
+                ReleaseGCChunk(rt, chunk);
+                continue;
+            }
+            rt->gcChunksWaitingToExpire++;
+        }
+    }
 }
 
 JS_FRIEND_API(bool)
 IsAboutToBeFinalized(JSContext *cx, const void *thing)
 {
     if (JSAtom::isStatic(thing))
         return false;
     JS_ASSERT(cx);
@@ -556,16 +592,19 @@ js_InitGC(JSRuntime *rt, uint32 maxbytes
 {
     /*
      * Make room for at least 16 chunks so the table would not grow before
      * the browser starts up.
      */
     if (!rt->gcChunkSet.init(16))
         return false;
 
+    if (!rt->gcSystemChunkSet.init(16))
+        return false;
+
     if (!rt->gcRootsHash.init(256))
         return false;
 
     if (!rt->gcLocksHash.init(256))
         return false;
 
 #ifdef JS_THREADSAFE
     rt->gcLock = JS_NEW_LOCK();
@@ -695,17 +734,18 @@ MarkIfGCThingWord(JSTracer *trc, jsuword
 #if JS_BITS_PER_WORD == 32
     jsuword addr = w & JSID_PAYLOAD_MASK;
 #elif JS_BITS_PER_WORD == 64
     jsuword addr = w & JSID_PAYLOAD_MASK & JSVAL_PAYLOAD_MASK;
 #endif
 
     Chunk *chunk = Chunk::fromAddress(addr);
 
-    if (!trc->context->runtime->gcChunkSet.has(chunk))
+    if (!trc->context->runtime->gcChunkSet.has(chunk) && 
+        !trc->context->runtime->gcSystemChunkSet.has(chunk))
         return CGCT_NOTCHUNK;
 
     /*
      * We query for pointers outside the arena array after checking for an
      * allocated chunk. Such pointers are rare and we want to reject them
      * after doing more likely rejections.
      */
     if (!Chunk::withinArenasRange(addr))
@@ -904,17 +944,20 @@ js_FinishGC(JSRuntime *rt)
         comp->finishArenaLists();
         Foreground::delete_(comp);
     }
     rt->compartments.clear();
     rt->atomsCompartment = NULL;
 
     for (GCChunkSet::Range r(rt->gcChunkSet.all()); !r.empty(); r.popFront())
         ReleaseGCChunk(rt, r.front());
+    for (GCChunkSet::Range r(rt->gcSystemChunkSet.all()); !r.empty(); r.popFront())
+        ReleaseGCChunk(rt, r.front());
     rt->gcChunkSet.clear();
+    rt->gcSystemChunkSet.clear();
 
 #ifdef JS_THREADSAFE
     rt->gcHelperThread.finish(rt);
 #endif
 
 #ifdef DEBUG
     if (!rt->gcRootsHash.empty())
         CheckLeakedRoots(rt);
@@ -2225,17 +2268,20 @@ MarkAndSweep(JSContext *cx, JSCompartmen
      */
     GCTIMESTAMP(startMark);
     GCMarker gcmarker(cx);
     JS_ASSERT(IS_GC_MARKING_TRACER(&gcmarker));
     JS_ASSERT(gcmarker.getMarkColor() == BLACK);
     rt->gcMarkingTracer = &gcmarker;
 
     for (GCChunkSet::Range r(rt->gcChunkSet.all()); !r.empty(); r.popFront())
-         r.front()->bitmap.clear();
+        r.front()->bitmap.clear();
+
+    for (GCChunkSet::Range r(rt->gcSystemChunkSet.all()); !r.empty(); r.popFront())
+        r.front()->bitmap.clear();
 
     if (comp) {
         for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c)
             (*c)->markCrossCompartmentWrappers(&gcmarker);
     } else {
         js_MarkScriptFilenames(rt);
     }
 
@@ -2785,16 +2831,17 @@ IterateCompartmentsArenasCells(JSContext
 namespace gc {
 
 JSCompartment *
 NewCompartment(JSContext *cx, JSPrincipals *principals)
 {
     JSRuntime *rt = cx->runtime;
     JSCompartment *compartment = cx->new_<JSCompartment>(rt);
     if (compartment && compartment->init()) {
+        compartment->systemGCChunks = principals && !strcmp(principals->codebase, "[System Principal]");
         if (principals) {
             compartment->principals = principals;
             JSPRINCIPALS_HOLD(cx, principals);
         }
 
         compartment->setGCLastBytes(8192, GC_NORMAL);
 
         /*