Bug 1190074 - PerformanceGroup now uses mozilla::RefPtr. r=jandem
☠☠ backed out by 10ed10f5e05e ☠ ☠
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Mon, 27 Jul 2015 23:01:42 +0200
changeset 287540 00726df4997cf83e734d559316e6283e3cc1425f
parent 287539 85e86b04035c76ec2f30d5c91c1f67ca0c222c79
child 287541 487ee69fefe9c41dad46b9dd1335d6cacac79005
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1190074
milestone42.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 1190074 - PerformanceGroup now uses mozilla::RefPtr. r=jandem
js/src/jsapi.cpp
js/src/jsapi.h
js/src/vm/Interpreter.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -363,32 +363,32 @@ IterPerformanceStats(JSContext* cx,
             // reporting them doesn't really make sense.
             continue;
         }
         if (!c->performanceMonitoring.hasOwnGroup()) {
             // Don't report compartments that do not even have a PerformanceGroup.
             continue;
         }
         js::AutoCompartment autoCompartment(cx, compartment);
-        PerformanceGroup* ownGroup = compartment->performanceMonitoring.getOwnGroup(cx);
+        mozilla::RefPtr<PerformanceGroup> ownGroup = compartment->performanceMonitoring.getOwnGroup();
         if (ownGroup->data.ticks == 0) {
             // Don't report compartments that have never been used.
             continue;
         }
-        PerformanceGroup* sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx);
+        mozilla::RefPtr<PerformanceGroup> sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx);
         if (!(*walker)(cx,
                        ownGroup->data, ownGroup->uid, &sharedGroup->uid,
                        closure)) {
             // Issue in callback
             return false;
         }
     }
 
     // Finally, report the process stats
-    *processStats = rt->stopwatch.performance;
+    *processStats = rt->stopwatch.performance.getOwnGroup()->data;
     return true;
 }
 
 void
 AssertHeapIsIdle(JSRuntime* rt)
 {
     MOZ_ASSERT(!rt->isHeapBusy());
 }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -8,16 +8,17 @@
 
 #ifndef jsapi_h
 #define jsapi_h
 
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Range.h"
 #include "mozilla/RangedPtr.h"
+#include "mozilla/RefPtr.h"
 
 #include <stdarg.h>
 #include <stddef.h>
 #include <stdint.h>
 #include <stdio.h>
 
 #include "jsalloc.h"
 #include "jspubtd.h"
