Bug 1348959 - Remove wraparound indexing in ProfileBuffer. r?mystor, r?djvj, r?njn draft
authorMarkus Stange <mstange@themasta.com>
Tue, 06 Feb 2018 00:25:30 -0500
changeset 751652 31e084216c46c0fe99b20433df48786bf72de1d8
parent 751651 442a79190178ff156146fe1281c0f75be225409b
child 751654 f697cceec2a0ff572606955767b5e026099f486e
push id98034
push userbmo:mstange@themasta.com
push dateTue, 06 Feb 2018 21:39:48 +0000
reviewersmystor, djvj, njn
bugs1348959
milestone60.0a1
Bug 1348959 - Remove wraparound indexing in ProfileBuffer. r?mystor, r?djvj, r?njn MozReview-Commit-ID: LeBFSRE6GXR
js/public/ProfilingFrameIterator.h
js/src/jit/JitcodeMap.cpp
js/src/jit/JitcodeMap.h
js/src/vm/GeckoProfiler.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/Stack.cpp
tools/profiler/core/ProfileBuffer.cpp
tools/profiler/core/ProfileBuffer.h
tools/profiler/core/ProfileBufferEntry.cpp
tools/profiler/core/ProfilerMarker.h
tools/profiler/core/ThreadInfo.h
tools/profiler/core/platform.cpp
tools/profiler/gecko/nsProfiler.cpp
tools/profiler/public/GeckoProfiler.h
tools/profiler/tests/gtest/GeckoProfiler.cpp
tools/profiler/tests/gtest/ThreadProfileTest.cpp
--- a/js/public/ProfilingFrameIterator.h
+++ b/js/public/ProfilingFrameIterator.h
@@ -49,17 +49,17 @@ class MOZ_NON_PARAM JS_PUBLIC_API(Profil
   public:
     enum class Kind : bool {
         JSJit,
         Wasm
     };
 
   private:
     JSContext* cx_;
-    uint32_t sampleBufferGen_;
+    mozilla::Maybe<uint64_t> samplePositionInProfilerBuffer_;
     js::Activation* activation_;
     Kind kind_;
 
     static const unsigned StorageSpace = 8 * sizeof(void*);
     alignas(void*) unsigned char storage_[StorageSpace];
 
     void* storage() { return storage_; }
     const void* storage() const { return storage_; }
@@ -85,32 +85,28 @@ class MOZ_NON_PARAM JS_PUBLIC_API(Profil
         MOZ_ASSERT(!done());
         MOZ_ASSERT(isJSJit());
         return *static_cast<const js::jit::JSJitProfilingFrameIterator*>(storage());
     }
 
     void settleFrames();
     void settle();
 
-    bool hasSampleBufferGen() const {
-        return sampleBufferGen_ != UINT32_MAX;
-    }
-
   public:
     struct RegisterState
     {
         RegisterState() : pc(nullptr), sp(nullptr), fp(nullptr), lr(nullptr) {}
         void* pc;
         void* sp;
         void* fp;
         void* lr;
     };
 
     ProfilingFrameIterator(JSContext* cx, const RegisterState& state,
-                           uint32_t sampleBufferGen = UINT32_MAX);
+                           const mozilla::Maybe<uint64_t>& samplePositionInProfilerBuffer = mozilla::Nothing());
     ~ProfilingFrameIterator();
     void operator++();
     bool done() const { return !activation_; }
 
     // Assuming the stack grows down (we do), the return value:
     //  - always points into the stack
     //  - is weakly monotonically increasing (may be equal for successive frames)
     //  - will compare greater than newer native and psuedo-stack frame addresses
@@ -148,26 +144,25 @@ class MOZ_NON_PARAM JS_PUBLIC_API(Profil
     void iteratorDestroy();
     bool iteratorDone();
 } JS_HAZ_GC_INVALIDATED;
 
 JS_FRIEND_API(bool)
 IsProfilingEnabledForContext(JSContext* cx);
 
 /**
- * After each sample run, this method should be called with the latest sample
- * buffer generation, and the lapCount.  It will update corresponding fields on
- * JSRuntime.
+ * After each sample run, this method should be called with the current buffer
+ * position at which the buffer contents start. This will update the
+ * corresponding field on the JSRuntime.
  *
- * See fields |profilerSampleBufferGen|, |profilerSampleBufferLapCount| on
- * JSRuntime for documentation about what these values are used for.
+ * See the field |profilerSampleBufferRangeStart| on JSRuntime for documentation
+ * about what this value is used for.
  */
 JS_FRIEND_API(void)
