Bug 996785 - Allow multiple JS finalize callbacks (r=jonco)
authorBill McCloskey <wmccloskey@mozilla.com>
Fri, 16 May 2014 16:40:34 -0700
changeset 183648 cb9571f1400bc39a5fa52b2ef8c901733d40ab17
parent 183647 2882b91ad758b8d1ecc7c66246607315c75bb65b
child 183649 48a84f76656dea28f489c306212c17a4b5edbff8
push id6844
push userphilringnalda@gmail.com
push dateSun, 18 May 2014 01:12:08 +0000
treeherderfx-team@41a54c8add09 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs996785
milestone32.0a1
Bug 996785 - Allow multiple JS finalize callbacks (r=jonco)
js/src/gc/GCRuntime.h
js/src/gc/RootMarking.cpp
js/src/jsapi-tests/testGCFinalizeCallback.cpp
js/src/jsapi-tests/testIntern.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsgc.cpp
js/src/jsgc.h
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcprivate.h
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -80,16 +80,32 @@ struct ConservativeGCData
     }
 #endif
 
     bool hasStackToScan() const {
         return !!nativeStackTop;
     }
 };
 
+template<typename F>
+struct Callback {
+    F op;
+    void *data;
+
+    Callback()
+      : op(nullptr), data(nullptr)
+    {}
+    Callback(F op, void *data)
+      : op(op), data(data)
+    {}
+};
+
+template<typename F>
+class CallbackVector : public Vector<Callback<F>, 4, SystemAllocPolicy> {};
+
 class GCRuntime
 {
   public:
     GCRuntime(JSRuntime *rt);
     bool init(uint32_t maxbytes);
     void finish();
 
     void setGCZeal(uint8_t zeal, uint32_t frequency);
@@ -393,20 +409,20 @@ class GCRuntime
 
     js::Vector<JSObject *, 0, js::SystemAllocPolicy>   selectedForMarking;
 #endif
 
     bool                  validate;
     bool                  fullCompartmentChecks;
 
     JSGCCallback          gcCallback;
+    void                  *gcCallbackData;
+
     JS::GCSliceCallback   sliceCallback;
-    JSFinalizeCallback    finalizeCallback;
-
-    void                  *gcCallbackData;
+    CallbackVector<JSFinalizeCallback> finalizeCallbacks;
 
     /*
      * Malloc counter to measure memory pressure for GC scheduling. It runs
      * from   maxMallocBytes down to zero.
      */
     mozilla::Atomic<ptrdiff_t, mozilla::ReleaseAcquire>   mallocBytes;
 
     /*
@@ -416,19 +432,18 @@ class GCRuntime
     mozilla::Atomic<bool, mozilla::ReleaseAcquire>   mallocGCTriggered;
 
     /*
      * The trace operations to trace embedding-specific GC roots. One is for
      * tracing through black roots and the other is for tracing through gray
      * roots. The black/gray distinction is only relevant to the cycle
      * collector.
      */
-    typedef js::Vector<ExtraTracer, 4, js::SystemAllocPolicy> ExtraTracerVector;
-    ExtraTracerVector     blackRootTracers;
-    ExtraTracer           grayRootTracer;
+    CallbackVector<JSTraceDataOp> blackRootTracers;
+    Callback<JSTraceDataOp> grayRootTracer;
 
     /*
      * The GC can only safely decommit memory when the page size of the
      * running process matches the compiled arena size.
      */
     size_t                systemPageSize;
 
     /* The OS allocation granularity may not match the page size. */
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -780,17 +780,17 @@ js::gc::MarkRuntime(JSTracer *trc, bool 
         /*
          * The embedding can register additional roots here.
          *
          * We don't need to trace these in a minor GC because all pointers into
          * the nursery should be in the store buffer, and we want to avoid the
          * time taken to trace all these roots.
          */
         for (size_t i = 0; i < rt->gc.blackRootTracers.length(); i++) {
-            const ExtraTracer &e = rt->gc.blackRootTracers[i];
+            const Callback<JSTraceDataOp> &e = rt->gc.blackRootTracers[i];
             (*e.op)(trc, e.data);
         }
 
         /* During GC, we don't mark gray roots at this stage. */
         if (JSTraceDataOp op = rt->gc.grayRootTracer.op) {
             if (!IS_GC_MARKING_TRACER(trc))
                 (*op)(trc, rt->gc.grayRootTracer.data);
         }
--- a/js/src/jsapi-tests/testGCFinalizeCallback.cpp
+++ b/js/src/jsapi-tests/testGCFinalizeCallback.cpp
@@ -7,17 +7,17 @@
 static const unsigned BufferSize = 20;
 static unsigned FinalizeCalls = 0;
 static JSFinalizeStatus StatusBuffer[BufferSize];
 static bool IsCompartmentGCBuffer[BufferSize];
 
 BEGIN_TEST(testGCFinalizeCallback)
 {
     JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL);
-    JS_SetFinalizeCallback(rt, FinalizeCallback);
+    JS_AddFinalizeCallback(rt, FinalizeCallback, nullptr);
 
     /* Full GC, non-incremental. */
     FinalizeCalls = 0;
     JS_GC(rt);
     CHECK(rt->gc.isFull);
     CHECK(checkSingleGroup());
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(false));
@@ -111,17 +111,17 @@ BEGIN_TEST(testGCFinalizeCallback)
      * Make some use of the globals here to ensure the compiler doesn't optimize
      * them away in release builds, causing the compartments to be collected and
      * the test to fail.
      */
     CHECK(JS_IsGlobalObject(global1));
     CHECK(JS_IsGlobalObject(global2));
     CHECK(JS_IsGlobalObject(global3));
 
-    JS_SetFinalizeCallback(rt, nullptr);
+    JS_RemoveFinalizeCallback(rt, FinalizeCallback);
     return true;
 }
 
 bool checkSingleGroup()
 {
     CHECK(FinalizeCalls < BufferSize);
     CHECK(FinalizeCalls == 3);
     return true;
@@ -157,17 +157,17 @@ bool checkFinalizeIsCompartmentGC(bool i
 {
     for (unsigned i = 0; i < FinalizeCalls; ++i)
         CHECK(IsCompartmentGCBuffer[i] == isCompartmentGC);
 
     return true;
 }
 
 static void
-FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC)
+FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC, void *data)
 {
     if (FinalizeCalls < BufferSize) {
         StatusBuffer[FinalizeCalls] = status;
         IsCompartmentGCBuffer[FinalizeCalls] = isCompartmentGC;
     }
     ++FinalizeCalls;
 }
 END_TEST(testGCFinalizeCallback)
