Backed out 2 changesets (bug 1437428) for frequent xpcfailures on marAppApplyUpdateStageOldVersionFailure.js a=backout
authorCosmin Sabou <csabou@mozilla.com>
Sun, 18 Feb 2018 23:57:55 +0200
changeset 404358 ad133cd410a719c0b67e61b8d3b1c77a32fd80a9
parent 404357 5b52d51bbaf2f6c8c6388b5bc22b0b8fe1172eba
child 404363 2ff1d57860b67bdd7fec1648c92e135971e44393
child 404373 07e8cd273ed69cc74a0d0e12a394f897cc5e81ac
push id33470
push usercsabou@mozilla.com
push dateSun, 18 Feb 2018 21:58:25 +0000
treeherdermozilla-central@ad133cd410a7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1437428
milestone60.0a1
backs outb915e160a690eb75d647c3681682064f87869f10
0fcad4eaabb63afc6987d46dc4aba31764eebf34
first release with
nightly linux32
ad133cd410a7 / 60.0a1 / 20180218220057 / files
nightly linux64
ad133cd410a7 / 60.0a1 / 20180218220057 / files
nightly mac
ad133cd410a7 / 60.0a1 / 20180218220057 / files
nightly win32
ad133cd410a7 / 60.0a1 / 20180218220057 / files
nightly win64
ad133cd410a7 / 60.0a1 / 20180218220057 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 2 changesets (bug 1437428) for frequent xpcfailures on marAppApplyUpdateStageOldVersionFailure.js a=backout Backed out changeset b915e160a690 (bug 1437428) Backed out changeset 0fcad4eaabb6 (bug 1437428)
js/public/ProfilingStack.h
tools/profiler/core/ProfiledThreadData.cpp
tools/profiler/core/ProfiledThreadData.h
tools/profiler/core/RegisteredThread.cpp
tools/profiler/core/RegisteredThread.h
tools/profiler/core/ThreadInfo.cpp
tools/profiler/core/ThreadInfo.h
tools/profiler/core/platform-linux-android.cpp
tools/profiler/core/platform-macos.cpp
tools/profiler/core/platform-win32.cpp
tools/profiler/core/platform.cpp
tools/profiler/moz.build
tools/profiler/tests/gtest/ThreadProfileTest.cpp
--- a/js/public/ProfilingStack.h
+++ b/js/public/ProfilingStack.h
@@ -289,17 +289,17 @@ RegisterContextProfilingEventMarker(JSCo
 //   entry visible to the sampler thread -- only after the new entry has been
 //   fully written. The stack pointer is Atomic<uint32_t,ReleaseAcquire>, so
 //   the increment is a release-store, which ensures that this store is not
 //   reordered before the writes of the entry.
 //
 // - When popping an old entry, the only operation is the decrementing of the
 //   stack pointer, which is obviously atomic.
 //
-class PseudoStack final
+class PseudoStack
 {
   public:
     PseudoStack()
       : stackPointer(0)
     {}
 
     ~PseudoStack() {
         // The label macros keep a reference to the PseudoStack to avoid a TLS
deleted file mode 100644
--- a/tools/profiler/core/ProfiledThreadData.cpp
+++ /dev/null
@@ -1,285 +0,0 @@
-/* -*- 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 "ProfiledThreadData.h"
-
-#if defined(GP_OS_darwin)
-#include <pthread.h>
-#endif
-
-#ifdef XP_WIN
-#include <process.h>
-#define getpid _getpid
-#else
-#include <unistd.h> // for getpid()
-#endif
-
-ProfiledThreadData::ProfiledThreadData(ThreadInfo* aThreadInfo,
-                                       nsIEventTarget* aEventTarget)
-  : mThreadInfo(aThreadInfo)
-{
-  MOZ_COUNT_CTOR(ProfiledThreadData);
-  mResponsiveness.emplace(aEventTarget, aThreadInfo->IsMainThread());
-}
-
-ProfiledThreadData::~ProfiledThreadData()
-{
-  MOZ_COUNT_DTOR(ProfiledThreadData);
-}
-
-void
-ProfiledThreadData::StreamJSON(const ProfileBuffer& aBuffer, JSContext* aCx,
-                               SpliceableJSONWriter& aWriter,
-                               const TimeStamp& aProcessStartTime, double aSinceTime)
-{
-  UniquePtr<PartialThreadProfile> partialProfile = Move(mPartialProfile);
-
-  UniquePtr<UniqueStacks> uniqueStacks = partialProfile
-    ? Move(partialProfile->mUniqueStacks)
-    : MakeUnique<UniqueStacks>();
-
-  uniqueStacks->AdvanceStreamingGeneration();
-
-  UniquePtr<char[]> partialSamplesJSON;
-  UniquePtr<char[]> partialMarkersJSON;
-  if (partialProfile) {
-    partialSamplesJSON = Move(partialProfile->mSamplesJSON);
-    partialMarkersJSON = Move(partialProfile->mMarkersJSON);
-  }
-
-  aWriter.Start();
-  {
-    StreamSamplesAndMarkers(mThreadInfo->Name(), mThreadInfo->ThreadId(),
-                            aBuffer, aWriter,
-                            aProcessStartTime,
-                            mThreadInfo->RegisterTime(), mUnregisterTime,
-                            aSinceTime, aCx,
-                            Move(partialSamplesJSON),
-                            Move(partialMarkersJSON),
-                            *uniqueStacks);
-
-    aWriter.StartObjectProperty("stackTable");
-    {
-      {
-        JSONSchemaWriter schema(aWriter);
-        schema.WriteField("prefix");
-        schema.WriteField("frame");
-      }
-
-      aWriter.StartArrayProperty("data");
-      {
-        uniqueStacks->SpliceStackTableElements(aWriter);
-      }
-      aWriter.EndArray();
-    }
-    aWriter.EndObject();
-
-    aWriter.StartObjectProperty("frameTable");
-    {
-      {
-        JSONSchemaWriter schema(aWriter);
-        schema.WriteField("location");
-        schema.WriteField("implementation");
-        schema.WriteField("optimizations");
-        schema.WriteField("line");
-        schema.WriteField("category");
-      }
-
-      aWriter.StartArrayProperty("data");
-      {
-        uniqueStacks->SpliceFrameTableElements(aWriter);
-      }
-      aWriter.EndArray();
-    }
-    aWriter.EndObject();
-
-    aWriter.StartArrayProperty("stringTable");
-    {
-      uniqueStacks->mUniqueStrings.SpliceStringTableElements(aWriter);
-    }
-    aWriter.EndArray();
-  }
-
-  aWriter.End();
-}
-
-void
-StreamSamplesAndMarkers(const char* aName,
-                        int aThreadId,
-                        const ProfileBuffer& aBuffer,
-                        SpliceableJSONWriter& aWriter,
-                        const TimeStamp& aProcessStartTime,
-                        const TimeStamp& aRegisterTime,
-                        const TimeStamp& aUnregisterTime,
-                        double aSinceTime,
-                        JSContext* aContext,
-                        UniquePtr<char[]>&& aPartialSamplesJSON,
-                        UniquePtr<char[]>&& aPartialMarkersJSON,
-                        UniqueStacks& aUniqueStacks)
-{
-  aWriter.StringProperty("processType",
-                         XRE_ChildProcessTypeToString(XRE_GetProcessType()));
-
-  aWriter.StringProperty("name", aName);
-  aWriter.IntProperty("tid", static_cast<int64_t>(aThreadId));
-  aWriter.IntProperty("pid", static_cast<int64_t>(getpid()));
-
-  if (aRegisterTime) {
-    aWriter.DoubleProperty("registerTime",
-      (aRegisterTime - aProcessStartTime).ToMilliseconds());
-  } else {
-    aWriter.NullProperty("registerTime");
-  }
-
-  if (aUnregisterTime) {
-    aWriter.DoubleProperty("unregisterTime",
-      (aUnregisterTime - aProcessStartTime).ToMilliseconds());
-  } else {
-    aWriter.NullProperty("unregisterTime");
-  }
-
-  aWriter.StartObjectProperty("samples");
-  {
-    {
-      JSONSchemaWriter schema(aWriter);
-      schema.WriteField("stack");
-      schema.WriteField("time");
-      schema.WriteField("responsiveness");
-      schema.WriteField("rss");
-      schema.WriteField("uss");
-    }
-
-    aWriter.StartArrayProperty("data");
-    {
-      if (aPartialSamplesJSON) {
-        // We would only have saved streamed samples during shutdown
-        // streaming, which cares about dumping the entire buffer, and thus
-        // should have passed in 0 for aSinceTime.
-        MOZ_ASSERT(aSinceTime == 0);
-        aWriter.Splice(aPartialSamplesJSON.get());
-      }
-      aBuffer.StreamSamplesToJSON(aWriter, aThreadId, aSinceTime,
-                                  aContext, aUniqueStacks);
-    }
-    aWriter.EndArray();
-  }
-  aWriter.EndObject();
-
-  aWriter.StartObjectProperty("markers");
-  {
-    {
-      JSONSchemaWriter schema(aWriter);
-      schema.WriteField("name");
-      schema.WriteField("time");
-      schema.WriteField("data");
-    }
-
-    aWriter.StartArrayProperty("data");
-    {
-      if (aPartialMarkersJSON) {
-        MOZ_ASSERT(aSinceTime == 0);
-        aWriter.Splice(aPartialMarkersJSON.get());
-      }
-      aBuffer.StreamMarkersToJSON(aWriter, aThreadId, aProcessStartTime,
-                                  aSinceTime, aUniqueStacks);
-    }
-    aWriter.EndArray();
-  }
-  aWriter.EndObject();
-}
-
-void
-ProfiledThreadData::FlushSamplesAndMarkers(JSContext* aCx,
-                                           const TimeStamp& aProcessStartTime,
-                                           ProfileBuffer& aBuffer)
-{
-  // This function is used to serialize the current buffer just before
-  // JSContext destruction.
-  MOZ_ASSERT(aCx);
-
-  // Unlike StreamJSObject, do not surround the samples in brackets by calling
-  // aWriter.{Start,End}BareList. The result string will be a comma-separated
-  // list of JSON object literals that will prepended by StreamJSObject into
-  // an existing array.
-  //
-  // Note that the UniqueStacks instance is persisted so that the frame-index
-  // mapping is stable across JS shutdown.
-  UniquePtr<UniqueStacks> uniqueStacks = mPartialProfile
-    ? Move(mPartialProfile->mUniqueStacks)
-    : MakeUnique<UniqueStacks>();
-
-  uniqueStacks->AdvanceStreamingGeneration();
-
-  UniquePtr<char[]> samplesJSON;
-  UniquePtr<char[]> markersJSON;
-
-  {
-    SpliceableChunkedJSONWriter b;
-    b.StartBareList();
-    bool haveSamples = false;
-    {
-      if (mPartialProfile && mPartialProfile->mSamplesJSON) {
-        b.Splice(mPartialProfile->mSamplesJSON.get());
-        haveSamples = true;
-      }
-
-      // We deliberately use a new variable instead of writing something like
-      // `haveSamples || aBuffer.StreamSamplesToJSON(...)` because we don't want
-      // to short-circuit the call.
-      bool streamedNewSamples =
-        aBuffer.StreamSamplesToJSON(b, mThreadInfo->ThreadId(),
-                                    /* aSinceTime = */ 0,
-                                    aCx, *uniqueStacks);
-      haveSamples = haveSamples || streamedNewSamples;
-    }
-    b.EndBareList();
-
-    // https://bugzilla.mozilla.org/show_bug.cgi?id=1428076
-    // If we don't have any data, keep samplesJSON set to null. That
-    // way we won't try to splice it into the JSON later on, which would
-    // result in an invalid JSON due to stray commas.
-    if (haveSamples) {
-      samplesJSON = b.WriteFunc()->CopyData();
-    }
-  }
-
-  {
-    SpliceableChunkedJSONWriter b;
-    b.StartBareList();
-    bool haveMarkers = false;
-    {
-      if (mPartialProfile && mPartialProfile->mMarkersJSON) {
-        b.Splice(mPartialProfile->mMarkersJSON.get());
-        haveMarkers = true;
-      }
-
-      // We deliberately use a new variable instead of writing something like
-      // `haveMarkers || aBuffer.StreamMarkersToJSON(...)` because we don't want
-      // to short-circuit the call.
-      bool streamedNewMarkers =
-        aBuffer.StreamMarkersToJSON(b, mThreadInfo->ThreadId(),
-                                    aProcessStartTime,
-                                    /* aSinceTime = */ 0, *uniqueStacks);
-      haveMarkers = haveMarkers || streamedNewMarkers;
-    }
-    b.EndBareList();
-
-    // https://bugzilla.mozilla.org/show_bug.cgi?id=1428076
-    // If we don't have any data, keep markersJSON set to null. That
-    // way we won't try to splice it into the JSON later on, which would
-    // result in an invalid JSON due to stray commas.
-    if (haveMarkers) {
-      markersJSON = b.WriteFunc()->CopyData();
-    }
-  }
-
-  mPartialProfile = MakeUnique<PartialThreadProfile>(
-    Move(samplesJSON), Move(markersJSON), Move(uniqueStacks));
-
-  // Reset the buffer. Attempting to symbolicate JS samples after mContext has
-  // gone away will crash.
-  aBuffer.Reset();
-}
deleted file mode 100644
--- a/tools/profiler/core/ProfiledThreadData.h
+++ /dev/null
@@ -1,144 +0,0 @@
-/* -*- 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/. */
-
-#ifndef ProfiledThreadData_h
-#define ProfiledThreadData_h
-
-#include "mozilla/NotNull.h"
-#include "mozilla/TimeStamp.h"
-#include "mozilla/UniquePtrExtensions.h"
-
-#include "js/ProfilingStack.h"
-#include "platform.h"
-#include "ProfileBuffer.h"
-#include "ThreadInfo.h"
-
-// Contains data for partial profiles that get saved when
-// ThreadInfo::FlushSamplesAndMarkers gets called.
-struct PartialThreadProfile final
-{
-  PartialThreadProfile(mozilla::UniquePtr<char[]>&& aSamplesJSON,
-                       mozilla::UniquePtr<char[]>&& aMarkersJSON,
-                       mozilla::UniquePtr<UniqueStacks>&& aUniqueStacks)
-    : mSamplesJSON(mozilla::Move(aSamplesJSON))
-    , mMarkersJSON(mozilla::Move(aMarkersJSON))
-    , mUniqueStacks(mozilla::Move(aUniqueStacks))
-  {}
-
-  mozilla::UniquePtr<char[]> mSamplesJSON;
-  mozilla::UniquePtr<char[]> mMarkersJSON;
-  mozilla::UniquePtr<UniqueStacks> mUniqueStacks;
-};
-
-// This class contains information about a thread that is only relevant while
-// the profiler is running, for any threads (both alive and dead) whose thread
-// name matches the "thread filter" in the current profiler run.
-// ProfiledThreadData objects may be kept alive even after the thread is
-// unregistered, as long as there is still data for that thread in the profiler
-// buffer.
-//
-// Accesses to this class are protected by the profiler state lock.
-//
-// Created as soon as the following are true for the thread:
-//  - The profiler is running, and
-//  - the thread matches the profiler's thread filter, and
-//  - the thread is registered with the profiler.
-// So it gets created in response to either (1) the profiler being started (for
-// an existing registered thread) or (2) the thread being registered (if the
-// profiler is already running).
-//
-// The thread may be unregistered during the lifetime of ProfiledThreadData.
-// If that happens, NotifyUnregistered() is called.
-//
-// This class is the right place to store buffer positions. Profiler buffer
-// positions become invalid if the profiler buffer is destroyed, which happens
-// when the profiler is stopped.
-class ProfiledThreadData final
-{
-public:
-  ProfiledThreadData(ThreadInfo* aThreadInfo, nsIEventTarget* aEventTarget);
-  ~ProfiledThreadData();
-
-  void NotifyUnregistered(uint64_t aBufferPosition)
-  {
-    mResponsiveness.reset();
-    mLastSample = mozilla::Nothing();
-    mUnregisterTime = TimeStamp::Now();
-    mBufferPositionWhenUnregistered = mozilla::Some(aBufferPosition);
-  }
-  mozilla::Maybe<uint64_t> BufferPositionWhenUnregistered() { return mBufferPositionWhenUnregistered; }
-
-  mozilla::Maybe<uint64_t>& LastSample() { return mLastSample; }
-
-  void StreamJSON(const ProfileBuffer& aBuffer, JSContext* aCx,
-                  SpliceableJSONWriter& aWriter,
-                  const mozilla::TimeStamp& aProcessStartTime,
-                  double aSinceTime);
-
-  // Call this method when the JS entries inside the buffer are about to
-  // become invalid, i.e., just before JS shutdown.
-  void FlushSamplesAndMarkers(JSContext* aCx,
-                              const mozilla::TimeStamp& aProcessStartTime,
-                              ProfileBuffer& aBuffer);
-
-  // Returns nullptr if this is not the main thread or if this thread is not
-  // being profiled.
-  ThreadResponsiveness* GetThreadResponsiveness()
-  {
-    ThreadResponsiveness* responsiveness = mResponsiveness.ptrOr(nullptr);
-    return responsiveness;
-  }
-
-  const RefPtr<ThreadInfo> Info() const { return mThreadInfo; }
-
-private:
-  // Group A:
-  // The following fields are interesting for the entire lifetime of a
-  // ProfiledThreadData object.
-
-  // This thread's thread info.
-  const RefPtr<ThreadInfo> mThreadInfo;
-
-  // JS frames in the buffer may require a live JSRuntime to stream (e.g.,
-  // stringifying JIT frames). In the case of JSRuntime destruction,
-  // FlushSamplesAndMarkers should be called to save them. These are spliced
-  // into the final stream.
-  UniquePtr<PartialThreadProfile> mPartialProfile;
-
-  // Group B:
-  // The following fields are only used while this thread is alive and
-  // registered. They become Nothing() once the thread is unregistered.
-
-  // A helper object that instruments nsIThreads to obtain responsiveness
-  // information about their event loop.
-  mozilla::Maybe<ThreadResponsiveness> mResponsiveness;
-
-  // 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;
-
-  // Group C:
-  // The following fields are only used once this thread has been unregistered.
-
-  mozilla::Maybe<uint64_t> mBufferPositionWhenUnregistered;
-  mozilla::TimeStamp mUnregisterTime;
-};
-
-void
-StreamSamplesAndMarkers(const char* aName, int aThreadId,
-                        const ProfileBuffer& aBuffer,
-                        SpliceableJSONWriter& aWriter,
-                        const mozilla::TimeStamp& aProcessStartTime,
-                        const TimeStamp& aRegisterTime,
-                        const TimeStamp& aUnregisterTime,
-                        double aSinceTime,
-                        JSContext* aContext,
-                        UniquePtr<char[]>&& aPartialSamplesJSON,
-                        UniquePtr<char[]>&& aPartialMarkersJSON,
-                        UniqueStacks& aUniqueStacks);
-
-#endif  // ProfiledThreadData_h
deleted file mode 100644
--- a/tools/profiler/core/RegisteredThread.h
+++ /dev/null
@@ -1,309 +0,0 @@
-/* -*- 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/. */
-
-#ifndef RegisteredThread_h
-#define RegisteredThread_h
-
-#include "mozilla/UniquePtrExtensions.h"
-
-#include "platform.h"
-#include "ThreadInfo.h"
-
-// This class contains the state for a single thread that is accessible without
-// protection from gPSMutex in platform.cpp. Because there is no external
-// protection against data races, it must provide internal protection. Hence
-// the "Racy" prefix.
-//
-class RacyRegisteredThread final
-{
-public:
-  explicit RacyRegisteredThread(int aThreadId)
-    : mThreadId(aThreadId)
-    , mSleep(AWAKE)
-  {
-    MOZ_COUNT_CTOR(RacyRegisteredThread);
-  }
-
-  ~RacyRegisteredThread()
-  {
-    MOZ_COUNT_DTOR(RacyRegisteredThread);
-  }
-
-  void AddPendingMarker(const char* aMarkerName,
-                        mozilla::UniquePtr<ProfilerMarkerPayload> aPayload,
-                        double aTime)
-  {
-    ProfilerMarker* marker =
-      new ProfilerMarker(aMarkerName, mThreadId, Move(aPayload), aTime);
-    mPendingMarkers.insert(marker);
-  }
-
-  // Called within signal. Function must be reentrant.
-  ProfilerMarkerLinkedList* GetPendingMarkers()
-  {
-    // The profiled thread is interrupted, so we can access the list safely.
-    // Unless the profiled thread was in the middle of changing the list when
-    // we interrupted it - in that case, accessList() will return null.
-    return mPendingMarkers.accessList();
-  }
-
-  // This is called on every profiler restart. Put things that should happen at
-  // that time here.
-  void ReinitializeOnResume()
-  {
-    // This is needed to cause an initial sample to be taken from sleeping
-    // threads that had been observed prior to the profiler stopping and
-    // restarting. Otherwise sleeping threads would not have any samples to
-    // copy forward while sleeping.
-    (void)mSleep.compareExchange(SLEEPING_OBSERVED, SLEEPING_NOT_OBSERVED);
-  }
-
-  // This returns true for the second and subsequent calls in each sleep cycle.
-  bool CanDuplicateLastSampleDueToSleep()
-  {
-    if (mSleep == AWAKE) {
-      return false;
-    }
-
-    if (mSleep.compareExchange(SLEEPING_NOT_OBSERVED, SLEEPING_OBSERVED)) {
-      return false;
-    }
-
-    return true;
-  }
-
-  // Call this whenever the current thread sleeps. Calling it twice in a row
-  // without an intervening setAwake() call is an error.
-  void SetSleeping()
-  {
-    MOZ_ASSERT(mSleep == AWAKE);
-    mSleep = SLEEPING_NOT_OBSERVED;
-  }
-
-  // Call this whenever the current thread wakes. Calling it twice in a row
-  // without an intervening setSleeping() call is an error.
-  void SetAwake()
-  {
-    MOZ_ASSERT(mSleep != AWAKE);
-    mSleep = AWAKE;
-  }
-
-  bool IsSleeping() { return mSleep != AWAKE; }
-
-  int ThreadId() const { return mThreadId; }
-
-  class PseudoStack& PseudoStack() { return mPseudoStack; }
-  const class PseudoStack& PseudoStack() const { return mPseudoStack; }
-
-private:
-  class PseudoStack mPseudoStack;
-
-  // A list of pending markers that must be moved to the circular buffer.
-  ProfilerSignalSafeLinkedList<ProfilerMarker> mPendingMarkers;
-
-  // mThreadId contains the thread ID of the current thread. It is safe to read
-  // this from multiple threads concurrently, as it will never be mutated.
-  const int mThreadId;
-
-  // mSleep tracks whether the thread is sleeping, and if so, whether it has
-  // been previously observed. This is used for an optimization: in some cases,
-  // when a thread is asleep, we duplicate the previous sample, which is
-  // cheaper than taking a new sample.
-  //
-  // mSleep is atomic because it is accessed from multiple threads.
-  //
-  // - It is written only by this thread, via setSleeping() and setAwake().
-  //
-  // - It is read by SamplerThread::Run().
-  //
-  // There are two cases where racing between threads can cause an issue.
-  //
-  // - If CanDuplicateLastSampleDueToSleep() returns false but that result is
-  //   invalidated before being acted upon, we will take a full sample
-  //   unnecessarily. This is additional work but won't cause any correctness
-  //   issues. (In actual fact, this case is impossible. In order to go from
-  //   CanDuplicateLastSampleDueToSleep() returning false to it returning true
-  //   requires an intermediate call to it in order for mSleep to go from
-  //   SLEEPING_NOT_OBSERVED to SLEEPING_OBSERVED.)
-  //
-  // - If CanDuplicateLastSampleDueToSleep() returns true but that result is
-  //   invalidated before being acted upon -- i.e. the thread wakes up before
-  //   DuplicateLastSample() is called -- we will duplicate the previous
-  //   sample. This is inaccurate, but only slightly... we will effectively
-  //   treat the thread as having slept a tiny bit longer than it really did.
-  //
-  // This latter inaccuracy could be avoided by moving the
-  // CanDuplicateLastSampleDueToSleep() check within the thread-freezing code,
-  // e.g. the section where Tick() is called. But that would reduce the
-  // effectiveness of the optimization because more code would have to be run
-  // before we can tell that duplication is allowed.
-  //
-  static const int AWAKE = 0;
-  static const int SLEEPING_NOT_OBSERVED = 1;
-  static const int SLEEPING_OBSERVED = 2;
-  mozilla::Atomic<int> mSleep;
-};
-
-// This class contains information that's relevant to a single thread only
-// while that thread is running and registered with the profiler, but
-// regardless of whether the profiler is running. All accesses to it are
-// protected by the profiler state lock.
-class RegisteredThread final
-{
-public:
-  RegisteredThread(ThreadInfo* aInfo, nsIEventTarget* aThread,
-                   void* aStackTop);
-  ~RegisteredThread();
-
-  class RacyRegisteredThread& RacyRegisteredThread() { return mRacyRegisteredThread; }
-  const class RacyRegisteredThread& RacyRegisteredThread() const { return mRacyRegisteredThread; }
-
-  PlatformData* GetPlatformData() const { return mPlatformData.get(); }
-  const void* StackTop() const { return mStackTop; }
-
-  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
-
-  // Set the JSContext of the thread to be sampled. Sampling cannot begin until
-  // this has been set.
-  void SetJSContext(JSContext* aContext)
-  {
-    // This function runs on-thread.
-
-    MOZ_ASSERT(aContext && !mContext);
-
-    mContext = aContext;
-
-    // We give the JS engine a non-owning reference to the PseudoStack. It's
-    // important that the JS engine doesn't touch this once the thread dies.
-    js::SetContextProfilingStack(aContext, &RacyRegisteredThread().PseudoStack());
-
-    PollJSSampling();
-  }
-
-  void ClearJSContext()
-  {
-    // This function runs on-thread.
-    mContext = nullptr;
-  }
-
-  JSContext* GetJSContext() const { return mContext; }
-
-  const RefPtr<ThreadInfo> Info() const { return mThreadInfo; }
-  const nsCOMPtr<nsIEventTarget> GetEventTarget() const { return mThread; }
-
-  // Request that this thread start JS sampling. JS sampling won't actually
-  // start until a subsequent PollJSSampling() call occurs *and* mContext has
-  // been set.
-  void StartJSSampling()
-  {
-    // This function runs on-thread or off-thread.
-
-    MOZ_RELEASE_ASSERT(mJSSampling == INACTIVE ||
-                       mJSSampling == INACTIVE_REQUESTED);
-    mJSSampling = ACTIVE_REQUESTED;
-  }
-
-  // Request that this thread stop JS sampling. JS sampling won't actually stop
-  // until a subsequent PollJSSampling() call occurs.
-  void StopJSSampling()
-  {
-    // This function runs on-thread or off-thread.
-
-    MOZ_RELEASE_ASSERT(mJSSampling == ACTIVE ||
-                       mJSSampling == ACTIVE_REQUESTED);
-    mJSSampling = INACTIVE_REQUESTED;
-  }
-
-  // Poll to see if JS sampling should be started/stopped.
-  void PollJSSampling()
-  {
-    // This function runs on-thread.
-
-    // We can't start/stop profiling until we have the thread's JSContext.
-    if (mContext) {
-      // It is possible for mJSSampling to go through the following sequences.
-      //
-      // - INACTIVE, ACTIVE_REQUESTED, INACTIVE_REQUESTED, INACTIVE
-      //
-      // - ACTIVE, INACTIVE_REQUESTED, ACTIVE_REQUESTED, ACTIVE
-      //
-      // Therefore, the if and else branches here aren't always interleaved.
-      // This is ok because the JS engine can handle that.
-      //
-      if (mJSSampling == ACTIVE_REQUESTED) {
-        mJSSampling = ACTIVE;
-        js::EnableContextProfilingStack(mContext, true);
-        js::RegisterContextProfilingEventMarker(mContext, profiler_add_marker);
-
-      } else if (mJSSampling == INACTIVE_REQUESTED) {
-        mJSSampling = INACTIVE;
-        js::EnableContextProfilingStack(mContext, false);
-      }
-    }
-  }
-
-private:
-  class RacyRegisteredThread mRacyRegisteredThread;
-
-  const UniquePlatformData mPlatformData;
-  const void* mStackTop;
-
-  const RefPtr<ThreadInfo> mThreadInfo;
-  const nsCOMPtr<nsIEventTarget> mThread;
-
-  // If this is a JS thread, this is its JSContext, which is required for any
-  // JS sampling.
-  JSContext* mContext;
-
-  // The profiler needs to start and stop JS sampling of JS threads at various
-  // times. However, the JS engine can only do the required actions on the
-  // JS thread itself ("on-thread"), not from another thread ("off-thread").
-  // Therefore, we have the following two-step process.
-  //
-  // - The profiler requests (on-thread or off-thread) that the JS sampling be
-  //   started/stopped, by changing mJSSampling to the appropriate REQUESTED
-  //   state.
-  //
-  // - The relevant JS thread polls (on-thread) for changes to mJSSampling.
-  //   When it sees a REQUESTED state, it performs the appropriate actions to
-  //   actually start/stop JS sampling, and changes mJSSampling out of the
-  //   REQUESTED state.
-  //
-  // The state machine is as follows.
-  //
-  //             INACTIVE --> ACTIVE_REQUESTED
-  //                  ^       ^ |
-  //                  |     _/  |
-  //                  |   _/    |
-  //                  |  /      |
-  //                  | v       v
-  //   INACTIVE_REQUESTED <-- ACTIVE
-  //
-  // The polling is done in the following two ways.
-  //
-  // - Via the interrupt callback mechanism; the JS thread must call
-  //   profiler_js_interrupt_callback() from its own interrupt callback.
-  //   This is how sampling must be started/stopped for threads where the
-  //   request was made off-thread.
-  //
-  // - When {Start,Stop}JSSampling() is called on-thread, we can immediately
-  //   follow it with a PollJSSampling() call to avoid the delay between the
-  //   two steps. Likewise, setJSContext() calls PollJSSampling().
-  //
-  // One non-obvious thing about all this: these JS sampling requests are made
-  // on all threads, even non-JS threads. mContext needs to also be set (via
-  // setJSContext(), which can only happen for JS threads) for any JS sampling
-  // to actually happen.
-  //
-  enum {
-    INACTIVE = 0,
-    ACTIVE_REQUESTED = 1,
-    ACTIVE = 2,
-    INACTIVE_REQUESTED = 3,
-  } mJSSampling;
-};
-
-#endif  // RegisteredThread_h
rename from tools/profiler/core/RegisteredThread.cpp
rename to tools/profiler/core/ThreadInfo.cpp
--- a/tools/profiler/core/RegisteredThread.cpp
+++ b/tools/profiler/core/ThreadInfo.cpp
@@ -1,47 +1,341 @@
 /* -*- 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 "RegisteredThread.h"
+#include "ThreadInfo.h"
+
+#include "mozilla/DebugOnly.h"
+
+#if defined(GP_OS_darwin)
+#include <pthread.h>
+#endif
 
-RegisteredThread::RegisteredThread(ThreadInfo* aInfo, nsIEventTarget* aThread,
-                                   void* aStackTop)
-  : mRacyRegisteredThread(aInfo->ThreadId())
-  , mPlatformData(AllocPlatformData(aInfo->ThreadId()))
+#ifdef XP_WIN
+#include <process.h>
+#define getpid _getpid
+#else
+#include <unistd.h> // for getpid()
+#endif
+
+ThreadInfo::ThreadInfo(const char* aName,
+                       int aThreadId,
+                       bool aIsMainThread,
+                       nsIEventTarget* aThread,
+                       void* aStackTop)
+  : mName(strdup(aName))
+  , mRegisterTime(TimeStamp::Now())
+  , mIsMainThread(aIsMainThread)
+  , mThread(aThread)
+  , mRacyInfo(mozilla::MakeNotNull<RacyThreadInfo*>(aThreadId))
+  , mPlatformData(AllocPlatformData(aThreadId))
   , mStackTop(aStackTop)
-  , mThreadInfo(aInfo)
-  , mThread(aThread)
+  , mIsBeingProfiled(false)
   , mContext(nullptr)
   , mJSSampling(INACTIVE)
+  , mLastSample()
 {
-  MOZ_COUNT_CTOR(RegisteredThread);
+  MOZ_COUNT_CTOR(ThreadInfo);
 
   // We don't have to guess on mac
 #if defined(GP_OS_darwin)
   pthread_t self = pthread_self();
   mStackTop = pthread_get_stackaddr_np(self);
 #endif
+
+  // I don't know if we can assert this. But we should warn.
+  MOZ_ASSERT(aThreadId >= 0, "native thread ID is < 0");
+  MOZ_ASSERT(aThreadId <= INT32_MAX, "native thread ID is > INT32_MAX");
+}
+
+ThreadInfo::~ThreadInfo()
+{
+  MOZ_COUNT_DTOR(ThreadInfo);
+
+  delete mRacyInfo;
+}
+
+void
+ThreadInfo::StartProfiling()
+{
+  mIsBeingProfiled = true;
+  mRacyInfo->ReinitializeOnResume();
+  mResponsiveness.emplace(mThread, mIsMainThread);
+}
+
+void
+ThreadInfo::StopProfiling()
+{
+  mResponsiveness.reset();
+  mPartialProfile = nullptr;
+  mIsBeingProfiled = false;
+}
+
+void
+ThreadInfo::StreamJSON(const ProfileBuffer& aBuffer,
+                       SpliceableJSONWriter& aWriter,
+                       const TimeStamp& aProcessStartTime, double aSinceTime)
+{
+  UniquePtr<PartialThreadProfile> partialProfile = Move(mPartialProfile);
+
+  UniquePtr<UniqueStacks> uniqueStacks = partialProfile
+    ? Move(partialProfile->mUniqueStacks)
+    : MakeUnique<UniqueStacks>();
+
+  uniqueStacks->AdvanceStreamingGeneration();
+
+  UniquePtr<char[]> partialSamplesJSON;
+  UniquePtr<char[]> partialMarkersJSON;
+  if (partialProfile) {
+    partialSamplesJSON = Move(partialProfile->mSamplesJSON);
+    partialMarkersJSON = Move(partialProfile->mMarkersJSON);
+  }
+
+  aWriter.Start();
+  {
+    StreamSamplesAndMarkers(Name(), ThreadId(), aBuffer, aWriter,
+                            aProcessStartTime,
+                            mRegisterTime, mUnregisterTime,
+                            aSinceTime, mContext,
+                            Move(partialSamplesJSON),
+                            Move(partialMarkersJSON),
+                            *uniqueStacks);
+
+    aWriter.StartObjectProperty("stackTable");
+    {
+      {
+        JSONSchemaWriter schema(aWriter);
+        schema.WriteField("prefix");
+        schema.WriteField("frame");
+      }
+
+      aWriter.StartArrayProperty("data");
+      {
+        uniqueStacks->SpliceStackTableElements(aWriter);
+      }
+      aWriter.EndArray();
+    }
+    aWriter.EndObject();
+
+    aWriter.StartObjectProperty("frameTable");
+    {
+      {
+        JSONSchemaWriter schema(aWriter);
+        schema.WriteField("location");
+        schema.WriteField("implementation");
+        schema.WriteField("optimizations");
+        schema.WriteField("line");
+        schema.WriteField("category");
+      }
+
+      aWriter.StartArrayProperty("data");
+      {
+        uniqueStacks->SpliceFrameTableElements(aWriter);
+      }
+      aWriter.EndArray();
+    }
+    aWriter.EndObject();
+
+    aWriter.StartArrayProperty("stringTable");
+    {
+      uniqueStacks->mUniqueStrings.SpliceStringTableElements(aWriter);
+    }
+    aWriter.EndArray();
+  }
+
+  aWriter.End();
 }
 
-RegisteredThread::~RegisteredThread()
+void
+StreamSamplesAndMarkers(const char* aName,
+                        int aThreadId,
+                        const ProfileBuffer& aBuffer,
+                        SpliceableJSONWriter& aWriter,
+                        const TimeStamp& aProcessStartTime,
+                        const TimeStamp& aRegisterTime,
+                        const TimeStamp& aUnregisterTime,
+                        double aSinceTime,
+                        JSContext* aContext,
+                        UniquePtr<char[]>&& aPartialSamplesJSON,
+                        UniquePtr<char[]>&& aPartialMarkersJSON,
+                        UniqueStacks& aUniqueStacks)
 {
-  MOZ_COUNT_DTOR(RegisteredThread);
+  aWriter.StringProperty("processType",
+                         XRE_ChildProcessTypeToString(XRE_GetProcessType()));
+
+  aWriter.StringProperty("name", aName);
+  aWriter.IntProperty("tid", static_cast<int64_t>(aThreadId));
+  aWriter.IntProperty("pid", static_cast<int64_t>(getpid()));
+
+  if (aRegisterTime) {
+    aWriter.DoubleProperty("registerTime",
+      (aRegisterTime - aProcessStartTime).ToMilliseconds());
+  } else {
+    aWriter.NullProperty("registerTime");
+  }
+
+  if (aUnregisterTime) {
+    aWriter.DoubleProperty("unregisterTime",
+      (aUnregisterTime - aProcessStartTime).ToMilliseconds());
+  } else {
+    aWriter.NullProperty("unregisterTime");
+  }
+
+  aWriter.StartObjectProperty("samples");
+  {
+    {
+      JSONSchemaWriter schema(aWriter);
+      schema.WriteField("stack");
+      schema.WriteField("time");
+      schema.WriteField("responsiveness");
+      schema.WriteField("rss");
+      schema.WriteField("uss");
+    }
+
+    aWriter.StartArrayProperty("data");
+    {
+      if (aPartialSamplesJSON) {
+        // We would only have saved streamed samples during shutdown
+        // streaming, which cares about dumping the entire buffer, and thus
+        // should have passed in 0 for aSinceTime.
+        MOZ_ASSERT(aSinceTime == 0);
+        aWriter.Splice(aPartialSamplesJSON.get());
+      }
+      aBuffer.StreamSamplesToJSON(aWriter, aThreadId, aSinceTime,
+                                  aContext, aUniqueStacks);
+    }
+    aWriter.EndArray();
+  }
+  aWriter.EndObject();
+
+  aWriter.StartObjectProperty("markers");
+  {
+    {
+      JSONSchemaWriter schema(aWriter);
+      schema.WriteField("name");
+      schema.WriteField("time");
+      schema.WriteField("data");
+    }
+
+    aWriter.StartArrayProperty("data");
+    {
+      if (aPartialMarkersJSON) {
+        MOZ_ASSERT(aSinceTime == 0);
+        aWriter.Splice(aPartialMarkersJSON.get());
+      }
+      aBuffer.StreamMarkersToJSON(aWriter, aThreadId, aProcessStartTime,
+                                  aSinceTime, aUniqueStacks);
+    }
+    aWriter.EndArray();
+  }
+  aWriter.EndObject();
+}
+
+void
+ThreadInfo::FlushSamplesAndMarkers(const TimeStamp& aProcessStartTime,
+                                   ProfileBuffer& aBuffer)
+{
+  // This function is used to serialize the current buffer just before
+  // JSContext destruction.
+  MOZ_ASSERT(mContext);
+
+  // Unlike StreamJSObject, do not surround the samples in brackets by calling
+  // aWriter.{Start,End}BareList. The result string will be a comma-separated
+  // list of JSON object literals that will prepended by StreamJSObject into
+  // an existing array.
+  //
+  // Note that the UniqueStacks instance is persisted so that the frame-index
+  // mapping is stable across JS shutdown.
+  UniquePtr<UniqueStacks> uniqueStacks = mPartialProfile
+    ? Move(mPartialProfile->mUniqueStacks)
+    : MakeUnique<UniqueStacks>();
+
+  uniqueStacks->AdvanceStreamingGeneration();
+
+  UniquePtr<char[]> samplesJSON;
+  UniquePtr<char[]> markersJSON;
+
+  {
+    SpliceableChunkedJSONWriter b;
+    b.StartBareList();
+    bool haveSamples = false;
+    {
+      if (mPartialProfile && mPartialProfile->mSamplesJSON) {
+        b.Splice(mPartialProfile->mSamplesJSON.get());
+        haveSamples = true;
+      }
+
+      // We deliberately use a new variable instead of writing something like
+      // `haveSamples || aBuffer.StreamSamplesToJSON(...)` because we don't want
+      // to short-circuit the call.
+      bool streamedNewSamples =
+        aBuffer.StreamSamplesToJSON(b, ThreadId(), /* aSinceTime = */ 0,
+                                    mContext, *uniqueStacks);
+      haveSamples = haveSamples || streamedNewSamples;
+    }
+    b.EndBareList();
+
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=1428076
+    // If we don't have any data, keep samplesJSON set to null. That
+    // way we won't try to splice it into the JSON later on, which would
+    // result in an invalid JSON due to stray commas.
+    if (haveSamples) {
+      samplesJSON = b.WriteFunc()->CopyData();
+    }
+  }
+
+  {
+    SpliceableChunkedJSONWriter b;
+    b.StartBareList();
+    bool haveMarkers = false;
+    {
+      if (mPartialProfile && mPartialProfile->mMarkersJSON) {
+        b.Splice(mPartialProfile->mMarkersJSON.get());
+        haveMarkers = true;
+      }
+
+      // We deliberately use a new variable instead of writing something like
+      // `haveMarkers || aBuffer.StreamMarkersToJSON(...)` because we don't want
+      // to short-circuit the call.
+      bool streamedNewMarkers =
+        aBuffer.StreamMarkersToJSON(b, ThreadId(), aProcessStartTime,
+                                    /* aSinceTime = */ 0, *uniqueStacks);
+      haveMarkers = haveMarkers || streamedNewMarkers;
+    }
+    b.EndBareList();
+
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=1428076
+    // If we don't have any data, keep markersJSON set to null. That
+    // way we won't try to splice it into the JSON later on, which would
+    // result in an invalid JSON due to stray commas.
+    if (haveMarkers) {
+      markersJSON = b.WriteFunc()->CopyData();
+    }
+  }
+
+  mPartialProfile = MakeUnique<PartialThreadProfile>(
+    Move(samplesJSON), Move(markersJSON), Move(uniqueStacks));
+
+  // Reset the buffer. Attempting to symbolicate JS samples after mContext has
+  // gone away will crash.
+  aBuffer.Reset();
 }
 
 size_t