@@ -5509,67 +5510,68 @@ struct PerformanceGroup {
     void releaseStopwatch(uint64_t iteration, const AutoStopwatch* stopwatch) {
         if (iteration_ != iteration)
             return;
 
         MOZ_ASSERT(stopwatch == stopwatch_ || stopwatch_ == nullptr);
         stopwatch_ = nullptr;
     }
 
-    explicit PerformanceGroup(JSContext* cx, void* key);
-    ~PerformanceGroup()
-    {
-        MOZ_ASSERT(refCount_ == 0);
-    }
-  private:
+    // Refcounting. For use with mozilla::RefPtr.
+    void AddRef();
+    void Release();
+
+    // Construct a PerformanceGroup for a single compartment.
+    explicit PerformanceGroup(JSRuntime* rt);
+
+    // Construct a PerformanceGroup for a group of compartments.
+    explicit PerformanceGroup(JSContext* rt, void* key);
+
+private:
     PerformanceGroup& operator=(const PerformanceGroup&) = delete;
     PerformanceGroup(const PerformanceGroup&) = delete;
 
+    JSRuntime* runtime_;
+
     // The stopwatch currently monitoring the group,
     // or `nullptr` if none. Used ony for comparison.
     const AutoStopwatch* stopwatch_;
 
     // The current iteration of the event loop. If necessary,
     // may safely overflow.
     uint64_t iteration_;
 
     // The hash key for this PerformanceGroup.
     void* const key_;
 
-    // Increment/decrement the refcounter, return the updated value.
-    uint64_t incRefCount() {
-        MOZ_ASSERT(refCount_ + 1 > 0);
-        return ++refCount_;
-    }
-    uint64_t decRefCount() {
-        MOZ_ASSERT(refCount_ > 0);
-        return --refCount_;
-    }
-    friend struct PerformanceGroupHolder;
-
-private:
-    // A reference counter. Maintained by PerformanceGroupHolder.
+    // A reference counter.
     uint64_t refCount_;
+
+
+    // `true` if this PerformanceGroup may be shared by several
+    // compartments, `false` if it is dedicated to a single
+    // compartment.
+    const bool isSharedGroup_;
 };
 
 //
 // Each PerformanceGroupHolder handles:
 // - a reference-counted indirection towards a PerformanceGroup shared
 //   by several compartments
 // - a owned PerformanceGroup representing the performance of a single
 //   compartment.
 //
 struct PerformanceGroupHolder {
     // Get the shared group.
     // On first call, this causes a single Hashtable lookup.
     // Successive calls do not require further lookups.
     js::PerformanceGroup* getSharedGroup(JSContext*);
 
     // Get the own group.
-    js::PerformanceGroup* getOwnGroup(JSContext*);
+    js::PerformanceGroup* getOwnGroup();
 
     // `true` if the this holder is currently associated to a shared
     // PerformanceGroup, `false` otherwise. Use this method to avoid
     // instantiating a PerformanceGroup if you only need to get
     // available performance data.
     inline bool hasSharedGroup() const {
         return sharedGroup_ != nullptr;
     }
@@ -5579,34 +5581,32 @@ struct PerformanceGroupHolder {
 
     // Remove the link to the PerformanceGroup. This method is designed
     // as an invalidation mechanism if the JSCompartment changes nature
     // (new values of `isSystem()`, `principals()` or `addonId`).
     void unlink();
 
     explicit PerformanceGroupHolder(JSRuntime* runtime)
       : runtime_(runtime)
-      , sharedGroup_(nullptr)
-      , ownGroup_(nullptr)
     {   }
     ~PerformanceGroupHolder();
 
   private:
     // Return the key representing this PerformanceGroup in
     // Runtime::Stopwatch.
     // Do not deallocate the key.
     void* getHashKey(JSContext* cx);
 
     JSRuntime *runtime_;
 
     // The PerformanceGroups held by this object.
     // Initially set to `nullptr` until the first call to `getGroup`.
     // May be reset to `nullptr` by a call to `unlink`.
-    js::PerformanceGroup* sharedGroup_;
-    js::PerformanceGroup* ownGroup_;
+    mozilla::RefPtr<js::PerformanceGroup> sharedGroup_;
+    mozilla::RefPtr<js::PerformanceGroup> ownGroup_;
 };
 
 /**
  * Reset any stopwatch currently measuring.
  *
  * This function is designed to be called when we process a new event.
  */
 extern JS_PUBLIC_API(void)
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -384,115 +384,81 @@ class AutoStopwatch final
     // The context with which this object was initialized.
     // Non-null.
     JSContext* const cx_;
 
     // An indication of the number of times we have entered the event
     // loop.  Used only for comparison.
     uint64_t iteration_;
 
-    // `true` if this object is currently used to monitor performance
-    // for a shared PerformanceGroup, `false` otherwise, i.e. if the
-    // stopwatch mechanism is off or if another stopwatch is already
-    // in charge of monitoring for the same PerformanceGroup.
-    bool isMonitoringForGroup_;
-
-    // `true` if this object is currently used to monitor performance
-    // for a single compartment, `false` otherwise, i.e. if the
-    // stopwatch mechanism is off or if another stopwatch is already
-    // in charge of monitoring for the same PerformanceGroup.
-    bool isMonitoringForSelf_;
-
-    // `true` if this stopwatch is the topmost stopwatch on the stack
-    // for this event, `false` otherwise.
-    bool isMonitoringForTop_;
-
     // `true` if we are monitoring jank, `false` otherwise.
     bool isMonitoringJank_;
     // `true` if we are monitoring CPOW, `false` otherwise.
     bool isMonitoringCPOW_;
 
     // Timestamps captured while starting the stopwatch.
     uint64_t userTimeStart_;
     uint64_t systemTimeStart_;
     uint64_t CPOWTimeStart_;
 
+   // The performance group shared by this compartment and possibly
+   // others, or `nullptr` if another AutoStopwatch is already in
+   // charge of monitoring that group.
+   mozilla::RefPtr<js::PerformanceGroup> sharedGroup_;
+
+   // The toplevel group, representing the entire process, or `nullptr`
+   // if another AutoStopwatch is already in charge of monitoring that group.
+   mozilla::RefPtr<js::PerformanceGroup> topGroup_;
+
+   // The performance group specific to this compartment, or
+   // `nullptr` if another AutoStopwatch is already in charge of
+   // monitoring that group.
+   mozilla::RefPtr<js::PerformanceGroup> ownGroup_;
+
    public:
     // If the stopwatch is active, constructing an instance of
     // AutoStopwatch causes it to become the current owner of the
     // stopwatch.
     //
     // Previous owner is restored upon destruction.
     explicit inline AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : cx_(cx)
       , iteration_(0)
-      , isMonitoringForGroup_(false)
-      , isMonitoringForSelf_(false)
-      , isMonitoringForTop_(false)
       , isMonitoringJank_(false)
       , isMonitoringCPOW_(false)
       , userTimeStart_(0)
       , systemTimeStart_(0)
       , CPOWTimeStart_(0)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 
         JSCompartment* compartment = cx_->compartment();
         if (compartment->scheduledForDestruction)
             return;
 
         JSRuntime* runtime = cx_->runtime();
         iteration_ = runtime->stopwatch.iteration;
 
-        PerformanceGroup* sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx);
-        if (!sharedGroup) {
-            // Either this Runtime is not configured for Performance Monitoring, or we couldn't
-            // allocate the group, or there was a problem with the hashtable.
-            return;
-        }
-
-        if (!sharedGroup->hasStopwatch(iteration_)) {
-            // We are now in charge of monitoring this group for the tick,
-            // until destruction of `this` or until we enter a nested event
-            // loop and `iteration_` is incremented.
-            sharedGroup->acquireStopwatch(iteration_, this);
-            isMonitoringForGroup_ = true;
-        }
-
-        PerformanceGroup* ownGroup = nullptr;
-        if (runtime->stopwatch.isMonitoringPerCompartment()) {
-            // As above, but for the group representing just this compartment.
-            ownGroup = compartment->performanceMonitoring.getOwnGroup(cx);
-            if (!ownGroup->hasStopwatch(iteration_)) {
-                ownGroup->acquireStopwatch(iteration_, this);
-                isMonitoringForSelf_ = true;
-            }
-        }
-
-        if (runtime->stopwatch.isEmpty) {
-            // This is the topmost stopwatch on the stack.
-            // It will be in charge of updating the per-process
-            // performance data.
-            runtime->stopwatch.isEmpty = false;
-            isMonitoringForTop_ = true;
-
-            MOZ_ASSERT(isMonitoringForGroup_);
-        }
-
-        if (!isMonitoringForGroup_ && !isMonitoringForSelf_) {
+        sharedGroup_ = acquireGroup(compartment->performanceMonitoring.getSharedGroup(cx));
+        if (sharedGroup_)
+            topGroup_ = acquireGroup(runtime->stopwatch.performance.getOwnGroup());
+
+        if (runtime->stopwatch.isMonitoringPerCompartment())
+            ownGroup_ = acquireGroup(compartment->performanceMonitoring.getOwnGroup());
+
+        if (!sharedGroup_ && !ownGroup_) {
             // We are not in charge of monitoring anything.
-            // (isMonitoringForTop_ implies isMonitoringForGroup_,
-            // so we do not need to check it)
             return;
         }
 
         enter();
     }