--- a/js/src/jsapi-tests/testIntern.cpp
+++ b/js/src/jsapi-tests/testIntern.cpp
@@ -28,21 +28,21 @@ struct StringWrapperStruct
     bool     strOk;
 } sw;
 
 BEGIN_TEST(testInternAcrossGC)
 {
     sw.str = JS_InternString(cx, "wrapped chars that another test shouldn't be using");
     sw.strOk = false;
     CHECK(sw.str);
-    JS_SetFinalizeCallback(rt, FinalizeCallback);
+    JS_AddFinalizeCallback(rt, FinalizeCallback, nullptr);
     JS_GC(rt);
     CHECK(sw.strOk);
     return true;
 }
 
 static void
-FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC)
+FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC, void *data)
 {
     if (status == JSFINALIZE_GROUP_START)
         sw.strOk = js::gc::IsStringMarked(&sw.str);
 }
 END_TEST(testInternAcrossGC)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1614,25 +1614,25 @@ JS::RemoveScriptRootRT(JSRuntime *rt, JS
     RemoveRoot(rt, (void *)rp);
     *rp = nullptr;
 }
 
 JS_PUBLIC_API(bool)
 JS_AddExtraGCRootsTracer(JSRuntime *rt, JSTraceDataOp traceOp, void *data)
 {
     AssertHeapIsIdle(rt);
-    return !!rt->gc.blackRootTracers.append(ExtraTracer(traceOp, data));
+    return !!rt->gc.blackRootTracers.append(Callback<JSTraceDataOp>(traceOp, data));
 }
 
 JS_PUBLIC_API(void)
 JS_RemoveExtraGCRootsTracer(JSRuntime *rt, JSTraceDataOp traceOp, void *data)
 {
     AssertHeapIsIdle(rt);
     for (size_t i = 0; i < rt->gc.blackRootTracers.length(); i++) {
-        ExtraTracer *e = &rt->gc.blackRootTracers[i];
+        Callback<JSTraceDataOp> *e = &rt->gc.blackRootTracers[i];
         if (e->op == traceOp && e->data == data) {
             rt->gc.blackRootTracers.erase(e);
             break;
         }
     }
 }
 
 #ifdef DEBUG
