Bug 1341321 - Require runtimes to be single threaded when using a Debugger, r=jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 01 Mar 2017 07:15:50 -0700
changeset 345320 e297bafab4ae9d6320bff24d9aa02202c1a33e10
parent 345319 33c9d4c02376826733a4a35687e1e8be21b58f4d
child 345321 39a67f6289c441fd3d1ae4726697c87dd412d8fb
push id31436
push userkwierso@gmail.com
push dateThu, 02 Mar 2017 01:18:52 +0000
treeherdermozilla-central@e91de6fb2b3d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1341321
milestone54.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 1341321 - Require runtimes to be single threaded when using a Debugger, r=jandem.
js/src/jit-test/tests/basic/bug1341321.js
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jscompartment.cpp
js/src/shell/js.cpp
js/src/vm/Debugger.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1341321.js
@@ -0,0 +1,20 @@
+if (helperThreadCount() == 0)
+    quit();
+
+// The new Debugger here should throw but we don't have a way to verify this
+// (exceptions that worker threads throw do not cause the test to fail).
+evalInCooperativeThread('cooperativeYield(); var dbg = new Debugger();');
+
+var dbg = new Debugger;
+assertEq(dbg.addAllGlobalsAsDebuggees(), undefined);
+
+function assertThrows(f) {
+    var exception = false;
+    try { f(); } catch (e) { exception = true; }
+    assertEq(exception, true);
+}
+
+var dbg = new Debugger;
+dbg.onNewGlobalObject = function(global) {};
+assertThrows(() => evalInCooperativeThread("var x = 3"));
+assertThrows(cooperativeYield);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -560,16 +560,25 @@ JS_EndRequest(JSContext* cx)
 }
 
 JS_PUBLIC_API(JSRuntime*)
 JS_GetParentRuntime(JSContext* cx)
 {
     return cx->runtime()->parentRuntime ? cx->runtime()->parentRuntime : cx->runtime();
 }
 
+JS_PUBLIC_API(void)
+JS::SetSingleThreadedExecutionCallbacks(JSContext* cx,
+                                        BeginSingleThreadedExecutionCallback begin,
+                                        EndSingleThreadedExecutionCallback end)
+{
+    cx->runtime()->beginSingleThreadedExecutionCallback = begin;
+    cx->runtime()->endSingleThreadedExecutionCallback = end;
+}
+
 JS_PUBLIC_API(JSVersion)
 JS_GetVersion(JSContext* cx)
 {
     return VersionNumber(cx->findVersion());
 }
 
 JS_PUBLIC_API(void)
 JS_SetVersionForCompartment(JSCompartment* compartment, JSVersion version)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1028,16 +1028,41 @@ extern JS_PUBLIC_API(void)
 JS_BeginRequest(JSContext* cx);
 
 extern JS_PUBLIC_API(void)
 JS_EndRequest(JSContext* cx);
 
 extern JS_PUBLIC_API(void)
 JS_SetFutexCanWait(JSContext* cx);
 