-RegisteredThread::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ThreadInfo::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   size_t n = aMallocSizeOf(this);
+  n += aMallocSizeOf(mName.get());
+  n += mRacyInfo->SizeOfIncludingThis(aMallocSizeOf);
 
   // Measurement of the following members may be added later if DMD finds it
   // is worthwhile:
   // - mPlatformData
-  // - mRacyRegisteredThread.mPendingMarkers
+  // - mPartialProfile
   //
   // The following members are not measured:
-  // - mThreadInfo: because it is non-owning
+  // - mThread: because it is non-owning
 
   return n;
 }
--- a/tools/profiler/core/ThreadInfo.h
+++ b/tools/profiler/core/ThreadInfo.h
@@ -2,47 +2,420 @@
 /* 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/. */
 
 #ifndef ThreadInfo_h
 #define ThreadInfo_h
 
+#include "mozilla/NotNull.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+#include "platform.h"
+#include "ProfileBuffer.h"
+#include "js/ProfilingStack.h"
+
+// This class contains the info for a single thread that is accessible without
+// protection from gPSMutex in platform.cpp. Because there is no external
+// protection against data races, it must provide internal protection. Hence
+// the "Racy" prefix.
+//
+class RacyThreadInfo final : public PseudoStack
+{
+public:
+  explicit RacyThreadInfo(int aThreadId)
+    : PseudoStack()
+    , mThreadId(aThreadId)
+    , mSleep(AWAKE)
+  {
+    MOZ_COUNT_CTOR(RacyThreadInfo);
+  }
+
+  ~RacyThreadInfo()
+  {
+    MOZ_COUNT_DTOR(RacyThreadInfo);
+  }
+
+  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+  {
+    size_t n = aMallocSizeOf(this);
+
+    // Measurement of the following members may be added later if DMD finds it
+    // is worthwhile:
+    // - things in the PseudoStack
+    // - mPendingMarkers
+    //
+    // If these measurements are added, the code must be careful to avoid data
+    // races. (The current code doesn't have any race issues because the
+    // contents of the PseudoStack object aren't accessed; |this| is used only
+    // as an address for lookup by aMallocSizeof).
 
-#include "nsString.h"
+    return n;
+  }
+
+  void AddPendingMarker(const char* aMarkerName,
+                        mozilla::UniquePtr<ProfilerMarkerPayload> aPayload,
+                        double aTime)
+  {
+    ProfilerMarker* marker =
+      new ProfilerMarker(aMarkerName, mThreadId, Move(aPayload), aTime);
+    mPendingMarkers.insert(marker);
+  }
+
+  // Called within signal. Function must be reentrant.
+  ProfilerMarkerLinkedList* GetPendingMarkers()
+  {
+    // The profiled thread is interrupted, so we can access the list safely.
+    // Unless the profiled thread was in the middle of changing the list when
+    // we interrupted it - in that case, accessList() will return null.
+    return mPendingMarkers.accessList();
+  }
+
+  // This is called on every profiler restart. Put things that should happen at
+  // that time here.
+  void ReinitializeOnResume()
+  {
+    // This is needed to cause an initial sample to be taken from sleeping
+    // threads that had been observed prior to the profiler stopping and
+    // restarting. Otherwise sleeping threads would not have any samples to
+    // copy forward while sleeping.
+    (void)mSleep.compareExchange(SLEEPING_OBSERVED, SLEEPING_NOT_OBSERVED);
+  }
+
+  // This returns true for the second and subsequent calls in each sleep cycle.
+  bool CanDuplicateLastSampleDueToSleep()
+  {
+    if (mSleep == AWAKE) {
+      return false;
+    }
+
+    if (mSleep.compareExchange(SLEEPING_NOT_OBSERVED, SLEEPING_OBSERVED)) {
+      return false;
+    }
+
+    return true;
+  }
 
-// This class contains information about a thread which needs to be stored
-// across restarts of the profiler and which can be useful even after the
-// thread has stopped running.
-// It uses threadsafe refcounting and only contains immutable data.
+  // Call this whenever the current thread sleeps. Calling it twice in a row
+  // without an intervening setAwake() call is an error.
+  void SetSleeping()
+  {
+    MOZ_ASSERT(mSleep == AWAKE);
+    mSleep = SLEEPING_NOT_OBSERVED;
+  }
+
+  // Call this whenever the current thread wakes. Calling it twice in a row
+  // without an intervening setSleeping() call is an error.
+  void SetAwake()
+  {
+    MOZ_ASSERT(mSleep != AWAKE);
+    mSleep = AWAKE;
+  }
+
+  bool IsSleeping() { return mSleep != AWAKE; }
+
+  int ThreadId() const { return mThreadId; }
+
+private:
+  // A list of pending markers that must be moved to the circular buffer.
+  ProfilerSignalSafeLinkedList<ProfilerMarker> mPendingMarkers;
+
+  // mThreadId contains the thread ID of the current thread. It is safe to read
+  // this from multiple threads concurrently, as it will never be mutated.
+  const int mThreadId;
+
+  // mSleep tracks whether the thread is sleeping, and if so, whether it has
+  // been previously observed. This is used for an optimization: in some cases,
+  // when a thread is asleep, we duplicate the previous sample, which is
+  // cheaper than taking a new sample.
+  //
+  // mSleep is atomic because it is accessed from multiple threads.
+  //
+  // - It is written only by this thread, via setSleeping() and setAwake().
+  //
+  // - It is read by SamplerThread::Run().
+  //
+  // There are two cases where racing between threads can cause an issue.
+  //
+  // - If CanDuplicateLastSampleDueToSleep() returns false but that result is
+  //   invalidated before being acted upon, we will take a full sample
+  //   unnecessarily. This is additional work but won't cause any correctness
+  //   issues. (In actual fact, this case is impossible. In order to go from
+  //   CanDuplicateLastSampleDueToSleep() returning false to it returning true
+  //   requires an intermediate call to it in order for mSleep to go from
+  //   SLEEPING_NOT_OBSERVED to SLEEPING_OBSERVED.)
+  //
+  // - If CanDuplicateLastSampleDueToSleep() returns true but that result is
+  //   invalidated before being acted upon -- i.e. the thread wakes up before
+  //   DuplicateLastSample() is called -- we will duplicate the previous
+  //   sample. This is inaccurate, but only slightly... we will effectively
+  //   treat the thread as having slept a tiny bit longer than it really did.
+  //
+  // This latter inaccuracy could be avoided by moving the
+  // CanDuplicateLastSampleDueToSleep() check within the thread-freezing code,
+  // e.g. the section where Tick() is called. But that would reduce the
+  // effectiveness of the optimization because more code would have to be run
+  // before we can tell that duplication is allowed.
+  //
+  static const int AWAKE = 0;
+  static const int SLEEPING_NOT_OBSERVED = 1;
+  static const int SLEEPING_OBSERVED = 2;
+  mozilla::Atomic<int> mSleep;
+};
+
+// Contains data for partial profiles that get saved when
+// ThreadInfo::FlushSamplesAndMarkers gets called.
+struct PartialThreadProfile final
+{
+  PartialThreadProfile(mozilla::UniquePtr<char[]>&& aSamplesJSON,
+                       mozilla::UniquePtr<char[]>&& aMarkersJSON,
+                       mozilla::UniquePtr<UniqueStacks>&& aUniqueStacks)
+    : mSamplesJSON(mozilla::Move(aSamplesJSON))
+    , mMarkersJSON(mozilla::Move(aMarkersJSON))
+    , mUniqueStacks(mozilla::Move(aUniqueStacks))
+  {}
+
+  mozilla::UniquePtr<char[]> mSamplesJSON;
+  mozilla::UniquePtr<char[]> mMarkersJSON;
+  mozilla::UniquePtr<UniqueStacks> mUniqueStacks;
+};
+
+// This class contains the info for a single thread.
+//
+// Note: A thread's ThreadInfo can be held onto after the thread itself exits,
+// because we may need to output profiling information about that thread. But
+// some of the fields in this class are only relevant while the thread is
+// alive. It's possible that this class could be refactored so there is a
+// clearer split between those fields and the fields that are still relevant
+// after the thread exists.
 class ThreadInfo final
 {
 public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadInfo)
