Bug 1488260 Part 1 - Consolidate per-thread event and assertion streams, NOT REVIEWED YET.
authorBrian Hackett <bhackett1024@gmail.com>
Mon, 03 Sep 2018 08:08:45 -1000
changeset 435271 37ebf94c745214cdb0c44891e5cb0c8d9fcf2d89
parent 435270 cef9a1ef6b543644623200695b072f01d1d0aff4
child 435272 0a960ddaa8d7bd43353308ce53554c2ab3f346fc
push id34602
push userdvarga@mozilla.com
push dateSat, 08 Sep 2018 03:54:14 +0000
treeherdermozilla-central@da268c77ac76 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1488260
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1488260 Part 1 - Consolidate per-thread event and assertion streams, NOT REVIEWED YET.
mfbt/RecordReplay.cpp
mfbt/RecordReplay.h
toolkit/recordreplay/Callback.cpp
toolkit/recordreplay/File.cpp
toolkit/recordreplay/File.h
toolkit/recordreplay/Lock.cpp
toolkit/recordreplay/ProcessRecordReplay.cpp
toolkit/recordreplay/ProcessRecordReplay.h
toolkit/recordreplay/ProcessRedirect.h
toolkit/recordreplay/ProcessRedirectDarwin.cpp
toolkit/recordreplay/ProcessRewind.cpp
toolkit/recordreplay/Thread.cpp
toolkit/recordreplay/Thread.h
toolkit/recordreplay/Trigger.cpp
toolkit/recordreplay/ipc/ChildIPC.cpp
--- a/mfbt/RecordReplay.cpp
+++ b/mfbt/RecordReplay.cpp
@@ -42,18 +42,16 @@ namespace recordreplay {
 
 #define FOR_EACH_INTERFACE_VOID(Macro)                          \
   Macro(InternalBeginOrderedAtomicAccess, (), ())               \
   Macro(InternalEndOrderedAtomicAccess, (), ())                 \
   Macro(InternalBeginPassThroughThreadEvents, (), ())           \
   Macro(InternalEndPassThroughThreadEvents, (), ())             \
   Macro(InternalBeginDisallowThreadEvents, (), ())              \
   Macro(InternalEndDisallowThreadEvents, (), ())                \
-  Macro(InternalBeginCaptureEventStacks, (), ())                \
-  Macro(InternalEndCaptureEventStacks, (), ())                  \
   Macro(InternalRecordReplayBytes,                              \
         (void* aData, size_t aSize), (aData, aSize))            \
   Macro(NotifyUnrecordedWait,                                   \
         (const std::function<void()>& aCallback), (aCallback))  \
   Macro(MaybeWaitForCheckpointSave, (), ())                     \
   Macro(InternalInvalidateRecording, (const char* aWhy), (aWhy)) \
   Macro(InternalDestroyPLDHashTableCallbacks,                   \
         (const PLDHashTableOps* aOps), (aOps))                  \
--- a/mfbt/RecordReplay.h
+++ b/mfbt/RecordReplay.h
@@ -162,28 +162,16 @@ static inline bool AreThreadEventsDisall
 
 // RAII class for a region where thread events are disallowed.
 struct MOZ_RAII AutoDisallowThreadEvents
 {
   AutoDisallowThreadEvents() { BeginDisallowThreadEvents(); }
   ~AutoDisallowThreadEvents() { EndDisallowThreadEvents(); }
 };
 
-// Mark a region where thread events should have stack information captured.
-// These stacks help in tracking down record/replay inconsistencies.
-static inline void BeginCaptureEventStacks();
-static inline void EndCaptureEventStacks();
-
-// RAII class for a region where thread event stacks should be captured.
-struct MOZ_RAII AutoCaptureEventStacks
-{
-  AutoCaptureEventStacks() { BeginCaptureEventStacks(); }
-  ~AutoCaptureEventStacks() { EndCaptureEventStacks(); }
-};
-
 // Record or replay a value in the current thread's event stream.
 static inline size_t RecordReplayValue(size_t aValue);
 
 // Record or replay the contents of a range of memory in the current thread's
 // event stream.
 static inline void RecordReplayBytes(void* aData, size_t aSize);
 
 // During recording or replay, mark the recording as unusable. There are some
@@ -425,18 +413,16 @@ NoteContentParse(const void* aToken,
 MOZ_MakeRecordReplayWrapperVoid(BeginOrderedAtomicAccess, (), ())
 MOZ_MakeRecordReplayWrapperVoid(EndOrderedAtomicAccess, (), ())
 MOZ_MakeRecordReplayWrapperVoid(BeginPassThroughThreadEvents, (), ())
 MOZ_MakeRecordReplayWrapperVoid(EndPassThroughThreadEvents, (), ())
 MOZ_MakeRecordReplayWrapper(AreThreadEventsPassedThrough, bool, false, (), ())
 MOZ_MakeRecordReplayWrapperVoid(BeginDisallowThreadEvents, (), ())
 MOZ_MakeRecordReplayWrapperVoid(EndDisallowThreadEvents, (), ())
 MOZ_MakeRecordReplayWrapper(AreThreadEventsDisallowed, bool, false, (), ())
-MOZ_MakeRecordReplayWrapperVoid(BeginCaptureEventStacks, (), ())
-MOZ_MakeRecordReplayWrapperVoid(EndCaptureEventStacks, (), ())
 MOZ_MakeRecordReplayWrapper(RecordReplayValue, size_t, aValue, (size_t aValue), (aValue))
 MOZ_MakeRecordReplayWrapperVoid(RecordReplayBytes, (void* aData, size_t aSize), (aData, aSize))
 MOZ_MakeRecordReplayWrapper(HasDivergedFromRecording, bool, false, (), ())
 MOZ_MakeRecordReplayWrapper(GeneratePLDHashTableCallbacks,
                             const PLDHashTableOps*, aOps, (const PLDHashTableOps* aOps), (aOps))
 MOZ_MakeRecordReplayWrapper(UnwrapPLDHashTableCallbacks,
                             const PLDHashTableOps*, aOps, (const PLDHashTableOps* aOps), (aOps))
 MOZ_MakeRecordReplayWrapperVoid(DestroyPLDHashTableCallbacks,
--- a/toolkit/recordreplay/Callback.cpp
+++ b/toolkit/recordreplay/Callback.cpp
@@ -24,18 +24,16 @@ void
 RegisterCallbackData(void* aData)
 {
   MOZ_RELEASE_ASSERT(IsRecordingOrReplaying());
   MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
   if (!aData) {
     return;
   }
 
-  RecordReplayAssert("RegisterCallbackData");
-
   AutoOrderedAtomicAccess at;
   StaticMutexAutoLock lock(gCallbackMutex);
   if (!gCallbackData) {
     gCallbackData = new ValueIndex();
   }
   gCallbackData->Insert(aData);
 }
 
@@ -67,24 +65,20 @@ EndCallback()
     child::BeginIdleTime();
   }
   thread->SetPassThrough(true);
 }
 
 void
 SaveOrRestoreCallbackData(void** aData)
 {
-  MOZ_RELEASE_ASSERT(IsRecordingOrReplaying());
-  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
-  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
   MOZ_RELEASE_ASSERT(gCallbackData);
 
   Thread* thread = Thread::Current();
-
-  RecordReplayAssert("RestoreCallbackData");
+  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
 
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::RestoreCallbackData);
 
   size_t index = 0;
   if (IsRecording() && *aData) {
     StaticMutexAutoLock lock(gCallbackMutex);
     index = gCallbackData->GetIndex(*aData);
   }
@@ -102,20 +96,18 @@ RemoveCallbackData(void* aData)
 
   StaticMutexAutoLock lock(gCallbackMutex);
   gCallbackData->Remove(aData);
 }
 
 void
 PassThroughThreadEventsAllowCallbacks(const std::function<void()>& aFn)
 {
-  MOZ_RELEASE_ASSERT(IsRecordingOrReplaying());
-  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
-
   Thread* thread = Thread::Current();
+  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
 
   if (IsRecording()) {
     if (thread->IsMainThread()) {
       child::BeginIdleTime();
     }
     thread->SetPassThrough(true);
     aFn();
     if (thread->IsMainThread()) {
--- a/toolkit/recordreplay/File.cpp
+++ b/toolkit/recordreplay/File.cpp
@@ -46,17 +46,17 @@ Stream::ReadBytes(void* aData, size_t aS
       return;
     }
 
     MOZ_RELEASE_ASSERT(mBufferPos == mBufferLength);
 
     // If we try to read off the end of a stream then we must have hit the end
     // of the replay for this thread.
     while (mChunkIndex == mChunks.length()) {
-      MOZ_RELEASE_ASSERT(mName == StreamName::Event || mName == StreamName::Assert);
+      MOZ_RELEASE_ASSERT(mName == StreamName::Event);
       HitEndOfRecording();
     }
 
     const StreamChunkLocation& chunk = mChunks[mChunkIndex++];
 
     EnsureMemory(&mBallast, &mBallastSize, chunk.mCompressedSize, BallastMaxSize(),
                  DontCopyExistingData);
     mFile->ReadChunk(mBallast.get(), chunk);
@@ -151,24 +151,83 @@ Stream::WriteScalar(size_t aValue)
     if (aValue) {
       bits |= 128;
     }
     WriteBytes(&bits, 1);
   } while (aValue);
 }
 
 void
+Stream::RecordOrReplayThreadEvent(ThreadEvent aEvent)
+{
+  if (IsRecording()) {
+    WriteScalar((size_t) aEvent);
+  } else {
+    ThreadEvent oldEvent = (ThreadEvent) ReadScalar();
+    if (oldEvent != aEvent) {
+      child::ReportFatalError(Nothing(), "Event Mismatch: Recorded %s Replayed %s",
+                              ThreadEventName(oldEvent), ThreadEventName(aEvent));
+    }
+    mLastEvent = aEvent;
+  }
+
+  // Check the execution progress counter for events executing on the main thread.
+  if (mNameIndex == MainThreadId) {
+    CheckInput(*ExecutionProgressCounter());
+  }
+}
+
+void
 Stream::CheckInput(size_t aValue)
 {
-  size_t oldValue = aValue;
-  RecordOrReplayScalar(&oldValue);
-  if (oldValue != aValue) {
-    child::ReportFatalError(Nothing(), "Input Mismatch: Recorded: %zu Replayed %zu\n",
-                            oldValue, aValue);
-    Unreachable();
+  if (IsRecording()) {
+    WriteScalar(aValue);
+  } else {
+    size_t oldValue = ReadScalar();
+    if (oldValue != aValue) {
+      child::ReportFatalError(Nothing(), "Input Mismatch: %s Recorded %llu Replayed %llu",
+                              ThreadEventName(mLastEvent), oldValue, aValue);
+    }
+  }
+}
+
+void
+Stream::CheckInput(const char* aValue)
+{
+  size_t len = strlen(aValue);
+  if (IsRecording()) {
+    WriteScalar(len);
+    WriteBytes(aValue, len);
+  } else {
+    size_t oldLen = ReadScalar();
+    EnsureInputBallast(oldLen + 1);
+    ReadBytes(mInputBallast.get(), oldLen);
+    mInputBallast[oldLen] = 0;
+
+    if (len != oldLen || memcmp(aValue, mInputBallast.get(), len) != 0) {
+      child::ReportFatalError(Nothing(), "Input Mismatch: %s Recorded %s Replayed %s",
+                              ThreadEventName(mLastEvent), mInputBallast.get(), aValue);
+    }
+  }
+}
+
+void
+Stream::CheckInput(const void* aData, size_t aSize)
+{
+  CheckInput(aSize);
+  if (IsRecording()) {
+    WriteBytes(aData, aSize);
+  } else {
+    EnsureInputBallast(aSize);
+    ReadBytes(mInputBallast.get(), aSize);
+
+    if (memcmp(aData, mInputBallast.get(), aSize) != 0) {
+      child::ReportFatalError(Nothing(), "Input Buffer Mismatch: %s",
+                              ThreadEventName(mLastEvent));
+    }
   }
 }
 
 void
 Stream::EnsureMemory(UniquePtr<char[]>* aBuf, size_t* aSize,
                      size_t aNeededSize, size_t aMaxSize, ShouldCopy aCopy)
 {
   // Once a stream buffer grows, it never shrinks again. Buffers start out
@@ -182,16 +241,22 @@ Stream::EnsureMemory(UniquePtr<char[]>* 
       memcpy(newBuf, aBuf->get(), *aSize);
     }
     aBuf->reset(newBuf);
     *aSize = newSize;
   }
 }
 
 void
+Stream::EnsureInputBallast(size_t aSize)
+{
+  EnsureMemory(&mInputBallast, &mInputBallastSize, aSize, (size_t) -1, DontCopyExistingData);
+}
+
+void
 Stream::Flush(bool aTakeLock)
 {
   MOZ_RELEASE_ASSERT(mFile && mFile->OpenForWriting());
 
   if (!mBufferPos) {
     return;
   }
 
--- a/toolkit/recordreplay/File.h
+++ b/toolkit/recordreplay/File.h
@@ -51,17 +51,16 @@ struct StreamChunkLocation
   }
 };
 
 enum class StreamName
 {
   Main,
   Lock,
   Event,
-  Assert,
   Count
 };
 
 class File;
 
 class Stream
 {
   friend class File;
@@ -98,16 +97,23 @@ class Stream
 
   // The number of uncompressed bytes read or written from the stream.
   size_t mStreamPos;
 
   // Any buffer available for use when decompressing or compressing data.
   UniquePtr<char[]> mBallast;
   size_t mBallastSize;
 
+  // Any buffer available to check for input mismatches.
+  UniquePtr<char[]> mInputBallast;
+  size_t mInputBallastSize;
+
+  // The last event in this stream, in case of an input mismatch.
+  ThreadEvent mLastEvent;
+
   // The number of chunks that have been completely read or written. When
   // writing, this equals mChunks.length().
   size_t mChunkIndex;
 
   // When writing, the number of chunks in this stream when the file was last
   // flushed.
   size_t mFlushedChunks;
 
@@ -117,16 +123,19 @@ class Stream
     , mNameIndex(aNameIndex)
     , mBuffer(nullptr)
     , mBufferSize(0)
     , mBufferLength(0)
     , mBufferPos(0)
     , mStreamPos(0)
     , mBallast(nullptr)
     , mBallastSize(0)
+    , mInputBallast(nullptr)
+    , mInputBallastSize(0)
+    , mLastEvent((ThreadEvent) 0)
     , mChunkIndex(0)
     , mFlushedChunks(0)
   {}
 
 public:
   StreamName Name() const { return mName; }
   size_t NameIndex() const { return mNameIndex; }
 
@@ -153,40 +162,39 @@ public:
     }
   }
 
   template <typename T>
   inline void RecordOrReplayValue(T* aPtr) {
     RecordOrReplayBytes(aPtr, sizeof(T));
   }
 