+namespace JS {
+
+// Single threaded execution callbacks are used to notify API clients that a
+// feature is in use on a context's runtime that is not yet compatible with
+// cooperatively multithreaded execution.
+//
+// Between a call to BeginSingleThreadedExecutionCallback and a corresponding
+// call to EndSingleThreadedExecutionCallback, only one thread at a time may
+// enter compartments in the runtime. The begin callback may yield as necessary
+// to permit other threads to finish up what they're doing, while the end
+// callback may not yield or otherwise operate on the runtime (it may be called
+// during GC).
+//
+// These callbacks may be left unspecified for runtimes which only ever have a
+// single context.
+typedef void (*BeginSingleThreadedExecutionCallback)(JSContext* cx);
+typedef void (*EndSingleThreadedExecutionCallback)(JSContext* cx);
+
+extern JS_PUBLIC_API(void)
+SetSingleThreadedExecutionCallbacks(JSContext* cx,
+                                    BeginSingleThreadedExecutionCallback begin,
+                                    EndSingleThreadedExecutionCallback end);
+
+} // namespace JS
+
 namespace js {
 
 void
 AssertHeapIsIdle();
 
 } /* namespace js */
 
 class MOZ_RAII JSAutoRequest
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -1159,19 +1159,18 @@ JSCompartment::updateDebuggerObservesCov
 {
     bool previousState = debuggerObservesCoverage();
     updateDebuggerObservesFlag(DebuggerObservesCoverage);
     if (previousState == debuggerObservesCoverage())
         return;
 
     if (debuggerObservesCoverage()) {
         // Interrupt any running interpreter frame. The scriptCounts are
-        // allocated on demand when a script resume its execution.
+        // allocated on demand when a script resumes its execution.
         JSContext* cx = TlsContext.get();
-        MOZ_ASSERT(zone()->group()->ownedByCurrentThread());
         for (ActivationIterator iter(cx); !iter.done(); ++iter) {
             if (iter->isInterpreter())
                 iter->asInterpreter()->enableInterruptsUnconditionally();
         }
         return;
     }
 
     // If code coverage is enabled by any other means, keep it.
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3379,23 +3379,25 @@ EvalInContext(JSContext* cx, unsigned ar
 
 struct CooperationState
 {
     CooperationState()
       : lock(mutexid::ShellThreadCooperation)
       , idle(false)
       , numThreads(0)
       , yieldCount(0)
+      , singleThreaded(false)
     {}
 
     Mutex lock;
     ConditionVariable cvar;
     bool idle;
     size_t numThreads;
     uint64_t yieldCount;
+    bool singleThreaded;
 };
 static CooperationState* cooperationState = nullptr;
 
 static void
 CooperativeBeginWait(JSContext* cx)
 {
     MOZ_ASSERT(cx == TlsContext.get());
     JS_YieldCooperativeContext(cx);
@@ -3431,34 +3433,68 @@ CooperativeYield()
     }
 }
 
 static bool
 CooperativeYieldThread(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    if (!cooperationState) {
-        JS_ReportErrorASCII(cx, "No cooperative threads have been created");
+    if (!cx->runtime()->gc.canChangeActiveContext(cx)) {
+        JS_ReportErrorASCII(cx, "Cooperating multithreading context switches are not currently allowed");
         return false;
     }
 
     if (GetShellContext(cx)->isWorker) {
         JS_ReportErrorASCII(cx, "Worker threads cannot yield");
         return false;
     }
 
+    if (cooperationState->singleThreaded) {
+        JS_ReportErrorASCII(cx, "Yielding is not allowed while single threaded");
+        return false;
+    }
+
     CooperativeBeginWait(cx);
     CooperativeYield();
     CooperativeEndWait(cx);
 
     args.rval().setUndefined();
     return true;
 }
 
+static void
+CooperativeBeginSingleThreadedExecution(JSContext* cx)
+{
+    MOZ_ASSERT(!cooperationState->singleThreaded);
+
+    // Yield until all other threads have exited any zone groups they are in.
+    while (true) {
+        bool done = true;
+        for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next()) {
+            if (!group->ownedByCurrentThread() && group->ownerContext().context())
+                done = false;
+        }
+        if (done)
+            break;
+        CooperativeBeginWait(cx);
+        CooperativeYield();
+        CooperativeEndWait(cx);
+    }
+
+    cooperationState->singleThreaded = true;
+}
+
+static void
+CooperativeEndSingleThreadedExecution(JSContext* cx)
+{
+    if (cooperationState)
+        cooperationState->singleThreaded = false;
+}
+
 struct WorkerInput
 {
     JSRuntime* parentRuntime;
     JSContext* siblingContext;
     char16_t* chars;
     size_t length;
 
     WorkerInput(JSRuntime* parentRuntime, char16_t* chars, size_t length)
@@ -3616,16 +3652,21 @@ EvalInThread(JSContext* cx, unsigned arg
         return false;
     }
 
     if (cooperative && !cx->runtime()->gc.canChangeActiveContext(cx)) {
         JS_ReportErrorASCII(cx, "Cooperating multithreading context switches are not currently allowed");
         return false;
     }
 
+    if (cooperative && cooperationState->singleThreaded) {
+        JS_ReportErrorASCII(cx, "Creating cooperative threads is not allowed while single threaded");
+        return false;
+    }
+
     if (!args[0].toString()->ensureLinear(cx))
         return false;
 
     if (!workerThreadsLock) {
         workerThreadsLock = js_new<Mutex>(mutexid::ShellWorkerThreads);
         if (!workerThreadsLock) {
             ReportOutOfMemory(cx);
             return false;
@@ -3647,18 +3688,16 @@ EvalInThread(JSContext* cx, unsigned arg
         ? js_new<WorkerInput>(cx, chars, str->length())
         : js_new<WorkerInput>(JS_GetParentRuntime(cx), chars, str->length());
     if (!input) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     if (cooperative) {
-        if (!cooperationState)
-            cooperationState = js_new<CooperationState>();
         cooperationState->numThreads++;
         CooperativeBeginWait(cx);
     }
 
     auto thread = js_new<Thread>(Thread::Options().setStackSize(gMaxStackSize + 128 * 1024));
     if (!thread || !thread->init(WorkerMain, input)) {
         ReportOutOfMemory(cx);
         if (cooperative) {
@@ -3895,17 +3934,17 @@ KillWorkerThreads(JSContext* cx)
         }
         thread->join();
     }
 
     js_delete(workerThreadsLock);
     workerThreadsLock = nullptr;
 
     // Yield until all other cooperative threads in the main runtime finish.
-    while (cooperationState && cooperationState->numThreads) {
+    while (cooperationState->numThreads) {
         CooperativeBeginWait(cx);
         CooperativeYield();
         CooperativeEndWait(cx);
     }
 
     js_delete(cooperationState);
     cooperationState = nullptr;
 }
@@ -8364,16 +8403,21 @@ main(int argc, char** argv, char** envp)
         JS_SetGCParameter(cx, JSGC_DYNAMIC_HEAP_GROWTH, 1);
         JS_SetGCParameter(cx, JSGC_DYNAMIC_MARK_SLICE, 1);
         JS_SetGCParameter(cx, JSGC_SLICE_TIME_BUDGET, 10);
     }
 #endif
 
     js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
 
+    cooperationState = js_new<CooperationState>();
+    JS::SetSingleThreadedExecutionCallbacks(cx,
+                                            CooperativeBeginSingleThreadedExecution,
+                                            CooperativeEndSingleThreadedExecution);
+
     result = Shell(cx, &op, envp);
 
 #ifdef DEBUG
     if (OOM_printAllocationCount)
         printf("OOM max count: %" PRIu64 "\n", js::oom::counter);
 #endif
 
     JS::SetLargeAllocationFailureCallback(cx, nullptr, nullptr);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -708,16 +708,19 @@ Debugger::~Debugger()
     /*
      * Since the inactive state for this link is a singleton cycle, it's always
      * safe to apply JS_REMOVE_LINK to it, regardless of whether we're in the list or not.
      *
      * We don't have to worry about locking here since Debugger is not
      * background finalized.
      */
     JS_REMOVE_LINK(&onNewGlobalObjectWatchersLink);
+
+    JSContext* cx = TlsContext.get();
+    cx->runtime()->endSingleThreadedExecution(cx);
 }
 
 bool
 Debugger::init(JSContext* cx)
 {
     if (!debuggees.init() ||
         !debuggeeZones.init() ||
         !frames.init() ||
@@ -2399,22 +2402,17 @@ class MOZ_RAII ExecutionObservableCompar
                                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : compartments_(cx),
         zones_(cx)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     }
 
     bool init() { return compartments_.init() && zones_.init(); }
-    bool add(JSCompartment* comp) {
-        // The current cx should have exclusive access to observed content,
-        // since debuggees must be in the same zone group as ther debugger.
-        MOZ_ASSERT(comp->zone()->group() == TlsContext.get()->zone()->group());
-        return compartments_.put(comp) && zones_.put(comp->zone());
-    }
+    bool add(JSCompartment* comp) { return compartments_.put(comp) && zones_.put(comp->zone()); }
 
     typedef HashSet<JSCompartment*>::Range CompartmentRange;
     const HashSet<JSCompartment*>* compartments() const { return &compartments_; }
 
     const HashSet<Zone*>* zones() const { return &zones_; }
     bool shouldRecompileOrInvalidate(JSScript* script) const {
         return script->hasBaselineScript() && compartments_.has(script->compartment());
     }
@@ -2498,19 +2496,16 @@ class MOZ_RAII ExecutionObservableScript
 {
     RootedScript script_;
 
   public:
     ExecutionObservableScript(JSContext* cx, JSScript* script
                               MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : script_(cx, script)
     {
-        // The current cx should have exclusive access to observed content,
-        // since debuggees must be in the same zone group as ther debugger.
-        MOZ_ASSERT(singleZone()->group() == cx->zone()->group());
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     }
 
     Zone* singleZone() const { return script_->compartment()->zone(); }
     JSScript* singleScriptForZoneInvalidation() const { return script_; }
     bool shouldRecompileOrInvalidate(JSScript* script) const {
         return script->hasBaselineScript() && script == script_;
     }
@@ -3931,21 +3926,34 @@ Debugger::construct(JSContext* cx, unsig
      */
     RootedNativeObject obj(cx, NewNativeObjectWithGivenProto(cx, &Debugger::class_, proto));
     if (!obj)
         return false;
     for (unsigned slot = JSSLOT_DEBUG_PROTO_START; slot < JSSLOT_DEBUG_PROTO_STOP; slot++)
         obj->setReservedSlot(slot, proto->getReservedSlot(slot));
     obj->setReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE, NullValue());
 
+    // Debuggers currently require single threaded execution. A debugger may be
+    // used to debug content in other zone groups, and may be used to observe
+    // all activity in the runtime via hooks like OnNewGlobalObject.
+    if (!cx->runtime()->beginSingleThreadedExecution(cx)) {
+        JS_ReportErrorASCII(cx, "Cannot ensure single threaded execution in Debugger");
+        return false;
+    }
+
     Debugger* debugger;
     {
         /* Construct the underlying C++ object. */
         auto dbg = cx->make_unique<Debugger>(cx, obj.get());
-        if (!dbg || !dbg->init(cx))
+        if (!dbg) {
+            JS::AutoSuppressGCAnalysis nogc; // Suppress warning about |dbg|.
+            cx->runtime()->endSingleThreadedExecution(cx);
+            return false;
+        }
+        if (!dbg->init(cx))
             return false;
 
         debugger = dbg.release();
         obj->setPrivate(debugger); // owns the released pointer
     }
 
     /* Add the initial debuggees, if any. */
     for (unsigned i = 0; i < args.length(); i++) {
@@ -3957,23 +3965,16 @@ Debugger::construct(JSContext* cx, unsig
 
     args.rval().setObject(*obj);
     return true;
 }
 
 bool
 Debugger::addDebuggeeGlobal(JSContext* cx, Handle<GlobalObject*> global)
 {
-    // Debuggers are required to be in the same zone group as their debuggees.
-    // The debugger must be able to observe all activity in the debuggee
-    // compartment, which requires that its thread have exclusive access to
-    // that compartment's contents.
-    MOZ_ASSERT(cx->zone() == object->zone());
-    MOZ_RELEASE_ASSERT(global->zone()->group() == cx->zone()->group());
-
     if (debuggees.has(global))
         return true;
 
     // Callers should generally be unable to get a reference to a debugger-
     // invisible global in order to pass it to addDebuggee. But this is possible
     // with certain testing aides we expose in the shell, so just make addDebuggee
     // throw in that case.
     JSCompartment* debuggeeCompartment = global->compartment();
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -92,16 +92,20 @@ ReturnZeroSize(const void* p)
 
 JSRuntime::JSRuntime(JSRuntime* parentRuntime)
   : parentRuntime(parentRuntime),
 #ifdef DEBUG
     updateChildRuntimeCount(parentRuntime),
 #endif
     activeContext_(nullptr),
     activeContextChangeProhibited_(0),
+    singleThreadedExecutionRequired_(0),
+    startingSingleThreadedExecution_(false),
+    beginSingleThreadedExecutionCallback(nullptr),
+    endSingleThreadedExecutionCallback(nullptr),
     profilerSampleBufferGen_(0),
     profilerSampleBufferLapCount_(1),
     telemetryCallback(nullptr),
     getIncumbentGlobalCallback(nullptr),
     enqueuePromiseJobCallback(nullptr),
     enqueuePromiseJobCallbackData(nullptr),
     promiseRejectionTrackerCallback(nullptr),
     promiseRejectionTrackerCallbackData(nullptr),
@@ -303,16 +307,17 @@ JSRuntime::destroyRuntime()
         JS::PrepareForFullGC(cx);
         gc.gc(GC_NORMAL, JS::gcreason::DESTROY_RUNTIME);
     }
 
     AutoNoteSingleThreadedRegion anstr;
 
     MOZ_ASSERT(ionLazyLinkListSize_ == 0);
     MOZ_ASSERT(ionLazyLinkList().isEmpty());
+    MOZ_ASSERT(!singleThreadedExecutionRequired_);
 
     MOZ_ASSERT(!hasHelperThreadZones());
     AutoLockForExclusiveAccess lock(this);
 
     /*
      * Even though all objects in the compartment are dead, we may have keep
      * some filenames around because of gcKeepAtoms.
      */
@@ -329,51 +334,92 @@ JSRuntime::destroyRuntime()
 
     js_free(defaultLocale);
     js_delete(jitRuntime_.ref());
 
     DebugOnly<size_t> oldCount = liveRuntimesCount--;
     MOZ_ASSERT(oldCount > 0);
 }
 
+static void
+CheckCanChangeActiveContext(JSRuntime* rt)
+{
+    MOZ_RELEASE_ASSERT(!rt->activeContextChangeProhibited());
+    MOZ_RELEASE_ASSERT(!rt->activeContext() || rt->gc.canChangeActiveContext(rt->activeContext()));
+
+    if (rt->singleThreadedExecutionRequired()) {
+        for (ZoneGroupsIter group(rt); !group.done(); group.next())
+            MOZ_RELEASE_ASSERT(group->ownerContext().context() == nullptr);
+    }
+}
+
 void
 JSRuntime::setActiveContext(JSContext* cx)
 {
+    CheckCanChangeActiveContext(this);
     MOZ_ASSERT_IF(cx, cx->isCooperativelyScheduled());
-    MOZ_RELEASE_ASSERT(!activeContextChangeProhibited());
-    MOZ_RELEASE_ASSERT(!activeContext() || gc.canChangeActiveContext(activeContext()));
 
     activeContext_ = cx;
 }
 
 void
 JSRuntime::setNewbornActiveContext(JSContext* cx)
 {
-    MOZ_ASSERT_IF(cx, cx->isCooperativelyScheduled());
-    MOZ_RELEASE_ASSERT(!activeContextChangeProhibited());
-    MOZ_RELEASE_ASSERT(!activeContext());
+    CheckCanChangeActiveContext(this);
 
     activeContext_ = cx;
 
     AutoEnterOOMUnsafeRegion oomUnsafe;
     if (!cooperatingContexts().append(cx))
         oomUnsafe.crash("Add cooperating context");
 }
 
 void
 JSRuntime::deleteActiveContext(JSContext* cx)
 {
+    CheckCanChangeActiveContext(this);
     MOZ_ASSERT(cx == activeContext());
-    MOZ_RELEASE_ASSERT(!activeContextChangeProhibited());
-    MOZ_RELEASE_ASSERT(gc.canChangeActiveContext(cx));
 
     js_delete_poison(cx);
     activeContext_ = nullptr;
 }
 
+bool
+JSRuntime::beginSingleThreadedExecution(JSContext* cx)
+{
+    if (singleThreadedExecutionRequired_ == 0) {
+        if (startingSingleThreadedExecution_)
+            return false;
+        startingSingleThreadedExecution_ = true;
+        if (beginSingleThreadedExecutionCallback)
+            beginSingleThreadedExecutionCallback(cx);
+        MOZ_ASSERT(startingSingleThreadedExecution_);
+        startingSingleThreadedExecution_ = false;
+    }
+
+    singleThreadedExecutionRequired_++;
+
+    for (ZoneGroupsIter group(this); !group.done(); group.next()) {
+        MOZ_RELEASE_ASSERT(group->ownedByCurrentThread() ||
+                           group->ownerContext().context() == nullptr);
+    }
+
+    return true;
+}
+
+void
+JSRuntime::endSingleThreadedExecution(JSContext* cx)
+{
+    MOZ_ASSERT(singleThreadedExecutionRequired_);
+    if (--singleThreadedExecutionRequired_ == 0) {
+        if (endSingleThreadedExecutionCallback)
+            endSingleThreadedExecutionCallback(cx);
+    }
+}
+
 void
 JSRuntime::addTelemetry(int id, uint32_t sample, const char* key)
 {
     if (telemetryCallback)
         (*telemetryCallback)(id, sample, key);
 }
 
 void
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -337,16 +337,24 @@ struct JSRuntime : public js::MallocProv
 
     // All contexts participating in cooperative scheduling. All threads other
     // than |activeContext_| are suspended.
     js::ActiveThreadData<js::Vector<js::CooperatingContext, 4, js::SystemAllocPolicy>> cooperatingContexts_;
 
     // Count of AutoProhibitActiveContextChange instances on the active context.
     mozilla::Atomic<size_t> activeContextChangeProhibited_;
 
+    // Count of beginSingleThreadedExecution() calls that have occurred with no
+    // matching endSingleThreadedExecution().
+    mozilla::Atomic<size_t> singleThreadedExecutionRequired_;
+
+    // Whether some thread has called beginSingleThreadedExecution() and we are
+    // in the associated callback (which may execute JS on other threads).
+    js::ActiveThreadData<bool> startingSingleThreadedExecution_;
+
   public:
     JSContext* activeContext() const { return activeContext_; }
     const void* addressOfActiveContext() { return &activeContext_; }
 
     void setActiveContext(JSContext* cx);
     void setNewbornActiveContext(JSContext* cx);
     void deleteActiveContext(JSContext* cx);
 
@@ -369,16 +377,27 @@ struct JSRuntime : public js::MallocProv
 
         ~AutoProhibitActiveContextChange()
         {
             rt->activeContextChangeProhibited_--;
         }
     };
 
     bool activeContextChangeProhibited() { return activeContextChangeProhibited_; }
+    bool singleThreadedExecutionRequired() { return singleThreadedExecutionRequired_; }
+
+    js::ActiveThreadData<JS::BeginSingleThreadedExecutionCallback> beginSingleThreadedExecutionCallback;
+    js::ActiveThreadData<JS::EndSingleThreadedExecutionCallback> endSingleThreadedExecutionCallback;
+
+    // Ensure there is only a single thread interacting with this runtime.
+    // beginSingleThreadedExecution() returns false if some context has already
+    // started forcing this runtime to be single threaded. Calls to these
+    // functions must be balanced.
+    bool beginSingleThreadedExecution(JSContext* cx);
+    void endSingleThreadedExecution(JSContext* cx);
 
     /*
      * The profiler sampler generation after the latest sample.
      *
      * The lapCount indicates the number of largest number of 'laps'
      * (wrapping from high to low) that occurred when writing entries
      * into the sample buffer.  All JitcodeGlobalMap entries referenced
      * from a given sample are assigned the generation of the sample buffer