+  ThreadInfo(const char* aName, int aThreadId, bool aIsMainThread,
+             nsIEventTarget* aThread, void* aStackTop);
 
-  ThreadInfo(const char* aName, int aThreadId, bool aIsMainThread)
-    : mName(aName)
-    , mRegisterTime(TimeStamp::Now())
-    , mThreadId(aThreadId)
-    , mIsMainThread(aIsMainThread)
-  {
-    // I don't know if we can assert this. But we should warn.
-    MOZ_ASSERT(aThreadId >= 0, "native thread ID is < 0");
-    MOZ_ASSERT(aThreadId <= INT32_MAX, "native thread ID is > INT32_MAX");
-  }
+  ~ThreadInfo();
 
   const char* Name() const { return mName.get(); }
-  mozilla::TimeStamp RegisterTime() const { return mRegisterTime; }
-  int ThreadId() const { return mThreadId; }
+
+  // This is a safe read even when the target thread is not blocked, as this
+  // thread id is never mutated.
+  int ThreadId() const { return RacyInfo()->ThreadId(); }
+
   bool IsMainThread() const { return mIsMainThread; }
 
+  mozilla::NotNull<RacyThreadInfo*> RacyInfo() const { return mRacyInfo; }
+
+  void StartProfiling();
+  void StopProfiling();
+  bool IsBeingProfiled() { return mIsBeingProfiled; }
+
+  void NotifyUnregistered(uint64_t aBufferPosition)
+  {
+    mUnregisterTime = TimeStamp::Now();
+    mBufferPositionWhenUnregistered = mozilla::Some(aBufferPosition);
+  }
+  mozilla::Maybe<uint64_t> BufferPositionWhenUnregistered() { return mBufferPositionWhenUnregistered; }
+
+  PlatformData* GetPlatformData() const { return mPlatformData.get(); }
+  void* StackTop() const { return mStackTop; }
+
+  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+  mozilla::Maybe<uint64_t>& LastSample() { return mLastSample; }
+
 private:
-  ~ThreadInfo() {}
+  mozilla::UniqueFreePtr<char> mName;
+  mozilla::TimeStamp mRegisterTime;
+  mozilla::TimeStamp mUnregisterTime;
+  mozilla::Maybe<uint64_t> mBufferPositionWhenUnregistered;
+  const bool mIsMainThread;
+  nsCOMPtr<nsIEventTarget> mThread;
+
+  // The thread's RacyThreadInfo. This is an owning pointer. It could be an
+  // inline member, but we don't do that because RacyThreadInfo is quite large
+  // (due to the PseudoStack within it), and we have ThreadInfo vectors and so
+  // we'd end up wasting a lot of space in those vectors for excess elements.
+  mozilla::NotNull<RacyThreadInfo*> mRacyInfo;
+
+  UniquePlatformData mPlatformData;
+  void* mStackTop;
+
+  //
+  // The following code is only used for threads that are being profiled, i.e.
+  // for which IsBeingProfiled() returns true.
+  //
+
+public:
+  void StreamJSON(const ProfileBuffer& aBuffer, SpliceableJSONWriter& aWriter,
+                  const mozilla::TimeStamp& aProcessStartTime,
+                  double aSinceTime);
+
+  // Call this method when the JS entries inside the buffer are about to
+  // become invalid, i.e., just before JS shutdown.
+  void FlushSamplesAndMarkers(const mozilla::TimeStamp& aProcessStartTime,
+                              ProfileBuffer& aBuffer);
+
+  // Returns nullptr if this is not the main thread or if this thread is not
+  // being profiled.
+  ThreadResponsiveness* GetThreadResponsiveness()
+  {
+    ThreadResponsiveness* responsiveness = mResponsiveness.ptrOr(nullptr);
+    MOZ_ASSERT(!responsiveness || mIsBeingProfiled);
+    return responsiveness;
+  }
+
+  // Set the JSContext of the thread to be sampled. Sampling cannot begin until
+  // this has been set.
+  void SetJSContext(JSContext* aContext)
+  {
+    // This function runs on-thread.
+
+    MOZ_ASSERT(aContext && !mContext);
+
+    mContext = aContext;
+
+    // We give the JS engine a non-owning reference to the RacyInfo (just the
+    // PseudoStack, really). It's important that the JS engine doesn't touch
+    // this once the thread dies.
+    js::SetContextProfilingStack(aContext, RacyInfo());
+
+    PollJSSampling();
+  }
+
+  // Request that this thread start JS sampling. JS sampling won't actually
+  // start until a subsequent PollJSSampling() call occurs *and* mContext has
+  // been set.
+  void StartJSSampling()
+  {
+    // This function runs on-thread or off-thread.
+
+    MOZ_RELEASE_ASSERT(mJSSampling == INACTIVE ||
+                       mJSSampling == INACTIVE_REQUESTED);
+    mJSSampling = ACTIVE_REQUESTED;
+  }
+
+  // Request that this thread stop JS sampling. JS sampling won't actually stop
+  // until a subsequent PollJSSampling() call occurs.
+  void StopJSSampling()
+  {
+    // This function runs on-thread or off-thread.
+
+    MOZ_RELEASE_ASSERT(mJSSampling == ACTIVE ||
+                       mJSSampling == ACTIVE_REQUESTED);
+    mJSSampling = INACTIVE_REQUESTED;
+  }
+
+  // Poll to see if JS sampling should be started/stopped.
+  void PollJSSampling()
+  {
+    // This function runs on-thread.
 
-  const nsCString mName;
-  const mozilla::TimeStamp mRegisterTime;
-  const int mThreadId;
-  const bool mIsMainThread;
+    // We can't start/stop profiling until we have the thread's JSContext.
+    if (mContext) {
+      // It is possible for mJSSampling to go through the following sequences.
+      //
+      // - INACTIVE, ACTIVE_REQUESTED, INACTIVE_REQUESTED, INACTIVE
+      //
+      // - ACTIVE, INACTIVE_REQUESTED, ACTIVE_REQUESTED, ACTIVE
+      //
+      // Therefore, the if and else branches here aren't always interleaved.
+      // This is ok because the JS engine can handle that.
+      //
+      if (mJSSampling == ACTIVE_REQUESTED) {
+        mJSSampling = ACTIVE;
+        js::EnableContextProfilingStack(mContext, true);
+        js::RegisterContextProfilingEventMarker(mContext, profiler_add_marker);
+
+      } else if (mJSSampling == INACTIVE_REQUESTED) {
+        mJSSampling = INACTIVE;
+        js::EnableContextProfilingStack(mContext, false);
+      }
+    }
+  }
+
+private:
+  bool mIsBeingProfiled;
+
+  // JS frames in the buffer may require a live JSRuntime to stream (e.g.,
+  // stringifying JIT frames). In the case of JSRuntime destruction,
+  // FlushSamplesAndMarkers should be called to save them. These are spliced
+  // into the final stream.
+  UniquePtr<PartialThreadProfile> mPartialProfile;
+
+  // This is used only for nsIThreads.
+  mozilla::Maybe<ThreadResponsiveness> mResponsiveness;
+
+public:
+  // If this is a JS thread, this is its JSContext, which is required for any
+  // JS sampling.
+  JSContext* mContext;
+
+private:
+  // The profiler needs to start and stop JS sampling of JS threads at various
+  // times. However, the JS engine can only do the required actions on the
+  // JS thread itself ("on-thread"), not from another thread ("off-thread").
+  // Therefore, we have the following two-step process.
+  //
+  // - The profiler requests (on-thread or off-thread) that the JS sampling be
+  //   started/stopped, by changing mJSSampling to the appropriate REQUESTED
+  //   state.
+  //
+  // - The relevant JS thread polls (on-thread) for changes to mJSSampling.
+  //   When it sees a REQUESTED state, it performs the appropriate actions to
+  //   actually start/stop JS sampling, and changes mJSSampling out of the
+  //   REQUESTED state.
+  //
+  // The state machine is as follows.
+  //
+  //             INACTIVE --> ACTIVE_REQUESTED
+  //                  ^       ^ |
+  //                  |     _/  |
+  //                  |   _/    |
+  //                  |  /      |
+  //                  | v       v
+  //   INACTIVE_REQUESTED <-- ACTIVE
+  //
+  // The polling is done in the following two ways.
+  //
+  // - Via the interrupt callback mechanism; the JS thread must call
+  //   profiler_js_interrupt_callback() from its own interrupt callback.
+  //   This is how sampling must be started/stopped for threads where the
+  //   request was made off-thread.
+  //
+  // - When {Start,Stop}JSSampling() is called on-thread, we can immediately
+  //   follow it with a PollJSSampling() call to avoid the delay between the
+  //   two steps. Likewise, setJSContext() calls PollJSSampling().
+  //
+  // One non-obvious thing about all this: these JS sampling requests are made
+  // on all threads, even non-JS threads. mContext needs to also be set (via
+  // setJSContext(), which can only happen for JS threads) for any JS sampling
+  // to actually happen.
+  //
+  enum {
+    INACTIVE = 0,
+    ACTIVE_REQUESTED = 1,
+    ACTIVE = 2,
+    INACTIVE_REQUESTED = 3,
+  } mJSSampling;
+
+  // 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,
+                        const TimeStamp& aUnregisterTime,
+                        double aSinceTime,
+                        JSContext* aContext,
+                        UniquePtr<char[]>&& aPartialSamplesJSON,
+                        UniquePtr<char[]>&& aPartialMarkersJSON,
+                        UniqueStacks& aUniqueStacks);
+
 #endif  // ThreadInfo_h