-  // Make sure that a value is the same while replaying as it was while
-  // recording.
-  void CheckInput(size_t aValue);
+  // Note a new thread event for this stream, and make sure it is the same
+  // while replaying as it was while recording.
+  void RecordOrReplayThreadEvent(ThreadEvent aEvent);
 
-  // Add a thread event to this file. Each thread event in a file is followed
-  // by additional data specific to that event. Generally, CheckInput should be
-  // used while recording or replaying the data for a thread event so that any
-  // discrepancies with the recording are found immediately.
-  inline void RecordOrReplayThreadEvent(ThreadEvent aEvent) {
-    CheckInput((size_t)aEvent);
-  }
+  // Make sure that a value or buffer is the same while replaying as it was
+  // while recording.
+  void CheckInput(size_t aValue);
+  void CheckInput(const char* aValue);
+  void CheckInput(const void* aData, size_t aSize);
 
   inline size_t StreamPosition() {
     return mStreamPos;
   }
 
 private:
   enum ShouldCopy {
     DontCopyExistingData,
     CopyExistingData
   };
 
   void EnsureMemory(UniquePtr<char[]>* aBuf, size_t* aSize, size_t aNeededSize, size_t aMaxSize,
                     ShouldCopy aCopy);
+  void EnsureInputBallast(size_t aSize);
   void Flush(bool aTakeLock);
 
   static size_t BallastMaxSize();
 };
 
 class File
 {
 public:
--- a/toolkit/recordreplay/Lock.cpp
+++ b/toolkit/recordreplay/Lock.cpp
@@ -63,25 +63,23 @@ static ChunkAllocator<LockAcquires> gLoc
 // every recorded lock in existence.
 typedef std::unordered_map<void*, Lock*> LockMap;
 static LockMap* gLocks;
 static ReadWriteSpinLock gLocksLock;
 
 /* static */ void
 Lock::New(void* aNativeLock)
 {
-  if (AreThreadEventsPassedThrough() || HasDivergedFromRecording()) {
+  Thread* thread = Thread::Current();
+  if (!thread || thread->PassThroughEvents() || HasDivergedFromRecording()) {
     Destroy(aNativeLock); // Clean up any old lock, as below.
     return;
   }
 
-  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
-  Thread* thread = Thread::Current();
-
-  RecordReplayAssert("CreateLock");
+  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
 
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::CreateLock);
 
   size_t id;
   if (IsRecording()) {
     id = gNumLocks++;
   }
   thread->Events().RecordOrReplayScalar(&id);
@@ -89,17 +87,17 @@ Lock::New(void* aNativeLock)
   LockAcquires* info = gLockAcquires.Create(id);
   info->mAcquires = gRecordingFile->OpenStream(StreamName::Lock, id);
 
   if (IsReplaying()) {
     info->ReadAndNotifyNextOwner(thread);
   }
 
   // Tolerate new locks being created with identical pointers, even if there
-  // was no DestroyLock call for the old one.
+  // was no explicit Destroy() call for the old one.
   Destroy(aNativeLock);
 
   AutoWriteSpinLock ex(gLocksLock);
   thread->BeginDisallowEvents();
 
   if (!gLocks) {
     gLocks = new LockMap();
   }
@@ -148,26 +146,23 @@ Lock::Find(void* aNativeLock)
   }
 
   return nullptr;
 }
 
 void
 Lock::Enter()
 {
-  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough() && !HasDivergedFromRecording());
-  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
-
-  RecordReplayAssert("Lock %d", (int) mId);
+  Thread* thread = Thread::Current();
+  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
 
   // Include an event in each thread's record when a lock acquire begins. This
   // is not required by the replay but is used to check that lock acquire order
   // is consistent with the recording and that we will fail explicitly instead
   // of deadlocking.