-UpdateJSContextProfilerSampleBufferGen(JSContext* cx, uint32_t generation,
-                                       uint32_t lapCount);
+SetJSContextProfilerSampleBufferRangeStart(JSContext* cx, uint64_t rangeStart);
 
 struct ForEachProfiledFrameOp
 {
     // A handle to the underlying JitcodeGlobalEntry, so as to avoid repeated
     // lookups on JitcodeGlobalTable.
     class MOZ_STACK_CLASS FrameHandle
     {
         friend JS_PUBLIC_API(void) ForEachProfiledFrame(JSContext* cx, void* addr,
--- a/js/src/jit/JitcodeMap.cpp
+++ b/js/src/jit/JitcodeMap.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/JitcodeMap.h"
 
-#include "mozilla/DebugOnly.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Sprintf.h"
 
 #include "jsprf.h"
 
 #include "gc/Marking.h"
 #include "gc/Statistics.h"
@@ -429,27 +428,27 @@ JitcodeGlobalTable::Enum::popFront()
 void
 JitcodeGlobalTable::Enum::removeFront()
 {
     MOZ_ASSERT(!empty());
     table_.releaseEntry(*cur_, prevTower_, rt_);
 }
 
 const JitcodeGlobalEntry&
-JitcodeGlobalTable::lookupForSamplerInfallible(void* ptr, JSRuntime* rt, uint32_t sampleBufferGen)
+JitcodeGlobalTable::lookupForSamplerInfallible(void* ptr, JSRuntime* rt, uint64_t samplePosInBuffer)
 {
     JitcodeGlobalEntry* entry = lookupInternal(ptr);
     MOZ_ASSERT(entry);
 
-    entry->setGeneration(sampleBufferGen);
+    entry->setSamplePositionInBuffer(samplePosInBuffer);
 
     // IonCache entries must keep their corresponding Ion entries alive.
     if (entry->isIonCache()) {
         JitcodeGlobalEntry& rejoinEntry = RejoinEntry(rt, entry->ionCacheEntry(), ptr);
-        rejoinEntry.setGeneration(sampleBufferGen);
+        rejoinEntry.setSamplePositionInBuffer(samplePosInBuffer);
     }
 
     // JitcodeGlobalEntries are marked at the end of the mark phase. A read
     // barrier is not needed. Any JS frames sampled during the sweep phase of
     // the GC must be on stack, and on-stack frames must already be marked at
     // the beginning of the sweep phase. It's not possible to assert this here
     // as we may be off main thread when called from the gecko profiler.
 
@@ -565,19 +564,20 @@ JitcodeGlobalTable::removeEntry(JitcodeG
     entry = JitcodeGlobalEntry();
     entry.addToFreeList(&freeEntries_);
 }
 
 void
 JitcodeGlobalTable::releaseEntry(JitcodeGlobalEntry& entry, JitcodeGlobalEntry** prevTower,
                                  JSRuntime* rt)
 {
-    mozilla::DebugOnly<uint32_t> gen = rt->profilerSampleBufferGen();
-    mozilla::DebugOnly<uint32_t> lapCount = rt->profilerSampleBufferLapCount();
-    MOZ_ASSERT_IF(gen != UINT32_MAX, !entry.isSampled(gen, lapCount));
+#ifdef DEBUG
+    Maybe<uint64_t> rangeStart = rt->profilerSampleBufferRangeStart();
+    MOZ_ASSERT_IF(rangeStart, !entry.isSampled(*rangeStart));
+#endif
     removeEntry(entry, prevTower, rt);
 }
 
 void
 JitcodeGlobalTable::searchInternal(const JitcodeGlobalEntry& query, JitcodeGlobalEntry** towerOut)
 {
     JitcodeGlobalEntry* cur = nullptr;
     for (int level = JitcodeSkiplistTower::MAX_HEIGHT - 1; level >= 0; level--) {
@@ -790,35 +790,33 @@ JitcodeGlobalTable::markIteratively(GCMa
     // been newly pushed, and thus are already marked.
     //
     // The approach above obviates the need for read barriers. The assumption
     // above is checked in JitcodeGlobalTable::lookupForSampler.
 
     MOZ_ASSERT(!JS::CurrentThreadIsHeapMinorCollecting());
 
     AutoSuppressProfilerSampling suppressSampling(TlsContext.get());
-    uint32_t gen = marker->runtime()->profilerSampleBufferGen();
-    uint32_t lapCount = marker->runtime()->profilerSampleBufferLapCount();
 
-    // If the profiler is off, all entries are considered to be expired.
-    if (!marker->runtime()->geckoProfiler().enabled())
-        gen = UINT32_MAX;
+    // If the profiler is off, rangeStart will be Nothing() and all entries are
+    // considered to be expired.
+    Maybe<uint64_t> rangeStart = marker->runtime()->profilerSampleBufferRangeStart();
 
     bool markedAny = false;
     for (Range r(*this); !r.empty(); r.popFront()) {
         JitcodeGlobalEntry* entry = r.front();
 
-        // If an entry is not sampled, reset its generation to the invalid
-        // generation, and conditionally mark the rest of the entry if its
+        // If an entry is not sampled, reset its buffer position to the invalid
+        // position, and conditionally mark the rest of the entry if its
         // JitCode is not already marked. This conditional marking ensures
         // that so long as the JitCode *may* be sampled, we keep any
         // information that may be handed out to the sampler, like tracked
         // types used by optimizations and scripts used for pc to line number
         // mapping, alive as well.
-        if (!entry->isSampled(gen, lapCount)) {
+        if (!rangeStart || !entry->isSampled(*rangeStart)) {
             if (entry->canHoldNurseryPointers())
                 removeFromNurseryList(&entry->ionEntry());
             entry->setAsExpired();
             if (!entry->baseEntry().isJitcodeMarkedFromAnyThread(marker->runtime()))
                 continue;
         }
 
         // The table is runtime-wide. Not all zones may be participating in
--- a/js/src/jit/JitcodeMap.h
+++ b/js/src/jit/JitcodeMap.h
@@ -138,55 +138,59 @@ class JitcodeGlobalEntry
         jsbytecode* pc;
         BytecodeLocation(JSScript* script, jsbytecode* pc) : script(script), pc(pc) {}
     };
     typedef Vector<BytecodeLocation, 0, SystemAllocPolicy> BytecodeLocationVector;
     typedef Vector<const char*, 0, SystemAllocPolicy> ProfileStringVector;
 
     struct BaseEntry
     {
+        static const uint64_t kNoSampleInBuffer = UINT64_MAX;
+
         JitCode* jitcode_;
         void* nativeStartAddr_;
         void* nativeEndAddr_;
-        uint32_t gen_;
+        // If this entry is referenced from the profiler buffer, this is the
+        // position where the most recent sample that references it starts.
+        // Otherwise set to kNoSampleInBuffer.
+        uint64_t samplePositionInBuffer_;
         Kind kind_ : 7;
 
         void init() {
             jitcode_ = nullptr;
             nativeStartAddr_ = nullptr;
             nativeEndAddr_ = nullptr;
-            gen_ = UINT32_MAX;
+            samplePositionInBuffer_ = kNoSampleInBuffer;
             kind_ = INVALID;
         }
 
         void init(Kind kind, JitCode* code,
                   void* nativeStartAddr, void* nativeEndAddr)
         {
             MOZ_ASSERT_IF(kind != Query, code);
             MOZ_ASSERT(nativeStartAddr);
             MOZ_ASSERT(nativeEndAddr);
             MOZ_ASSERT(kind > INVALID && kind < LIMIT);
             jitcode_ = code;
             nativeStartAddr_ = nativeStartAddr;
             nativeEndAddr_ = nativeEndAddr;
-            gen_ = UINT32_MAX;
+            samplePositionInBuffer_ = kNoSampleInBuffer;
             kind_ = kind;
         }
 
-        uint32_t generation() const {
-            return gen_;
-        }
-        void setGeneration(uint32_t gen) {
-            gen_ = gen;
+        void setSamplePositionInBuffer(uint64_t bufferWritePos) {
+            samplePositionInBuffer_ = bufferWritePos;
         }
-        bool isSampled(uint32_t currentGen, uint32_t lapCount) {
-            if (gen_ == UINT32_MAX || currentGen == UINT32_MAX)
+        void setAsExpired() {
+            samplePositionInBuffer_ = kNoSampleInBuffer;
+        }
+        bool isSampled(uint64_t bufferRangeStart) {
+            if (samplePositionInBuffer_ == kNoSampleInBuffer)
                 return false;
-            MOZ_ASSERT(currentGen >= gen_);
-            return (currentGen - gen_) <= lapCount;
+            return bufferRangeStart <= samplePositionInBuffer_;
         }
 
         Kind kind() const {
             return kind_;
         }
         JitCode* jitcode() const {
             return jitcode_;
         }
@@ -632,27 +636,24 @@ class JitcodeGlobalEntry
     }
     void* nativeStartAddr() const {
         return base_.nativeStartAddr();
     }
     void* nativeEndAddr() const {
         return base_.nativeEndAddr();
     }
 
-    uint32_t generation() const {
-        return baseEntry().generation();
-    }
-    void setGeneration(uint32_t gen) {
-        baseEntry().setGeneration(gen);
+    void setSamplePositionInBuffer(uint64_t samplePositionInBuffer) {
+        baseEntry().setSamplePositionInBuffer(samplePositionInBuffer);
     }
     void setAsExpired() {
-        baseEntry().setGeneration(UINT32_MAX);
+        baseEntry().setAsExpired();
     }
-    bool isSampled(uint32_t currentGen, uint32_t lapCount) {
-        return baseEntry().isSampled(currentGen, lapCount);
+    bool isSampled(uint64_t bufferRangeStart) {
+        return baseEntry().isSampled(bufferRangeStart);
     }
 
     bool startsBelowPointer(void* ptr) const {
         return base_.startsBelowPointer(ptr);
     }
     bool endsAbovePointer(void* ptr) const {
         return base_.endsAbovePointer(ptr);
     }
@@ -1048,17 +1049,17 @@ class JitcodeGlobalTable
 
     JitcodeGlobalEntry& lookupInfallible(void* ptr) {
         JitcodeGlobalEntry* entry = lookupInternal(ptr);
         MOZ_ASSERT(entry);
         return *entry;
     }
 
     const JitcodeGlobalEntry& lookupForSamplerInfallible(void* ptr, JSRuntime* rt,
-                                                         uint32_t sampleBufferGen);
+                                                         uint64_t samplePosInBuffer);
 
     MOZ_MUST_USE bool addEntry(const JitcodeGlobalEntry::IonEntry& entry, JSRuntime* rt) {
         return addEntry(JitcodeGlobalEntry(entry), rt);
     }
     MOZ_MUST_USE bool addEntry(const JitcodeGlobalEntry::BaselineEntry& entry, JSRuntime* rt) {
         return addEntry(JitcodeGlobalEntry(entry), rt);
     }
     MOZ_MUST_USE bool addEntry(const JitcodeGlobalEntry::IonCacheEntry& entry, JSRuntime* rt) {
--- a/js/src/vm/GeckoProfiler.cpp
+++ b/js/src/vm/GeckoProfiler.cpp
@@ -105,22 +105,20 @@ GeckoProfilerRuntime::enable(bool enable
     /*
      * Ensure all future generated code will be instrumented, or that all
      * currently instrumented code is discarded
      */
     ReleaseAllJITCode(rt->defaultFreeOp());
 
     // This function is called when the Gecko profiler makes a new Sampler
     // (and thus, a new circular buffer). Set all current entries in the
-    // JitcodeGlobalTable as expired and reset the buffer generation and lap
-    // count.
+    // JitcodeGlobalTable as expired and reset the buffer range start.
     if (rt->hasJitRuntime() && rt->jitRuntime()->hasJitcodeGlobalTable())
         rt->jitRuntime()->getJitcodeGlobalTable()->setAllEntriesAsExpired(rt);
-    rt->resetProfilerSampleBufferGen();
-    rt->resetProfilerSampleBufferLapCount();
+    rt->setProfilerSampleBufferRangeStart(0);
 
     // Ensure that lastProfilingFrame is null for all threads before 'enabled' becomes true.
     for (const CooperatingContext& target : rt->cooperatingContexts()) {
         if (target.context()->jitActivation) {
             target.context()->jitActivation->setLastProfilingFrame(nullptr);
             target.context()->jitActivation->setLastProfilingCallSite(nullptr);
         }
     }
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -96,18 +96,17 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     initialized_(false),
 #endif
     activeContext_(nullptr),
     activeContextChangeProhibited_(0),
     singleThreadedExecutionRequired_(0),
     startingSingleThreadedExecution_(false),
     beginSingleThreadedExecutionCallback(nullptr),
     endSingleThreadedExecutionCallback(nullptr),
-    profilerSampleBufferGen_(0),
-    profilerSampleBufferLapCount_(1),
+    profilerSampleBufferRangeStart_(0),
     telemetryCallback(nullptr),
     consumeStreamCallback(nullptr),
     readableStreamDataRequestCallback(nullptr),
     readableStreamWriteIntoReadRequestCallback(nullptr),
     readableStreamCancelCallback(nullptr),
     readableStreamClosedCallback(nullptr),
     readableStreamErroredCallback(nullptr),
     readableStreamFinalizeCallback(nullptr),
@@ -311,19 +310,16 @@ JSRuntime::destroyRuntime()
          * Flag us as being destroyed. This allows the GC to free things like
          * interned atoms and Ion trampolines.
          */
         beingDestroyed_ = true;
 
         /* Allow the GC to release scripts that were being profiled. */
         profilingScripts = false;
 
-        /* Set the profiler sampler buffer generation to invalid. */
-        profilerSampleBufferGen_ = UINT32_MAX;
-
         JS::PrepareForFullGC(cx);
         gc.gc(GC_NORMAL, JS::gcreason::DESTROY_RUNTIME);
     }
 
     AutoNoteSingleThreadedRegion anstr;
 
     MOZ_ASSERT(!hasHelperThreadZones());
 
@@ -924,21 +920,19 @@ js::CurrentThreadCanAccessZone(Zone* zon
 bool
 js::CurrentThreadIsPerformingGC()
 {
     return TlsContext.get()->performingGC;
 }
 #endif
 
 JS_FRIEND_API(void)
-JS::UpdateJSContextProfilerSampleBufferGen(JSContext* cx, uint32_t generation,
-                                           uint32_t lapCount)
+JS::SetJSContextProfilerSampleBufferRangeStart(JSContext* cx, uint64_t rangeStart)
 {
-    cx->runtime()->setProfilerSampleBufferGen(generation);
-    cx->runtime()->updateProfilerSampleBufferLapCount(lapCount);
+    cx->runtime()->setProfilerSampleBufferRangeStart(rangeStart);
 }
 
 JS_FRIEND_API(bool)
 JS::IsProfilingEnabledForContext(JSContext* cx)
 {
     MOZ_ASSERT(cx);
     return cx->runtime()->geckoProfiler().enabled();
 }
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -6,16 +6,17 @@
 
 #ifndef vm_Runtime_h
 #define vm_Runtime_h
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DoublyLinkedList.h"
 #include "mozilla/LinkedList.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/MaybeOneOf.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/Vector.h"
 
 #include <setjmp.h>
@@ -343,69 +344,40 @@ struct JSRuntime : public js::MallocProv
     // 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
-     * at the START of the run.  If multiple laps occur, then some entries
-     * (towards the end) will be written out with the "wrong" generation.
-     * The lapCount indicates the required fudge factor to use to compare
-     * entry generations with the sample buffer generation.
+     * The start of the range stored in the profiler sample buffer, as measured
+     * after the most recent sample.
+     * All JitcodeGlobalMap entries referenced from a given sample are assigned
+     * the buffer position of the START of the sample. The buffer entries that
+     * reference the JitcodeGlobalMap entries will only ever be read from the
+     * buffer while the entire sample is still inside the buffer; if some
+     * buffer entries at the start of the sample have left the buffer, the
+     * entire sample will be considered inaccessible.
+     * This means that, once profilerSampleBufferRangeStart_ advances beyond
+     * the sample position that's stored on a JitcodeGlobalMap entry, the buffer
+     * entries that reference this JitcodeGlobalMap entry will be considered
+     * inaccessible, and those JitcodeGlobalMap entry can be disposed of.
      */
-    mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> profilerSampleBufferGen_;
-    mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> profilerSampleBufferLapCount_;
+    mozilla::Atomic<uint64_t, mozilla::ReleaseAcquire> profilerSampleBufferRangeStart_;
 
-    uint32_t profilerSampleBufferGen() {
-        return profilerSampleBufferGen_;
-    }
-    void resetProfilerSampleBufferGen() {
-        profilerSampleBufferGen_ = 0;
+    mozilla::Maybe<uint64_t> profilerSampleBufferRangeStart() {
+        if (beingDestroyed_ || !geckoProfiler().enabled()) {
+            return mozilla::Nothing();
+        }
+        uint64_t rangeStart = profilerSampleBufferRangeStart_;
+        return mozilla::Some(rangeStart);
     }
-    void setProfilerSampleBufferGen(uint32_t gen) {
-        // May be called from sampler thread or signal handler; use
-        // compareExchange to make sure we have monotonic increase.
-        for (;;) {
-            uint32_t curGen = profilerSampleBufferGen_;
-            if (curGen >= gen)
-                break;
-
-            if (profilerSampleBufferGen_.compareExchange(curGen, gen))
-                break;
-        }
-    }
-
-    uint32_t profilerSampleBufferLapCount() {
-        MOZ_ASSERT(profilerSampleBufferLapCount_ > 0);
-        return profilerSampleBufferLapCount_;
-    }
-    void resetProfilerSampleBufferLapCount() {
-        profilerSampleBufferLapCount_ = 1;
-    }
-    void updateProfilerSampleBufferLapCount(uint32_t lapCount) {
-        MOZ_ASSERT(profilerSampleBufferLapCount_ > 0);
-
-        // May be called from sampler thread or signal handler; use
-        // compareExchange to make sure we have monotonic increase.
-        for (;;) {
-            uint32_t curLapCount = profilerSampleBufferLapCount_;
-            if (curLapCount >= lapCount)
-                break;
-
-            if (profilerSampleBufferLapCount_.compareExchange(curLapCount, lapCount))
-                break;
-        }
+    void setProfilerSampleBufferRangeStart(uint64_t rangeStart) {
+        profilerSampleBufferRangeStart_ = rangeStart;
     }
 
     /* Call this to accumulate telemetry data. */
     js::ActiveThreadData<JSAccumulateTelemetryDataCallback> telemetryCallback;
 
     /* Call this to accumulate use counter data. */
     js::ActiveThreadData<JSSetUseCounterCallback> useCounterCallback;
 
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1896,19 +1896,19 @@ ActivationIterator&
 ActivationIterator::operator++()
 {
     MOZ_ASSERT(activation_);
     activation_ = activation_->prev();
     return *this;
 }
 
 JS::ProfilingFrameIterator::ProfilingFrameIterator(JSContext* cx, const RegisterState& state,
-                                                   uint32_t sampleBufferGen)
+                                                   const Maybe<uint64_t>& samplePositionInProfilerBuffer)
   : cx_(cx),
-    sampleBufferGen_(sampleBufferGen),
+    samplePositionInProfilerBuffer_(samplePositionInProfilerBuffer),
     activation_(nullptr)
 {
     if (!cx->runtime()->geckoProfiler().enabled())
         MOZ_CRASH("ProfilingFrameIterator called when geckoProfiler not enabled for runtime.");
 
     if (!cx->profilingActivation())
         return;
 
@@ -2077,18 +2077,19 @@ JS::ProfilingFrameIterator::getPhysicalF
         return mozilla::Some(frame);
     }
 
     MOZ_ASSERT(isJSJit());
 
     // Look up an entry for the return address.
     void* returnAddr = jsJitIter().returnAddressToFp();
     jit::JitcodeGlobalTable* table = cx_->runtime()->jitRuntime()->getJitcodeGlobalTable();
-    if (hasSampleBufferGen())
-        *entry = table->lookupForSamplerInfallible(returnAddr, cx_->runtime(), sampleBufferGen_);
+    if (samplePositionInProfilerBuffer_)
+        *entry = table->lookupForSamplerInfallible(returnAddr, cx_->runtime(),
+                                                   *samplePositionInProfilerBuffer_);
     else
         *entry = table->lookupInfallible(returnAddr);
 
     MOZ_ASSERT(entry->isIon() || entry->isIonCache() || entry->isBaseline() || entry->isDummy());
 
     // Dummy frames produce no stack frames.
     if (entry->isDummy())
         return mozilla::Nothing();
--- a/tools/profiler/core/ProfileBuffer.cpp
+++ b/tools/profiler/core/ProfileBuffer.cpp
@@ -1,74 +1,73 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ProfileBuffer.h"
 
+#include "mozilla/MathAlgorithms.h"
+
 #include "ProfilerMarker.h"
 #include "jsfriendapi.h"
 #include "nsScriptSecurityManager.h"
 #include "nsJSPrincipals.h"
 
 using namespace mozilla;
 
 ProfileBuffer::ProfileBuffer(uint32_t aEntrySize)
-  : mEntries(mozilla::MakeUnique<ProfileBufferEntry[]>(aEntrySize))
-  , mWritePos(0)
-  , mReadPos(0)
-  , mEntrySize(aEntrySize)
-  , mGeneration(0)
+  : mEntryIndexMask(0)
+  , mRangeStart(0)
+  , mRangeEnd(0)
+  , mEntrySize(0)
 {
+  // Round aEntrySize up to the nearest power of two, so that we can index
+  // mEntries with a simple mask and don't need to do a slow modulo operation.
+  const uint32_t UINT32_MAX_POWER_OF_TWO = 1 << 31;
+  MOZ_RELEASE_ASSERT(aEntrySize <= UINT32_MAX_POWER_OF_TWO,
+                     "aEntrySize is larger than what we support");
+  mEntrySize = RoundUpPow2(aEntrySize);
+  mEntryIndexMask = mEntrySize - 1;
+  mEntries = MakeUnique<ProfileBufferEntry[]>(mEntrySize);
 }
 
 ProfileBuffer::~ProfileBuffer()
 {
   while (mStoredMarkers.peek()) {
     delete mStoredMarkers.popHead();
   }
 }
 
 // Called from signal, call only reentrant functions
 void
 ProfileBuffer::AddEntry(const ProfileBufferEntry& aEntry)
 {
-  mEntries[mWritePos++] = aEntry;
-  if (mWritePos == mEntrySize) {
-    // Wrapping around may result in things referenced in the buffer (e.g.,
-    // JIT code addresses and markers) being incorrectly collected.
-    MOZ_ASSERT(mGeneration != UINT32_MAX);
-    mGeneration++;
-    mWritePos = 0;
-  }
+  GetEntry(mRangeEnd++) = aEntry;
 
-  if (mWritePos == mReadPos) {
-    // Keep one slot open.
-    mEntries[mReadPos] = ProfileBufferEntry();
-    mReadPos = (mReadPos + 1) % mEntrySize;
+  // The distance between mRangeStart and mRangeEnd must never exceed
+  // mEntrySize, so advance mRangeStart if necessary.
+  if (mRangeEnd - mRangeStart > mEntrySize) {
+    mRangeStart++;
   }
 }
 
-void
-ProfileBuffer::AddThreadIdEntry(int aThreadId, LastSample* aLS)
+uint64_t
+ProfileBuffer::AddThreadIdEntry(int aThreadId)
 {
-  if (aLS) {
-    // This is the start of a sample, so make a note of its location in |aLS|.
-    aLS->mGeneration = mGeneration;
-    aLS->mPos = Some(mWritePos);
-  }
+  uint64_t pos = mRangeEnd;
   AddEntry(ProfileBufferEntry::ThreadId(aThreadId));
+  return pos;
 }
 
 void
 ProfileBuffer::AddStoredMarker(ProfilerMarker *aStoredMarker)
 {
-  aStoredMarker->SetGeneration(mGeneration);
+  aStoredMarker->SetPositionInBuffer(mRangeEnd);
   mStoredMarkers.insert(aStoredMarker);
 }
 
 void
 ProfileBuffer::CollectCodeLocation(
   const char* aLabel, const char* aStr, int aLineNumber,
   const Maybe<js::ProfileEntry::Category>& aCategory)
 {
@@ -100,28 +99,31 @@ ProfileBuffer::CollectCodeLocation(
   }
 }
 
 void
 ProfileBuffer::DeleteExpiredStoredMarkers()
 {
   // Delete markers of samples that have been overwritten due to circular
   // buffer wraparound.
-  uint32_t generation = mGeneration;
   while (mStoredMarkers.peek() &&
-         mStoredMarkers.peek()->HasExpired(generation)) {
+         mStoredMarkers.peek()->HasExpired(mRangeStart)) {
     delete mStoredMarkers.popHead();
   }
 }
 
 void
 ProfileBuffer::Reset()
 {
-  mGeneration += 2;
-  mReadPos = mWritePos = 0;
+  // Empty the buffer (make sure that mRangeEnd - mRangeStart == 0) and
+  // increase the positions by twice the buffer size for good measure.
+  // This method isn't really intended to stay around for long; the only user
+  // wants to flush only one thread's data and ends up accidentally flushing
+  // the whole buffer which holds data for all threads, see bug 1429904.
+  mRangeStart = mRangeEnd = mRangeEnd + 2 * mEntrySize;
 }
 
 size_t
 ProfileBuffer::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   size_t n = aMallocSizeOf(this);
   n += aMallocSizeOf(mEntries.get());
 
@@ -143,22 +145,16 @@ IsChromeJSScript(JSScript* aScript)
   nsIScriptSecurityManager* const secman =
     nsScriptSecurityManager::GetScriptSecurityManager();
   NS_ENSURE_TRUE(secman, false);
 
   JSPrincipals* const principals = JS_GetScriptPrincipals(aScript);
   return secman->IsSystemPrincipal(nsJSPrincipals::get(principals));
 }
 
-Maybe<uint32_t>
-ProfileBufferCollector::Generation()
-{
-  return Some(mBuf.mGeneration);
-}
-
 void
 ProfileBufferCollector::CollectNativeLeafAddr(void* aAddr)
 {
   mBuf.AddEntry(ProfileBufferEntry::NativeLeafAddr(aAddr));
 }
 
 void
 ProfileBufferCollector::CollectJitReturnAddr(void* aAddr)
--- a/tools/profiler/core/ProfileBuffer.h
+++ b/tools/profiler/core/ProfileBuffer.h
@@ -8,44 +8,45 @@
 
 #include "platform.h"
 #include "ProfileBufferEntry.h"
 #include "ProfilerMarker.h"
 #include "ProfileJSONWriter.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/RefCounted.h"
 
+// A fixed-capacity circular buffer.
+// This class is used as a queue of entries which, after construction, never
+// allocates. This makes it safe to use in the profiler's "critical section".
+// Entries are appended at the end. Once the queue capacity has been reached,
+// adding a new entry will evict an old entry from the start of the queue.
+// Positions in the queue are represented as 64-bit unsigned integers which
+// only increase and never wrap around.
+// mRangeStart and mRangeEnd describe the range in that uint64_t space which is
+// covered by the queue contents.
+// Internally, the buffer uses a fixed-size storage and applies a modulo
+// operation when accessing entries in that storage buffer. "Evicting" an entry
+// really just means that an existing entry in the storage buffer gets
+// overwritten and that mRangeStart gets incremented.
 class ProfileBuffer final
 {
 public:
+  // ProfileBuffer constructor
+  // @param aEntrySize The minimum capacity of the buffer. The actual buffer
+  //                   capacity will be rounded up to the next power of two.
   explicit ProfileBuffer(uint32_t aEntrySize);
 
   ~ProfileBuffer();
 
-  // LastSample is used to record the buffer location of the most recent
-  // sample for each thread. It is used for periodic samples stored in the
-  // global ProfileBuffer, but *not* for synchronous samples.
-  struct LastSample {
-    LastSample()
-      : mGeneration(0)
-      , mPos()
-    {}
-
-    // The profiler-buffer generation number at which the sample was created.
-    uint32_t mGeneration;
-    // And its position in the buffer.
-    mozilla::Maybe<uint32_t> mPos;
-  };
-
   // Add |aEntry| to the buffer, ignoring what kind of entry it is.
   void AddEntry(const ProfileBufferEntry& aEntry);
 
-  // Add to the buffer a sample start (ThreadId) entry for aThreadId. Also,
-  // record the resulting generation and index in |aLS| if it's non-null.
-  void AddThreadIdEntry(int aThreadId, LastSample* aLS = nullptr);
+  // Add to the buffer a sample start (ThreadId) entry for aThreadId.
+  // Returns the position of the entry.
+  uint64_t AddThreadIdEntry(int aThreadId);
 
   void CollectCodeLocation(
     const char* aLabel, const char* aStr, int aLineNumber,
     const mozilla::Maybe<js::ProfileEntry::Category>& aCategory);
 
   // Maximum size of a frameKey string that we'll handle.
   static const size_t kMaxFrameKeyLength = 512;
 
@@ -55,72 +56,104 @@ public:
                            UniqueStacks& aUniqueStacks) const;
   bool StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
                            const mozilla::TimeStamp& aProcessStartTime,
                            double aSinceTime,
                            UniqueStacks& aUniqueStacks) const;
   void StreamPausedRangesToJSON(SpliceableJSONWriter& aWriter,
                                 double aSinceTime) const;
 
-  // Find (via |aLS|) the most recent sample for the thread denoted by
-  // |aThreadId| and clone it, patching in |aProcessStartTime| as appropriate.
+  // Find (via |aLastSample|) the most recent sample for the thread denoted by
+  // |aThreadId| and clone it, patching in the current time as appropriate.
+  // Mutate |aLastSample| to point to the newly inserted sample.
+  // Returns whether duplication was successful.
   bool DuplicateLastSample(int aThreadId,
                            const mozilla::TimeStamp& aProcessStartTime,
-                           LastSample& aLS);
+                           mozilla::Maybe<uint64_t>& aLastSample);
 
   void AddStoredMarker(ProfilerMarker* aStoredMarker);
 
   // The following two methods are not signal safe! They delete markers.
   void DeleteExpiredStoredMarkers();
   void Reset();
 
+  // Access an entry in the buffer.
+  ProfileBufferEntry& GetEntry(uint64_t aPosition) const
+  {
+    return mEntries[aPosition & mEntryIndexMask];
+  }
+
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
 private:
-  mozilla::Maybe<uint32_t> FindLastSampleOfThread(int aThreadId, const LastSample& aLS) const;
-
-public:
-  // Circular buffer 'Keep One Slot Open' implementation for simplicity
+  // The storage that backs our buffer. Holds mEntrySize entries.
+  // All accesses to entries in mEntries need to go through GetEntry(), which
+  // translates the given buffer position from the near-infinite uint64_t space
+  // into the entry storage space.
   mozilla::UniquePtr<ProfileBufferEntry[]> mEntries;
 
-  // Points to the next entry we will write to, which is also the one at which
-  // we need to stop reading.
-  uint32_t mWritePos;
+  // A mask such that pos & mEntryIndexMask == pos % mEntrySize.
+  uint32_t mEntryIndexMask;
 
-  // Points to the entry at which we can start reading.
-  uint32_t mReadPos;
+public:
+  // mRangeStart and mRangeEnd are uint64_t values that strictly advance and
+  // never wrap around. mRangeEnd is always greater than or equal to
+  // mRangeStart, but never gets more than mEntrySize steps ahead of
+  // mRangeStart, because we can only store a fixed number of entries in the
+  // buffer. Once the entire buffer is in use, adding a new entry will evict an
+  // entry from the front of the buffer (and increase mRangeStart).
+  // In other words, the following conditions hold true at all times:
+  //  (1) mRangeStart <= mRangeEnd
+  //  (2) mRangeEnd - mRangeStart <= mEntrySize
+  //
+  // If there are no live entries, then mRangeStart == mRangeEnd.
+  // Otherwise, mRangeStart is the first live entry and mRangeEnd is one past
+  // the last live entry, and also the position at which the next entry will be
+  // added.
+  // (mRangeEnd - mRangeStart) always gives the number of live entries.
+  uint64_t mRangeStart;
+  uint64_t mRangeEnd;
 
-  // The number of entries in our buffer.
+  // The number of entries in our buffer. Always a power of two.
   uint32_t mEntrySize;
 
-  // How many times mWritePos has wrapped around.
-  uint32_t mGeneration;
-
   // Markers that marker entries in the buffer might refer to.
   ProfilerMarkerLinkedList mStoredMarkers;
 };
 
 /**
  * Helper type used to implement ProfilerStackCollector. This type is used as
  * the collector for MergeStacks by ProfileBuffer. It holds a reference to the
  * buffer, as well as additional feature flags which are needed to control the
  * data collection strategy
  */
 class ProfileBufferCollector final : public ProfilerStackCollector
 {
 public:
-  ProfileBufferCollector(ProfileBuffer& aBuf, uint32_t aFeatures)
+  ProfileBufferCollector(ProfileBuffer& aBuf, uint32_t aFeatures,
+                         uint64_t aSamplePos)
     : mBuf(aBuf)
+    , mSamplePositionInBuffer(aSamplePos)
     , mFeatures(aFeatures)
   {}
 
-  virtual mozilla::Maybe<uint32_t> Generation() override;
+  mozilla::Maybe<uint64_t> SamplePositionInBuffer() override
+  {
+    return mozilla::Some(mSamplePositionInBuffer);
+  }
+
+  mozilla::Maybe<uint64_t> BufferRangeStart() override
+  {
+    return mozilla::Some(mBuf.mRangeStart);
+  }
+
   virtual void CollectNativeLeafAddr(void* aAddr) override;
   virtual void CollectJitReturnAddr(void* aAddr) override;
   virtual void CollectWasmFrame(const char* aLabel) override;
   virtual void CollectPseudoEntry(const js::ProfileEntry& aEntry) override;
 
 private:
   ProfileBuffer& mBuf;
+  uint64_t mSamplePositionInBuffer;
   uint32_t mFeatures;
 };
 
 #endif
--- a/tools/profiler/core/ProfileBufferEntry.cpp
+++ b/tools/profiler/core/ProfileBufferEntry.cpp
@@ -568,32 +568,35 @@ static void WriteSample(SpliceableJSONWr
   if (aSample.mUSS.isSome()) {
     writer.DoubleElement(USS, *aSample.mUSS);
   }
 }
 
 class EntryGetter
 {
 public:
-  explicit EntryGetter(const ProfileBuffer& aBuffer)
-    : mEntries(aBuffer.mEntries.get())
-    , mReadPos(aBuffer.mReadPos)
-    , mWritePos(aBuffer.mWritePos)
-    , mEntrySize(aBuffer.mEntrySize)
-  {}
+  explicit EntryGetter(const ProfileBuffer& aBuffer,
+                       uint64_t aInitialReadPos = 0)
+    : mBuffer(aBuffer)
+    , mReadPos(aBuffer.mRangeStart)
+  {
+    if (aInitialReadPos != 0) {
+      MOZ_RELEASE_ASSERT(aInitialReadPos >= aBuffer.mRangeStart &&
+                         aInitialReadPos <= aBuffer.mRangeEnd);
+      mReadPos = aInitialReadPos;
+    }
+  }
 
-  bool Has() const { return mReadPos != mWritePos; }
-  const ProfileBufferEntry& Get() const { return mEntries[mReadPos]; }
-  void Next() { mReadPos = (mReadPos + 1) % mEntrySize; }
+  bool Has() const { return mReadPos != mBuffer.mRangeEnd; }
+  const ProfileBufferEntry& Get() const { return mBuffer.GetEntry(mReadPos); }
+  void Next() { mReadPos++; }
 
 private:
-  const ProfileBufferEntry* const mEntries;
-  uint32_t mReadPos;
-  const uint32_t mWritePos;
-  const uint32_t mEntrySize;
+  const ProfileBuffer& mBuffer;
+  uint64_t mReadPos;
 };
 
 // The following grammar shows legal sequences of profile buffer entries.
 // The sequences beginning with a ThreadId entry are known as "samples".
 //
 // (
 //   (
 //     ThreadId
@@ -995,90 +998,65 @@ ProfileBuffer::StreamPausedRangesToJSON(
                    currentPauseStartTime, Nothing());
   }
   if (currentCollectionStartTime) {
     AddPausedRange(aWriter, "collecting",
                    currentCollectionStartTime, Nothing());
   }
 }
 
-Maybe<uint32_t>
-ProfileBuffer::FindLastSampleOfThread(int aThreadId, const LastSample& aLS)
-  const
-{
-  // |aLS| has a valid generation number if either it matches the buffer's
-  // generation, or is one behind the buffer's generation, since the buffer's
-  // generation is incremented on wraparound.  There's no ambiguity relative to
-  // ProfileBuffer::reset, since that increments mGeneration by two.
-  if (aLS.mGeneration == mGeneration ||
-      (mGeneration > 0 && aLS.mGeneration == mGeneration - 1)) {
-    if (!aLS.mPos) {
-      // There's no record of |aLS|'s thread ever having recorded a sample in
-      // the buffer.
-      return Nothing();
-    }
-
-    uint32_t ix = *aLS.mPos;
-
-    // It might be that the sample has since been overwritten, so check that it
-    // is still valid.
-    MOZ_RELEASE_ASSERT(0 <= ix && ix < mEntrySize);
-    ProfileBufferEntry& entry = mEntries[ix];
-    bool isStillValid = entry.IsThreadId() && entry.u.mInt == aThreadId;
-    return isStillValid ? Some(ix) : Nothing();
-  }
-
-  // |aLS| denotes a sample which is older than either two wraparounds or one
-  // call to ProfileBuffer::reset.  In either case it is no longer valid.
-  MOZ_ASSERT(aLS.mGeneration <= mGeneration - 2);
-  return Nothing();
-}
-
 bool
 ProfileBuffer::DuplicateLastSample(int aThreadId,
                                    const TimeStamp& aProcessStartTime,
-                                   LastSample& aLS)
+                                   Maybe<uint64_t>& aLastSample)
 {
-  Maybe<uint32_t> maybeLastSampleStartPos =
-    FindLastSampleOfThread(aThreadId, aLS);
-  if (!maybeLastSampleStartPos) {
+  if (aLastSample && *aLastSample < mRangeStart) {
+    // The last sample is no longer within the buffer range, so we cannot use
+    // it. Reset the stored buffer position to Nothing().
+    aLastSample.reset();
+  }
+
+  if (!aLastSample) {
     return false;
   }
 
-  uint32_t lastSampleStartPos = *maybeLastSampleStartPos;
+  uint64_t lastSampleStartPos = *aLastSample;
 
-  MOZ_ASSERT(mEntries[lastSampleStartPos].IsThreadId() &&
-             mEntries[lastSampleStartPos].u.mInt == aThreadId);
+  MOZ_RELEASE_ASSERT(GetEntry(lastSampleStartPos).IsThreadId() &&
+                     GetEntry(lastSampleStartPos).u.mInt == aThreadId);
 
-  AddThreadIdEntry(aThreadId, &aLS);
+  aLastSample = Some(AddThreadIdEntry(aThreadId));
+
+  EntryGetter e(*this, lastSampleStartPos + 1);
 
   // Go through the whole entry and duplicate it, until we find the next one.
-  for (uint32_t readPos = (lastSampleStartPos + 1) % mEntrySize;
-       readPos != mWritePos;
-       readPos = (readPos + 1) % mEntrySize) {
-    switch (mEntries[readPos].GetKind()) {
+  while (e.Has()) {
+    switch (e.Get().GetKind()) {
       case ProfileBufferEntry::Kind::Pause:
       case ProfileBufferEntry::Kind::Resume:
       case ProfileBufferEntry::Kind::CollectionStart:
       case ProfileBufferEntry::Kind::CollectionEnd:
       case ProfileBufferEntry::Kind::ThreadId:
         // We're done.
         return true;
       case ProfileBufferEntry::Kind::Time:
         // Copy with new time
         AddEntry(ProfileBufferEntry::Time(
           (TimeStamp::Now() - aProcessStartTime).ToMilliseconds()));
         break;
       case ProfileBufferEntry::Kind::Marker:
         // Don't copy markers
         break;
-      default:
+      default: {
         // Copy anything else we don't know about.
-        AddEntry(mEntries[readPos]);
+        ProfileBufferEntry entry = e.Get();
+        AddEntry(entry);
         break;
+      }
     }
+    e.Next();
   }
   return true;
 }
 
 // END ProfileBuffer
 ////////////////////////////////////////////////////////////////////////
 
--- a/tools/profiler/core/ProfilerMarker.h
+++ b/tools/profiler/core/ProfilerMarker.h
@@ -25,23 +25,26 @@ public:
                           int aThreadId,
                           mozilla::UniquePtr<ProfilerMarkerPayload>
                             aPayload = nullptr,
                           double aTime = 0)
     : mMarkerName(strdup(aMarkerName))
     , mPayload(Move(aPayload))
     , mNext{nullptr}
     , mTime(aTime)
-    , mGenID{0}
+    , mPositionInBuffer{0}
     , mThreadId{aThreadId}
     {}
 
-  void SetGeneration(uint32_t aGenID) { mGenID = aGenID; }
+  void SetPositionInBuffer(uint64_t aPosition) { mPositionInBuffer = aPosition; }
 
-  bool HasExpired(uint32_t aGenID) const { return mGenID + 2 <= aGenID; }
+  bool HasExpired(uint64_t aBufferRangeStart) const
+  {
+    return mPositionInBuffer < aBufferRangeStart;
+  }
 
   double GetTime() const { return mTime; }
 
   int GetThreadId() const { return mThreadId; }
 
   void StreamJSON(SpliceableJSONWriter& aWriter,
                   const mozilla::TimeStamp& aProcessStartTime,
                   UniqueStacks& aUniqueStacks) const
@@ -67,17 +70,17 @@ public:
     aWriter.EndArray();
   }
 
 private:
   mozilla::UniqueFreePtr<char> mMarkerName;
   mozilla::UniquePtr<ProfilerMarkerPayload> mPayload;
   ProfilerMarker* mNext;
   double mTime;
-  uint32_t mGenID;
+  uint64_t mPositionInBuffer;
   int mThreadId;
 };
 
 template<typename T>
 class ProfilerLinkedList
 {
 public:
   ProfilerLinkedList()
--- a/tools/profiler/core/ThreadInfo.h
+++ b/tools/profiler/core/ThreadInfo.h
@@ -195,17 +195,17 @@ public:
 
   void NotifyUnregistered() { mUnregisterTime = TimeStamp::Now(); }
 
   PlatformData* GetPlatformData() const { return mPlatformData.get(); }
   void* StackTop() const { return mStackTop; }
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
-  ProfileBuffer::LastSample& LastSample() { return mLastSample; }
+  mozilla::Maybe<uint64_t>& LastSample() { return mLastSample; }
 
 private:
   mozilla::UniqueFreePtr<char> mName;
   mozilla::TimeStamp mRegisterTime;
   mozilla::TimeStamp mUnregisterTime;
   const bool mIsMainThread;
   nsCOMPtr<nsIEventTarget> mThread;
 
@@ -375,19 +375,20 @@ private:
   //
   enum {
     INACTIVE = 0,
     ACTIVE_REQUESTED = 1,
     ACTIVE = 2,
     INACTIVE_REQUESTED = 3,
   } mJSSampling;
 
-  // When sampling, this holds the generation number and offset in
-  // ActivePS::mBuffer of the most recent sample for this thread.
-  ProfileBuffer::LastSample mLastSample;
+  // When sampling, this holds the position in ActivePS::mBuffer of the most
+  // recent sample for this thread, or Nothing() if there is no sample for this
+  // thread in the buffer.
+  mozilla::Maybe<uint64_t> mLastSample;
 };
 
 void
 StreamSamplesAndMarkers(const char* aName, int aThreadId,
                         const ProfileBuffer& aBuffer,
                         SpliceableJSONWriter& aWriter,
                         const mozilla::TimeStamp& aProcessStartTime,
                         const TimeStamp& aRegisterTime,
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -44,16 +44,17 @@
 #include "ProfilerIOInterposeObserver.h"
 #include "mozilla/AutoProfilerLabel.h"
 #include "mozilla/ExtensionPolicyService.h"
 #include "mozilla/Scheduler.h"
 #include "mozilla/StackWalk.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/Tuple.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/extensions/WebExtensionPolicy.h"
 #include "ThreadInfo.h"
 #include "nsIHttpProtocolHandler.h"
 #include "nsIObserverService.h"
 #include "nsIXULAppInfo.h"
 #include "nsIXULRuntime.h"
 #include "nsDirectoryServiceUtils.h"
@@ -772,43 +773,43 @@ MergeStacks(uint32_t aFeatures, bool aIs
   js::ProfileEntry* pseudoEntries = racyInfo->entries;
   uint32_t pseudoCount = racyInfo->stackSize();
   JSContext* context = aThreadInfo.mContext;
 
   // Make a copy of the JS stack into a JSFrame array. This is necessary since,
   // like the native stack, the JS stack is iterated youngest-to-oldest and we
   // need to iterate oldest-to-youngest when adding entries to aInfo.
 
-  // Synchronous sampling reports an invalid buffer generation to
-  // ProfilingFrameIterator to avoid incorrectly resetting the generation of
-  // sampled JIT entries inside the JS engine. See note below concerning 'J'
-  // entries.
-  uint32_t startBufferGen = UINT32_MAX;
-  if (!aIsSynchronous && aCollector.Generation().isSome()) {
-    startBufferGen = *aCollector.Generation();
+  // Non-periodic sampling passes Nothing() as the buffer write position to
+  // ProfilingFrameIterator to avoid incorrectly resetting the buffer position
+  // of sampled JIT entries inside the JS engine.
+  Maybe<uint64_t> samplePosInBuffer;
+  if (!aIsSynchronous) {
+    // aCollector.SamplePositionInBuffer() will return Nothing() when
+    // profiler_suspend_and_sample_thread is called from the background hang
+    // reporter.
+    samplePosInBuffer = aCollector.SamplePositionInBuffer();
   }
   uint32_t jsCount = 0;
   JS::ProfilingFrameIterator::Frame jsFrames[MAX_JS_FRAMES];
 
   // Only walk jit stack if profiling frame iterator is turned on.
   if (context && JS::IsProfilingEnabledForContext(context)) {
     AutoWalkJSStack autoWalkJSStack;
     const uint32_t maxFrames = ArrayLength(jsFrames);
 
     if (autoWalkJSStack.walkAllowed) {
       JS::ProfilingFrameIterator::RegisterState registerState;
       registerState.pc = aRegs.mPC;
       registerState.sp = aRegs.mSP;
       registerState.lr = aRegs.mLR;
       registerState.fp = aRegs.mFP;
 
-      JS::ProfilingFrameIterator jsIter(context, registerState,
-                                        startBufferGen);
+      JS::ProfilingFrameIterator jsIter(context, registerState, samplePosInBuffer);
       for (; jsCount < maxFrames && !jsIter.done(); ++jsIter) {
-        // See note below regarding 'J' entries.
         if (aIsSynchronous || jsIter.isWasm()) {
           uint32_t extracted =
             jsIter.extractStack(jsFrames, jsCount, maxFrames);
           jsCount += extracted;
           if (jsCount == maxFrames) {
             break;
           }
         } else {
@@ -945,24 +946,23 @@ MergeStacks(uint32_t aFeatures, bool aIs
     }
     if (nativeIndex >= 0) {
       nativeIndex--;
     }
   }
 
   // Update the JS context with the current profile sample buffer generation.
   //
-  // Do not do this for synchronous samples, which use their own
-  // ProfileBuffers instead of the global one in CorePS.
-  if (!aIsSynchronous && context && aCollector.Generation().isSome()) {
-    MOZ_ASSERT(*aCollector.Generation() >= startBufferGen);
-    uint32_t lapCount = *aCollector.Generation() - startBufferGen;
-    JS::UpdateJSContextProfilerSampleBufferGen(context,
-                                               *aCollector.Generation(),
-                                               lapCount);
+  // Only do this for periodic samples. We don't want to do this for
+  // synchronous samples, and we also don't want to do it for calls to
+  // profiler_suspend_and_sample_thread() from the background hang reporter -
+  // in that case, aCollector.BufferRangeStart() will return Nothing().
+  if (!aIsSynchronous && context && aCollector.BufferRangeStart()) {
+    uint64_t bufferRangeStart = *aCollector.BufferRangeStart();
+    JS::SetJSContextProfilerSampleBufferRangeStart(context, bufferRangeStart);
   }
 }
 
 #if defined(GP_OS_windows)
 static HANDLE GetThreadHandle(PlatformData* aData);
 #endif
 
 #if defined(USE_FRAME_POINTER_STACK_WALK) || defined(USE_MOZ_STACK_WALK)
@@ -1270,29 +1270,33 @@ DoNativeBacktrace(PSLockRef aLock, const
 // ActivePS's ProfileBuffer. (This should only be called from DoSyncSample()
 // and DoPeriodicSample().)
 //
 // The grammar for entry sequences is in a comment above
 // ProfileBuffer::StreamSamplesToJSON.
 static inline void
 DoSharedSample(PSLockRef aLock, bool aIsSynchronous,
                ThreadInfo& aThreadInfo, const TimeStamp& aNow,
-               const Registers& aRegs, ProfileBuffer::LastSample* aLS,
+               const Registers& aRegs, Maybe<uint64_t>* aLastSample,
                ProfileBuffer& aBuffer)
 {
   // WARNING: this function runs within the profiler's "critical section".
 
   MOZ_RELEASE_ASSERT(ActivePS::Exists(aLock));
 
-  aBuffer.AddThreadIdEntry(aThreadInfo.ThreadId(), aLS);
+  uint64_t samplePos = aBuffer.AddThreadIdEntry(aThreadInfo.ThreadId());
+  if (aLastSample) {
+    *aLastSample = Some(samplePos);
+  }
 
   TimeDuration delta = aNow - CorePS::ProcessStartTime();
   aBuffer.AddEntry(ProfileBufferEntry::Time(delta.ToMilliseconds()));
 
-  ProfileBufferCollector collector(aBuffer, ActivePS::Features(aLock));
+  ProfileBufferCollector collector(aBuffer, ActivePS::Features(aLock),
+                                   samplePos);
   NativeStack nativeStack;
 #if defined(HAVE_NATIVE_UNWIND)
   if (ActivePS::FeatureStackWalk(aLock)) {
     DoNativeBacktrace(aLock, aThreadInfo, aRegs, nativeStack);
 
     MergeStacks(ActivePS::Features(aLock), aIsSynchronous, aThreadInfo, aRegs,
                 nativeStack, collector);
   } else
@@ -2691,19 +2695,18 @@ profiler_get_buffer_info()
 
   PSAutoLock lock(gPSMutex);
 
   if (!ActivePS::Exists(lock)) {
     return Nothing();
   }
 
   return Some(ProfilerBufferInfo {
-    ActivePS::Buffer(lock).mWritePos,
-    ActivePS::Buffer(lock).mReadPos,
-    ActivePS::Buffer(lock).mGeneration,
+    ActivePS::Buffer(lock).mRangeStart,
+    ActivePS::Buffer(lock).mRangeEnd,
     ActivePS::Entries(lock)
   });
 }
 
 static void
 PollJSSamplingForCurrentThread()
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
--- a/tools/profiler/gecko/nsProfiler.cpp
+++ b/tools/profiler/gecko/nsProfiler.cpp
@@ -491,19 +491,19 @@ NS_IMETHODIMP
 nsProfiler::GetBufferInfo(uint32_t* aCurrentPosition, uint32_t* aTotalSize,
                           uint32_t* aGeneration)
 {
   MOZ_ASSERT(aCurrentPosition);
   MOZ_ASSERT(aTotalSize);
   MOZ_ASSERT(aGeneration);
   Maybe<ProfilerBufferInfo> info = profiler_get_buffer_info();
   if (info) {
-    *aCurrentPosition = info->mWritePosition;
+    *aCurrentPosition = info->mRangeEnd % info->mEntryCount;
     *aTotalSize = info->mEntryCount;
-    *aGeneration = info->mGeneration;
+    *aGeneration = info->mRangeEnd / info->mEntryCount;
   } else {
     *aCurrentPosition = 0;
     *aTotalSize = 0;
     *aGeneration = 0;
   }
   return NS_OK;
 }
 
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -302,17 +302,18 @@ int profiler_current_thread_id();
 // An object of this class is passed to profiler_suspend_and_sample_thread().
 // For each stack frame, one of the Collect methods will be called.
 class ProfilerStackCollector
 {
 public:
   // Some collectors need to worry about possibly overwriting previous
   // generations of data. If that's not an issue, this can return Nothing,
   // which is the default behaviour.
-  virtual mozilla::Maybe<uint32_t> Generation() { return mozilla::Nothing(); }
+  virtual mozilla::Maybe<uint64_t> SamplePositionInBuffer() { return mozilla::Nothing(); }
+  virtual mozilla::Maybe<uint64_t> BufferRangeStart() { return mozilla::Nothing(); }
 
   // This method will be called once if the thread being suspended is the main
   // thread. Default behaviour is to do nothing.
   virtual void SetIsMainThread() {}
 
   // WARNING: The target thread is suspended when the Collect methods are
   // called. Do not try to allocate or acquire any locks, or you could
   // deadlock. The target thread will have resumed by the time this function
@@ -344,19 +345,18 @@ using UniqueProfilerBacktrace =
   mozilla::UniquePtr<ProfilerBacktrace, ProfilerBacktraceDestructor>;
 
 // Immediately capture the current thread's call stack and return it. A no-op
 // if the profiler is inactive or in privacy mode.
 UniqueProfilerBacktrace profiler_get_backtrace();
 
 struct ProfilerBufferInfo
 {
-  uint32_t mWritePosition;
-  uint32_t mReadPosition;
-  uint32_t mGeneration;
+  uint64_t mRangeStart;
+  uint64_t mRangeEnd;
   uint32_t mEntryCount;
 };
 
 // Get information about the current buffer status.
 // Returns Nothing() if the profiler is inactive.
 //
 // This information may be useful to a user-interface displaying the current
 // status of the profiler, allowing the user to get a sense for how fast the
--- a/tools/profiler/tests/gtest/GeckoProfiler.cpp
+++ b/tools/profiler/tests/gtest/GeckoProfiler.cpp
@@ -189,32 +189,30 @@ TEST(GeckoProfiler, EnsureStarted)
 
   {
     // Active -> Active with same settings
 
     // First, write some samples into the buffer.
     PR_Sleep(PR_MillisecondsToInterval(500));
 
     Maybe<ProfilerBufferInfo> info1 = profiler_get_buffer_info();
-    ASSERT_TRUE(info1->mGeneration > 0 || info1->mWritePosition > 0);
+    ASSERT_TRUE(info1->mRangeEnd > 0);
 
     // Call profiler_ensure_started with the same settings as before.
     // This operation must not clear our buffer!
     profiler_ensure_started(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
                             features, filters, MOZ_ARRAY_LENGTH(filters));
 
     ActiveParamsCheck(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
                       features, filters, MOZ_ARRAY_LENGTH(filters));
 
     // Check that our position in the buffer stayed the same or advanced.
     // In particular, it shouldn't have reverted to the start.
     Maybe<ProfilerBufferInfo> info2 = profiler_get_buffer_info();
-    ASSERT_TRUE(info2->mGeneration >= info1->mGeneration);
-    ASSERT_TRUE(info2->mGeneration > info1->mGeneration ||
-                info2->mWritePosition >= info1->mWritePosition);
+    ASSERT_TRUE(info2->mRangeEnd >= info1->mRangeEnd);
   }
 
   {
     // Active -> Active with *different* settings
 
     Maybe<ProfilerBufferInfo> info1 = profiler_get_buffer_info();
 
     // Call profiler_ensure_started with a different feature set than the one it's
@@ -224,19 +222,17 @@ TEST(GeckoProfiler, EnsureStarted)
     profiler_ensure_started(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
                             differentFeatures,
                             filters, MOZ_ARRAY_LENGTH(filters));
 
     ActiveParamsCheck(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
                       differentFeatures, filters, MOZ_ARRAY_LENGTH(filters));
 
     Maybe<ProfilerBufferInfo> info2 = profiler_get_buffer_info();
-    ASSERT_TRUE(info2->mGeneration <= info1->mGeneration);
-    ASSERT_TRUE(info2->mGeneration < info1->mGeneration ||
-                info2->mWritePosition < info1->mWritePosition);
+    ASSERT_TRUE(info2->mRangeEnd < info1->mRangeEnd);
   }
 
   {
     // Active -> Inactive
 
     profiler_stop();
 
     InactiveFeaturesAndParamsCheck();
@@ -379,27 +375,27 @@ TEST(GeckoProfiler, Pause)
                  features, filters, MOZ_ARRAY_LENGTH(filters));
 
   ASSERT_TRUE(!profiler_is_paused());
 
   // Check that we are writing samples while not paused.
   Maybe<ProfilerBufferInfo> info1 = profiler_get_buffer_info();
   PR_Sleep(PR_MillisecondsToInterval(500));
   Maybe<ProfilerBufferInfo> info2 = profiler_get_buffer_info();
-  ASSERT_TRUE(info1->mWritePosition != info2->mWritePosition);
+  ASSERT_TRUE(info1->mRangeEnd != info2->mRangeEnd);
 
   profiler_pause();
 
   ASSERT_TRUE(profiler_is_paused());
 
   // Check that we are not writing samples while paused.
   info1 = profiler_get_buffer_info();
   PR_Sleep(PR_MillisecondsToInterval(500));
   info2 = profiler_get_buffer_info();
-  ASSERT_TRUE(info1->mWritePosition == info2->mWritePosition);
+  ASSERT_TRUE(info1->mRangeEnd == info2->mRangeEnd);
 
   profiler_resume();
 
   ASSERT_TRUE(!profiler_is_paused());
 
   profiler_stop();
 
   ASSERT_TRUE(!profiler_is_paused());
--- a/tools/profiler/tests/gtest/ThreadProfileTest.cpp
+++ b/tools/profiler/tests/gtest/ThreadProfileTest.cpp
@@ -21,59 +21,56 @@ TEST(ThreadProfile, Initialization) {
 // Make sure we can record one entry and read it
 TEST(ThreadProfile, InsertOneEntry) {
   int tid = 1000;
   nsCOMPtr<nsIThread> mainThread;
   NS_GetMainThread(getter_AddRefs(mainThread));
   ThreadInfo info("testThread", tid, true, mainThread, nullptr);
   auto pb = MakeUnique<ProfileBuffer>(10);
   pb->AddEntry(ProfileBufferEntry::Time(123.1));
-  ASSERT_TRUE(pb->mEntries != nullptr);
-  ASSERT_TRUE(pb->mEntries[pb->mReadPos].IsTime());
-  ASSERT_TRUE(pb->mEntries[pb->mReadPos].u.mDouble == 123.1);
+  ASSERT_TRUE(pb->GetEntry(pb->mRangeStart).IsTime());
+  ASSERT_TRUE(pb->GetEntry(pb->mRangeStart).u.mDouble == 123.1);
 }
 
 // See if we can insert some entries
 TEST(ThreadProfile, InsertEntriesNoWrap) {
   int tid = 1000;
   nsCOMPtr<nsIThread> mainThread;
   NS_GetMainThread(getter_AddRefs(mainThread));
   ThreadInfo info("testThread", tid, true, mainThread, nullptr);
   auto pb = MakeUnique<ProfileBuffer>(100);
   int test_size = 50;
   for (int i = 0; i < test_size; i++) {
     pb->AddEntry(ProfileBufferEntry::Time(i));
   }
-  ASSERT_TRUE(pb->mEntries != nullptr);
-  uint32_t readPos = pb->mReadPos;
-  while (readPos != pb->mWritePos) {
-    ASSERT_TRUE(pb->mEntries[readPos].IsTime());
-    ASSERT_TRUE(pb->mEntries[readPos].u.mDouble == readPos);
-    readPos = (readPos + 1) % pb->mEntrySize;
+  uint64_t readPos = pb->mRangeStart;
+  while (readPos != pb->mRangeEnd) {
+    ASSERT_TRUE(pb->GetEntry(readPos).IsTime());
+    ASSERT_TRUE(pb->GetEntry(readPos).u.mDouble == readPos);
+    readPos++;
   }
 }
 
-// See if wrapping works as it should in the basic case
+// See if evicting works as it should in the basic case
 TEST(ThreadProfile, InsertEntriesWrap) {
   int tid = 1000;
-  // we can fit only 24 entries in this buffer because of the empty slot
-  int entries = 24;
-  int buffer_size = entries + 1;
+  int entries = 32;
   nsCOMPtr<nsIThread> mainThread;
   NS_GetMainThread(getter_AddRefs(mainThread));
   ThreadInfo info("testThread", tid, true, mainThread, nullptr);
-  auto pb = MakeUnique<ProfileBuffer>(buffer_size);
+  auto pb = MakeUnique<ProfileBuffer>(entries);
+  ASSERT_TRUE(pb->mRangeStart == 0);
+  ASSERT_TRUE(pb->mRangeEnd == 0);
   int test_size = 43;
   for (int i = 0; i < test_size; i++) {
     pb->AddEntry(ProfileBufferEntry::Time(i));
   }
-  ASSERT_TRUE(pb->mEntries != nullptr);
-  uint32_t readPos = pb->mReadPos;
-  int ctr = 0;
-  while (readPos != pb->mWritePos) {
-    ASSERT_TRUE(pb->mEntries[readPos].IsTime());
-    // the first few entries were discarded when we wrapped
-    ASSERT_TRUE(pb->mEntries[readPos].u.mDouble == ctr + (test_size - entries));
-    ctr++;
-    readPos = (readPos + 1) % pb->mEntrySize;
+  // We inserted 11 more entries than fit in the buffer, so the first 11 entries
+  // should have been evicted, and the range start should have increased to 11.
+  ASSERT_TRUE(pb->mRangeStart == 11);
+  uint64_t readPos = pb->mRangeStart;
+  while (readPos != pb->mRangeEnd) {
+    ASSERT_TRUE(pb->GetEntry(readPos).IsTime());
+    ASSERT_TRUE(pb->GetEntry(readPos).u.mDouble == readPos);
+    readPos++;
   }
 }