@@ -1902,21 +1902,34 @@ JS_MaybeGC(JSContext *cx)
 JS_PUBLIC_API(void)
 JS_SetGCCallback(JSRuntime *rt, JSGCCallback cb, void *data)
 {
     AssertHeapIsIdle(rt);
     rt->gc.gcCallback = cb;
     rt->gc.gcCallbackData = data;
 }
 
-JS_PUBLIC_API(void)
-JS_SetFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb)
+JS_PUBLIC_API(bool)
+JS_AddFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb, void *data)
 {
     AssertHeapIsIdle(rt);
-    rt->gc.finalizeCallback = cb;
+    return rt->gc.finalizeCallbacks.append(Callback<JSFinalizeCallback>(cb, data));
+}
+
+JS_PUBLIC_API(void)
+JS_RemoveFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb)
+{
+    for (Callback<JSFinalizeCallback> *p = rt->gc.finalizeCallbacks.begin();
+         p < rt->gc.finalizeCallbacks.end(); p++)
+    {
+        if (p->op == cb) {
+            rt->gc.finalizeCallbacks.erase(p);
+            break;
+        }
+    }
 }
 
 JS_PUBLIC_API(bool)
 JS_IsAboutToBeFinalized(JS::Heap<JSObject *> *objp)
 {
     return IsObjectAboutToBeFinalized(objp->unsafeGet());
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -703,17 +703,17 @@ typedef enum JSFinalizeStatus {
 
     /*
      * Called at the end of collection when everything has been swept.
      */
     JSFINALIZE_COLLECTION_END
 } JSFinalizeStatus;
 
 typedef void
-(* JSFinalizeCallback)(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartment);
+(* JSFinalizeCallback)(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartment, void *data);
 
 typedef bool
 (* JSInterruptCallback)(JSContext *cx);
 
 typedef void
 (* JSErrorReporter)(JSContext *cx, const char *message, JSErrorReport *report);
 
 #ifdef MOZ_TRACE_JSCALLS
@@ -2069,18 +2069,21 @@ extern JS_PUBLIC_API(void)
 JS_GC(JSRuntime *rt);
 
 extern JS_PUBLIC_API(void)
 JS_MaybeGC(JSContext *cx);
 
 extern JS_PUBLIC_API(void)
 JS_SetGCCallback(JSRuntime *rt, JSGCCallback cb, void *data);
 
+extern JS_PUBLIC_API(bool)
+JS_AddFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb, void *data);
+
 extern JS_PUBLIC_API(void)
-JS_SetFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb);
+JS_RemoveFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb);
 
 extern JS_PUBLIC_API(bool)
 JS_IsGCMarkingTracer(JSTracer *trc);
 
 /* For assertions only. */
 #ifdef JS_DEBUG
 extern JS_PUBLIC_API(bool)
 JS_IsMarkingGray(JSTracer *trc);
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1080,17 +1080,16 @@ GCRuntime::GCRuntime(JSRuntime *rt) :
     nextScheduled(0),
     deterministicOnly(false),
     incrementalLimit(0),
 #endif
     validate(true),
     fullCompartmentChecks(false),
     gcCallback(nullptr),
     sliceCallback(nullptr),