-  Thread* thread = Thread::Current();
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Lock);
   thread->Events().CheckInput(mId);
 
   LockAcquires* acquires = gLockAcquires.Get(mId);
   if (IsRecording()) {
     acquires->mAcquires->WriteScalar(thread->Id());
   } else {
     // Wait until this thread is next in line to acquire the lock.
--- a/toolkit/recordreplay/ProcessRecordReplay.cpp
+++ b/toolkit/recordreplay/ProcessRecordReplay.cpp
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ProcessRecordReplay.h"
 
 #include "ipc/ChildInternal.h"
 #include "mozilla/Compression.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Sprintf.h"
-#include "mozilla/StackWalk.h"
 #include "mozilla/StaticMutex.h"
 #include "DirtyMemoryHandler.h"
 #include "Lock.h"
 #include "MemorySnapshot.h"
 #include "ProcessRedirect.h"
 #include "ProcessRewind.h"
 #include "Trigger.h"
 #include "ValueIndex.h"
@@ -40,18 +39,16 @@ BusyWait()
 ///////////////////////////////////////////////////////////////////////////////
 
 File* gRecordingFile;
 const char* gSnapshotMemoryPrefix;
 const char* gSnapshotStackPrefix;
 
 char* gInitializationFailureMessage;
 
-static void DumpRecordingAssertions();
-
 bool gInitialized;
 ProcessKind gProcessKind;
 char* gRecordingFilename;
 
 // Current process ID.
 static int gPid;
 
 // Whether to spew record/replay messages to stderr.
@@ -136,20 +133,16 @@ RecordReplayInterface_Initialize(int aAr
   Thread::InitializeThreads();
 
   Thread* thread = Thread::GetById(MainThreadId);
   MOZ_ASSERT(thread->Id() == MainThreadId);
 
   thread->BindToCurrent();
   thread->SetPassThrough(true);
 
-  if (IsReplaying() && TestEnv("DUMP_RECORDING")) {
-    DumpRecordingAssertions();
-  }
-
   InitializeTriggers();
   InitializeWeakPointers();
   InitializeMemorySnapshots();
   Thread::SpawnAllThreads();
   InitializeCountdownThread();
   SetupDirtyMemoryHandler();
 
   // Don't create a stylo thread pool when recording or replaying.
@@ -161,46 +154,38 @@ RecordReplayInterface_Initialize(int aAr
   InitializeRewindState();
 
   gInitialized = true;
 }
 
 MOZ_EXPORT size_t
 RecordReplayInterface_InternalRecordReplayValue(size_t aValue)
 {
-  MOZ_ASSERT(IsRecordingOrReplaying());
-
-  if (AreThreadEventsPassedThrough()) {
+  Thread* thread = Thread::Current();
+  if (thread->PassThroughEvents()) {
     return aValue;
   }
   EnsureNotDivergedFromRecording();
 
-  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
-  Thread* thread = Thread::Current();
-
-  RecordReplayAssert("Value");
+  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Value);
   thread->Events().RecordOrReplayValue(&aValue);
   return aValue;
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_InternalRecordReplayBytes(void* aData, size_t aSize)
 {
-  MOZ_ASSERT(IsRecordingOrReplaying());
-
-  if (AreThreadEventsPassedThrough()) {
+  Thread* thread = Thread::Current();
+  if (thread->PassThroughEvents()) {
     return;
   }
   EnsureNotDivergedFromRecording();
 
-  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
-  Thread* thread = Thread::Current();
-
-  RecordReplayAssert("Bytes %d", (int) aSize);
+  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
   thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Bytes);
   thread->Events().CheckInput(aSize);
   thread->Events().RecordOrReplayBytes(aData, aSize);
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_InternalInvalidateRecording(const char* aWhy)
 {
@@ -322,302 +307,67 @@ InternalPrint(const char* aFormat, va_li
 {
   char buf1[2048];
   VsprintfLiteral(buf1, aFormat, aArgs);
   char buf2[2048];
   SprintfLiteral(buf2, "Spew[%d]: %s", gPid, buf1);
   DirectPrint(buf2);
 }
 
+const char*
+ThreadEventName(ThreadEvent aEvent)
+{
+  switch (aEvent) {
+#define EnumToString(Kind) case ThreadEvent::Kind: return #Kind;
+    ForEachThreadEvent(EnumToString)
+#undef EnumToString
+  case ThreadEvent::CallStart: break;
+  }
+  size_t callId = (size_t) aEvent - (size_t) ThreadEvent::CallStart;
+  return gRedirections[callId].mName;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Record/Replay Assertions
 ///////////////////////////////////////////////////////////////////////////////
 
-struct StackWalkData
-{
-  char* mBuf;
-  size_t mSize;
-
-  StackWalkData(char* aBuf, size_t aSize)
-    : mBuf(aBuf), mSize(aSize)
-  {}
-
-  void append(const char* aText) {
-    size_t len = strlen(aText);
-    if (len <= mSize) {
-      strcpy(mBuf, aText);
-      mBuf += len;
-      mSize -= len;
-    }
-  }
-};
-
-static void
-StackWalkCallback(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure)
-{
-  StackWalkData* data = (StackWalkData*) aClosure;
-
-  MozCodeAddressDetails details;
-  MozDescribeCodeAddress(aPC, &details);
-
-  data->append(" ### ");
-  data->append(details.function[0] ? details.function : "???");
-}
-
-static void
-SetCurrentStackString(const char* aAssertion, char* aBuf, size_t aSize)
-{
-  size_t frameCount = 12;
-
-  // Locking operations usually have extra stack goop.
-  if (!strcmp(aAssertion, "Lock 1")) {
-    frameCount += 8;
-  } else if (!strncmp(aAssertion, "Lock ", 5)) {
-    frameCount += 4;
-  }
-
-  StackWalkData data(aBuf, aSize);
-  MozStackWalk(StackWalkCallback, /* aSkipFrames = */ 2, frameCount, &data);
-}
-
-// For debugging.
-char*
-PrintCurrentStackString()
-{
-  AutoEnsurePassThroughThreadEvents pt;
-  char* buf = new char[1000];
-  SetCurrentStackString("", buf, 1000);
-  return buf;
-}
-
-static inline bool
-AlwaysCaptureEventStack(const char* aText)
-{
-  return false;
-}
-
-// Bit included in assertion stream when the assertion is a text assert, rather
-// than a byte sequence.
-static const size_t AssertionBit = 1;
-
 extern "C" {
 
 MOZ_EXPORT void
 RecordReplayInterface_InternalRecordReplayAssert(const char* aFormat, va_list aArgs)
 {
-#ifdef INCLUDE_RECORD_REPLAY_ASSERTIONS
-  if (AreThreadEventsPassedThrough() || HasDivergedFromRecording()) {
+  Thread* thread = Thread::Current();
+  if (thread->PassThroughEvents() || thread->HasDivergedFromRecording()) {
     return;
   }
+  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
 
-  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
-  Thread* thread = Thread::Current();
-
-  // Record an assertion string consisting of the name of the assertion and
-  // stack information about the current point of execution.
+  // Add the asserted string to the recording.
   char text[1024];
   VsprintfLiteral(text, aFormat, aArgs);
-  if (IsRecording() && (thread->ShouldCaptureEventStacks() || AlwaysCaptureEventStack(text))) {
-    AutoPassThroughThreadEvents pt;
-    SetCurrentStackString(text, text + strlen(text), sizeof(text) - strlen(text));
-  }
 
-  size_t textLen = strlen(text);
-
-  if (IsRecording()) {
-    thread->Asserts().WriteScalar(thread->Events().StreamPosition());
-    if (thread->IsMainThread()) {
-      thread->Asserts().WriteScalar(*ExecutionProgressCounter());
-    }
-    thread->Asserts().WriteScalar((textLen << 1) | AssertionBit);
-    thread->Asserts().WriteBytes(text, textLen);
-  } else {
-    // While replaying, both the assertion's name and the current position in
-    // the thread's events need to match up with what was recorded. The stack
-    // portion of the assertion text does not need to match, it is used to help
-    // track down the reason for the mismatch.
-    bool match = true;
-    size_t streamPos = thread->Asserts().ReadScalar();
-    if (streamPos != thread->Events().StreamPosition()) {
-      match = false;
-    }
-    size_t progress = 0;
-    if (thread->IsMainThread()) {
-      progress = thread->Asserts().ReadScalar();
-      if (progress != *ExecutionProgressCounter()) {
-        match = false;
-      }
-    }
-    size_t assertLen = thread->Asserts().ReadScalar() >> 1;
-
-    char* buffer = thread->TakeBuffer(assertLen + 1);
-
-    thread->Asserts().ReadBytes(buffer, assertLen);
-    buffer[assertLen] = 0;
-
-    if (assertLen < textLen || memcmp(buffer, text, textLen) != 0) {
-      match = false;
-    }
-
-    if (!match) {
-      for (int i = Thread::NumRecentAsserts - 1; i >= 0; i--) {
-        if (thread->RecentAssert(i).mText) {
-          Print("Thread %d Recent %d: %s [%d]\n",
-                (int) thread->Id(), (int) i,
-                thread->RecentAssert(i).mText, (int) thread->RecentAssert(i).mPosition);
-        }
-      }
-
-      {
-        AutoPassThroughThreadEvents pt;
-        SetCurrentStackString(text, text + strlen(text), sizeof(text) - strlen(text));
-      }
-
-      child::ReportFatalError(Nothing(),
-                              "Assertion Mismatch: Thread %d\n"
-                              "Recorded: %s [%d,%d]\n"
-                              "Replayed: %s [%d,%d]\n",
-                              (int) thread->Id(), buffer, (int) streamPos, (int) progress, text,
-                              (int) thread->Events().StreamPosition(),
-                              (int) (thread->IsMainThread() ? *ExecutionProgressCounter() : 0));
-      Unreachable();
-    }
-
-    thread->RestoreBuffer(buffer);
-
-    // Push this assert onto the recent assertions in the thread.
-    free(thread->RecentAssert(Thread::NumRecentAsserts - 1).mText);
-    for (size_t i = Thread::NumRecentAsserts - 1; i >= 1; i--) {
-      thread->RecentAssert(i) = thread->RecentAssert(i - 1);
-    }
-    thread->RecentAssert(0).mText = strdup(text);
-    thread->RecentAssert(0).mPosition = thread->Events().StreamPosition();
-  }
-#endif // INCLUDE_RECORD_REPLAY_ASSERTIONS
+  thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Assert);
+  thread->Events().CheckInput(text);
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_InternalRecordReplayAssertBytes(const void* aData, size_t aSize)
 {
-#ifdef INCLUDE_RECORD_REPLAY_ASSERTIONS
-  RecordReplayAssert("AssertBytes");
-
-  if (AreThreadEventsPassedThrough() || HasDivergedFromRecording()) {
+  Thread* thread = Thread::Current();
+  if (thread->PassThroughEvents() || thread->HasDivergedFromRecording()) {
     return;
   }
-
-  MOZ_ASSERT(!AreThreadEventsDisallowed());
-  Thread* thread = Thread::Current();
-
-  if (IsRecording()) {
-    thread->Asserts().WriteScalar(thread->Events().StreamPosition());
-    thread->Asserts().WriteScalar(aSize << 1);
-    thread->Asserts().WriteBytes(aData, aSize);
-  } else {
-    bool match = true;
-    size_t streamPos = thread->Asserts().ReadScalar();
-    if (streamPos != thread->Events().StreamPosition()) {
-      match = false;
-    }
-    size_t oldSize = thread->Asserts().ReadScalar() >> 1;
-    if (oldSize != aSize) {
-      match = false;
-    }
-
-    char* buffer = thread->TakeBuffer(oldSize);
-
-    thread->Asserts().ReadBytes(buffer, oldSize);
-    if (match && memcmp(buffer, aData, oldSize) != 0) {
-      match = false;
-    }
+  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
 
-    if (!match) {
-      // On a byte mismatch, print out some of the mismatched bytes, up to a
-      // cutoff in case there are many mismatched bytes.
-      if (oldSize == aSize) {
-        static const size_t MAX_MISMATCHES = 100;
-        size_t mismatches = 0;
-        for (size_t i = 0; i < aSize; i++) {
-          if (((char*)aData)[i] != buffer[i]) {
-            Print("Position %d: %d %d\n", (int) i, (int) buffer[i], (int) ((char*)aData)[i]);
-            if (++mismatches == MAX_MISMATCHES) {
-              break;
-            }
-          }
-        }
-        if (mismatches == MAX_MISMATCHES) {
-          Print("Position ...\n");
-        }
-      }
-
-      child::ReportFatalError(Nothing(),
-                              "Byte Comparison Check Failed: Position %d %d Length %d %d\n",
-                              (int) streamPos, (int) thread->Events().StreamPosition(),
-                              (int) oldSize, (int) aSize);
-      Unreachable();
-    }
-
-    thread->RestoreBuffer(buffer);
-  }
-#endif // INCLUDE_RECORD_REPLAY_ASSERTIONS
-}
-
-MOZ_EXPORT void
-RecordReplayRust_Assert(const uint8_t* aBuffer)
-{
-  RecordReplayAssert("%s", (const char*) aBuffer);
-}
-
-MOZ_EXPORT void
-RecordReplayRust_BeginPassThroughThreadEvents()
-{
-  BeginPassThroughThreadEvents();
-}
-
-MOZ_EXPORT void
-RecordReplayRust_EndPassThroughThreadEvents()
-{
-  EndPassThroughThreadEvents();
+  thread->Events().RecordOrReplayThreadEvent(ThreadEvent::AssertBytes);
+  thread->Events().CheckInput(aData, aSize);
 }
 
 } // extern "C"
 
-static void
-DumpRecordingAssertions()
-{
-  Thread* thread = Thread::Current();
-
-  for (size_t id = MainThreadId; id <= MaxRecordedThreadId; id++) {
-    Stream* asserts = gRecordingFile->OpenStream(StreamName::Assert, id);
-    if (asserts->AtEnd()) {
-      continue;
-    }
-
-    fprintf(stderr, "Thread Assertions %d:\n", (int) id);
-    while (!asserts->AtEnd()) {
-      (void) asserts->ReadScalar();
-      size_t shiftedLen = asserts->ReadScalar();
-      size_t assertLen = shiftedLen >> 1;
-
-      char* buffer = thread->TakeBuffer(assertLen + 1);
-      asserts->ReadBytes(buffer, assertLen);
-      buffer[assertLen] = 0;
-
-      if (shiftedLen & AssertionBit) {
-        fprintf(stderr, "%s\n", buffer);
-      }
-
-      thread->RestoreBuffer(buffer);
-    }
-  }
-
-  fprintf(stderr, "Done with assertions, exiting...\n");
-  _exit(0);
-}
-
 static ValueIndex* gGenericThings;
 static StaticMutexNotRecorded gGenericThingsMutex;
 
 extern "C" {
 
 MOZ_EXPORT void
 RecordReplayInterface_InternalRegisterThing(void* aThing)
 {
--- a/toolkit/recordreplay/ProcessRecordReplay.h
+++ b/toolkit/recordreplay/ProcessRecordReplay.h
@@ -19,78 +19,82 @@ namespace recordreplay {
 //
 // See mfbt/RecordReplay.h for the main record/replay public API and a high
 // level description of the record/replay system.
 //
 // This directory contains files used for recording, replaying, and rewinding a
 // process. The ipc subdirectory contains files used for IPC between a
 // replaying and middleman process, and between a middleman and chrome process.
 
+// Instantiate _Macro for each of the platform independent thread events.
+#define ForEachThreadEvent(_Macro)                             \
+  /* Spawned another thread. */                                \
+  _Macro(CreateThread)                                        \
+                                                               \
+  /* Created a recorded lock. */                               \
+  _Macro(CreateLock)                                           \
+                                                               \
+  /* Acquired a recorded lock. */                              \
+  _Macro(Lock)                                                 \
+                                                               \
+  /* Called RecordReplayValue. */                              \
+  _Macro(Value)                                                \
+                                                               \
+  /* Called RecordReplayBytes. */                              \
+  _Macro(Bytes)                                                \
+                                                               \
+  /* Called RecordReplayAssert or RecordReplayAssertBytes. */  \
+  _Macro(Assert)                                               \
+  _Macro(AssertBytes)                                          \
+                                                               \
+  /* Executed a nested callback (see Callback.h). */           \
+  _Macro(ExecuteCallback)                                      \
+                                                               \
+  /* Finished executing nested callbacks in a library API (see Callback.h). */ \
+  _Macro(CallbacksFinished)                                    \
+                                                               \
+  /* Restoring a data pointer used in a callback (see Callback.h). */ \
+  _Macro(RestoreCallbackData)                                  \
+                                                               \
+  /* Called RegisterTrigger. */                                \
+  _Macro(RegisterTrigger)                                      \
+                                                               \
+  /* Executed a trigger within a call to ExecuteTriggers. */   \
+  _Macro(ExecuteTrigger)                                       \
+                                                               \
+  /* Finished executing triggers within a call to ExecuteTriggers. */ \
+  _Macro(ExecuteTriggersFinished)
+
 // ID of an event in a thread's event stream. Each ID in the stream is followed
-// by data associated with the event (see File::RecordOrReplayThreadEvent).
+// by data associated with the event.
 enum class ThreadEvent : uint32_t
 {
-  // Spawned another thread.
-  CreateThread,
-
-  // Created a recorded lock.
-  CreateLock,
-
-  // Acquired a recorded lock.
-  Lock,
-
-  // Wait for a condition variable with a timeout.
-  WaitForCvarUntil,
-
-  // Called RecordReplayValue.
-  Value,
-
-  // Called RecordReplayBytes.
-  Bytes,
-
-  // Executed a nested callback (see Callback.h).
-  ExecuteCallback,
-
-  // Finished executing nested callbacks in a library API (see Callback.h).
-  CallbacksFinished,
-
-  // Restoring a data pointer used in a callback (see Callback.h).
-  RestoreCallbackData,
-
-  // Executed a trigger within a call to ExecuteTriggers.
-  ExecuteTrigger,
-
-  // Finished executing triggers within a call to ExecuteTriggers.
-  ExecuteTriggersFinished,
-
-  // Encoded information about an argument/rval used by a graphics call.
-  GraphicsArgument,
-  GraphicsRval,
+#define DefineEnum(Kind) Kind,
+  ForEachThreadEvent(DefineEnum)
+#undef DefineEnum
 
   // The start of event IDs for redirected call events. Event IDs after this
   // point are platform specific.
   CallStart
 };
 
+// Get the printable name for a thread event.
+const char* ThreadEventName(ThreadEvent aEvent);
+
 class File;
 
 // File used during recording and replay.
 extern File* gRecordingFile;
 
 // Whether record/replay state has finished initialization.
 extern bool gInitialized;
 
 // If we failed to initialize, any associated message.
 extern char* gInitializationFailureMessage;
 
-// Whether record/replay assertions should be performed.
-//#ifdef DEBUG
-#define INCLUDE_RECORD_REPLAY_ASSERTIONS 1
-//#endif
-
 // Flush any new recording data to disk.
 void FlushRecording();
 
 // Called when any thread hits the end of its event stream.
 void HitEndOfRecording();
 
 // Called when the main thread hits the latest recording endpoint it knows
 // about.
--- a/toolkit/recordreplay/ProcessRedirect.h
+++ b/toolkit/recordreplay/ProcessRedirect.h
@@ -132,43 +132,49 @@ OriginalFunction(size_t aCallId)
 #define OriginalCallABI(aName, aReturnType, aABI, ...)          \
   TokenPaste(CallFunction, aABI) <aReturnType>                  \
     (OriginalFunction(CallEvent_ ##aName), ##__VA_ARGS__)
 
 // Call the original function for a call event ID with the default ABI.
 #define OriginalCall(aName, aReturnType, ...)                   \
   OriginalCallABI(aName, aReturnType, DEFAULTABI, ##__VA_ARGS__)
 
+static inline ThreadEvent
+CallIdToThreadEvent(size_t aCallId)
+{
+  return (ThreadEvent)((uint32_t)ThreadEvent::CallStart + aCallId);
+}
+
 // State for a function redirection which performs the standard steps (see the
 // comment at the start of this file). This should not be created directly, but
 // rather through one of the macros below.
 struct AutoRecordReplayFunctionVoid
 {
   // The current thread, or null if events are being passed through.
   Thread* mThread;
 
   // Any system error generated by the call which was redirected.
   ErrorType mError;
 
 protected:
   // Information about the call being recorded.
   size_t mCallId;
-  const char* mCallName;
 
 public:
-  AutoRecordReplayFunctionVoid(size_t aCallId, const char* aCallName)
-    : mThread(AreThreadEventsPassedThrough() ? nullptr : Thread::Current()),
-      mError(0), mCallId(aCallId), mCallName(aCallName)
+  explicit AutoRecordReplayFunctionVoid(size_t aCallId)
+    : mThread(Thread::Current()), mError(0), mCallId(aCallId)
   {
-    if (mThread) {
+    if (mThread && mThread->PassThroughEvents()) {
+      mThread = nullptr;
+    } else if (mThread) {
       // Calling any redirection which performs the standard steps will cause
       // debugger operations that have diverged from the recording to fail.
       EnsureNotDivergedFromRecording();
 
-      MOZ_ASSERT(!AreThreadEventsDisallowed());
+      MOZ_RELEASE_ASSERT(mThread->CanAccessRecording());
 
       // Pass through events in case we are calling the original function.
       mThread->SetPassThrough(true);
     }
   }
 
   ~AutoRecordReplayFunctionVoid()
   {
@@ -185,48 +191,46 @@ public:
 
     // Save any system error in case we want to record/replay it.
     mError = SaveError();
 
     // Stop the event passing through that was initiated in the constructor.
     mThread->SetPassThrough(false);
 
     // Add an event for the thread.
-    RecordReplayAssert("%s", mCallName);
-    ThreadEvent ev = (ThreadEvent)((uint32_t)ThreadEvent::CallStart + mCallId);
-    mThread->Events().RecordOrReplayThreadEvent(ev);
+    mThread->Events().RecordOrReplayThreadEvent(CallIdToThreadEvent(mCallId));
   }
 };
 
 // State for a function redirection that performs the standard steps and also
 // returns a value.
 template <typename ReturnType>
 struct AutoRecordReplayFunction : AutoRecordReplayFunctionVoid
 {
   // The value which this function call should return.
   ReturnType mRval;
 
-  AutoRecordReplayFunction(size_t aCallId, const char* aCallName)
-    : AutoRecordReplayFunctionVoid(aCallId, aCallName)
+  explicit AutoRecordReplayFunction(size_t aCallId)
+    : AutoRecordReplayFunctionVoid(aCallId)
   {}
 };
 
 // Macros for recording or replaying a function that performs the standard
 // steps. These macros should be used near the start of the body of a
 // redirection function, and will fall through only if events are not
 // passed through and the outputs of the function need to be recorded or
 // replayed.
 //
 // These macros define an AutoRecordReplayFunction local |rrf| with state for
 // the redirection, and additional locals |events| and (if the function has a
 // return value) |rval| for convenient access.
 
 // Record/replay a function that returns a value and has a particular ABI.
 #define RecordReplayFunctionABI(aName, aReturnType, aABI, ...)          \
-  AutoRecordReplayFunction<aReturnType> rrf(CallEvent_ ##aName, #aName); \
+  AutoRecordReplayFunction<aReturnType> rrf(CallEvent_ ##aName);        \
   if (!rrf.mThread) {                                                   \
     return OriginalCallABI(aName, aReturnType, aABI, ##__VA_ARGS__);    \
   }                                                                     \
   if (IsRecording()) {                                                  \
     rrf.mRval = OriginalCallABI(aName, aReturnType, aABI, ##__VA_ARGS__); \
   }                                                                     \
   rrf.StartRecordReplay();                                              \
   Stream& events = rrf.mThread->Events();                               \
@@ -234,17 +238,17 @@ struct AutoRecordReplayFunction : AutoRe
   aReturnType& rval = rrf.mRval
 
 // Record/replay a function that returns a value and has the default ABI.
 #define RecordReplayFunction(aName, aReturnType, ...)                   \
   RecordReplayFunctionABI(aName, aReturnType, DEFAULTABI, ##__VA_ARGS__)
 
 // Record/replay a function that has no return value and has a particular ABI.
 #define RecordReplayFunctionVoidABI(aName, aABI, ...)                   \
-  AutoRecordReplayFunctionVoid rrf(CallEvent_ ##aName, #aName);         \
+  AutoRecordReplayFunctionVoid rrf(CallEvent_ ##aName);                 \
   if (!rrf.mThread) {                                                   \
     OriginalCallABI(aName, void, aABI, ##__VA_ARGS__);                  \
     return;                                                             \
   }                                                                     \
   if (IsRecording()) {                                                  \
     OriginalCallABI(aName, void, aABI, ##__VA_ARGS__);                  \
   }                                                                     \
   rrf.StartRecordReplay();                                              \
--- a/toolkit/recordreplay/ProcessRedirectDarwin.cpp
+++ b/toolkit/recordreplay/ProcessRedirectDarwin.cpp
@@ -659,18 +659,16 @@ RR_mmap(void* aAddress, size_t aSize, in
     } else if (memory && memory != (void*)-1) {
       RegisterAllocatedMemory(memory, RoundupSizeToPageBoundary(aSize), MemoryKind::Tracked);
     }
   }
 
   if (!(aFlags & MAP_ANON) && !AreThreadEventsPassedThrough()) {
     // Include the data just mapped in the recording.
     MOZ_RELEASE_ASSERT(memory && memory != (void*)-1);
-    RecordReplayAssert("mmap");
-    MOZ_RELEASE_ASSERT(aSize == RecordReplayValue(aSize));
     RecordReplayBytes(memory, aSize);
   }
 
   return memory;
 }
 
 static ssize_t
 RR_munmap(void* aAddress, size_t aSize)
@@ -1172,17 +1170,16 @@ static ssize_t
 WaitForCvar(pthread_mutex_t* aMutex, bool aRecordReturnValue,
             const std::function<ssize_t()>& aCallback)
 {
   Lock* lock = Lock::Find(aMutex);
   if (!lock) {
     AutoEnsurePassThroughThreadEvents pt;
     return aCallback();
   }
-  RecordReplayAssert("WaitForCvar %d", (int) lock->Id());
   ssize_t rv = 0;
   if (IsRecording()) {
     AutoPassThroughThreadEvents pt;
     rv = aCallback();
   } else {
     DirectUnlockMutex(aMutex);
   }
   lock->Exit();
@@ -1729,23 +1726,25 @@ struct NSFastEnumerationState
 // thrown by mutating the array while it is being iterated over.
 static unsigned long gNeverChange;
 
 extern "C" {
 
 size_t __attribute__((used))
 RecordReplayInterceptObjCMessage(MessageArguments* aArguments)
 {
-  if (AreThreadEventsPassedThrough()) {
+  Thread* thread = Thread::Current();
+  if (!thread || thread->PassThroughEvents()) {
     aArguments->scratch = (size_t) OriginalFunction(CallEvent_objc_msgSend);
     return 1;
   }
   EnsureNotDivergedFromRecording();
 
-  RecordReplayAssert("objc_msgSend: %s", aArguments->msg);
+  thread->Events().RecordOrReplayThreadEvent(CallIdToThreadEvent(CallEvent_objc_msgSend));
+  thread->Events().CheckInput(aArguments->msg);
 
   size_t rval = 0;
   double floatRval = 0;
   bool handled = false;
 
   // Watch for some top level NSApplication messages that can cause Gecko
   // events to be processed.
   if ((!strcmp(aArguments->msg, "run") ||
--- a/toolkit/recordreplay/ProcessRewind.cpp
+++ b/toolkit/recordreplay/ProcessRewind.cpp
@@ -178,34 +178,37 @@ NewCheckpoint(bool aTemporary)
 
   gRewindInfo->mLastCheckpoint = checkpoint;
 
   navigation::AfterCheckpoint(checkpoint);
 
   return reachedCheckpoint;
 }
 
-static bool gRecordingDiverged;
 static bool gUnhandledDivergeAllowed;
 
 void
 DivergeFromRecording()
 {
-  MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
   MOZ_RELEASE_ASSERT(IsReplaying());
-  gRecordingDiverged = true;
+
+  Thread* thread = Thread::Current();
+  MOZ_RELEASE_ASSERT(thread->IsMainThread());
+  thread->DivergeFromRecording();
+
   gUnhandledDivergeAllowed = true;
 }
 
 extern "C" {
 
 MOZ_EXPORT bool
 RecordReplayInterface_InternalHasDivergedFromRecording()
 {
-  return Thread::CurrentIsMainThread() && gRecordingDiverged;
+  Thread* thread = Thread::Current();
+  return thread && thread->HasDivergedFromRecording();
 }
 
 } // extern "C"
 
 void
 DisallowUnhandledDivergeFromRecording()
 {
   MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
@@ -245,17 +248,17 @@ MainThreadShouldPause()
   return gMainThreadShouldPause;
 }
 
 void
 PauseMainThreadAndServiceCallbacks()
 {
   MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
   MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
-  MOZ_RELEASE_ASSERT(!gRecordingDiverged);
+  MOZ_RELEASE_ASSERT(!HasDivergedFromRecording());
 
   // Whether there is a PauseMainThreadAndServiceCallbacks frame on the stack.
   static bool gMainThreadIsPaused = false;
 
   if (gMainThreadIsPaused) {
     return;
   }
   gMainThreadIsPaused = true;
@@ -278,17 +281,17 @@ PauseMainThreadAndServiceCallbacks()
   }
 
   // As for RestoreCheckpointAndResume, we shouldn't resume the main thread while
   // it still has callbacks to execute.
   MOZ_RELEASE_ASSERT(gMainThreadCallbacks.empty());
 
   // If we diverge from the recording the only way we can get back to resuming
   // normal execution is to rewind to a checkpoint prior to the divergence.
-  MOZ_RELEASE_ASSERT(!gRecordingDiverged);
+  MOZ_RELEASE_ASSERT(!HasDivergedFromRecording());
 
   gMainThreadIsPaused = false;
 }
 
 void
 PauseMainThreadAndInvokeCallback(const std::function<void()>& aCallback)
 {
   {
--- a/toolkit/recordreplay/Thread.cpp
+++ b/toolkit/recordreplay/Thread.cpp
@@ -120,17 +120,16 @@ Thread::InitializeThreads()
     Thread* thread = &gThreads[i];
     PodZero(thread);
     new(thread) Thread();
 
     thread->mId = i;
 
     if (i <= MaxRecordedThreadId) {
       thread->mEvents = gRecordingFile->OpenStream(StreamName::Event, i);
-      thread->mAsserts = gRecordingFile->OpenStream(StreamName::Assert, i);
     }
 
     DirectCreatePipe(&thread->mNotifyfd, &thread->mIdlefd);
   }
 
   if (!gTlsThreadKey.init()) {
     MOZ_CRASH();
   }
@@ -230,24 +229,20 @@ Thread::SpawnThread(Thread* aThread)
 {
   DirectSpawnThread(ThreadMain, aThread);
   WaitUntilInitialized(aThread);
 }
 
 /* static */ NativeThreadId
 Thread::StartThread(Callback aStart, void* aArgument, bool aNeedsJoin)
 {
-  MOZ_ASSERT(IsRecordingOrReplaying());
-  MOZ_ASSERT(!AreThreadEventsPassedThrough());
-  MOZ_ASSERT(!AreThreadEventsDisallowed());
+  EnsureNotDivergedFromRecording();
 
-  EnsureNotDivergedFromRecording();
   Thread* thread = Thread::Current();
-
-  RecordReplayAssert("StartThread");
+  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
 
   MonitorAutoLock lock(*gMonitor);
 
   size_t id = 0;
   if (IsRecording()) {
     // Look for an idle thread.
     for (id = MainThreadId + 1; id <= MaxRecordedThreadId; id++) {
       Thread* targetThread = Thread::GetById(id);
@@ -296,43 +291,16 @@ Thread::Join()
       mNeedsJoin = false;
       break;
     }
     gMonitor->Wait();
   }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// Thread Buffers
-///////////////////////////////////////////////////////////////////////////////
-
-char*
-Thread::TakeBuffer(size_t aSize)
-{
-  MOZ_ASSERT(mBuffer != (char*) 0x1);
-  if (aSize > mBufferCapacity) {
-    mBufferCapacity = aSize;
-    mBuffer = (char*) realloc(mBuffer, aSize);
-  }
-  char* buf = mBuffer;
-
-  // Poison the buffer in case this thread tries to use it again reentrantly.
-  mBuffer = (char*) 0x1;
-
-  return buf;
-}
-
-void
-Thread::RestoreBuffer(char* aBuf)
-{
-  MOZ_ASSERT(mBuffer == (char*) 0x1);
-  mBuffer = aBuf;
-}
-
-///////////////////////////////////////////////////////////////////////////////
 // Thread Public API Accessors
 ///////////////////////////////////////////////////////////////////////////////
 
 extern "C" {
 
 MOZ_EXPORT void
 RecordReplayInterface_InternalBeginPassThroughThreadEvents()
 {
@@ -376,30 +344,16 @@ RecordReplayInterface_InternalEndDisallo
 MOZ_EXPORT bool
 RecordReplayInterface_InternalAreThreadEventsDisallowed()
 {
   MOZ_ASSERT(IsRecordingOrReplaying());
   Thread* thread = Thread::Current();
   return thread && thread->AreEventsDisallowed();
 }
 
-MOZ_EXPORT void
-RecordReplayInterface_InternalBeginCaptureEventStacks()
-{
-  MOZ_ASSERT(IsRecordingOrReplaying());
-  Thread::Current()->BeginCaptureEventStacks();
-}
-
-MOZ_EXPORT void
-RecordReplayInterface_InternalEndCaptureEventStacks()
-{
-  MOZ_ASSERT(IsRecordingOrReplaying());
-  Thread::Current()->EndCaptureEventStacks();
-}
-
 } // extern "C"
 
 ///////////////////////////////////////////////////////////////////////////////
 // Thread Coordination
 ///////////////////////////////////////////////////////////////////////////////
 
 // Whether all threads should attempt to idle.
 static Atomic<bool, SequentiallyConsistent, Behavior::DontPreserve> gThreadsShouldIdle;
--- a/toolkit/recordreplay/Thread.h
+++ b/toolkit/recordreplay/Thread.h
@@ -73,67 +73,50 @@ typedef pthread_t NativeThreadId;
 
 // Information about the execution state of a thread.
 class Thread
 {
 public:
   // Signature for the start function of a thread.
   typedef void (*Callback)(void*);
 
-  // Number of recent assertions remembered.
-  static const size_t NumRecentAsserts = 128;
-
-  struct RecentAssertInfo {
-    char* mText;
-    size_t mPosition;
-  };
-
 private:
   // Monitor used to protect various thread information (see Thread.h) and to
   // wait on or signal progress for a thread.
   static Monitor* gMonitor;
 
   // Thread ID in the recording, fixed at creation.
   size_t mId;
 
   // Whether to pass events in the thread through without recording/replaying.
   // This is only used by the associated thread.
   bool mPassThroughEvents;
 
   // Whether to crash if we try to record/replay thread events. This is only
   // used by the associated thread.
   size_t mDisallowEvents;
 
-  // Whether to capture stack information for events while recording. This is
-  // only used by the associated thread.
-  size_t mCaptureEventStacks;
+  // Whether execution has diverged from the recording and the thread's
+  // recorded events cannot be accessed.
+  bool mDivergedFromRecording;
 
   // Start routine and argument which the thread is currently executing. This
   // is cleared after the routine finishes and another start routine may be
   // assigned to the thread. mNeedsJoin specifies whether the thread must be
   // joined before it is completely dead and can be reused. This is protected
   // by the thread monitor.
   Callback mStart;
   void* mStartArg;
   bool mNeedsJoin;
 
   // ID for this thread used by the system.
   NativeThreadId mNativeId;
 
-  // Streams with events and assertions for the thread. These are only used by
-  // the associated thread.
+  // Stream with events for the thread. This is only used on the thread itself.
   Stream* mEvents;
-  Stream* mAsserts;
-
-  // Recent assertions that have been encountered, for debugging.
-  RecentAssertInfo mRecentAsserts[NumRecentAsserts];
-
-  // Buffer for general use. This is only used by the associated thread.
-  char* mBuffer;
-  size_t mBufferCapacity;
 
   // Stack boundary of the thread, protected by the thread monitor.
   uint8_t* mStackBase;
   size_t mStackSize;
 
   // File descriptor to block on when the thread is idle, fixed at creation.
   FileHandle mIdlefd;
 
@@ -153,68 +136,59 @@ public:
 ///////////////////////////////////////////////////////////////////////////////
 // Public Routines
 ///////////////////////////////////////////////////////////////////////////////
 
   // Accessors for some members that never change.
   size_t Id() { return mId; }
   NativeThreadId NativeId() { return mNativeId; }
   Stream& Events() { return *mEvents; }
-  Stream& Asserts() { return *mAsserts; }
   uint8_t* StackBase() { return mStackBase; }
   size_t StackSize() { return mStackSize; }
 
-  inline bool IsMainThread() { return mId == MainThreadId; }
-  inline bool IsRecordedThread() { return mId <= MaxRecordedThreadId; }
-  inline bool IsNonMainRecordedThread() { return IsRecordedThread() && !IsMainThread(); }
+  inline bool IsMainThread() const { return mId == MainThreadId; }
+  inline bool IsRecordedThread() const { return mId <= MaxRecordedThreadId; }
+  inline bool IsNonMainRecordedThread() const { return IsRecordedThread() && !IsMainThread(); }
 
   // Access the flag for whether this thread is passing events through.
   void SetPassThrough(bool aPassThrough) {
     MOZ_RELEASE_ASSERT(mPassThroughEvents == !aPassThrough);
     mPassThroughEvents = aPassThrough;
   }
-  bool PassThroughEvents() {
+  bool PassThroughEvents() const {
     return mPassThroughEvents;
   }
 
   // Access the counter for whether events are disallowed in this thread.
   void BeginDisallowEvents() {
     mDisallowEvents++;
   }
   void EndDisallowEvents() {
     MOZ_RELEASE_ASSERT(mDisallowEvents);
     mDisallowEvents--;
   }
-  bool AreEventsDisallowed() {
+  bool AreEventsDisallowed() const {
     return mDisallowEvents != 0;
   }
 
-  // Access the counter for whether event stacks are captured while recording.
-  void BeginCaptureEventStacks() {
-    mCaptureEventStacks++;
+  // Access the flag for whether this thread's execution has diverged from the
+  // recording. Once set, this is only unset by rewinding to a point where the
+  // flag is clear.
+  void DivergeFromRecording() {
+    mDivergedFromRecording = true;
   }
-  void EndCaptureEventStacks() {
-    MOZ_RELEASE_ASSERT(mCaptureEventStacks);
-    mCaptureEventStacks--;
-  }
-  bool ShouldCaptureEventStacks() {
-    return mCaptureEventStacks != 0;
+  bool HasDivergedFromRecording() const {
+    return mDivergedFromRecording;
   }
 
-  // Access the array of recent assertions in the thread.
-  RecentAssertInfo& RecentAssert(size_t i) {
-    MOZ_ASSERT(i < NumRecentAsserts);
-    return mRecentAsserts[i];
+  // Return whether this thread may read or write to its recorded event stream.
+  bool CanAccessRecording() const {
+    return !PassThroughEvents() && !AreEventsDisallowed() && !HasDivergedFromRecording();
   }
 
-  // Access a thread local buffer of a guaranteed size. The buffer must be
-  // restored before it can be taken again.
-  char* TakeBuffer(size_t aSize);
-  void RestoreBuffer(char* aBuf);
-
   // The actual start routine at the root of all recorded threads, and of all
   // threads when replaying.
   static void ThreadMain(void* aArgument);
 
   // Bind this Thread to the current system thread, setting Thread::Current()
   // and some other basic state.
   void BindToCurrent();
 
--- a/toolkit/recordreplay/Trigger.cpp
+++ b/toolkit/recordreplay/Trigger.cpp
@@ -53,47 +53,44 @@ InitializeTriggers()
   gTriggerInfoMap = new TriggerInfoMap();
 }
 
 extern "C" {
 
 MOZ_EXPORT void
 RecordReplayInterface_RegisterTrigger(void* aObj, const std::function<void()>& aCallback)
 {
-  MOZ_ASSERT(IsRecordingOrReplaying());
-  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
   MOZ_RELEASE_ASSERT(aObj);
 
-  if (HasDivergedFromRecording()) {
+  Thread* thread = Thread::Current();
+  if (thread->HasDivergedFromRecording()) {
     return;
   }
-
-  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
-
-  size_t threadId = Thread::Current()->Id();
+  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
 
   size_t id;
   {
     AutoOrderedAtomicAccess order;
     StaticMutexAutoLock lock(gTriggersMutex);
 
     TriggerInfoMap::iterator iter = gTriggerInfoMap->find(aObj);
     if (iter != gTriggerInfoMap->end()) {
       id = gTriggers->GetIndex(aObj);
-      MOZ_RELEASE_ASSERT(iter->second.mThreadId == threadId);
+      MOZ_RELEASE_ASSERT(iter->second.mThreadId == thread->Id());
       iter->second.mCallback = aCallback;
       iter->second.mRegisterCount++;
     } else {
       id = gTriggers->Insert(aObj);
-      TriggerInfo info(threadId, aCallback);
+      TriggerInfo info(thread->Id(), aCallback);
       gTriggerInfoMap->insert(TriggerInfoMap::value_type(aObj, info));
     }
   }
 
-  RecordReplayAssert("RegisterTrigger %zu", id);
+  thread->Events().RecordOrReplayThreadEvent(ThreadEvent::RegisterTrigger);
+  thread->Events().CheckInput(id);
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_UnregisterTrigger(void* aObj)
 {
   MOZ_ASSERT(IsRecordingOrReplaying());
   MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
 
@@ -154,54 +151,47 @@ RemoveTriggerCallbackForThreadId(size_t 
     }
   }
   return Nothing();
 }
 
 MOZ_EXPORT void
 RecordReplayInterface_ExecuteTriggers()
 {
-  MOZ_ASSERT(IsRecordingOrReplaying());
-  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
-  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
-
   Thread* thread = Thread::Current();
-
-  RecordReplayAssert("ExecuteTriggers");
+  MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
 
   if (IsRecording()) {
     // Invoke the callbacks for any triggers waiting for execution, including
     // any whose callbacks are triggered by earlier callback invocations.
     while (true) {
       Maybe<size_t> id = RemoveTriggerCallbackForThreadId(thread->Id());
       if (id.isNothing()) {
         break;
       }
 
-      thread->Events().RecordOrReplayThreadEvent(ThreadEvent::ExecuteTrigger);
+      thread->Events().WriteScalar((size_t) ThreadEvent::ExecuteTrigger);
       thread->Events().WriteScalar(id.ref());
       InvokeTriggerCallback(id.ref());
     }
-    thread->Events().RecordOrReplayThreadEvent(ThreadEvent::ExecuteTriggersFinished);
+    thread->Events().WriteScalar((size_t) ThreadEvent::ExecuteTriggersFinished);
   } else {
     // Execute the same callbacks which were executed at this point while
     // recording.
     while (true) {
       ThreadEvent ev = (ThreadEvent) thread->Events().ReadScalar();
       if (ev != ThreadEvent::ExecuteTrigger) {
         if (ev != ThreadEvent::ExecuteTriggersFinished) {
           child::ReportFatalError(Nothing(), "ExecuteTrigger Mismatch");
           Unreachable();
         }
         break;
       }
       size_t id = thread->Events().ReadScalar();
       InvokeTriggerCallback(id);
     }
   }
-
-  RecordReplayAssert("ExecuteTriggers DONE");
 }
 
 } // extern "C"
 
 } // namespace recordreplay
 } // namespace mozilla
--- a/toolkit/recordreplay/ipc/ChildIPC.cpp
+++ b/toolkit/recordreplay/ipc/ChildIPC.cpp
@@ -13,16 +13,17 @@
 #include "base/task.h"
 #include "chrome/common/child_thread.h"
 #include "chrome/common/mach_ipc_mac.h"
 #include "ipc/Channel.h"
 #include "mac/handler/exception_handler.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/layers/ImageDataSerializer.h"
 #include "mozilla/Sprintf.h"
+#include "mozilla/StackWalk.h"
 #include "mozilla/VsyncDispatcher.h"
 
 #include "InfallibleVector.h"
 #include "MemorySnapshot.h"
 #include "ParentInternal.h"
 #include "ProcessRecordReplay.h"
 #include "ProcessRedirect.h"
 #include "ProcessRewind.h"
@@ -321,16 +322,56 @@ DebuggerRunsInMiddleman()
 }
 
 void
 MaybeCreateInitialCheckpoint()
 {
   NewCheckpoint(/* aTemporary = */ false);
 }
 
+struct StackWalkData
+{
+  // Current buffer and allocated size, which may be internal to the original
+  // allocation.
+  char* mBuf;
+  size_t mSize;
+
+  StackWalkData(char* aBuf, size_t aSize)
+    : mBuf(aBuf), mSize(aSize)
+  {}
+
+  void append(const char* aText) {
+    size_t len = strlen(aText);
+    if (len <= mSize) {
+      memcpy(mBuf, aText, len);
+      mBuf += len;
+      mSize -= len;
+    }
+  }
+};
+
+static void
+StackWalkCallback(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure)
+{
+  StackWalkData* data = (StackWalkData*) aClosure;
+
+  MozCodeAddressDetails details;
+  MozDescribeCodeAddress(aPC, &details);
+
+  data->append(" ### ");
+  data->append(details.function[0] ? details.function : "???");
+}
+
+static void
+SetCurrentStackString(const char* aAssertion, char* aBuf, size_t aSize)
+{
+  StackWalkData data(aBuf, aSize);
+  MozStackWalk(StackWalkCallback, /* aSkipFrames = */ 2, /* aFrameCount = */ 32, &data);
+}
+
 void
 ReportFatalError(const Maybe<MinidumpInfo>& aMinidump, const char* aFormat, ...)
 {
   // Unprotect any memory which might be written while producing the minidump.
   UnrecoverableSnapshotFailure();
 
   AutoEnsurePassThroughThreadEvents pt;
 
@@ -343,16 +384,23 @@ ReportFatalError(const Maybe<MinidumpInf
 #endif
 
   va_list ap;
   va_start(ap, aFormat);
   char buf[2048];
   VsprintfLiteral(buf, aFormat, ap);
   va_end(ap);
 
+  // Include stack information in the error message as well, if we are on the
+  // thread where the fatal error occurred.
+  if (aMinidump.isNothing()) {
+    size_t len = strlen(buf);
+    SetCurrentStackString(buf, buf + len, sizeof(buf) - len);
+  }
+
   // Construct a FatalErrorMessage on the stack, to avoid touching the heap.
   char msgBuf[4096];
   size_t header = sizeof(FatalErrorMessage);
   size_t len = std::min(strlen(buf) + 1, sizeof(msgBuf) - header);
   FatalErrorMessage* msg = new(msgBuf) FatalErrorMessage(header + len);
   memcpy(&msgBuf[header], buf, len);
   msgBuf[sizeof(msgBuf) - 1] = 0;