-    ~AutoStopwatch() {
-        if (!isMonitoringForGroup_ && !isMonitoringForSelf_) {
+    ~AutoStopwatch()
+    {
+        if (!sharedGroup_ && !ownGroup_) {
             // We are not in charge of monitoring anything.
             // (isMonitoringForTop_ implies isMonitoringForGroup_,
             // so we do not need to check it)
             return;
         }
 
         JSCompartment* compartment = cx_->compartment();
         if (compartment->scheduledForDestruction)
@@ -500,41 +466,29 @@ class AutoStopwatch final
 
         JSRuntime* runtime = cx_->runtime();
         if (iteration_ != runtime->stopwatch.iteration) {
             // We have entered a nested event loop at some point.
             // Any information we may have is obsolete.
             return;
         }
 
+        releaseGroup(sharedGroup_);
+        releaseGroup(topGroup_);
+        releaseGroup(ownGroup_);
+
         // Finish and commit measures
         exit();
-
-        // Now release groups.
-        if (isMonitoringForGroup_) {
-            PerformanceGroup* sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx_);
-            MOZ_ASSERT(sharedGroup);
-            sharedGroup->releaseStopwatch(iteration_, this);
-        }
-
-        if (isMonitoringForSelf_) {
-            PerformanceGroup* ownGroup = compartment->performanceMonitoring.getOwnGroup(cx_);
-            MOZ_ASSERT(ownGroup);
-            ownGroup->releaseStopwatch(iteration_, this);
-        }
-
-        if (isMonitoringForTop_)
-            runtime->stopwatch.isEmpty = true;
     }
    private:
     void enter() {
         JSRuntime* runtime = cx_->runtime();
 
         if (runtime->stopwatch.isMonitoringCPOW()) {
-            CPOWTimeStart_ = runtime->stopwatch.performance.totalCPOWTime;
+            CPOWTimeStart_ = runtime->stopwatch.performance.getOwnGroup()->data.totalCPOWTime;
             isMonitoringCPOW_ = true;
         }
 
         if (runtime->stopwatch.isMonitoringJank()) {
             if (this->getTimes(runtime, &userTimeStart_, &systemTimeStart_)) {
                 isMonitoringJank_ = true;
             }
         }
@@ -558,69 +512,77 @@ class AutoStopwatch final
             }
             userTimeDelta = userTimeEnd - userTimeStart_;
             systemTimeDelta = systemTimeEnd - systemTimeStart_;
         }
 
         uint64_t CPOWTimeDelta = 0;
         if (isMonitoringCPOW_ && runtime->stopwatch.isMonitoringCPOW()) {
             // We were monitoring CPOW when we entered and we still are.
-            CPOWTimeDelta = runtime->stopwatch.performance.totalCPOWTime - CPOWTimeStart_;
+            CPOWTimeDelta = runtime->stopwatch.performance.getOwnGroup()->data.totalCPOWTime - CPOWTimeStart_;
 
         }
         commitDeltasToGroups(userTimeDelta, systemTimeDelta, CPOWTimeDelta);
     }
 