-    finalizeCallback(nullptr),
     mallocBytes(0),
     mallocGCTriggered(false),
     scriptAndCountsVector(nullptr),
     alwaysPreserveCode(false),
 #ifdef DEBUG
     noGCOrAllocationCheck(0),
 #endif
     lock(nullptr),
@@ -3820,18 +3819,21 @@ GCRuntime::beginSweepingZoneGroup()
     }
 
     validateIncrementalMarking();
 
     FreeOp fop(rt, sweepOnBackgroundThread);
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_START);
-        if (finalizeCallback)
-            finalizeCallback(&fop, JSFINALIZE_GROUP_START, !isFull /* unused */);
+        for (Callback<JSFinalizeCallback> *p = rt->gc.finalizeCallbacks.begin();
+             p < rt->gc.finalizeCallbacks.end(); p++)
+        {
+            p->op(&fop, JSFINALIZE_GROUP_START, !isFull /* unused */, p->data);
+        }
     }
 
     if (sweepingAtoms) {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_ATOMS);
         rt->sweepAtoms();
     }
 
     /* Prune out dead views from ArrayBuffer's view lists. */
@@ -3913,18 +3915,21 @@ GCRuntime::beginSweepingZoneGroup()
     }
 
     finalizePhase = 0;
     sweepZone = currentZoneGroup;
     sweepKindIndex = 0;
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_END);
-        if (finalizeCallback)
-            finalizeCallback(&fop, JSFINALIZE_GROUP_END, !isFull /* unused */);
+        for (Callback<JSFinalizeCallback> *p = rt->gc.finalizeCallbacks.begin();
+             p < rt->gc.finalizeCallbacks.end(); p++)
+        {
+            p->op(&fop, JSFINALIZE_GROUP_END, !isFull /* unused */, p->data);
+        }
     }
 }
 
 void
 GCRuntime::endSweepingZoneGroup()
 {
     /* Update the GC state for zones we have swept and unlink the list. */
     for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
@@ -4131,18 +4136,21 @@ GCRuntime::endSweepPhase(JSGCInvocationK
             AutoLockGC lock(rt);
             ExpireChunksAndArenas(rt, gckind == GC_SHRINK);
         }
     }
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_END);
 
-        if (finalizeCallback)
-            finalizeCallback(&fop, JSFINALIZE_COLLECTION_END, !isFull);
+        for (Callback<JSFinalizeCallback> *p = rt->gc.finalizeCallbacks.begin();
+             p < rt->gc.finalizeCallbacks.end(); p++)
+        {
+            p->op(&fop, JSFINALIZE_COLLECTION_END, !isFull, p->data);
+        }
 
         /* If we finished a full GC, then the gray bits are correct. */
         if (isFull)
             grayBitsValid = true;
     }
 
     /* Set up list of zones for sweeping of background things. */
     JS_ASSERT(!sweepingZones);
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -46,28 +46,16 @@ unsigned GetCPUCount();
 
 enum HeapState {
     Idle,             // doing nothing with the GC heap
     Tracing,          // tracing the GC heap without collecting, e.g. IterateCompartments()
     MajorCollecting,  // doing a GC of the major heap
     MinorCollecting   // doing a GC of the minor heap (nursery)
 };
 
