Bug 779975 - Refcount ScriptSource. r=jorendorff
authorBenjamin Peterson <benjamin@python.org>
Mon, 06 Aug 2012 13:25:58 -0700
changeset 101565 fa77c8c2a3464037199d9333fa051d89857a6c52
parent 101564 853059c5f3db02bc58d8c0f27368835e62bcde32
child 101566 ef62a9a2ee425de0ae494159190d4e51eaf8f09a
push id13058
push userbpeterson@mozilla.com
push dateMon, 06 Aug 2012 20:26:11 +0000
treeherdermozilla-inbound@fa77c8c2a346 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs779975
milestone17.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 779975 - Refcount ScriptSource. r=jorendorff
js/src/MemoryMetrics.cpp
js/src/frontend/BytecodeCompiler.cpp
js/src/jsapi.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/vm/GlobalObject.cpp
--- a/js/src/MemoryMetrics.cpp
+++ b/js/src/MemoryMetrics.cpp
@@ -17,21 +17,27 @@
 #include "jsobjinlines.h"
 
 #ifdef JS_THREADSAFE
 
 namespace JS {
 
 using namespace js;
 
+typedef HashSet<ScriptSource *, DefaultHasher<ScriptSource *>, SystemAllocPolicy> SourceSet;
+
 struct IteratorClosure
 {
   RuntimeStats *rtStats;
   ObjectPrivateVisitor *opv;
+  SourceSet seenSources;
   IteratorClosure(RuntimeStats *rt, ObjectPrivateVisitor *v) : rtStats(rt), opv(v) {}
+  bool init() {
+      return seenSources.init();
+  }
 };
 
 size_t
 CompartmentStats::gcHeapThingsSize()
 {
     // These are just the GC-thing measurements.
     size_t n = 0;
     n += gcHeapObjectsNonFunction;
@@ -168,16 +174,22 @@ StatsCellCallback(JSRuntime *rt, void *d
     case JSTRACE_SCRIPT:
     {
         JSScript *script = static_cast<JSScript *>(thing);
         cStats->gcHeapScripts += thingSize;
         cStats->scriptData += script->sizeOfData(rtStats->mallocSizeOf);
 #ifdef JS_METHODJIT
         cStats->mjitData += script->sizeOfJitScripts(rtStats->mallocSizeOf);
 #endif
+        ScriptSource *ss = script->scriptSource();
+        SourceSet::AddPtr entry = closure->seenSources.lookupForAdd(ss);
+        if (!entry) {
+            closure->seenSources.add(entry, ss); // Not much to be done on failure.
+            rtStats->runtime.scriptSources += ss->sizeOfIncludingThis(rtStats->mallocSizeOf);
+        }
         break;
     }
     case JSTRACE_TYPE_OBJECT:
     {
         types::TypeObject *obj = static_cast<types::TypeObject *>(thing);
         cStats->gcHeapTypeObjects += thingSize;
         obj->sizeOfExcludingThis(&cStats->typeInferenceSizes, rtStats->mallocSizeOf);
         break;
@@ -206,16 +218,19 @@ CollectRuntimeStats(JSRuntime *rt, Runti
     rtStats->gcHeapUnusedChunks =
         size_t(JS_GetGCParameter(rt, JSGC_UNUSED_CHUNKS)) * gc::ChunkSize;
 
     // This just computes rtStats->gcHeapDecommittedArenas.
     IterateChunks(rt, rtStats, StatsChunkCallback);
 
     // Take the per-compartment measurements.
     IteratorClosure closure(rtStats, opv);
+    if (!closure.init())
+        return false;
+    rtStats->runtime.scriptSources = 0;
     IterateCompartmentsArenasCells(rt, &closure, StatsCompartmentCallback,
                                    StatsArenaCallback, StatsCellCallback);
 
     // Take the "explicit/js/runtime/" measurements.
     rt->sizeOfIncludingThis(rtStats->mallocSizeOf, &rtStats->runtime);
 
     rtStats->gcHeapGcThings = 0;
     for (size_t i = 0; i < rtStats->compartmentStatsVector.length(); i++) {
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -16,30 +16,16 @@
 
 #include "jsinferinlines.h"
 
 #include "frontend/TreeContext-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 
-class AutoAttachToRuntime {
-    JSRuntime *rt;
-    ScriptSource *ss;
-  public:
-    AutoAttachToRuntime(JSRuntime *rt, ScriptSource *ss)
-      : rt(rt), ss(ss) {}
-    ~AutoAttachToRuntime() {
-        // This makes the source visible to the GC. If compilation fails, and no
-        // script refers to it, it will be collected.
-        if (ss)
-            ss->attachToRuntime(rt);
-    }
-};
-
 static bool
 CheckLength(JSContext *cx, size_t length)
 {
     // Note this limit is simply so we can store sourceStart and sourceEnd in
     // JSScript as 32-bits. It could be lifted fairly easily, since the compiler
     // is using size_t internally already.
     if (length > UINT32_MAX) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SOURCE_TOO_LONG);
@@ -77,17 +63,17 @@ frontend::CompileScript(JSContext *cx, H
     JS_ASSERT_IF(callerFrame, options.compileAndGo);
     JS_ASSERT_IF(staticLevel != 0, callerFrame);
 
     if (!CheckLength(cx, length))
         return NULL;
     ScriptSource *ss = cx->new_<ScriptSource>();
     if (!ss)
         return NULL;
-    AutoAttachToRuntime attacher(cx->runtime, ss);
+    ScriptSourceHolder ssh(cx->runtime, ss);
     SourceCompressionToken sct(cx);
     if (!cx->hasRunOption(JSOPTION_ONLY_CNG_SOURCE) || options.compileAndGo) {
         if (!ss->setSourceCopy(cx, chars, length, false, &sct))
             return NULL;
     }
 
     Parser parser(cx, options, chars, length, /* foldConstants = */ true);
     if (!parser.init())
@@ -243,17 +229,17 @@ bool
 frontend::CompileFunctionBody(JSContext *cx, HandleFunction fun, CompileOptions options,
                               Bindings *bindings, const jschar *chars, size_t length)
 {
     if (!CheckLength(cx, length))
         return NULL;
     ScriptSource *ss = cx->new_<ScriptSource>();
     if (!ss)
         return NULL;
-    AutoAttachToRuntime attacher(cx->runtime, ss);
+    ScriptSourceHolder ssh(cx->runtime, ss);
     SourceCompressionToken sct(cx);
     if (!ss->setSourceCopy(cx, chars, length, true, &sct))
         return NULL;
 
     options.setCompileAndGo(false);
     Parser parser(cx, options, chars, length, /* foldConstants = */ true);
     if (!parser.init())
         return false;
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -840,17 +840,16 @@ JSRuntime::JSRuntime()
     mathCache_(NULL),
     dtoaState(NULL),
     pendingProxyOperation(NULL),
     trustedPrincipals_(NULL),
     wrapObjectCallback(TransparentObjectWrapper),
     sameCompartmentWrapObjectCallback(NULL),
     preWrapObjectCallback(NULL),
     preserveWrapperCallback(NULL),
-    scriptSources(NULL),
 #ifdef DEBUG
     noGCOrAllocationCheck(0),
 #endif
     inOOMReport(0),
     jitHardening(false)
 {
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&contextList);
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -100,20 +100,16 @@ JSRuntime::sizeOfIncludingThis(JSMallocS
     runtime->gcMarker = gcMarker.sizeOfExcludingThis(mallocSizeOf);
 
     runtime->mathCache = mathCache_ ? mathCache_->sizeOfIncludingThis(mallocSizeOf) : 0;
 
     runtime->scriptFilenames = scriptFilenameTable.sizeOfExcludingThis(mallocSizeOf);
     for (ScriptFilenameTable::Range r = scriptFilenameTable.all(); !r.empty(); r.popFront())
         runtime->scriptFilenames += mallocSizeOf(r.front());
 
-    runtime->scriptSources = 0;
-    for (ScriptSource *n = scriptSources; n; n = n->next)
-        runtime->scriptSources += n->sizeOfIncludingThis(mallocSizeOf);
-
     runtime->compartmentObjects = 0;
     CallbackData data(mallocSizeOf);
     JS_IterateCompartments(this, &data, CompartmentCallback);
     runtime->compartmentObjects = data.n;
 }
 
 size_t
 JSRuntime::sizeOfExplicitNonHeap()
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -853,18 +853,16 @@ struct JSRuntime : js::RuntimeFriendFiel
 
     JSWrapObjectCallback                   wrapObjectCallback;
     JSSameCompartmentWrapObjectCallback    sameCompartmentWrapObjectCallback;
     JSPreWrapCallback                      preWrapObjectCallback;
     js::PreserveWrapperCallback            preserveWrapperCallback;
 
     js::ScriptFilenameTable scriptFilenameTable;
 
-    js::ScriptSource *scriptSources;
-
 #ifdef DEBUG
     size_t              noGCOrAllocationCheck;
 #endif
 
     /*
      * To ensure that cx->malloc does not cause a GC, we set this flag during
      * OOM reporting (in js_ReportOutOfMemory). If a GC is requested while
      * reporting the OOM, we ignore it.
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3616,20 +3616,18 @@ EndSweepPhase(JSRuntime *rt, JSGCInvocat
             rt->freeLifoAlloc.freeAll();
 
         /*
          * Sweep script filenames after sweeping functions in the generic loop
          * above. In this way when a scripted function's finalizer destroys the
          * script and calls rt->destroyScriptHook, the hook can still access the
          * script's filename. See bug 323267.
          */
-        if (rt->gcIsFull) {
+        if (rt->gcIsFull)
             SweepScriptFilenames(rt);
-            ScriptSource::sweep(rt);
-        }
 
         /*
          * This removes compartments from rt->compartment, so we do it last to make
          * sure we don't miss sweeping any compartments.
          */
         SweepCompartments(&fop, gcReason);
 
 #ifndef JS_THREADSAFE
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -579,20 +579,19 @@ js::XDRScript(XDRState<mode> *xdr, Handl
         if (scriptBits & (1 << OwnSource)) {
             ss = cx->new_<ScriptSource>();
             if (!ss)
                 return NULL;
         } else {
             JS_ASSERT(enclosingScript);
             ss = enclosingScript->scriptSource();
         }
+        ScriptSourceHolder ssh(cx->runtime, ss);
         script = JSScript::Create(cx, enclosingScope, !!(scriptBits & (1 << SavedCallerFun)),
                                   options, /* staticLevel = */ 0, ss, 0, 0);
-        if (scriptBits & (1 << OwnSource))
-            ss->attachToRuntime(cx->runtime);
         if (!script || !JSScript::partiallyInit(cx, script,
                                                 length, nsrcnotes, natoms, nobjects,
                                                 nregexps, ntrynotes, nconsts, nClosedArgs,
                                                 nClosedVars, nTypeSets))
             return JS_FALSE;
 
         script->bindings.transfer(&bindings);
         JS_ASSERT(!script->mainOffset);
@@ -1089,17 +1088,16 @@ SourceCompressorThread::compress(SourceC
     PR_NotifyCondVar(wakeup);
     PR_Unlock(lock);
 }
 
 void
 SourceCompressorThread::waitOnCompression(SourceCompressionToken *userTok)
 {
     JS_ASSERT(userTok == tok);
-    JS_ASSERT(!tok->ss->onRuntime());
     PR_Lock(lock);
     if (state == COMPRESSING)
         PR_WaitCondVar(done, PR_INTERVAL_NO_TIMEOUT);
     JS_ASSERT(state == IDLE);
     SourceCompressionToken *saveTok = tok;
     tok = NULL;
     PR_Unlock(lock);
 
@@ -1126,21 +1124,17 @@ SourceCompressorThread::abort(SourceComp
     stop = true;
 }
 #endif /* JS_THREADSAFE */
 
 void
 JSScript::setScriptSource(JSContext *cx, ScriptSource *ss)
 {
     JS_ASSERT(ss);
-#ifdef JSGC_INCREMENTAL
-    // During IGC, we need to barrier writing to scriptSource_.
-    if (cx->runtime->gcIncrementalState != NO_INCREMENTAL && cx->runtime->gcIsFull)
-        ss->mark();
-#endif
+    ss->incref();
     scriptSource_ = ss;
 }
 
 bool
 JSScript::loadSource(JSContext *cx, bool *worked)
 {
     JS_ASSERT(!scriptSource_->hasSourceData());
     *worked = false;
@@ -1287,65 +1281,34 @@ void
 SourceCompressionToken::abort()
 {
 #ifdef JS_THREADSAFE
     cx->runtime->sourceCompressorThread.abort(this);
 #endif
 }
 
 void
-ScriptSource::attachToRuntime(JSRuntime *rt)
-{
-    JS_ASSERT(!onRuntime());
-    next = rt->scriptSources;
-    rt->scriptSources = this;
-    onRuntime_ = true;
-}
-
-void
 ScriptSource::destroy(JSRuntime *rt)
 {
     JS_ASSERT(ready());
-    JS_ASSERT(onRuntime());
     rt->free_(data.compressed);
-    onRuntime_ = marked = false;
 #ifdef DEBUG
     ready_ = false;
 #endif
     rt->free_(this);
 }
 
 size_t
 ScriptSource::sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf)
 {
-    JS_ASSERT(onRuntime());
     JS_ASSERT(ready());
-    // data is a union, but both members are pointers to allocated memory, so
-    // just using compressed will work.
-    return mallocSizeOf(this) + mallocSizeOf(data.compressed);
-}
 
-void
-ScriptSource::sweep(JSRuntime *rt)
-{
-    JS_ASSERT(rt->gcIsFull);
-    ScriptSource *next = rt->scriptSources, **prev = &rt->scriptSources;
-    while (next) {
-        ScriptSource *cur = next;
-        next = cur->next;
-        JS_ASSERT(cur->ready());
-        JS_ASSERT(cur->onRuntime());
-        if (cur->marked) {
-            cur->marked = false;
-            prev = &cur->next;
-        } else {
-            *prev = next;
-            cur->destroy(rt);
-        }
-    }
+    // data is a union, but both members are pointers to allocated memory or
+    // NULL, so just using compressed will work.
+    return mallocSizeOf(this) + mallocSizeOf(data.compressed);
 }
 
 template<XDRMode mode>
 bool
 ScriptSource::performXDR(XDRState<mode> *xdr)
 {
     uint8_t hasSource = hasSourceData();
     if (!xdr->codeUint8(&hasSource))
@@ -1949,16 +1912,17 @@ JSScript::finalize(FreeOp *fop)
 
 #ifdef JS_METHODJIT
     mjit::ReleaseScriptCode(fop, this);
 #endif
 
     destroyScriptCounts(fop);
     destroySourceMap(fop);
     destroyDebugScript(fop);
+    scriptSource_->decref(fop->runtime());
 
     if (data) {
         JS_POISON(data, 0xdb, computedSizeOfData());
         fop->free_(data);
     }
 }
 
 namespace js {
@@ -2599,23 +2563,18 @@ JSScript::markChildren(JSTracer *trc)
     }
 
     if (function())
         MarkObject(trc, &function_, "function");
 
     if (enclosingScope_)
         MarkObject(trc, &enclosingScope_, "enclosing");
 
-    if (IS_GC_MARKING_TRACER(trc)) {
-        if (filename)
-            MarkScriptFilename(trc->runtime, filename);
-
-        if (trc->runtime->gcIsFull)
-            scriptSource_->mark();
-    }
+    if (IS_GC_MARKING_TRACER(trc) && filename)
+        MarkScriptFilename(trc->runtime, filename);
 
     bindings.trace(trc);
 
 #ifdef JS_METHODJIT
     for (int constructing = 0; constructing <= 1; constructing++) {
         for (int barriers = 0; barriers <= 1; barriers++) {
             mjit::JITScript *jit = getJIT((bool) constructing, (bool) barriers);
             if (jit)
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -973,84 +973,97 @@ js_CallNewScriptHook(JSContext *cx, JSSc
 
 namespace js {
 
 struct SourceCompressionToken;
 
 struct ScriptSource
 {
     friend class SourceCompressorThread;
-    ScriptSource *next;
   private:
     union {
         // When the script source is ready, compressedLength_ != 0 implies
         // compressed holds the compressed data; otherwise, source holds the
         // uncompressed source.
         jschar *source;
         unsigned char *compressed;
     } data;
+    uint32_t refs;
     uint32_t length_;
     uint32_t compressedLength_;
-    bool marked:1;
-    bool onRuntime_:1;
     bool argumentsNotIncluded_:1;
 #ifdef DEBUG
     bool ready_:1;
 #endif
 
   public:
     ScriptSource()
-      : next(NULL),
+      : refs(0),
         length_(0),
         compressedLength_(0),
-        marked(false),
-        onRuntime_(false),
         argumentsNotIncluded_(false)
 #ifdef DEBUG
        ,ready_(true)
 #endif
     {
         data.source = NULL;
     }
+    void incref() { refs++; }
+    void decref(JSRuntime *rt) {
+        JS_ASSERT(refs != 0);
+        if (--refs == 0)
+            destroy(rt);
+    }
     bool setSourceCopy(JSContext *cx,
                        const jschar *src,
                        uint32_t length,
                        bool argumentsNotIncluded,
                        SourceCompressionToken *tok);
     void setSource(const jschar *src, uint32_t length);
-    void attachToRuntime(JSRuntime *rt);
-    void mark() { marked = true; }
-    bool onRuntime() const { return onRuntime_; }
 #ifdef DEBUG
     bool ready() const { return ready_; }
 #endif
     bool hasSourceData() const { return !!data.source; }
     uint32_t length() const {
         JS_ASSERT(hasSourceData());
         return length_;
     }
     bool argumentsNotIncluded() const {
         JS_ASSERT(hasSourceData());
         return argumentsNotIncluded_;
     }
     JSFixedString *substring(JSContext *cx, uint32_t start, uint32_t stop);
     size_t sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf);
 
-    // For the GC.
-    static void sweep(JSRuntime *rt);
-
     // XDR handling
     template <XDRMode mode>
     bool performXDR(XDRState<mode> *xdr);
 
   private:
     void destroy(JSRuntime *rt);
     bool compressed() { return compressedLength_ != 0; }
 };
 
+class ScriptSourceHolder
+{
+    JSRuntime *rt;
+    ScriptSource *ss;
+  public:
+    ScriptSourceHolder(JSRuntime *rt, ScriptSource *ss)
+      : rt(rt),
+        ss(ss)
+    {
+        ss->incref();
+    }
+    ~ScriptSourceHolder()
+    {
+        ss->decref(rt);
+    }
+};
+
 #ifdef JS_THREADSAFE
 /*
  * Background thread to compress JS source code. This happens only while parsing
  * and bytecode generation is happening in the main thread. If needed, the
  * compiler waits for compression to complete before returning.
  *
  * To use it, you have to have a SourceCompressionToken, tok, with tok.ss and
  * tok.chars set to the proper values. When the SourceCompressionToken is
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -238,30 +238,30 @@ GlobalObject::initFunctionAndObjectClass
         jschar *source = InflateString(cx, rawSource, &sourceLen);
         if (!source)
             return NULL;
         ScriptSource *ss = cx->new_<ScriptSource>();
         if (!ss) {
             cx->free_(source);
             return NULL;
         }
+        ScriptSourceHolder ssh(cx->runtime, ss);
         ss->setSource(source, sourceLen);
 
         CompileOptions options(cx);
         options.setNoScriptRval(true)
                .setVersion(JSVERSION_DEFAULT);
         Rooted<JSScript*> script(cx, JSScript::Create(cx,
                                                       /* enclosingScope = */ NullPtr(),
                                                       /* savedCallerFun = */ false,
                                                       options,
                                                       /* staticLevel = */ 0,
                                                       ss,
                                                       0,
                                                       ss->length()));
-        ss->attachToRuntime(cx->runtime);
         if (!script || !JSScript::fullyInitTrivial(cx, script))
             return NULL;
 
         functionProto->initScript(script);
         functionProto->getType(cx)->interpretedFunction = functionProto;
         script->setFunction(functionProto);
 
         if (!functionProto->setSingletonType(cx))