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
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 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);
 
         /*