-struct ExtraTracer {
-    JSTraceDataOp op;
-    void *data;
-
-    ExtraTracer()
-      : op(nullptr), data(nullptr)
-        {}
-    ExtraTracer(JSTraceDataOp op, void *data)
-      : op(op), data(data)
-        {}
-};
-
 namespace jit {
     class JitCode;
 }
 
 namespace gc {
 
 enum State {
     NO_INCREMENTAL,
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -809,17 +809,20 @@ XPCJSRuntime::CustomGCCallback(JSGCStatu
     }
 
     nsTArray<xpcGCCallback> callbacks(extraGCCallbacks);
     for (uint32_t i = 0; i < callbacks.Length(); ++i)
         callbacks[i](status);
 }
 
 /* static */ void
-XPCJSRuntime::FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC)
+XPCJSRuntime::FinalizeCallback(JSFreeOp *fop,
+                               JSFinalizeStatus status,
+                               bool isCompartmentGC,
+                               void *data)
 {
     XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance();
     if (!self)
         return;
 
     switch (status) {
         case JSFINALIZE_GROUP_START:
         {
@@ -1576,17 +1579,17 @@ ReloadPrefsCallback(const char *pref, vo
 
 XPCJSRuntime::~XPCJSRuntime()
 {
     // This destructor runs before ~CycleCollectedJSRuntime, which does the
     // actual JS_DestroyRuntime() call. But destroying the runtime triggers
     // one final GC, which can call back into the runtime with various
     // callback if we aren't careful. Null out the relevant callbacks.
     js::SetActivityCallback(Runtime(), nullptr, nullptr);
-    JS_SetFinalizeCallback(Runtime(), nullptr);
+    JS_RemoveFinalizeCallback(Runtime(), FinalizeCallback);
 
     // Clear any pending exception.  It might be an XPCWrappedJS, and if we try
     // to destroy it later we will crash.
     SetPendingException(nullptr);
 
     JS::SetGCSliceCallback(Runtime(), mPrevGCSliceCallback);
 
     xpc_DelocalizeRuntime(Runtime());
@@ -3155,17 +3158,17 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
     JS_SetNativeStackQuota(runtime,
                            kStackQuota,
                            kStackQuota - kSystemCodeBuffer,
                            kStackQuota - kSystemCodeBuffer - kTrustedScriptBuffer);
 
     JS_SetDestroyCompartmentCallback(runtime, CompartmentDestroyedCallback);
     JS_SetCompartmentNameCallback(runtime, CompartmentNameCallback);
     mPrevGCSliceCallback = JS::SetGCSliceCallback(runtime, GCSliceCallback);
-    JS_SetFinalizeCallback(runtime, FinalizeCallback);
+    JS_AddFinalizeCallback(runtime, FinalizeCallback, nullptr);
     JS_SetWrapObjectCallbacks(runtime, &WrapObjectCallbacks);
     js::SetPreserveWrapperCallback(runtime, PreserveWrapper);
 #ifdef MOZ_CRASHREPORTER
     JS_EnumerateDiagnosticMemoryRegions(DiagnosticMemoryCallback);
 #endif
 #ifdef MOZ_ENABLE_PROFILER_SPS
     if (PseudoStack *stack = mozilla_get_pseudo_stack())
         stack->sampleRuntime(runtime);
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -511,17 +511,20 @@ public:
     void EndCycleCollectionCallback(mozilla::CycleCollectorResults &aResults) MOZ_OVERRIDE;
     void DispatchDeferredDeletion(bool continuation) MOZ_OVERRIDE;
 
     void CustomGCCallback(JSGCStatus status) MOZ_OVERRIDE;
     bool CustomContextCallback(JSContext *cx, unsigned operation) MOZ_OVERRIDE;
     static void GCSliceCallback(JSRuntime *rt,
                                 JS::GCProgress progress,
                                 const JS::GCDescription &desc);
-    static void FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC);
+    static void FinalizeCallback(JSFreeOp *fop,
+                                 JSFinalizeStatus status,
+                                 bool isCompartmentGC,
+                                 void *data);
 
     inline void AddVariantRoot(XPCTraceableVariant* variant);
     inline void AddWrappedJSRoot(nsXPCWrappedJS* wrappedJS);
     inline void AddObjectHolderRoot(XPCJSObjectHolder* holder);
 
     static void SuspectWrappedNative(XPCWrappedNative *wrapper,
                                      nsCycleCollectionNoteRootCallback &cb);