-    void commitDeltasToGroups(uint64_t userTimeDelta,
-                              uint64_t systemTimeDelta,
-                              uint64_t CPOWTimeDelta)
-    {
-        JSCompartment* compartment = cx_->compartment();
-
-        PerformanceGroup* sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx_);
-        MOZ_ASSERT(sharedGroup);
-        applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, sharedGroup->data);
-
-        if (isMonitoringForSelf_) {
-            PerformanceGroup* ownGroup = compartment->performanceMonitoring.getOwnGroup(cx_);
-            MOZ_ASSERT(ownGroup);
-            applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, ownGroup->data);
-        }
-
-        if (isMonitoringForTop_) {
-            JSRuntime* runtime = cx_->runtime();
-            applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, runtime->stopwatch.performance);
-        }
+    // Attempt to acquire a group
+    // If the group is `null` or if the group already has a stopwatch,
+    // do nothing and return `null`.
+    // Otherwise, bind the group to `this` for the current iteration
+    // and return `group`.
+    PerformanceGroup* acquireGroup(PerformanceGroup* group) {
+        if (!group)
+            return nullptr;
+
+        if (group->hasStopwatch(iteration_))
+            return nullptr;
+
+        group->acquireStopwatch(iteration_, this);
+        return group;
     }
 