--- a/tools/profiler/core/platform-linux-android.cpp
+++ b/tools/profiler/core/platform-linux-android.cpp
@@ -291,27 +291,27 @@ Sampler::Disable(PSLockRef aLock)
   // Restore old signal handler. This is global state so it's important that
   // we do it now, while gPSMutex is locked.
   sigaction(SIGPROF, &mOldSigprofHandler, 0);
 }
 
 template<typename Func>
 void
 Sampler::SuspendAndSampleAndResumeThread(PSLockRef aLock,
-                                         const RegisteredThread& aRegisteredThread,
+                                         const ThreadInfo& aThreadInfo,
                                          const Func& aProcessRegs)
 {
   // Only one sampler thread can be sampling at once.  So we expect to have
   // complete control over |sSigHandlerCoordinator|.
   MOZ_ASSERT(!sSigHandlerCoordinator);
 
   if (mSamplerTid == -1) {
     mSamplerTid = gettid();
   }
-  int sampleeTid = aRegisteredThread.Info()->ThreadId();
+  int sampleeTid = aThreadInfo.ThreadId();
   MOZ_RELEASE_ASSERT(sampleeTid != mSamplerTid);
 
   //----------------------------------------------------------------//
   // Suspend the samplee thread and get its context.
 
   SigHandlerCoordinator coord;   // on sampler thread's stack
   sSigHandlerCoordinator = &coord;
 
--- a/tools/profiler/core/platform-macos.cpp
+++ b/tools/profiler/core/platform-macos.cpp
@@ -79,21 +79,21 @@ Sampler::Sampler(PSLockRef aLock)
 void
 Sampler::Disable(PSLockRef aLock)
 {
 }
 
 template<typename Func>
 void
 Sampler::SuspendAndSampleAndResumeThread(PSLockRef aLock,
-                                         const RegisteredThread& aRegisteredThread,
+                                         const ThreadInfo& aThreadInfo,
                                          const Func& aProcessRegs)
 {
   thread_act_t samplee_thread =
-    aRegisteredThread.GetPlatformData()->ProfiledThread();
+    aThreadInfo.GetPlatformData()->ProfiledThread();
 
   //----------------------------------------------------------------//
   // Suspend the samplee thread and get its context.
 
   // We're using thread_suspend on OS X because pthread_kill (which is what we
   // at one time used on Linux) has less consistent performance and causes
   // strange crashes, see bug 1166778 and bug 1166808.  thread_suspend
   // is also just a lot simpler to use.
--- a/tools/profiler/core/platform-win32.cpp
+++ b/tools/profiler/core/platform-win32.cpp
@@ -122,20 +122,20 @@ Sampler::Sampler(PSLockRef aLock)
 void
 Sampler::Disable(PSLockRef aLock)
 {
 }
 
 template<typename Func>
 void
 Sampler::SuspendAndSampleAndResumeThread(PSLockRef aLock,
-                                         const RegisteredThread& aRegisteredThread,
+                                         const ThreadInfo& aThreadInfo,
                                          const Func& aProcessRegs)
 {
-  HANDLE profiled_thread = aRegisteredThread.GetPlatformData()->ProfiledThread();
+  HANDLE profiled_thread = aThreadInfo.GetPlatformData()->ProfiledThread();
   if (profiled_thread == nullptr) {
     return;
   }
 
   // Context used for sampling the register state of the profiled thread.
   CONTEXT context;
   memset(&context, 0, sizeof(context));
 
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -204,33 +204,44 @@ typedef const PSAutoLock& PSLockRef;
 // accidental unlocked accesses to global state. There are ways to circumvent
 // this mechanism, but please don't do so without *very* good reason and a
 // detailed explanation.
 //
 // The exceptions to this rule:
 //
 // - mProcessStartTime, because it's immutable;
 //
-// - each thread's RacyRegisteredThread object is accessible without locking via
-//   TLSRegisteredThread::RacyRegisteredThread().
+// - each thread's RacyThreadInfo object is accessible without locking via
+//   TLSInfo::RacyThreadInfo().
 class CorePS
 {
 private:
   CorePS()
     : mProcessStartTime(TimeStamp::ProcessCreation())
 #ifdef USE_LUL_STACKWALK
     , mLul(nullptr)
 #endif
   {}
 
   ~CorePS()
   {
+    while (!mLiveThreads.empty()) {
+      delete mLiveThreads.back();
+      mLiveThreads.pop_back();
+    }
+
+    while (!mDeadThreads.empty()) {
+      delete mDeadThreads.back();
+      mDeadThreads.pop_back();
+    }
   }
 
 public:
+  typedef std::vector<ThreadInfo*> ThreadVector;
+
   static void Create(PSLockRef aLock) { sInstance = new CorePS(); }
 
   static void Destroy(PSLockRef aLock)
   {
     delete sInstance;
     sInstance = nullptr;
   }
 
@@ -239,50 +250,60 @@ public:
   // thread that we don't have to worry about it being racy.
   static bool Exists() { return !!sInstance; }
 
   static void AddSizeOf(PSLockRef, MallocSizeOf aMallocSizeOf,
                         size_t& aProfSize, size_t& aLulSize)
   {
     aProfSize += aMallocSizeOf(sInstance);
 
-    for (auto& registeredThread : sInstance->mRegisteredThreads) {
-      aProfSize += registeredThread->SizeOfIncludingThis(aMallocSizeOf);
+    for (uint32_t i = 0; i < sInstance->mLiveThreads.size(); i++) {
+      aProfSize +=
+        sInstance->mLiveThreads.at(i)->SizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    for (uint32_t i = 0; i < sInstance->mDeadThreads.size(); i++) {
+      aProfSize +=
+        sInstance->mDeadThreads.at(i)->SizeOfIncludingThis(aMallocSizeOf);
     }
 
     // Measurement of the following things may be added later if DMD finds it
     // is worthwhile:
-    // - CorePS::mRegisteredThreads itself (its elements' children are measured
+    // - CorePS::mLiveThreads itself (its elements' children are measured
     //   above)
+    // - CorePS::mDeadThreads itself (ditto)
     // - CorePS::mInterposeObserver
 
 #if defined(USE_LUL_STACKWALK)
     if (sInstance->mLul) {
       aLulSize += sInstance->mLul->SizeOfIncludingThis(aMallocSizeOf);
     }
 #endif
   }
 
   // No PSLockRef is needed for this field because it's immutable.
   PS_GET_LOCKLESS(TimeStamp, ProcessStartTime)
 
-  PS_GET(const nsTArray<UniquePtr<RegisteredThread>>&, RegisteredThreads)
-
-  static void AppendRegisteredThread(PSLockRef, UniquePtr<RegisteredThread>&& aRegisteredThread)
+  PS_GET(ThreadVector&, LiveThreads)
+  PS_GET(ThreadVector&, DeadThreads)
+
+  static void DiscardExpiredDeadThreads(PSLockRef, uint64_t aBufferRangeStart)
   {
-    sInstance->mRegisteredThreads.AppendElement(Move(aRegisteredThread));
-  }
-
-  static void RemoveRegisteredThread(PSLockRef, RegisteredThread* aRegisteredThread)
-  {
-    // Remove aRegisteredThread from mRegisteredThreads.
-    // Can't use RemoveElement() because we can't equality-compare a UniquePtr
-    // to a raw pointer.
-    sInstance->mRegisteredThreads.RemoveElementsBy(
-      [&](UniquePtr<RegisteredThread>& rt) { return rt.get() == aRegisteredThread; });
+    // Discard any dead threads that were unregistered before aBufferRangeStart.
+    ThreadVector& deadThreads = sInstance->mDeadThreads;
+    for (size_t i = 0; i < deadThreads.size(); i++) {
+      Maybe<uint64_t> bufferPosition =
+        deadThreads.at(i)->BufferPositionWhenUnregistered();
+      MOZ_RELEASE_ASSERT(bufferPosition, "should have unregistered this thread");
+      if (*bufferPosition < aBufferRangeStart) {
+        delete deadThreads.at(i);
+        deadThreads.erase(deadThreads.begin() + i);
+        i--;
+      }
+    }
   }
 
 #ifdef USE_LUL_STACKWALK
   static lul::LUL* Lul(PSLockRef) { return sInstance->mLul.get(); }
   static void SetLul(PSLockRef, UniquePtr<lul::LUL> aLul)
   {
     sInstance->mLul = Move(aLul);
   }
@@ -290,39 +311,37 @@ public:
 
 private:
   // The singleton instance
   static CorePS* sInstance;
 
   // The time that the process started.
   const TimeStamp mProcessStartTime;
 
-  // Info on all the registered threads.
-  // ThreadIds in mRegisteredThreads are unique.
-  nsTArray<UniquePtr<RegisteredThread>> mRegisteredThreads;
+  // Info on all the registered threads, both live and dead. ThreadIds in
+  // mLiveThreads are unique. ThreadIds in mDeadThreads may not be, because
+  // ThreadIds can be reused. IsBeingProfiled() is true for all ThreadInfos in
+  // mDeadThreads because we don't hold on to ThreadInfos for non-profiled dead
+  // threads.
+  ThreadVector mLiveThreads;
+  ThreadVector mDeadThreads;
 
 #ifdef USE_LUL_STACKWALK
   // LUL's state. Null prior to the first activation, non-null thereafter.
   UniquePtr<lul::LUL> mLul;
 #endif
 };
 
 CorePS* CorePS::sInstance = nullptr;
 
 class SamplerThread;
 
 static SamplerThread*
 NewSamplerThread(PSLockRef aLock, uint32_t aGeneration, double aInterval);
 
-struct LiveProfiledThreadData
-{
-  RegisteredThread* mRegisteredThread;
-  UniquePtr<ProfiledThreadData> mProfiledThreadData;
-};
-
 // This class contains the profiler's global state that is valid only when the
 // profiler is active. When not instantiated, the profiler is inactive.
 //
 // Accesses to ActivePS are guarded by gPSMutex, in much the same fashion as
 // CorePS.
 //
 class ActivePS
 {
@@ -470,22 +489,16 @@ public:
   }
 
   static size_t SizeOf(PSLockRef, MallocSizeOf aMallocSizeOf)
   {
     size_t n = aMallocSizeOf(sInstance);
 
     n += sInstance->mBuffer->SizeOfIncludingThis(aMallocSizeOf);
 
-    // Measurement of the following members may be added later if DMD finds it
-    // is worthwhile:
-    // - mLiveProfiledThreads (both the array itself, and the contents)
-    // - mDeadProfiledThreads (both the array itself, and the contents)
-    //
-
     return n;
   }
 
   static bool ShouldProfileThread(PSLockRef aLock, ThreadInfo* aInfo)
   {
     MOZ_RELEASE_ASSERT(sInstance);
 
     return ((aInfo->IsMainThread() || FeatureThreads(aLock)) &&
@@ -509,100 +522,22 @@ public:
   PROFILER_FOR_EACH_FEATURE(PS_GET_FEATURE)
 
   #undef PS_GET_FEATURE
 
   PS_GET(const Vector<std::string>&, Filters)
 
   static ProfileBuffer& Buffer(PSLockRef) { return *sInstance->mBuffer.get(); }
 
-  static const nsTArray<LiveProfiledThreadData>& LiveProfiledThreads(PSLockRef)
-  {
-    return sInstance->mLiveProfiledThreads;
-  }
-
-  // Returns an array containing (RegisteredThread*, ProfiledThreadData*) pairs
-  // for all threads that should be included in a profile, both for threads
-  // that are still registered, and for threads that have been unregistered but
-  // still have data in the buffer.
-  // For threads that have already been unregistered, the RegisteredThread
-  // pointer will be null.
-  // Do not hold on to the return value across thread registration or profiler
-  // restarts.
-  static nsTArray<Pair<RegisteredThread*, ProfiledThreadData*>> ProfiledThreads(PSLockRef)
-  {
-    nsTArray<Pair<RegisteredThread*, ProfiledThreadData*>> array;
-    for (auto& t : sInstance->mLiveProfiledThreads) {
-      array.AppendElement(MakePair(t.mRegisteredThread, t.mProfiledThreadData.get()));
-    }
-    for (auto& t : sInstance->mDeadProfiledThreads) {
-      array.AppendElement(MakePair((RegisteredThread*)nullptr, t.get()));
-    }
-    return array;
-  }
-
-  // Do a linear search through mLiveProfiledThreads to find the
-  // ProfiledThreadData object for a RegisteredThread.
-  static ProfiledThreadData* GetProfiledThreadData(PSLockRef,
-                                                   RegisteredThread* aRegisteredThread)
-  {
-    for (size_t i = 0; i < sInstance->mLiveProfiledThreads.Length(); i++) {
-      LiveProfiledThreadData& thread = sInstance->mLiveProfiledThreads[i];
-      if (thread.mRegisteredThread == aRegisteredThread) {
-        return thread.mProfiledThreadData.get();
-      }
-    }
-    return nullptr;
-  }
-
-  static void AddLiveProfiledThread(PSLockRef, RegisteredThread* aRegisteredThread,
-                                    UniquePtr<ProfiledThreadData>&& aProfiledThreadData)
-  {
-    sInstance->mLiveProfiledThreads.AppendElement(
-      LiveProfiledThreadData{ aRegisteredThread, Move(aProfiledThreadData) });
-  }
-
-  static void UnregisterThread(PSLockRef aLockRef, RegisteredThread* aRegisteredThread)
-  {
-    DiscardExpiredDeadProfiledThreads(aLockRef);
-
-    // Find the right entry in the mLiveProfiledThreads array and remove the
-    // element, moving the ProfiledThreadData object for the thread into the
-    // mDeadProfiledThreads array.
-    // The thread's RegisteredThread object gets destroyed here.
-    for (size_t i = 0; i < sInstance->mLiveProfiledThreads.Length(); i++) {
-      LiveProfiledThreadData& thread = sInstance->mLiveProfiledThreads[i];
-      if (thread.mRegisteredThread == aRegisteredThread) {
-        thread.mProfiledThreadData->NotifyUnregistered(sInstance->mBuffer->mRangeEnd);
-        sInstance->mDeadProfiledThreads.AppendElement(Move(thread.mProfiledThreadData));
-        sInstance->mLiveProfiledThreads.RemoveElementAt(i);
-        return;
-      }
-    }
-  }
-
   PS_GET_AND_SET(bool, IsPaused)
 
 #if defined(GP_OS_linux)
   PS_GET_AND_SET(bool, WasPaused)
 #endif
 
-  static void DiscardExpiredDeadProfiledThreads(PSLockRef)
-  {
-    uint64_t bufferRangeStart = sInstance->mBuffer->mRangeStart;
-    // Discard any dead threads that were unregistered before bufferRangeStart.
-    sInstance->mDeadProfiledThreads.RemoveElementsBy(
-      [bufferRangeStart](UniquePtr<ProfiledThreadData>& aProfiledThreadData) {
-        Maybe<uint64_t> bufferPosition =
-          aProfiledThreadData->BufferPositionWhenUnregistered();
-        MOZ_RELEASE_ASSERT(bufferPosition, "should have unregistered this thread");
-        return *bufferPosition < bufferRangeStart;
-      });
-  }
-
 private:
   // The singleton instance.
   static ActivePS* sInstance;
 
   // We need to track activity generations. If we didn't we could have the
   // following scenario.
   //
   // - profiler_stop() locks gPSMutex, de-instantiates ActivePS, unlocks
@@ -633,24 +568,16 @@ private:
 
   // Substrings of names of threads we want to profile.
   Vector<std::string> mFilters;
 
   // The buffer into which all samples are recorded. Always non-null. Always
   // used in conjunction with CorePS::m{Live,Dead}Threads.
   const UniquePtr<ProfileBuffer> mBuffer;
 
-  // ProfiledThreadData objects for any threads that were profiled at any point
-  // during this run of the profiler:
-  //  - mLiveProfiledThreads contains all threads that are still registered, and
-  //  - mDeadProfiledThreads contains all threads that have already been
-  //    unregistered but for which there is still data in the profile buffer.
-  nsTArray<LiveProfiledThreadData> mLiveProfiledThreads;
-  nsTArray<UniquePtr<ProfiledThreadData>> mDeadProfiledThreads;
-
   // The current sampler thread. This class is not responsible for destroying
   // the SamplerThread object; the Destroy() method returns it so the caller
   // can destroy it.
   SamplerThread* const mSamplerThread;
 
   // The interposer that records main thread I/O.
   const RefPtr<ProfilerIOInterposeObserver> mInterposeObserver;
 
@@ -671,82 +598,72 @@ uint32_t ActivePS::sNextGeneration = 0;
 #undef PS_GET_LOCKLESS
 #undef PS_GET_AND_SET
 
 // The mutex that guards accesses to CorePS and ActivePS.
 static PSMutex gPSMutex;
 
 Atomic<uint32_t> RacyFeatures::sActiveAndFeatures(0);
 
-// Each live thread has a RegisteredThread, and we store a reference to it in TLS.
+// Each live thread has a ThreadInfo, and we store a reference to it in TLS.
 // This class encapsulates that TLS.
-class TLSRegisteredThread
+class TLSInfo
 {
 public:
   static bool Init(PSLockRef)
   {
-    bool ok1 = sRegisteredThread.init();
+    bool ok1 = sThreadInfo.init();
     bool ok2 = AutoProfilerLabel::sPseudoStack.init();
     return ok1 && ok2;
   }
 
-  // Get the entire RegisteredThread. Accesses are guarded by gPSMutex.
-  static class RegisteredThread* RegisteredThread(PSLockRef)
+  // Get the entire ThreadInfo. Accesses are guarded by gPSMutex.
+  static ThreadInfo* Info(PSLockRef) { return sThreadInfo.get(); }
+
+  // Get only the RacyThreadInfo. Accesses are not guarded by gPSMutex.
+  static RacyThreadInfo* RacyInfo()
   {
-    return sRegisteredThread.get();
-  }
-
-  // Get only the RacyRegisteredThread. Accesses are not guarded by gPSMutex.
-  static class RacyRegisteredThread* RacyRegisteredThread()
-  {
-    class RegisteredThread* registeredThread = sRegisteredThread.get();
-    return registeredThread ? &registeredThread->RacyRegisteredThread()
-                            : nullptr;
+    ThreadInfo* info = sThreadInfo.get();
+    return info ? info->RacyInfo().get() : nullptr;
   }
 
-  // Get only the PseudoStack. Accesses are not guarded by gPSMutex.
-  // RacyRegisteredThread() can also be used to get the PseudoStack, but that
-  // is marginally slower because it requires an extra pointer indirection.
+  // Get only the PseudoStack. Accesses are not guarded by gPSMutex. RacyInfo()
+  // can also be used to get the PseudoStack, but that is marginally slower
+  // because it requires an extra pointer indirection.
   static PseudoStack* Stack() { return AutoProfilerLabel::sPseudoStack.get(); }
 
-  static void SetRegisteredThread(PSLockRef,
-                                  class RegisteredThread* aRegisteredThread)
+  static void SetInfo(PSLockRef, ThreadInfo* aInfo)
   {
-    sRegisteredThread.set(aRegisteredThread);
+    sThreadInfo.set(aInfo);
     AutoProfilerLabel::sPseudoStack.set(
-      aRegisteredThread
-        ? &aRegisteredThread->RacyRegisteredThread().PseudoStack()
-        : nullptr);
+      aInfo ? aInfo->RacyInfo().get() : nullptr);  // an upcast
   }
 
 private:
-  // This is a non-owning reference to the RegisteredThread;
-  // CorePS::mRegisteredThreads is the owning reference. On thread
-  // deregistration, this reference is cleared and the RegisteredThread is
-  // destroyed.
-  static MOZ_THREAD_LOCAL(class RegisteredThread*) sRegisteredThread;
+  // This is a non-owning reference to the ThreadInfo; CorePS::mLiveThreads is
+  // the owning reference. On thread destruction, this reference is cleared and
+  // the ThreadInfo is destroyed or transferred to CorePS::mDeadThreads.
+  static MOZ_THREAD_LOCAL(ThreadInfo*) sThreadInfo;
 };
 
-MOZ_THREAD_LOCAL(RegisteredThread*) TLSRegisteredThread::sRegisteredThread;
-
-// Although you can access a thread's PseudoStack via
-// TLSRegisteredThread::sRegisteredThread, we also have a second TLS pointer
-// directly to the PseudoStack. Here's why.
+MOZ_THREAD_LOCAL(ThreadInfo*) TLSInfo::sThreadInfo;
+
+// Although you can access a thread's PseudoStack via TLSInfo::sThreadInfo, we
+// also have a second TLS pointer directly to the PseudoStack. Here's why.
 //
 // - We need to be able to push to and pop from the PseudoStack in
 //   AutoProfilerLabel.
 //
 // - The class functions are hot and must be defined in GeckoProfiler.h so they
 //   can be inlined.
 //
-// - We don't want to expose TLSRegisteredThread (and RegisteredThread) in
-//   GeckoProfiler.h.
+// - We don't want to expose TLSInfo (and ThreadInfo) in GeckoProfiler.h.
 //
 // This second pointer isn't ideal, but does provide a way to satisfy those
-// constraints. TLSRegisteredThread is responsible for updating it.
+// constraints. TLSInfo is responsible for updating it.
 MOZ_THREAD_LOCAL(PseudoStack*) AutoProfilerLabel::sPseudoStack;
 
 // The name of the main thread.
 static const char* const kMainThreadName = "GeckoMain";
 
 ////////////////////////////////////////////////////////////////////////
 // BEGIN sampling/unwinding code
 
@@ -812,29 +729,28 @@ struct AutoWalkJSStack
     }
   }
 };
 
 // Merges the pseudo-stack, native stack, and JS stack, outputting the details
 // to aCollector.
 static void
 MergeStacks(uint32_t aFeatures, bool aIsSynchronous,
-            const RegisteredThread& aRegisteredThread, const Registers& aRegs,
+            const ThreadInfo& aThreadInfo, const Registers& aRegs,
             const NativeStack& aNativeStack,
             ProfilerStackCollector& aCollector)
 {
   // WARNING: this function runs within the profiler's "critical section".
   // WARNING: this function might be called while the profiler is inactive, and
   //          cannot rely on ActivePS.
 
-  const PseudoStack& pseudoStack =
-    aRegisteredThread.RacyRegisteredThread().PseudoStack();
-  const js::ProfileEntry* pseudoEntries = pseudoStack.entries;
-  uint32_t pseudoCount = pseudoStack.stackSize();
-  JSContext* context = aRegisteredThread.GetJSContext();
+  NotNull<RacyThreadInfo*> racyInfo = aThreadInfo.RacyInfo();
+  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.
 
   // 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.
@@ -894,17 +810,17 @@ MergeStacks(uint32_t aFeatures, bool aIs
   // Iterate as long as there is at least one frame remaining.
   while (pseudoIndex != pseudoCount || jsIndex >= 0 || nativeIndex >= 0) {
     // There are 1 to 3 frames available. Find and add the oldest.
     uint8_t* pseudoStackAddr = nullptr;
     uint8_t* jsStackAddr = nullptr;
     uint8_t* nativeStackAddr = nullptr;
 
     if (pseudoIndex != pseudoCount) {
-      const js::ProfileEntry& pseudoEntry = pseudoEntries[pseudoIndex];
+      js::ProfileEntry& pseudoEntry = pseudoEntries[pseudoIndex];
 
       if (pseudoEntry.isCpp()) {
         lastPseudoCppStackAddr = (uint8_t*) pseudoEntry.stackAddress();
       }
 
       // Skip any JS_OSR frames. Such frames are used when the JS interpreter
       // enters a jit frame on a loop edge (via on-stack-replacement, or OSR).
       // To avoid both the pseudoframe and jit frame being recorded (and
@@ -944,24 +860,24 @@ MergeStacks(uint32_t aFeatures, bool aIs
     MOZ_ASSERT_IF(jsStackAddr, jsStackAddr != pseudoStackAddr &&
                                jsStackAddr != nativeStackAddr);
     MOZ_ASSERT_IF(nativeStackAddr, nativeStackAddr != pseudoStackAddr &&
                                    nativeStackAddr != jsStackAddr);
 
     // Check to see if pseudoStack frame is top-most.
     if (pseudoStackAddr > jsStackAddr && pseudoStackAddr > nativeStackAddr) {
       MOZ_ASSERT(pseudoIndex < pseudoCount);
-      const js::ProfileEntry& pseudoEntry = pseudoEntries[pseudoIndex];
+      js::ProfileEntry& pseudoEntry = pseudoEntries[pseudoIndex];
 
       // Pseudo-frames with the CPP_MARKER_FOR_JS kind are just annotations and
       // should not be recorded in the profile.
       if (pseudoEntry.kind() != js::ProfileEntry::Kind::CPP_MARKER_FOR_JS) {
         // The JIT only allows the top-most entry to have a nullptr pc.
         MOZ_ASSERT_IF(pseudoEntry.isJs() && pseudoEntry.script() && !pseudoEntry.pc(),
-                      &pseudoEntry == &pseudoStack.entries[pseudoStack.stackSize() - 1]);
+                      &pseudoEntry == &racyInfo->entries[racyInfo->stackSize() - 1]);
         aCollector.CollectPseudoEntry(pseudoEntry);
       }
       pseudoIndex++;
       continue;
     }
 
     // Check to see if JS jit stack frame is top-most
     if (jsStackAddr > nativeStackAddr) {
@@ -1031,86 +947,85 @@ StackWalkCallback(uint32_t aFrameNumber,
   nativeStack->mSPs[nativeStack->mCount] = aSP;
   nativeStack->mPCs[nativeStack->mCount] = aPC;
   nativeStack->mCount++;
 }
 #endif
 
 #if defined(USE_FRAME_POINTER_STACK_WALK)
 static void
-DoFramePointerBacktrace(PSLockRef aLock, const RegisteredThread& aRegisteredThread,
+DoFramePointerBacktrace(PSLockRef aLock, const ThreadInfo& aThreadInfo,
                         const Registers& aRegs, NativeStack& aNativeStack)
 {
   // WARNING: this function runs within the profiler's "critical section".
   // WARNING: this function might be called while the profiler is inactive, and
   //          cannot rely on ActivePS.
 
   // Start with the current function. We use 0 as the frame number here because
   // the FramePointerStackWalk() call below will use 1..N. This is a bit weird
   // but it doesn't matter because StackWalkCallback() doesn't use the frame
   // number argument.
   StackWalkCallback(/* frameNum */ 0, aRegs.mPC, aRegs.mSP, &aNativeStack);
 
   uint32_t maxFrames = uint32_t(MAX_NATIVE_FRAMES - aNativeStack.mCount);
 
-  const void* stackEnd = aRegisteredThread.StackTop();
+  void* stackEnd = aThreadInfo.StackTop();
   if (aRegs.mFP >= aRegs.mSP && aRegs.mFP <= stackEnd) {
     FramePointerStackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames,
                           &aNativeStack, reinterpret_cast<void**>(aRegs.mFP),
-                          const_cast<void*>(stackEnd));
+                          stackEnd);
   }
 }
 #endif
 
 #if defined(USE_MOZ_STACK_WALK)
 static void
-DoMozStackWalkBacktrace(PSLockRef aLock, const RegisteredThread& aRegisteredThread,
+DoMozStackWalkBacktrace(PSLockRef aLock, const ThreadInfo& aThreadInfo,
                         const Registers& aRegs, NativeStack& aNativeStack)
 {
   // WARNING: this function runs within the profiler's "critical section".
   // WARNING: this function might be called while the profiler is inactive, and
   //          cannot rely on ActivePS.
 
   // Start with the current function. We use 0 as the frame number here because
   // the MozStackWalkThread() call below will use 1..N. This is a bit weird but
   // it doesn't matter because StackWalkCallback() doesn't use the frame number
   // argument.
   StackWalkCallback(/* frameNum */ 0, aRegs.mPC, aRegs.mSP, &aNativeStack);
 
   uint32_t maxFrames = uint32_t(MAX_NATIVE_FRAMES - aNativeStack.mCount);
 
-  HANDLE thread = GetThreadHandle(aRegisteredThread.GetPlatformData());
+  HANDLE thread = GetThreadHandle(aThreadInfo.GetPlatformData());
   MOZ_ASSERT(thread);
   MozStackWalkThread(StackWalkCallback, /* skipFrames */ 0, maxFrames,
                      &aNativeStack, thread, /* context */ nullptr);
 }
 #endif
 
 #ifdef USE_EHABI_STACKWALK
 static void
-DoEHABIBacktrace(PSLockRef aLock, const RegisteredThread& aRegisteredThread,
+DoEHABIBacktrace(PSLockRef aLock, const ThreadInfo& aThreadInfo,
                  const Registers& aRegs, NativeStack& aNativeStack)
 {
   // WARNING: this function runs within the profiler's "critical section".
   // WARNING: this function might be called while the profiler is inactive, and
   //          cannot rely on ActivePS.
 
   const mcontext_t* mcontext = &aRegs.mContext->uc_mcontext;
   mcontext_t savedContext;
-  const PseudoStack& pseudoStack =
-    aRegisteredThread.RacyRegisteredThread().PseudoStack();
+  NotNull<RacyThreadInfo*> racyInfo = aThreadInfo.RacyInfo();
 
   // The pseudostack contains an "EnterJIT" frame whenever we enter
   // JIT code with profiling enabled; the stack pointer value points
   // the saved registers.  We use this to unwind resume unwinding
   // after encounting JIT code.
-  for (uint32_t i = pseudoStack.stackSize(); i > 0; --i) {
+  for (uint32_t i = racyInfo->stackSize(); i > 0; --i) {
     // The pseudostack grows towards higher indices, so we iterate
     // backwards (from callee to caller).
-    const js::ProfileEntry& entry = pseudoStack.entries[i - 1];
+    js::ProfileEntry& entry = racyInfo->entries[i - 1];
     if (!entry.isJs() && strcmp(entry.label(), "EnterJIT") == 0) {
       // Found JIT entry frame.  Unwind up to that point (i.e., force
       // the stack walk to stop before the block of saved registers;
       // note that it yields nondecreasing stack pointers), then restore
       // the saved state.
       uint32_t* vSP = reinterpret_cast<uint32_t*>(entry.stackAddress());
 
       aNativeStack.mCount +=
@@ -1135,17 +1050,17 @@ DoEHABIBacktrace(PSLockRef aLock, const 
       savedContext.arm_pc  = savedContext.arm_lr;
       mcontext = &savedContext;
     }
   }
 
   // Now unwind whatever's left (starting from either the last EnterJIT frame
   // or, if no EnterJIT was found, the original registers).
   aNativeStack.mCount +=
-    EHABIStackWalk(*mcontext, const_cast<void*>(aRegisteredThread.StackTop()),
+    EHABIStackWalk(*mcontext, aThreadInfo.StackTop(),
                    aNativeStack.mSPs + aNativeStack.mCount,
                    aNativeStack.mPCs + aNativeStack.mCount,
                    MAX_NATIVE_FRAMES - aNativeStack.mCount);
 }
 #endif
 
 #ifdef USE_LUL_STACKWALK
 
@@ -1163,17 +1078,17 @@ ASAN_memcpy(void* aDst, const void* aSrc
 
   for (size_t i = 0; i < aLen; i++) {
     dst[i] = src[i];
   }
 }
 #endif
 
 static void
-DoLULBacktrace(PSLockRef aLock, const RegisteredThread& aRegisteredThread,
+DoLULBacktrace(PSLockRef aLock, const ThreadInfo& aThreadInfo,
                const Registers& aRegs, NativeStack& aNativeStack)
 {
   // WARNING: this function runs within the profiler's "critical section".
   // WARNING: this function might be called while the profiler is inactive, and
   //          cannot rely on ActivePS.
 
   const mcontext_t* mc = &aRegs.mContext->uc_mcontext;
 
@@ -1247,17 +1162,17 @@ DoLULBacktrace(PSLockRef aLock, const Re
     uintptr_t rEDZONE_SIZE = 0;
     uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE;
 #elif defined(GP_PLAT_mips64_linux)
     uintptr_t rEDZONE_SIZE = 0;
     uintptr_t start = startRegs.sp.Value() - rEDZONE_SIZE;
 #else
 #   error "Unknown plat"
 #endif
-    uintptr_t end = reinterpret_cast<uintptr_t>(aRegisteredThread.StackTop());
+    uintptr_t end = reinterpret_cast<uintptr_t>(aThreadInfo.StackTop());
     uintptr_t ws  = sizeof(void*);
     start &= ~(ws-1);
     end   &= ~(ws-1);
     uintptr_t nToCopy = 0;
     if (start < end) {
       nToCopy = end - start;
       if (nToCopy > lul::N_STACK_BYTES)
         nToCopy = lul::N_STACK_BYTES;
@@ -1297,119 +1212,117 @@ DoLULBacktrace(PSLockRef aLock, const Re
   lul->mStats.mCFI     += aNativeStack.mCount - 1 - framePointerFramesAcquired;
   lul->mStats.mFP      += framePointerFramesAcquired;
 }
 
 #endif
 
 #ifdef HAVE_NATIVE_UNWIND
 static void
-DoNativeBacktrace(PSLockRef aLock, const RegisteredThread& aRegisteredThread,
+DoNativeBacktrace(PSLockRef aLock, const ThreadInfo& aThreadInfo,
                   const Registers& aRegs, NativeStack& aNativeStack)
 {
   // This method determines which stackwalker is used for periodic and
   // synchronous samples. (Backtrace samples are treated differently, see
   // profiler_suspend_and_sample_thread() for details). The only part of the
   // ordering that matters is that LUL must precede FRAME_POINTER, because on
   // Linux they can both be present.
 #if defined(USE_LUL_STACKWALK)
-  DoLULBacktrace(aLock, aRegisteredThread, aRegs, aNativeStack);
+  DoLULBacktrace(aLock, aThreadInfo, aRegs, aNativeStack);
 #elif defined(USE_EHABI_STACKWALK)
-  DoEHABIBacktrace(aLock, aRegisteredThread, aRegs, aNativeStack);
+  DoEHABIBacktrace(aLock, aThreadInfo, aRegs, aNativeStack);
 #elif defined(USE_FRAME_POINTER_STACK_WALK)
-  DoFramePointerBacktrace(aLock, aRegisteredThread, aRegs, aNativeStack);
+  DoFramePointerBacktrace(aLock, aThreadInfo, aRegs, aNativeStack);
 #elif defined(USE_MOZ_STACK_WALK)
-  DoMozStackWalkBacktrace(aLock, aRegisteredThread, aRegs, aNativeStack);
+  DoMozStackWalkBacktrace(aLock, aThreadInfo, aRegs, aNativeStack);
 #else
   #error "Invalid configuration"
 #endif
 }
 #endif
 
 // Writes some components shared by periodic and synchronous profiles to
 // 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,
-               RegisteredThread& aRegisteredThread, const TimeStamp& aNow,
+               ThreadInfo& aThreadInfo, const TimeStamp& aNow,
                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));
 
-  uint64_t samplePos = aBuffer.AddThreadIdEntry(aRegisteredThread.Info()->ThreadId());
+  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),
                                    samplePos);
   NativeStack nativeStack;
 #if defined(HAVE_NATIVE_UNWIND)
   if (ActivePS::FeatureStackWalk(aLock)) {
-    DoNativeBacktrace(aLock, aRegisteredThread, aRegs, nativeStack);
-
-    MergeStacks(ActivePS::Features(aLock), aIsSynchronous, aRegisteredThread,
-                aRegs, nativeStack, collector);
+    DoNativeBacktrace(aLock, aThreadInfo, aRegs, nativeStack);
+
+    MergeStacks(ActivePS::Features(aLock), aIsSynchronous, aThreadInfo, aRegs,
+                nativeStack, collector);
   } else
 #endif
   {
-    MergeStacks(ActivePS::Features(aLock), aIsSynchronous, aRegisteredThread,
-                aRegs, nativeStack, collector);
+    MergeStacks(ActivePS::Features(aLock), aIsSynchronous, aThreadInfo, aRegs,
+                nativeStack, collector);
 
     // We can't walk the whole native stack, but we can record the top frame.
     if (ActivePS::FeatureLeaf(aLock)) {
       aBuffer.AddEntry(ProfileBufferEntry::NativeLeafAddr((void*)aRegs.mPC));
     }
   }
 }
 
 // Writes the components of a synchronous sample to the given ProfileBuffer.
 static void
-DoSyncSample(PSLockRef aLock, RegisteredThread& aRegisteredThread,
-             const TimeStamp& aNow, const Registers& aRegs,
-             ProfileBuffer& aBuffer)
+DoSyncSample(PSLockRef aLock, ThreadInfo& aThreadInfo, const TimeStamp& aNow,
+             const Registers& aRegs, ProfileBuffer& aBuffer)
 {
   // WARNING: this function runs within the profiler's "critical section".
 
-  DoSharedSample(aLock, /* aIsSynchronous = */ true, aRegisteredThread, aNow,
-                 aRegs, /* aLastSample = */ nullptr, aBuffer);
+  DoSharedSample(aLock, /* isSynchronous = */ true, aThreadInfo, aNow, aRegs,
+                 /* lastSample = */ nullptr, aBuffer);
 }
 
 // Writes the components of a periodic sample to ActivePS's ProfileBuffer.
 static void
-DoPeriodicSample(PSLockRef aLock, RegisteredThread& aRegisteredThread,
-                 ProfiledThreadData& aProfiledThreadData,
+DoPeriodicSample(PSLockRef aLock, ThreadInfo& aThreadInfo,
                  const TimeStamp& aNow, const Registers& aRegs,
                  int64_t aRSSMemory, int64_t aUSSMemory)
 {
   // WARNING: this function runs within the profiler's "critical section".
 
   ProfileBuffer& buffer = ActivePS::Buffer(aLock);
 
-  DoSharedSample(aLock, /* aIsSynchronous = */ false, aRegisteredThread, aNow,
-                 aRegs, &aProfiledThreadData.LastSample(), buffer);
+  DoSharedSample(aLock, /* isSynchronous = */ false, aThreadInfo, aNow, aRegs,
+                 &aThreadInfo.LastSample(), buffer);
 
   ProfilerMarkerLinkedList* pendingMarkersList =
-    aRegisteredThread.RacyRegisteredThread().GetPendingMarkers();
+    aThreadInfo.RacyInfo()->GetPendingMarkers();
   while (pendingMarkersList && pendingMarkersList->peek()) {
     ProfilerMarker* marker = pendingMarkersList->popHead();
     buffer.AddStoredMarker(marker);
     buffer.AddEntry(ProfileBufferEntry::Marker(marker));
   }
 
-  ThreadResponsiveness* resp = aProfiledThreadData.GetThreadResponsiveness();
+  ThreadResponsiveness* resp = aThreadInfo.GetThreadResponsiveness();
   if (resp && resp->HasData()) {
     double delta = resp->GetUnresponsiveDuration(
       (aNow - CorePS::ProcessStartTime()).ToMilliseconds());
     buffer.AddEntry(ProfileBufferEntry::Responsiveness(delta));
   }
 
   if (aRSSMemory != 0) {
     double rssMemory = static_cast<double>(aRSSMemory);
@@ -1492,21 +1405,26 @@ StreamTaskTracer(PSLockRef aLock, Splice
     for (uint32_t i = 0; i < data->Length(); ++i) {
       aWriter.StringElement((data->ElementAt(i)).get());
     }
   }
   aWriter.EndArray();
 
   aWriter.StartArrayProperty("threads");
   {
-    ActivePS::DiscardExpiredDeadProfiledThreads(aLock);
-    nsTArray<Pair<RegisteredThread*, ProfiledThreadData*>> threads =
-      ActivePS::ProfiledThreads(aLock);
-    for (auto& thread : threads) {
-      RefPtr<ThreadInfo> info = thread.second()->Info();
+    const CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(aLock);
+    for (size_t i = 0; i < liveThreads.size(); i++) {
+      ThreadInfo* info = liveThreads.at(i);
+      StreamNameAndThreadId(aWriter, info->Name(), info->ThreadId());
+    }
+
+    CorePS::DiscardExpiredDeadThreads(aLock, ActivePS::Buffer(aLock).mRangeStart);
+    const CorePS::ThreadVector& deadThreads = CorePS::DeadThreads(aLock);
+    for (size_t i = 0; i < deadThreads.size(); i++) {
+      ThreadInfo* info = deadThreads.at(i);
       StreamNameAndThreadId(aWriter, info->Name(), info->ThreadId());
     }
   }
   aWriter.EndArray();
 
   aWriter.DoubleProperty(
     "start", static_cast<double>(tasktracer::GetStartTime()));
 #endif
@@ -1730,26 +1648,31 @@ locked_profiler_stream_json_for_this_pro
     aWriter.StartObjectProperty("tasktracer");
     StreamTaskTracer(aLock, aWriter);
     aWriter.EndObject();
   }
 
   // Lists the samples for each thread profile
   aWriter.StartArrayProperty("threads");
   {
-    ActivePS::DiscardExpiredDeadProfiledThreads(aLock);
-    nsTArray<Pair<RegisteredThread*, ProfiledThreadData*>> threads =
-      ActivePS::ProfiledThreads(aLock);
-    for (auto& thread : threads) {
-      RegisteredThread* registeredThread = thread.first();
-      JSContext* cx =
-        registeredThread ? registeredThread->GetJSContext() : nullptr;
-      ProfiledThreadData* profiledThreadData = thread.second();
-      profiledThreadData->StreamJSON(buffer, cx, aWriter,
-                                     CorePS::ProcessStartTime(), aSinceTime);
+    const CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(aLock);
+    for (size_t i = 0; i < liveThreads.size(); i++) {
+      ThreadInfo* info = liveThreads.at(i);
+      if (!info->IsBeingProfiled()) {
+        continue;
+      }
+      info->StreamJSON(buffer, aWriter, CorePS::ProcessStartTime(), aSinceTime);
+    }
+
+    CorePS::DiscardExpiredDeadThreads(aLock, ActivePS::Buffer(aLock).mRangeStart);
+    const CorePS::ThreadVector& deadThreads = CorePS::DeadThreads(aLock);
+    for (size_t i = 0; i < deadThreads.size(); i++) {
+      ThreadInfo* info = deadThreads.at(i);
+      MOZ_ASSERT(info->IsBeingProfiled());
+      info->StreamJSON(buffer, aWriter, CorePS::ProcessStartTime(), aSinceTime);
     }
 
 #if defined(GP_OS_android)
     if (ActivePS::FeatureJava(aLock)) {
       java::GeckoJavaSampler::Pause();
 
       aWriter.Start();
       {
@@ -1901,17 +1824,17 @@ public:
 
   // This method suspends and resumes the samplee thread. It calls the passed-in
   // function-like object aProcessRegs (passing it a populated |const
   // Registers&| arg) while the samplee thread is suspended.
   //
   // Func must be a function-like object of type `void()`.
   template<typename Func>
   void SuspendAndSampleAndResumeThread(PSLockRef aLock,
-                                       const RegisteredThread& aRegisteredThread,
+                                       const ThreadInfo& aThreadInfo,
                                        const Func& aProcessRegs);
 
 private:
 #if defined(GP_OS_linux) || defined(GP_OS_android)
   // Used to restore the SIGPROF handler when ours is removed.
   struct sigaction mOldSigprofHandler;
 
   // This process' ID. Needed as an argument for tgkill in
@@ -2011,54 +1934,54 @@ SamplerThread::Run()
       // happens the generation won't match.
       if (ActivePS::Generation(lock) != mActivityGeneration) {
         return;
       }
 
       ActivePS::Buffer(lock).DeleteExpiredStoredMarkers();
 
       if (!ActivePS::IsPaused(lock)) {
-        const nsTArray<LiveProfiledThreadData>& liveThreads =
-          ActivePS::LiveProfiledThreads(lock);
-
-        int64_t rssMemory = 0;
-        int64_t ussMemory = 0;
-        if (!liveThreads.IsEmpty() && ActivePS::FeatureMemory(lock)) {
-          rssMemory = nsMemoryReporterManager::ResidentFast();
-#if defined(GP_OS_linux) || defined(GP_OS_android)
-          ussMemory = nsMemoryReporterManager::ResidentUnique();
-#endif
-        }
-
-        for (auto& thread : liveThreads) {
-          RegisteredThread* registeredThread = thread.mRegisteredThread;
-          ProfiledThreadData* profiledThreadData =
-            thread.mProfiledThreadData.get();
-          RefPtr<ThreadInfo> info = registeredThread->Info();
+        const CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(lock);
+        for (uint32_t i = 0; i < liveThreads.size(); i++) {
+          ThreadInfo* info = liveThreads.at(i);
+
+          if (!info->IsBeingProfiled()) {
+            // We are not interested in profiling this thread.
+            continue;
+          }
 
           // If the thread is asleep and has been sampled before in the same
           // sleep episode, find and copy the previous sample, as that's
           // cheaper than taking a new sample.
-          if (registeredThread->RacyRegisteredThread().CanDuplicateLastSampleDueToSleep()) {
+          if (info->RacyInfo()->CanDuplicateLastSampleDueToSleep()) {
             bool dup_ok =
               ActivePS::Buffer(lock).DuplicateLastSample(
                 info->ThreadId(), CorePS::ProcessStartTime(),
-                profiledThreadData->LastSample());
+                info->LastSample());
             if (dup_ok) {
               continue;
             }
           }
 
-          profiledThreadData->GetThreadResponsiveness()->Update();
+          info->GetThreadResponsiveness()->Update();
+
+          // We only get the memory measurements once for all live threads.
+          int64_t rssMemory = 0;
+          int64_t ussMemory = 0;
+          if (i == 0 && ActivePS::FeatureMemory(lock)) {
+            rssMemory = nsMemoryReporterManager::ResidentFast();
+#if defined(GP_OS_linux) || defined(GP_OS_android)
+            ussMemory = nsMemoryReporterManager::ResidentUnique();
+#endif
+          }
 
           TimeStamp now = TimeStamp::Now();
-          SuspendAndSampleAndResumeThread(lock, *registeredThread,
+          SuspendAndSampleAndResumeThread(lock, *info,
                                           [&](const Registers& aRegs) {
-            DoPeriodicSample(lock, *registeredThread, *profiledThreadData, now,
-                             aRegs, rssMemory, ussMemory);
+            DoPeriodicSample(lock, *info, now, aRegs, rssMemory, ussMemory);
           });
         }
 
 #if defined(USE_LUL_STACKWALK)
         // The LUL unwind object accumulates frame statistics. Periodically we
         // should poke it to give it a chance to print those statistics.  This
         // involves doing I/O (fprintf, __android_log_print, etc.) and so
         // can't safely be done from the critical section inside
@@ -2179,69 +2102,69 @@ ParseFeaturesFromStringArray(const char*
   uint32_t features = 0;
   PROFILER_FOR_EACH_FEATURE(ADD_FEATURE_BIT)
 
   #undef ADD_FEATURE_BIT
 
   return features;
 }
 
-// Find the RegisteredThread for the current thread. This should only be called
-// in places where TLSRegisteredThread can't be used.
-static RegisteredThread*
-FindCurrentThreadRegisteredThread(PSLockRef aLock)
+// Find the ThreadInfo for the current thread. This should only be called in
+// places where TLSInfo can't be used. On success, *aIndexOut is set to the
+// index if it is non-null.
+static ThreadInfo*
+FindLiveThreadInfo(PSLockRef aLock, int* aIndexOut = nullptr)
 {
+  ThreadInfo* ret = nullptr;
   int id = Thread::GetCurrentId();
-  const nsTArray<UniquePtr<RegisteredThread>>& registeredThreads =
-    CorePS::RegisteredThreads(aLock);
-  for (auto& registeredThread : registeredThreads) {
-    if (registeredThread->Info()->ThreadId() == id) {
-      return registeredThread.get();
+  const CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(aLock);
+  for (uint32_t i = 0; i < liveThreads.size(); i++) {
+    ThreadInfo* info = liveThreads.at(i);
+    if (info->ThreadId() == id) {
+      if (aIndexOut) {
+        *aIndexOut = i;
+      }
+      ret = info;
+      break;
     }
   }
 
-  return nullptr;
+  return ret;
 }
 
 static void
 locked_register_thread(PSLockRef aLock, const char* aName, void* aStackTop)
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
-  MOZ_RELEASE_ASSERT(!FindCurrentThreadRegisteredThread(aLock));
+  MOZ_RELEASE_ASSERT(!FindLiveThreadInfo(aLock));
 
   VTUNE_REGISTER_THREAD(aName);
 
-  if (!TLSRegisteredThread::Init(aLock)) {
+  if (!TLSInfo::Init(aLock)) {
     return;
   }
 
-  RefPtr<ThreadInfo> info =
-    new ThreadInfo(aName, Thread::GetCurrentId(), NS_IsMainThread());
-  UniquePtr<RegisteredThread> registeredThread =
-    MakeUnique<RegisteredThread>(info, NS_GetCurrentThreadNoCreate(),
-                                 aStackTop);
-
-  TLSRegisteredThread::SetRegisteredThread(aLock, registeredThread.get());
-
-  if (ActivePS::Exists(aLock) &&
-      ActivePS::ShouldProfileThread(aLock, info)) {
-    nsCOMPtr<nsIEventTarget> eventTarget = registeredThread->GetEventTarget();
-    ActivePS::AddLiveProfiledThread(aLock, registeredThread.get(),
-      MakeUnique<ProfiledThreadData>(info, eventTarget));
-
+  ThreadInfo* info = new ThreadInfo(aName, Thread::GetCurrentId(),
+                                    NS_IsMainThread(),
+                                    NS_GetCurrentThreadNoCreate(),
+                                    aStackTop);
+  TLSInfo::SetInfo(aLock, info);
+
+  if (ActivePS::Exists(aLock) && ActivePS::ShouldProfileThread(aLock, info)) {
+    info->StartProfiling();
     if (ActivePS::FeatureJS(aLock)) {
       // This StartJSSampling() call is on-thread, so we can poll manually to
       // start JS sampling immediately.
-      registeredThread->StartJSSampling();
-      registeredThread->PollJSSampling();
+      info->StartJSSampling();
+      info->PollJSSampling();
     }
   }
 
-  CorePS::AppendRegisteredThread(aLock, Move(registeredThread));
+  CorePS::LiveThreads(aLock).push_back(info);
 }
 
 static void
 NotifyObservers(const char* aTopic, nsISupports* aSubject = nullptr)
 {
   if (!NS_IsMainThread()) {
     // Dispatch a task to the main thread that notifies observers.
     // If NotifyObservers is called both on and off the main thread within a
@@ -2503,18 +2426,18 @@ profiler_shutdown()
       }
 
       samplerThread = locked_profiler_stop(lock);
     }
 
     CorePS::Destroy(lock);
 
     // We just destroyed CorePS and the ThreadInfos it contains, so we can
-    // clear this thread's TLSRegisteredThread.
-    TLSRegisteredThread::SetRegisteredThread(lock, nullptr);
+    // clear this thread's TLSInfo.
+    TLSInfo::SetInfo(lock, nullptr);
 
 #ifdef MOZ_TASK_TRACER
     tasktracer::ShutdownTaskTracer();
 #endif
   }
 
   // We do these operations with gPSMutex unlocked. The comments in
   // profiler_stop() explain why.
@@ -2739,23 +2662,22 @@ profiler_get_buffer_info()
 
 static void
 PollJSSamplingForCurrentThread()
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
   PSAutoLock lock(gPSMutex);
 
-  RegisteredThread* registeredThread =
-    TLSRegisteredThread::RegisteredThread(lock);
-  if (!registeredThread) {
+  ThreadInfo* info = TLSInfo::Info(lock);
+  if (!info) {
     return;
   }
 
-  registeredThread->PollJSSampling();
+  info->PollJSSampling();
 }
 
 // When the profiler is started on a background thread, we can't synchronously
 // call PollJSSampling on the main thread's ThreadInfo. And the next regular
 // call to PollJSSampling on the main thread would only happen once the main
 // thread triggers a JS interrupt callback.
 // This means that all the JS execution between profiler_start() and the first
 // JS interrupt would happen with JS sampling disabled, and we wouldn't get any
@@ -2811,42 +2733,43 @@ locked_profiler_start(PSLockRef aLock, u
   // Fall back to the default values if the passed-in values are unreasonable.
   uint32_t entries = aEntries > 0 ? aEntries : PROFILER_DEFAULT_ENTRIES;
   double interval = aInterval > 0 ? aInterval : PROFILER_DEFAULT_INTERVAL;
 
   ActivePS::Create(aLock, entries, interval, aFeatures, aFilters, aFilterCount);
 
   // Set up profiling for each registered thread, if appropriate.
   int tid = Thread::GetCurrentId();
-  const nsTArray<UniquePtr<RegisteredThread>>& registeredThreads =
-    CorePS::RegisteredThreads(aLock);
-  for (auto& registeredThread : registeredThreads) {
-    RefPtr<ThreadInfo> info = registeredThread->Info();
+  const CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(aLock);
+  for (uint32_t i = 0; i < liveThreads.size(); i++) {
+    ThreadInfo* info = liveThreads.at(i);
 
     if (ActivePS::ShouldProfileThread(aLock, info)) {
-      nsCOMPtr<nsIEventTarget> eventTarget = registeredThread->GetEventTarget();
-      ActivePS::AddLiveProfiledThread(aLock, registeredThread.get(),
-        MakeUnique<ProfiledThreadData>(info, eventTarget));
+      info->StartProfiling();
       if (ActivePS::FeatureJS(aLock)) {
-        registeredThread->StartJSSampling();
+        info->StartJSSampling();
         if (info->ThreadId() == tid) {
           // We can manually poll the current thread so it starts sampling
           // immediately.
-          registeredThread->PollJSSampling();
+          info->PollJSSampling();
         } else if (info->IsMainThread()) {
           // Dispatch a runnable to the main thread to call PollJSSampling(),
           // so that we don't have wait for the next JS interrupt callback in
           // order to start profiling JS.
           TriggerPollJSSamplingOnMainThread();
         }
       }
-      registeredThread->RacyRegisteredThread().ReinitializeOnResume();
     }
   }
 
+  // Dead ThreadInfos are deleted in profiler_stop(), and dead ThreadInfos
+  // aren't saved when the profiler is inactive. Therefore the dead threads
+  // vector should be empty here.
+  MOZ_RELEASE_ASSERT(CorePS::DeadThreads(aLock).empty());
+
 #ifdef MOZ_TASK_TRACER
   if (ActivePS::FeatureTaskTracer(aLock)) {
     tasktracer::StartLogging();
   }
 #endif
 
 #if defined(GP_OS_android)
   if (ActivePS::FeatureJava(aLock)) {
@@ -2959,30 +2882,39 @@ locked_profiler_stop(PSLockRef aLock)
 #ifdef MOZ_TASK_TRACER
   if (ActivePS::FeatureTaskTracer(aLock)) {
     tasktracer::StopLogging();
   }
 #endif
 
   // Stop sampling live threads.
   int tid = Thread::GetCurrentId();
-  const nsTArray<LiveProfiledThreadData>& liveProfiledThreads =
-    ActivePS::LiveProfiledThreads(aLock);
-  for (auto& thread : liveProfiledThreads) {
-    RegisteredThread* registeredThread = thread.mRegisteredThread;
-    if (ActivePS::FeatureJS(aLock)) {
-      registeredThread->StopJSSampling();
-      if (registeredThread->Info()->ThreadId() == tid) {
-        // We can manually poll the current thread so it stops profiling
-        // immediately.
-        registeredThread->PollJSSampling();
+  CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(aLock);
+  for (uint32_t i = 0; i < liveThreads.size(); i++) {
+    ThreadInfo* info = liveThreads.at(i);
+    if (info->IsBeingProfiled()) {
+      if (ActivePS::FeatureJS(aLock)) {
+        info->StopJSSampling();
+        if (info->ThreadId() == tid) {
+          // We can manually poll the current thread so it stops profiling
+          // immediately.
+          info->PollJSSampling();
+        }
       }
+      info->StopProfiling();
     }
   }
 
+  // This is where we destroy the ThreadInfos for all dead threads.
+  CorePS::ThreadVector& deadThreads = CorePS::DeadThreads(aLock);
+  while (!deadThreads.empty()) {
+    delete deadThreads.back();
+    deadThreads.pop_back();
+  }
+
   // The Stop() call doesn't actually stop Run(); that happens in this
   // function's caller when the sampler thread is destroyed. Stop() just gives
   // the SamplerThread a chance to do some cleanup with gPSMutex locked.
   SamplerThread* samplerThread = ActivePS::Destroy(aLock);
   samplerThread->Stop(aLock);
 
   return samplerThread;
 }
@@ -3113,94 +3045,92 @@ profiler_register_thread(const char* aNa
 void
 profiler_unregister_thread()
 {
   MOZ_ASSERT_IF(NS_IsMainThread(), Scheduler::IsCooperativeThread());
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
   PSAutoLock lock(gPSMutex);
 
-  // We don't call RegisteredThread::StopJSSampling() here; there's no point
-  // doing that for a JS thread that is in the process of disappearing.
-
-  RegisteredThread* registeredThread = FindCurrentThreadRegisteredThread(lock);
-  MOZ_RELEASE_ASSERT(registeredThread == TLSRegisteredThread::RegisteredThread(lock));
-  if (registeredThread) {
-    RefPtr<ThreadInfo> info = registeredThread->Info();
-
+  // We don't call ThreadInfo::StopJSSampling() here; there's no point doing
+  // that for a JS thread that is in the process of disappearing.
+
+  int i;
+  ThreadInfo* info = FindLiveThreadInfo(lock, &i);
+  MOZ_RELEASE_ASSERT(info == TLSInfo::Info(lock));
+  if (info) {
     DEBUG_LOG("profiler_unregister_thread: %s", info->Name());
-
-    if (ActivePS::Exists(lock)) {
-      ActivePS::UnregisterThread(lock, registeredThread);
+    if (ActivePS::Exists(lock) && info->IsBeingProfiled()) {
+      info->NotifyUnregistered(ActivePS::Buffer(lock).mRangeEnd);
+      CorePS::DeadThreads(lock).push_back(info);
+      CorePS::DiscardExpiredDeadThreads(lock, ActivePS::Buffer(lock).mRangeStart);
+    } else {
+      delete info;
     }
-
-    // Clear the pointer to the RegisteredThread object that we're about to
-    // destroy.
-    TLSRegisteredThread::SetRegisteredThread(lock, nullptr);
-
-    // Remove the thread from the list of registered threads. This deletes the
-    // registeredThread object.
-    CorePS::RemoveRegisteredThread(lock, registeredThread);
+    CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(lock);
+    liveThreads.erase(liveThreads.begin() + i);
+
+    // Whether or not we just destroyed the ThreadInfo or transferred it to the
+    // dead thread vector, we no longer need to access it via TLS.
+    TLSInfo::SetInfo(lock, nullptr);
+
   } else {
-    // There are two ways FindCurrentThreadRegisteredThread() might have failed.
+    // There are two ways FindLiveThreadInfo() might have failed.
     //
-    // - TLSRegisteredThread::Init() failed in locked_register_thread().
+    // - TLSInfo::Init() failed in locked_register_thread().
     //
     // - We've already called profiler_unregister_thread() for this thread.
     //   (Whether or not it should, this does happen in practice.)
     //
-    // Either way, TLSRegisteredThread should be empty.
-    MOZ_RELEASE_ASSERT(!TLSRegisteredThread::RegisteredThread(lock));
+    // Either way, TLSInfo should be empty.
+    MOZ_RELEASE_ASSERT(!TLSInfo::Info(lock));
   }
 }
 
 void
 profiler_thread_sleep()
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
-  RacyRegisteredThread* racyRegisteredThread =
-    TLSRegisteredThread::RacyRegisteredThread();
-  if (!racyRegisteredThread) {
+  RacyThreadInfo* racyInfo = TLSInfo::RacyInfo();
+  if (!racyInfo) {
     return;
   }
 
-  racyRegisteredThread->SetSleeping();
+  racyInfo->SetSleeping();
 }
 
 void
 profiler_thread_wake()
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
-  RacyRegisteredThread* racyRegisteredThread =
-    TLSRegisteredThread::RacyRegisteredThread();
-  if (!racyRegisteredThread) {
+  RacyThreadInfo* racyInfo = TLSInfo::RacyInfo();
+  if (!racyInfo) {
     return;
   }
 
-  racyRegisteredThread->SetAwake();
+  racyInfo->SetAwake();
 }
 
 bool
 profiler_thread_is_sleeping()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
-  RacyRegisteredThread* racyRegisteredThread =
-    TLSRegisteredThread::RacyRegisteredThread();
-  if (!racyRegisteredThread) {
+  RacyThreadInfo* racyInfo = TLSInfo::RacyInfo();
+  if (!racyInfo) {
     return false;
   }
-  return racyRegisteredThread->IsSleeping();
+  return racyInfo->IsSleeping();
 }
 
 void
 profiler_js_interrupt_callback()
 {
   // This function runs on JS threads being sampled.
   PollJSSamplingForCurrentThread();
 }
@@ -3220,20 +3150,19 @@ profiler_get_backtrace()
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
   PSAutoLock lock(gPSMutex);
 
   if (!ActivePS::Exists(lock) || ActivePS::FeaturePrivacy(lock)) {
     return nullptr;
   }
 
-  RegisteredThread* registeredThread =
-    TLSRegisteredThread::RegisteredThread(lock);
-  if (!registeredThread) {
-    MOZ_ASSERT(registeredThread);
+  ThreadInfo* info = TLSInfo::Info(lock);
+  if (!info) {
+    MOZ_ASSERT(info);
     return nullptr;
   }
 
   int tid = Thread::GetCurrentId();
 
   TimeStamp now = TimeStamp::Now();
 
   Registers regs;
@@ -3241,17 +3170,17 @@ profiler_get_backtrace()
   regs.SyncPopulate();
 #else
   regs.Clear();
 #endif
 
   // 1000 should be plenty for a single backtrace.
   auto buffer = MakeUnique<ProfileBuffer>(1000);
 
-  DoSyncSample(lock, *registeredThread, now, regs, *buffer.get());
+  DoSyncSample(lock, *info, now, regs, *buffer.get());
 
   return UniqueProfilerBacktrace(
     new ProfilerBacktrace("SyncProfile", tid, Move(buffer)));
 }
 
 void
 ProfilerBacktraceDestructor::operator()(ProfilerBacktrace* aBacktrace)
 {
@@ -3266,28 +3195,27 @@ racy_profiler_add_marker(const char* aMa
 
   // We don't assert that RacyFeatures::IsActiveWithoutPrivacy() is true here,
   // because it's possible that the result has changed since we tested it in
   // the caller.
   //
   // Because of this imprecision it's possible to miss a marker or record one
   // we shouldn't. Either way is not a big deal.
 
-  RacyRegisteredThread* racyRegisteredThread =
-    TLSRegisteredThread::RacyRegisteredThread();
-  if (!racyRegisteredThread) {
+  RacyThreadInfo* racyInfo = TLSInfo::RacyInfo();
+  if (!racyInfo) {
     return;
   }
 
   TimeStamp origin = (aPayload && !aPayload->GetStartTime().IsNull())
-                       ? aPayload->GetStartTime()
-                       : TimeStamp::Now();
+                   ? aPayload->GetStartTime()
+                   : TimeStamp::Now();
   TimeDuration delta = origin - CorePS::ProcessStartTime();
-  racyRegisteredThread->AddPendingMarker(aMarkerName, Move(aPayload),
-                                         delta.ToMilliseconds());
+  racyInfo->AddPendingMarker(aMarkerName, Move(aPayload),
+                             delta.ToMilliseconds());
 }
 
 void
 profiler_add_marker(const char* aMarkerName,
                     UniquePtr<ProfilerMarkerPayload> aPayload)
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
@@ -3323,20 +3251,19 @@ profiler_add_marker_for_thread(int aThre
     new ProfilerMarker(aMarkerName, aThreadId, Move(aPayload),
                        delta.ToMilliseconds());
 
   PSAutoLock lock(gPSMutex);
 
 #ifdef DEBUG
   // Assert that our thread ID makes sense
   bool realThread = false;
-  const nsTArray<UniquePtr<RegisteredThread>>& registeredThreads =
-    CorePS::RegisteredThreads(lock);
-  for (auto& thread : registeredThreads) {
-    RefPtr<ThreadInfo> info = thread->Info();
+  const CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(lock);
+  for (uint32_t i = 0; i < liveThreads.size(); i++) {
+    ThreadInfo* info = liveThreads.at(i);
     if (info->ThreadId() == aThreadId) {
       realThread = true;
       break;
     }
   }
   MOZ_ASSERT(realThread, "Invalid thread id");
 #endif
 
@@ -3379,87 +3306,77 @@ profiler_tracing(const char* aCategory, 
   auto payload =
     MakeUnique<TracingMarkerPayload>(aCategory, aKind, Move(aCause));
   racy_profiler_add_marker(aMarkerName, Move(payload));
 }
 
 PseudoStack*
 profiler_get_pseudo_stack()
 {
-  return TLSRegisteredThread::Stack();
+  return TLSInfo::Stack();
 }
 
 void
 profiler_set_js_context(JSContext* aCx)
 {
   MOZ_ASSERT(aCx);
 
   PSAutoLock lock(gPSMutex);
 
-  RegisteredThread* registeredThread =
-    TLSRegisteredThread::RegisteredThread(lock);
-  if (!registeredThread) {
+  ThreadInfo* info = TLSInfo::Info(lock);
+  if (!info) {
     return;
   }
 
-  registeredThread->SetJSContext(aCx);
+  info->SetJSContext(aCx);
 
   // This call is on-thread, so we can call PollJSSampling() to start JS
   // sampling immediately.
-  registeredThread->PollJSSampling();
+  info->PollJSSampling();
 }
 
 void
 profiler_clear_js_context()
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
   PSAutoLock lock(gPSMutex);
 
-  RegisteredThread* registeredThread =
-    TLSRegisteredThread::RegisteredThread(lock);
-  if (!registeredThread) {
-    return;
-  }
-
-  JSContext* cx = registeredThread->GetJSContext();
-  if (!cx) {
+  ThreadInfo* info = TLSInfo::Info(lock);
+  if (!info || !info->mContext) {
     return;
   }
 
   // On JS shut down, flush the current buffer as stringifying JIT samples
   // requires a live JSContext.
 
   if (ActivePS::Exists(lock)) {
-    // Flush this thread's profile data, if it is being profiled.
-    ProfiledThreadData* profiledThreadData =
-      ActivePS::GetProfiledThreadData(lock, registeredThread);
-    if (profiledThreadData) {
-      profiledThreadData->FlushSamplesAndMarkers(cx,
-                                                 CorePS::ProcessStartTime(),
-                                                 ActivePS::Buffer(lock));
+    // Flush this thread's ThreadInfo, if it is being profiled.
+    if (info->IsBeingProfiled()) {
+      info->FlushSamplesAndMarkers(CorePS::ProcessStartTime(),
+                                   ActivePS::Buffer(lock));
 
       if (ActivePS::FeatureJS(lock)) {
         // Notify the JS context that profiling for this context has stopped.
         // Do this by calling StopJSSampling and PollJSSampling before
         // nulling out the JSContext.
-        registeredThread->StopJSSampling();
-        registeredThread->PollJSSampling();
-
-        registeredThread->ClearJSContext();
-
-        // Tell the thread that we'd like to have JS sampling on this
+        info->StopJSSampling();
+        info->PollJSSampling();
+
+        info->mContext = nullptr;
+
+        // Tell the ThreadInfo that we'd like to have JS sampling on this
         // thread again, once it gets a new JSContext (if ever).
-        registeredThread->StartJSSampling();
+        info->StartJSSampling();
         return;
       }
     }
   }
 
-  registeredThread->ClearJSContext();
+  info->mContext = nullptr;
 }
 
 int
 profiler_current_thread_id()
 {
   return Thread::GetCurrentId();
 }
 
@@ -3470,57 +3387,55 @@ void
 profiler_suspend_and_sample_thread(int aThreadId,
                                    uint32_t aFeatures,
                                    ProfilerStackCollector& aCollector,
                                    bool aSampleNative /* = true */)
 {
   // Lock the profiler mutex
   PSAutoLock lock(gPSMutex);
 
-  const nsTArray<UniquePtr<RegisteredThread>>& registeredThreads =
-    CorePS::RegisteredThreads(lock);
-  for (auto& thread : registeredThreads) {
-    RefPtr<ThreadInfo> info = thread->Info();
-    RegisteredThread& registeredThread = *thread.get();
+  const CorePS::ThreadVector& liveThreads = CorePS::LiveThreads(lock);
+  for (uint32_t i = 0; i < liveThreads.size(); i++) {
+    ThreadInfo* info = liveThreads.at(i);
 
     if (info->ThreadId() == aThreadId) {
       if (info->IsMainThread()) {
         aCollector.SetIsMainThread();
       }
 
       // Allocate the space for the native stack
       NativeStack nativeStack;
 
       // Suspend, sample, and then resume the target thread.
       Sampler sampler(lock);
-      sampler.SuspendAndSampleAndResumeThread(lock, registeredThread,
+      sampler.SuspendAndSampleAndResumeThread(lock, *info,
                                               [&](const Registers& aRegs) {
         // The target thread is now suspended. Collect a native backtrace, and
         // call the callback.
         bool isSynchronous = false;
 #if defined(HAVE_FASTINIT_NATIVE_UNWIND)
         if (aSampleNative) {
           // We can only use FramePointerStackWalk or MozStackWalk from
           // suspend_and_sample_thread as other stackwalking methods may not be
           // initialized.
 # if defined(USE_FRAME_POINTER_STACK_WALK)
-          DoFramePointerBacktrace(lock, registeredThread, aRegs, nativeStack);
+          DoFramePointerBacktrace(lock, *info, aRegs, nativeStack);
 # elif defined(USE_MOZ_STACK_WALK)
-          DoMozStackWalkBacktrace(lock, registeredThread, aRegs, nativeStack);
+          DoMozStackWalkBacktrace(lock, *info, aRegs, nativeStack);
 # else
 #  error "Invalid configuration"
 # endif
 
-          MergeStacks(aFeatures, isSynchronous, registeredThread, aRegs,
-                      nativeStack, aCollector);
+          MergeStacks(aFeatures, isSynchronous, *info, aRegs, nativeStack,
+                      aCollector);
         } else
 #endif
         {
-          MergeStacks(aFeatures, isSynchronous, registeredThread, aRegs,
-                      nativeStack, aCollector);
+          MergeStacks(aFeatures, isSynchronous, *info, aRegs, nativeStack,
+                      aCollector);
 
           if (ProfilerFeature::HasLeaf(aFeatures)) {
             aCollector.CollectNativeLeafAddr((void*)aRegs.mPC);
           }
         }
       });
 
       // NOTE: Make sure to disable the sampler before it is destroyed, in case
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -16,21 +16,20 @@ if CONFIG['MOZ_GECKO_PROFILER']:
         'public/ProfilerMarkerPayload.h',
         'public/ProfilerParent.h',
         'public/shared-libraries.h',
     ]
     UNIFIED_SOURCES += [
         'core/platform.cpp',
         'core/ProfileBuffer.cpp',
         'core/ProfileBufferEntry.cpp',
-        'core/ProfiledThreadData.cpp',
         'core/ProfileJSONWriter.cpp',
         'core/ProfilerBacktrace.cpp',
         'core/ProfilerMarkerPayload.cpp',
-        'core/RegisteredThread.cpp',
+        'core/ThreadInfo.cpp',
         'gecko/ChildProfilerController.cpp',
         'gecko/nsProfilerFactory.cpp',
         'gecko/nsProfilerStartParams.cpp',
         'gecko/ProfilerChild.cpp',
         'gecko/ProfilerIOInterposeObserver.cpp',
         'gecko/ProfilerParent.cpp',
         'gecko/ThreadResponsiveness.cpp',
     ]
--- a/tools/profiler/tests/gtest/ThreadProfileTest.cpp
+++ b/tools/profiler/tests/gtest/ThreadProfileTest.cpp
@@ -4,42 +4,63 @@
  * 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 "gtest/gtest.h"
 
 #include "ProfileBufferEntry.h"
 #include "ThreadInfo.h"
 
+// Make sure we can initialize our thread profile
+TEST(ThreadProfile, Initialization) {
+  int tid = 1000;
+  nsCOMPtr<nsIThread> mainThread;
+  NS_GetMainThread(getter_AddRefs(mainThread));
+  ThreadInfo info("testThread", tid, true, mainThread, nullptr);
+  info.StartProfiling();
+}
+
 // 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->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));
   }
   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 evicting works as it should in the basic case
 TEST(ThreadProfile, InsertEntriesWrap) {
+  int tid = 1000;
   int entries = 32;
+  nsCOMPtr<nsIThread> mainThread;
+  NS_GetMainThread(getter_AddRefs(mainThread));
+  ThreadInfo info("testThread", tid, true, mainThread, nullptr);
   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));
   }
   // We inserted 11 more entries than fit in the buffer, so the first 11 entries