-
-    void applyDeltas(uint64_t userTimeDelta,
-                     uint64_t systemTimeDelta,
-                     uint64_t CPOWTimeDelta,
-                     PerformanceData& data) const {
-
-        data.ticks++;
+    // Release a group.
+    // Noop if `group` is null or if `this` is not the stopwatch
+    // of `group` for the current iteration.
+    void releaseGroup(PerformanceGroup* group) {
+        if (group)
+            group->releaseStopwatch(iteration_, this);
+    }
+
+    void commitDeltasToGroups(uint64_t userTimeDelta, uint64_t systemTimeDelta,
+                              uint64_t CPOWTimeDelta) const {
+        applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, sharedGroup_);
+        applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, topGroup_);
+        applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, ownGroup_);
+    }
+
+    void applyDeltas(uint64_t userTimeDelta, uint64_t systemTimeDelta,
+                     uint64_t CPOWTimeDelta, PerformanceGroup* group) const {
+        if (!group)
+            return;
+
+        group->data.ticks++;
 
         uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta;
-        data.totalUserTime += userTimeDelta;
-        data.totalSystemTime += systemTimeDelta;
-        data.totalCPOWTime += CPOWTimeDelta;
+        group->data.totalUserTime += userTimeDelta;
+        group->data.totalSystemTime += systemTimeDelta;
+        group->data.totalCPOWTime += CPOWTimeDelta;
 
         // Update an array containing the number of times we have missed
         // at least 2^0 successive ms, 2^1 successive ms, ...
         // 2^i successive ms.
 
         // Duration of one frame, i.e. 16ms in museconds
         size_t i = 0;
         uint64_t duration = 1000;
         for (i = 0, duration = 1000;
-             i < ArrayLength(data.durations) && duration < totalTimeDelta;
+             i < ArrayLength(group->data.durations) && duration < totalTimeDelta;
              ++i, duration *= 2)
         {
-            data.durations[i]++;
+            group->data.durations[i]++;
         }
     }
 
     // Get the OS-reported time spent in userland/systemland, in
     // microseconds. On most platforms, this data is per-thread,
     // but on some platforms we need to fall back to per-process.
     bool getTimes(JSRuntime* runtime, uint64_t* userTime, uint64_t* systemTime) const {
         MOZ_ASSERT(userTime);
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -217,17 +217,18 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     parallelParsingEnabled_(true),
     autoWritableJitCodeActive_(false),
 #ifdef DEBUG
     enteredPolicy(nullptr),
 #endif
     largeAllocationFailureCallback(nullptr),
     oomCallback(nullptr),
     debuggerMallocSizeOf(ReturnZeroSize),
-    lastAnimationTime(0)
+    lastAnimationTime(0),
+    stopwatch(thisFromCtor())
 {
     setGCStoreBufferPtr(&gc.storeBuffer);
 
     liveRuntimesCount++;
 
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&onNewGlobalObjectWatchers);
 
@@ -925,50 +926,27 @@ js::PerformanceGroupHolder::getHashKey(J
 
     // As a fallback, put everything in the same PerformanceGroup.
     return nullptr;
 }
 
 void
 js::PerformanceGroupHolder::unlink()
 {
-    if (ownGroup_) {
-        js_delete(ownGroup_);
-        ownGroup_ = nullptr;
-    }
-
-    if (!sharedGroup_) {
-        // The group has never been instantiated.
-        return;
-    }
-
-    js::PerformanceGroup* group = sharedGroup_;
+    ownGroup_ = nullptr;
     sharedGroup_ = nullptr;
-
-    if (group->decRefCount() > 0) {
-        // The group has at least another owner.
-        return;
-    }
-
-
-    JSRuntime::Stopwatch::Groups::Ptr ptr =
-        runtime_->stopwatch.groups().lookup(group->key_);
-    MOZ_ASSERT(ptr);
-    runtime_->stopwatch.groups().remove(ptr);
-    js_delete(group);
 }
 
 PerformanceGroup*
-js::PerformanceGroupHolder::getOwnGroup(JSContext* cx)
+js::PerformanceGroupHolder::getOwnGroup()
 {
     if (ownGroup_)
         return ownGroup_;
 
-    ownGroup_ = runtime_->new_<PerformanceGroup>(cx, nullptr);
-    return ownGroup_;
+    return ownGroup_ = runtime_->new_<PerformanceGroup>(runtime_);
 }
 
 PerformanceGroup*
 js::PerformanceGroupHolder::getSharedGroup(JSContext* cx)
 {
     if (sharedGroup_)
         return sharedGroup_;
 
@@ -980,40 +958,67 @@ js::PerformanceGroupHolder::getSharedGro
     if (ptr) {
         sharedGroup_ = ptr->value();
         MOZ_ASSERT(sharedGroup_);
     } else {
         sharedGroup_ = runtime_->new_<PerformanceGroup>(cx, key);
         if (!sharedGroup_)
             return nullptr;
 
-        if (!runtime_->stopwatch.groups().add(ptr, key, sharedGroup_)) {
-            js_delete(sharedGroup_);
-            sharedGroup_ = nullptr;
-            return nullptr;
-        }
+        runtime_->stopwatch.groups().add(ptr, key, sharedGroup_);
     }
 
-    sharedGroup_->incRefCount();
-
     return sharedGroup_;
 }
 
 PerformanceData*
 js::GetPerformanceData(JSRuntime* rt)
 {
-    return &rt->stopwatch.performance;
+    return &rt->stopwatch.performance.getOwnGroup()->data;
 }
 
-js::PerformanceGroup::PerformanceGroup(JSContext* cx, void* key)
-  : uid(cx->runtime()->stopwatch.uniqueId())
-  , stopwatch_(nullptr)
-  , iteration_(0)
-  , key_(key)
-  , refCount_(0)
+js::PerformanceGroup::PerformanceGroup(JSRuntime* rt)
+  : uid(rt->stopwatch.uniqueId()),
+    runtime_(rt),
+    stopwatch_(nullptr),
+    iteration_(0),
+    key_(nullptr),
+    refCount_(0),
+    isSharedGroup_(false)
+{ }
+
+ js::PerformanceGroup::PerformanceGroup(JSContext* cx, void* key)
+   : uid(cx->runtime()->stopwatch.uniqueId()),
+     runtime_(cx->runtime()),
+     stopwatch_(nullptr),
+     iteration_(0),
+     key_(key),
+     refCount_(0),
+     isSharedGroup_(true)
+{ }
+
+void
+js::PerformanceGroup::AddRef()
 {
+    ++refCount_;
+}
+
+void
+js::PerformanceGroup::Release()
+{
+    MOZ_ASSERT(refCount_ > 0);
+    --refCount_;
+    if (refCount_ > 0)
+        return;
+    if (!isSharedGroup_)
+        return;
+
+    JSRuntime::Stopwatch::Groups::Ptr ptr = runtime_->stopwatch.groups().lookup(key_);
+    MOZ_ASSERT(ptr);
+    runtime_->stopwatch.groups().remove(ptr);
+    js_delete(this);
 }
 
 void
 JS_SetCurrentPerfGroupCallback(JSRuntime *rt, JSCurrentPerfGroupCallback cb)
 {
     rt->stopwatch.currentPerfGroupCallback = cb;
 }
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1508,69 +1508,62 @@ struct JSRuntime : public JS::shadow::Ru
                             js::DefaultHasher<void*>,
                             js::SystemAllocPolicy> Groups;
 
         Groups& groups() {
             return groups_;
         }
 
         /**
+         * Performance data on the entire runtime.
+         */
+        js::PerformanceGroupHolder performance;
+
+        /**
          * The number of times we have entered the event loop.
          * Used to reset counters whenever we enter the loop,
          * which may be caused either by having completed the
          * previous run of the event loop, or by entering a
          * nested loop.
          *
          * Always incremented by 1, may safely overflow.
          */
         uint64_t iteration;
 
         /**
-         * `true` if no stopwatch has been registered for the
-         * current run of the event loop, `false` until then.
-         */
-        bool isEmpty;
-
-        /**
-         * Performance data on the entire runtime.
-         */
-        js::PerformanceData performance;
-
-        /**
          * Callback used to ask the embedding to determine in which
          * Performance Group the current execution belongs. Typically, this is
          * used to regroup JSCompartments from several iframes from the same
          * page or from several compartments of the same addon into a single
          * Performance Group.
          *
          * May be `nullptr`, in which case we put all the JSCompartments
          * in the same PerformanceGroup.
          */
         JSCurrentPerfGroupCallback currentPerfGroupCallback;
 
-        Stopwatch()
-          : iteration(0)
-          , isEmpty(true)
+        Stopwatch(JSRuntime* runtime)
+          : performance(runtime)
+          , iteration(0)
           , currentPerfGroupCallback(nullptr)
           , isMonitoringJank_(false)
           , isMonitoringCPOW_(false)
           , isMonitoringPerCompartment_(false)
           , idCounter_(0)
         { }
 
         /**
          * Reset the stopwatch.
          *
          * This method is meant to be called whenever we start processing
          * an event, to ensure that stop any ongoing measurement that would
          * otherwise provide irrelevant results.
          */
         void reset() {
             ++iteration;
-            isEmpty = true;
         }
         /**
          * Activate/deactivate stopwatch measurement of jank.
          *
          * Noop if `value` is `true` and the stopwatch is already active,
          * or if `value` is `false` and the stopwatch is already inactive.
          *
          * Otherwise, any pending measurements are dropped, but previous