Bug 1464903 Part 5 - Core record/replay functionality, r=froydnj.
authorBrian Hackett <bhackett1024@gmail.com>
Sun, 22 Jul 2018 11:44:20 +0000
changeset 427720 f4e9d6a808f11b1265eead3e5ce0f477e3d080a3
parent 427719 64c2c205de26aa480644e9491f4dd66259153cc8
child 427721 be3e40e703a0f7242098499483250e4054d5ae17
push id34314
push usercsabou@mozilla.com
push dateMon, 23 Jul 2018 09:31:12 +0000
treeherdermozilla-central@143984185dce [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1464903
milestone63.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 1464903 Part 5 - Core record/replay functionality, r=froydnj.
toolkit/recordreplay/Callback.cpp
toolkit/recordreplay/Callback.h
toolkit/recordreplay/Lock.cpp
toolkit/recordreplay/Lock.h
toolkit/recordreplay/ProcessRecordReplay.cpp
toolkit/recordreplay/ProcessRecordReplay.h
toolkit/recordreplay/Thread.cpp
toolkit/recordreplay/Thread.h
toolkit/recordreplay/Trigger.cpp
toolkit/recordreplay/Trigger.h
toolkit/recordreplay/WeakPointer.cpp
toolkit/recordreplay/WeakPointer.h
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/Callback.cpp
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 8; 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 "Callback.h"
+
+#include "ipc/ChildIPC.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/RecordReplay.h"
+#include "mozilla/StaticMutex.h"
+#include "ProcessRewind.h"
+#include "Thread.h"
+#include "ValueIndex.h"
+
+namespace mozilla {
+namespace recordreplay {
+
+static ValueIndex* gCallbackData;
+static StaticMutexNotRecorded gCallbackMutex;
+
+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);
+}
+
+void
+BeginCallback(size_t aCallbackId)
+{
+  MOZ_RELEASE_ASSERT(IsRecording());
+  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
+
+  Thread* thread = Thread::Current();
+  if (thread->IsMainThread()) {
+    child::EndIdleTime();
+  }
+  thread->SetPassThrough(false);
+
+  thread->Events().RecordOrReplayThreadEvent(ThreadEvent::ExecuteCallback);
+  thread->Events().WriteScalar(aCallbackId);
+}
+
+void
+EndCallback()
+{
+  MOZ_RELEASE_ASSERT(IsRecording());
+  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
+  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
+
+  Thread* thread = Thread::Current();
+  if (thread->IsMainThread()) {
+    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");
+
+  thread->Events().RecordOrReplayThreadEvent(ThreadEvent::RestoreCallbackData);
+
+  size_t index = 0;
+  if (IsRecording() && *aData) {
+    StaticMutexAutoLock lock(gCallbackMutex);
+    index = gCallbackData->GetIndex(*aData);
+  }
+  thread->Events().RecordOrReplayScalar(&index);
+
+  if (IsReplaying()) {
+    *aData = const_cast<void*>(gCallbackData->GetValue(index));
+  }
+}
+
+void
+RemoveCallbackData(void* aData)
+{
+  MOZ_RELEASE_ASSERT(IsRecordingOrReplaying());
+
+  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();
+
+  if (IsRecording()) {
+    if (thread->IsMainThread()) {
+      child::BeginIdleTime();
+    }
+    thread->SetPassThrough(true);
+    aFn();
+    if (thread->IsMainThread()) {
+      child::EndIdleTime();
+    }
+    thread->SetPassThrough(false);
+    thread->Events().RecordOrReplayThreadEvent(ThreadEvent::CallbacksFinished);
+  } else {
+    while (true) {
+      ThreadEvent ev = (ThreadEvent) thread->Events().ReadScalar();
+      if (ev != ThreadEvent::ExecuteCallback) {
+        if (ev != ThreadEvent::CallbacksFinished) {
+          child::ReportFatalError("Unexpected event while replaying callback events");
+        }
+        break;
+      }
+      size_t id = thread->Events().ReadScalar();
+      ReplayInvokeCallback(id);
+    }
+  }
+}
+
+} // namespace recordreplay
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/Callback.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; 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 mozilla_recordreplay_Callback_h
+#define mozilla_recordreplay_Callback_h
+
+#include "mozilla/GuardObjects.h"
+
+#include <functional>
+
+namespace mozilla {
+namespace recordreplay {
+
+// Callbacks Overview.
+//
+// Record/replay callbacks are used to record and replay the use of callbacks
+// within system libraries to reenter Gecko code. There are three challenges
+// to replaying callbacks:
+//
+// 1. Invocations of the callbacks must be replayed so that they occur inside
+//    the same system call and in the same order as during recording.
+//
+// 2. Data passed to the callback which originates in Gecko itself (e.g.
+//    opaque data pointers) need to match up with the Gecko data which was
+//    passed to the callback while recording.
+//
+// 3. Data passed to the callback which originates in the system library also
+//    needs to match up with the data passed while recording.
+//
+// Each platform defines a CallbackEvent enum with the different callback
+// signatures that the platform is able to redirect. Callback wrapper functions
+// are then defined for each callback event.
+//
+// The following additional steps are taken to handle #1 above:
+//
+// A. System libraries which Gecko callbacks are passed to are redirected so
+//    that they replace the Gecko callback with the callback wrapper for that
+//    signature.
+//
+// B. When recording, system libraries which can invoke Gecko callbacks are
+//    redirected to call the library API inside a call to
+//    PassThroughThreadEventsAllowCallbacks.
+//
+// C. When a callback wrapper is invoked within the library, it calls
+//    {Begin,End}Callback to stop passing through thread events while the
+//    callback executes.
+//
+// D. {Begin,End}Callback additionally adds ExecuteCallback events for the
+//    thread, and PassThroughThreadEventsAllowCallbacks adds a
+//    CallbacksFinished event at the end. While replaying, calling
+//    PassThroughThreadEventsAllowCallbacks will read these callback events
+//    from the thread's events file and plas back calls to the wrappers which
+//    executed while recording.
+//
+// #2 above is handled with the callback data API below. When a Gecko callback
+// or opaque data pointer is passed to a system library API, that API is
+// redirected so that it will call RegisterCallbackData on the Gecko pointer.
+// Later, when the callback wrapper actually executes, it can use
+// SaveOrRestoreCallbackData to record which Gecko pointer was used and later,
+// during replay, restore the corresponding value in that execution.
+//
+// #3 above can be recorded and replayed using the standard
+// RecordReplay{Value,Bytes} functions, in a similar manner to the handling of
+// outputs of redirected functions.
+
+// Note or remove a pointer passed to a system library API which might be a
+// Gecko callback or a data pointer used by a Gecko callback.
+void RegisterCallbackData(void* aData);
+void RemoveCallbackData(void* aData);
+
+// Record/replay a pointer that was passed to RegisterCallbackData earlier.
+void SaveOrRestoreCallbackData(void** aData);
+
+// If recording, call aFn with events passed through, allowing Gecko callbacks
+// to execute within aFn. If replaying, execute only the Gecko callbacks which
+// executed while recording.
+void PassThroughThreadEventsAllowCallbacks(const std::function<void()>& aFn);
+
+// Within a callback wrapper, bracket the execution of the code for the Gecko
+// callback and record the callback as having executed. This stops passing
+// through thread events so that behaviors in the Gecko callback are
+// recorded/replayed.
+void BeginCallback(size_t aCallbackId);
+void EndCallback();
+
+// During replay, invoke a callback with the specified id. This is platform
+// specific and is defined in the various ProcessRedirect*.cpp files.
+void ReplayInvokeCallback(size_t aCallbackId);
+
+} // namespace recordreplay
+} // namespace mozilla
+
+#endif // mozilla_recordreplay_Callback_h
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/Lock.cpp
@@ -0,0 +1,236 @@
+/* -*- Mode: C++; tab-width: 8; 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 "Lock.h"
+
+#include "mozilla/StaticMutex.h"
+
+#include "ChunkAllocator.h"
+#include "InfallibleVector.h"
+#include "SpinLock.h"
+#include "Thread.h"
+
+#include <unordered_map>
+
+namespace mozilla {
+namespace recordreplay {
+
+// The total number of locks that have been created. Reserved IDs:
+// 0: Locks that are not recorded.
+// 1: Used by gAtomicLock for atomic accesses.
+//
+// This is only used while recording, and increments gradually as locks are
+// created.
+static const size_t gAtomicLockId = 1;
+static Atomic<size_t, SequentiallyConsistent, Behavior::DontPreserve> gNumLocks;
+
+struct LockAcquires
+{
+  // List of thread acquire orders for the lock. This is protected by the lock
+  // itself.
+  Stream* mAcquires;
+
+  // During replay, the next thread id to acquire the lock. Writes to this are
+  // protected by the lock itself, though reads may occur on other threads.
+  Atomic<size_t, SequentiallyConsistent, Behavior::DontPreserve> mNextOwner;
+
+  static const size_t NoNextOwner = 0;
+
+  void ReadAndNotifyNextOwner(Thread* aCurrentThread) {
+    MOZ_RELEASE_ASSERT(IsReplaying());
+    if (mAcquires->AtEnd()) {
+      mNextOwner = NoNextOwner;
+    } else {
+      mNextOwner = mAcquires->ReadScalar();
+      if (mNextOwner != aCurrentThread->Id()) {
+        Thread::Notify(mNextOwner);
+      }
+    }
+  }
+};
+
+// Acquires for each lock, indexed by the lock ID.
+static ChunkAllocator<LockAcquires> gLockAcquires;
+
+///////////////////////////////////////////////////////////////////////////////
+// Locking Interface
+///////////////////////////////////////////////////////////////////////////////
+
+// Table mapping native lock pointers to the associated Lock structure, for
+// 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()) {
+    Destroy(aNativeLock); // Clean up any old lock, as below.
+    return;
+  }
+
+  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
+  Thread* thread = Thread::Current();
+
+  RecordReplayAssert("CreateLock");
+
+  thread->Events().RecordOrReplayThreadEvent(ThreadEvent::CreateLock);
+
+  size_t id;
+  if (IsRecording()) {
+    id = gNumLocks++;
+  }
+  thread->Events().RecordOrReplayScalar(&id);
+
+  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.
+  Destroy(aNativeLock);
+
+  AutoWriteSpinLock ex(gLocksLock);
+  thread->BeginDisallowEvents();
+
+  if (!gLocks) {
+    gLocks = new LockMap();
+  }
+
+  gLocks->insert(LockMap::value_type(aNativeLock, new Lock(id)));
+
+  thread->EndDisallowEvents();
+}
+
+/* static */ void
+Lock::Destroy(void* aNativeLock)
+{
+  Lock* lock = nullptr;
+  {
+    AutoWriteSpinLock ex(gLocksLock);
+    if (gLocks) {
+      LockMap::iterator iter = gLocks->find(aNativeLock);
+      if (iter != gLocks->end()) {
+        lock = iter->second;
+        gLocks->erase(iter);
+      }
+    }
+  }
+  delete lock;
+}
+
+/* static */ Lock*
+Lock::Find(void* aNativeLock)
+{
+  MOZ_RELEASE_ASSERT(IsRecordingOrReplaying());
+
+  AutoReadSpinLock ex(gLocksLock);
+
+  if (gLocks) {
+    LockMap::iterator iter = gLocks->find(aNativeLock);
+    if (iter != gLocks->end()) {
+      // Now that we know the lock is recorded, check whether thread events
+      // should be generated right now. Doing things in this order avoids
+      // reentrancy issues when initializing the thread-local state used by
+      // these calls.
+      if (AreThreadEventsPassedThrough() || HasDivergedFromRecording()) {
+        return nullptr;
+      }
+      return iter->second;
+    }
+  }
+
+  return nullptr;
+}
+
+void
+Lock::Enter(const std::function<void()>& aCallback)
+{
+  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough() && !HasDivergedFromRecording());
+  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
+
+  RecordReplayAssert("Lock %d", (int) mId);
+
+  // 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.
+    while (thread->Id() != acquires->mNextOwner) {
+      Thread::Wait();
+    }
+    // Acquire the lock before updating the next owner.
+    aCallback();
+    acquires->ReadAndNotifyNextOwner(thread);
+  }
+}
+
+struct AtomicLock : public detail::MutexImpl
+{
+  using detail::MutexImpl::lock;
+  using detail::MutexImpl::unlock;
+};
+
+// Lock which is held during code sections that run atomically. This is a
+// PRLock instead of an OffTheBooksMutex because the latter performs atomic
+// operations during initialization.
+static AtomicLock* gAtomicLock = nullptr;
+
+/* static */ void
+Lock::InitializeLocks()
+{
+  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
+  gNumLocks = gAtomicLockId;
+
+  gAtomicLock = new AtomicLock();
+  MOZ_RELEASE_ASSERT(!IsRecording() || gNumLocks == gAtomicLockId + 1);
+}
+
+/* static */ void
+Lock::LockAquiresUpdated(size_t aLockId)
+{
+  LockAcquires* acquires = gLockAcquires.MaybeGet(aLockId);
+  if (acquires && acquires->mAcquires && acquires->mNextOwner == LockAcquires::NoNextOwner) {
+    acquires->ReadAndNotifyNextOwner(Thread::Current());
+  }
+}
+
+extern "C" {
+
+MOZ_EXPORT void
+RecordReplayInterface_InternalBeginOrderedAtomicAccess()
+{
+  MOZ_RELEASE_ASSERT(IsRecordingOrReplaying());
+  if (!gInitializationFailureMessage) {
+    gAtomicLock->lock();
+  }
+}
+
+MOZ_EXPORT void
+RecordReplayInterface_InternalEndOrderedAtomicAccess()
+{
+  MOZ_RELEASE_ASSERT(IsRecordingOrReplaying());
+  if (!gInitializationFailureMessage) {
+    gAtomicLock->unlock();
+  }
+}
+
+} // extern "C"
+
+} // namespace recordreplay
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/Lock.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; 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 mozilla_recordreplay_Lock_h
+#define mozilla_recordreplay_Lock_h
+
+#include "mozilla/PodOperations.h"
+#include "mozilla/Types.h"
+
+#include "File.h"
+
+namespace mozilla {
+namespace recordreplay {
+
+// Recorded Locks Overview.
+//
+// Each platform has some types used for native locks (e.g. pthread_mutex_t or
+// CRITICAL_SECTION). System APIs which operate on these native locks are
+// redirected so that lock behavior can be tracked. If a native lock is
+// created when thread events are not passed through then that native lock is
+// recorded, and lock acquire orders will be replayed in the same order with
+// which they originally occurred.
+
+// Information about a recorded lock.
+class Lock
+{
+  // Unique ID for this lock.
+  size_t mId;
+
+public:
+  explicit Lock(size_t aId)
+    : mId(aId)
+  {
+    MOZ_ASSERT(aId);
+  }
+
+  size_t Id() { return mId; }
+
+  // When recording, this is called after the lock has been acquired, and
+  // records the acquire in the lock's acquire order stream. When replaying,
+  // this is called before the lock has been acquired, and blocks the thread
+  // until it is next in line to acquire the lock before acquiring it via
+  // aCallback.
+  void Enter(const std::function<void()>& aCallback);
+
+  // Create a new Lock corresponding to a native lock, with a fresh ID.
+  static void New(void* aNativeLock);
+
+  // Destroy any Lock associated with a native lock.
+  static void Destroy(void* aNativeLock);
+
+  // Get the recorded Lock for a native lock if there is one, otherwise null.
+  static Lock* Find(void* aNativeLock);
+
+  // Initialize locking state.
+  static void InitializeLocks();
+
+  // Note that new data has been read into a lock's acquires stream.
+  static void LockAquiresUpdated(size_t aLockId);
+};
+
+} // namespace recordreplay
+} // namespace mozilla
+
+#endif // mozilla_recordreplay_Lock_h
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/ProcessRecordReplay.cpp
@@ -0,0 +1,661 @@
+/* -*- Mode: C++; tab-width: 8; 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 "ProcessRecordReplay.h"
+
+#include "ipc/ChildIPC.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"
+#include "WeakPointer.h"
+#include "pratom.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+namespace mozilla {
+namespace recordreplay {
+
+MOZ_NEVER_INLINE void
+BusyWait()
+{
+  static volatile int value = 1;
+  while (value) {}
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Basic interface
+///////////////////////////////////////////////////////////////////////////////
+
+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.
+static bool gSpewEnabled;
+
+extern "C" {
+
+MOZ_EXPORT void
+RecordReplayInterface_Initialize(int aArgc, char* aArgv[])
+{
+  // Parse command line options for the process kind and recording file.
+  Maybe<ProcessKind> processKind;
+  Maybe<char*> recordingFile;
+  for (int i = 0; i < aArgc; i++) {
+    if (!strcmp(aArgv[i], gProcessKindOption)) {
+      MOZ_RELEASE_ASSERT(processKind.isNothing() && i + 1 < aArgc);
+      processKind.emplace((ProcessKind) atoi(aArgv[i + 1]));
+    }
+    if (!strcmp(aArgv[i], gRecordingFileOption)) {
+      MOZ_RELEASE_ASSERT(recordingFile.isNothing() && i + 1 < aArgc);
+      recordingFile.emplace(aArgv[i + 1]);
+    }
+  }
+  MOZ_RELEASE_ASSERT(processKind.isSome() && recordingFile.isSome());
+
+  gProcessKind = processKind.ref();
+  gRecordingFilename = strdup(recordingFile.ref());
+
+  switch (processKind.ref()) {
+  case ProcessKind::Recording:
+    gIsRecording = gIsRecordingOrReplaying = true;
+    fprintf(stderr, "RECORDING %d %s\n", getpid(), recordingFile.ref());
+    break;
+  case ProcessKind::Replaying:
+    gIsReplaying = gIsRecordingOrReplaying = true;
+    fprintf(stderr, "REPLAYING %d %s\n", getpid(), recordingFile.ref());
+    break;
+  case ProcessKind::MiddlemanRecording:
+  case ProcessKind::MiddlemanReplaying:
+    gIsMiddleman = true;
+    fprintf(stderr, "MIDDLEMAN %d %s\n", getpid(), recordingFile.ref());
+    break;
+  default:
+    MOZ_CRASH("Bad ProcessKind");
+  }
+
+  if (IsRecordingOrReplaying() && TestEnv("WAIT_AT_START")) {
+    BusyWait();
+  }
+
+  if (IsMiddleman() && TestEnv("MIDDLEMAN_WAIT_AT_START")) {
+    BusyWait();
+  }
+
+  gPid = getpid();
+  if (TestEnv("RECORD_REPLAY_SPEW")) {
+    gSpewEnabled = true;
+  }
+
+  EarlyInitializeRedirections();
+
+  if (!IsRecordingOrReplaying()) {
+    return;
+  }
+
+  gSnapshotMemoryPrefix = mktemp(strdup("/tmp/SnapshotMemoryXXXXXX"));
+  gSnapshotStackPrefix = mktemp(strdup("/tmp/SnapshotStackXXXXXX"));
+
+  InitializeCurrentTime();
+
+  gRecordingFile = new File();
+  if (!gRecordingFile->Open(recordingFile.ref(), IsRecording() ? File::WRITE : File::READ)) {
+    gInitializationFailureMessage = strdup("Bad recording file");
+    return;
+  }
+
+  if (!InitializeRedirections()) {
+    MOZ_RELEASE_ASSERT(gInitializationFailureMessage);
+    return;
+  }
+
+  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();
+
+  thread->SetPassThrough(false);
+
+  Lock::InitializeLocks();
+  InitializeRewindState();
+
+  gInitialized = true;
+}
+
+MOZ_EXPORT size_t
+RecordReplayInterface_InternalRecordReplayValue(size_t aValue)
+{
+  MOZ_ASSERT(IsRecordingOrReplaying());
+
+  if (AreThreadEventsPassedThrough()) {
+    return aValue;
+  }
+  EnsureNotDivergedFromRecording();
+
+  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
+  Thread* thread = Thread::Current();
+
+  RecordReplayAssert("Value");
+  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()) {
+    return;
+  }
+  EnsureNotDivergedFromRecording();
+
+  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
+  Thread* thread = Thread::Current();
+
+  RecordReplayAssert("Bytes %d", (int) aSize);
+  thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Bytes);
+  thread->Events().CheckInput(aSize);
+  thread->Events().RecordOrReplayBytes(aData, aSize);
+}
+
+MOZ_EXPORT void
+RecordReplayInterface_InternalInvalidateRecording(const char* aWhy)
+{
+  if (IsRecording()) {
+    child::ReportFatalError("Recording invalidated: %s", aWhy);
+  } else {
+    child::ReportFatalError("Recording invalidated while replaying: %s", aWhy);
+  }
+  Unreachable();
+}
+
+} // extern "C"
+
+// How many recording endpoints have been flushed to the recording.
+static size_t gNumEndpoints;
+
+void
+FlushRecording()
+{
+  MOZ_RELEASE_ASSERT(IsRecording());
+  MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
+
+  // Save the endpoint of the recording.
+  JS::replay::ExecutionPoint endpoint = JS::replay::hooks.getRecordingEndpoint();
+  Stream* endpointStream = gRecordingFile->OpenStream(StreamName::Main, 0);
+  endpointStream->WriteScalar(++gNumEndpoints);
+  endpointStream->WriteBytes(&endpoint, sizeof(endpoint));
+
+  gRecordingFile->PreventStreamWrites();
+
+  gRecordingFile->Flush();
+
+  child::NotifyFlushedRecording();
+
+  gRecordingFile->AllowStreamWrites();
+}
+
+// Try to load another recording index, returning whether one was found.
+static bool
+LoadNextRecordingIndex()
+{
+  Thread::WaitForIdleThreads();
+
+  InfallibleVector<Stream*> updatedStreams;
+  File::ReadIndexResult result = gRecordingFile->ReadNextIndex(&updatedStreams);
+  if (result == File::ReadIndexResult::InvalidFile) {
+    MOZ_CRASH("Bad recording file");
+  }
+
+  bool found = result == File::ReadIndexResult::FoundIndex;
+  if (found) {
+    for (Stream* stream : updatedStreams) {
+      if (stream->Name() == StreamName::Lock) {
+        Lock::LockAquiresUpdated(stream->NameIndex());
+      }
+    }
+  }
+
+  Thread::ResumeIdleThreads();
+  return found;
+}
+
+bool
+HitRecordingEndpoint()
+{
+  MOZ_RELEASE_ASSERT(IsReplaying());
+  MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
+
+  // The debugger will call this method in a loop, so we don't have to do
+  // anything fancy to try to get the most up to date endpoint. As long as we
+  // can make some progress in attempting to find a later endpoint, we can
+  // return control to the debugger.
+
+  // Check if there is a new endpoint in the endpoint data stream.
+  Stream* endpointStream = gRecordingFile->OpenStream(StreamName::Main, 0);
+  if (!endpointStream->AtEnd()) {
+    JS::replay::ExecutionPoint endpoint;
+    size_t index = endpointStream->ReadScalar();
+    endpointStream->ReadBytes(&endpoint, sizeof(endpoint));
+    JS::replay::hooks.setRecordingEndpoint(index, endpoint);
+    return true;
+  }
+
+  // Check if there is more data in the recording.
+  if (LoadNextRecordingIndex()) {
+    return true;
+  }
+
+  // OK, we hit the most up to date endpoint in the recording.
+  return false;
+}
+
+void
+HitEndOfRecording()
+{
+  MOZ_RELEASE_ASSERT(IsReplaying());
+  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
+
+  if (Thread::CurrentIsMainThread()) {
+    // Load more data from the recording. The debugger is not allowed to let us
+    // go past the recording endpoint, so there should be more data.
+    bool found = LoadNextRecordingIndex();
+    MOZ_RELEASE_ASSERT(found);
+  } else {
+    // Non-main threads may wait until more recording data is loaded by the
+    // main thread.
+    Thread::Wait();
+  }
+}
+
+extern "C" {
+
+MOZ_EXPORT bool
+RecordReplayInterface_SpewEnabled()
+{
+  return gSpewEnabled;
+}
+
+MOZ_EXPORT void
+RecordReplayInterface_InternalPrint(const char* aFormat, va_list aArgs)
+{
+  char buf1[2048];
+  VsprintfLiteral(buf1, aFormat, aArgs);
+  char buf2[2048];
+  SprintfLiteral(buf2, "Spew[%d]: %s", gPid, buf1);
+  DirectPrint(buf2);
+}
+
+} // extern "C"
+
+///////////////////////////////////////////////////////////////////////////////
+// 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()) {
+    return;
+  }
+
+  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.
+  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());
+    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 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("Assertion Mismatch: Thread %d\n"
+                              "Recorded: %s [%d]\n"
+                              "Replayed: %s [%d]\n",
+                              (int) thread->Id(), buffer, (int) streamPos, text,
+                              (int) thread->Events().StreamPosition());
+      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
+}
+
+MOZ_EXPORT void
+RecordReplayInterface_InternalRecordReplayAssertBytes(const void* aData, size_t aSize)
+{
+#ifdef INCLUDE_RECORD_REPLAY_ASSERTIONS
+  RecordReplayAssert("AssertBytes");
+
+  if (AreThreadEventsPassedThrough() || 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;
+    }
+
+    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("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();
+}
+
+} // 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)
+{
+  if (AreThreadEventsPassedThrough()) {
+    return;
+  }
+
+  AutoOrderedAtomicAccess at;
+  StaticMutexAutoLock lock(gGenericThingsMutex);
+  if (!gGenericThings) {
+    gGenericThings = new ValueIndex();
+  }
+  if (gGenericThings->Contains(aThing)) {
+    gGenericThings->Remove(aThing);
+  }
+  gGenericThings->Insert(aThing);
+}
+
+MOZ_EXPORT void
+RecordReplayInterface_InternalUnregisterThing(void* aThing)
+{
+  StaticMutexAutoLock lock(gGenericThingsMutex);
+  if (gGenericThings) {
+    gGenericThings->Remove(aThing);
+  }
+}
+
+MOZ_EXPORT size_t
+RecordReplayInterface_InternalThingIndex(void* aThing)
+{
+  if (!aThing) {
+    return 0;
+  }
+  StaticMutexAutoLock lock(gGenericThingsMutex);
+  size_t index = 0;
+  if (gGenericThings) {
+    gGenericThings->MaybeGetIndex(aThing, &index);
+  }
+  return index;
+}
+
+MOZ_EXPORT const char*
+RecordReplayInterface_InternalVirtualThingName(void* aThing)
+{
+  void* vtable = *(void**)aThing;
+  const char* name = SymbolNameRaw(vtable);
+  return name ? name : "(unknown)";
+}
+
+} // extern "C"
+
+} // namespace recordreplay
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/ProcessRecordReplay.h
@@ -0,0 +1,302 @@
+/* -*- Mode: C++; tab-width: 8; 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 mozilla_recordreplay_ProcessRecordReplay_h
+#define mozilla_recordreplay_ProcessRecordReplay_h
+
+#include "mozilla/PodOperations.h"
+#include "mozilla/RecordReplay.h"
+
+#include <algorithm>
+
+namespace mozilla {
+namespace recordreplay {
+
+// Record/Replay Internal API
+//
+// 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.
+
+// 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).
+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,
+
+  // The start of event IDs for redirected call events. Event IDs after this
+  // point are platform specific.
+  CallStart
+};
+
+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.
+bool HitRecordingEndpoint();
+
+// Possible directives to give via the RecordReplayDirective function.
+enum class Directive
+{
+  // Crash at the next use of MaybeCrash.
+  CrashSoon = 1,
+
+  // Irrevocably crash if CrashSoon has ever been used on the process.
+  MaybeCrash = 2,
+
+  // Always save temporary checkpoints when stepping around in the debugger.
+  AlwaysSaveTemporaryCheckpoints = 3,
+
+  // Mark all future checkpoints as major checkpoints in the middleman.
+  AlwaysMarkMajorCheckpoints = 4
+};
+
+// Get the process kind and recording file specified at the command line.
+// These are available in the middleman as well as while recording/replaying.
+extern ProcessKind gProcessKind;
+extern char* gRecordingFilename;
+
+///////////////////////////////////////////////////////////////////////////////
+// Helper Functions
+///////////////////////////////////////////////////////////////////////////////
+
+// Wait indefinitely for a debugger to be attached.
+void BusyWait();
+
+static inline void Unreachable() { MOZ_CRASH("Unreachable"); }
+
+// Get the symbol name for a function pointer address, if available.
+const char* SymbolNameRaw(void* aAddress);
+
+static inline bool
+MemoryContains(void* aBase, size_t aSize, void* aPtr, size_t aPtrSize = 1)
+{
+  MOZ_ASSERT(aPtrSize);
+  return (uint8_t*) aPtr >= (uint8_t*) aBase
+      && (uint8_t*) aPtr + aPtrSize <= (uint8_t*) aBase + aSize;
+}
+
+static inline bool
+MemoryIntersects(void* aBase0, size_t aSize0, void* aBase1, size_t aSize1)
+{
+  MOZ_ASSERT(aSize0 && aSize1);
+  return MemoryContains(aBase0, aSize0, aBase1)
+      || MemoryContains(aBase0, aSize0, (uint8_t*) aBase1 + aSize1 - 1)
+      || MemoryContains(aBase1, aSize1, aBase0);
+}
+
+static const size_t PageSize = 4096;
+
+static inline uint8_t*
+PageBase(void* aAddress)
+{
+  return (uint8_t*)aAddress - ((size_t)aAddress % PageSize);
+}
+
+static inline size_t
+RoundupSizeToPageBoundary(size_t aSize)
+{
+  if (aSize % PageSize) {
+    return aSize + PageSize - (aSize % PageSize);
+  }
+  return aSize;
+}
+
+static inline bool
+TestEnv(const char* env)
+{
+  const char* value = getenv(env);
+  return value && value[0];
+}
+
+// Check for membership in a vector.
+template <typename Vector, typename Entry>
+inline bool
+VectorContains(const Vector& aVector, const Entry& aEntry)
+{
+  return std::find(aVector.begin(), aVector.end(), aEntry) != aVector.end();
+}
+
+// Add or remove a unique entry to an unsorted vector.
+template <typename Vector, typename Entry>
+inline void
+VectorAddOrRemoveEntry(Vector& aVector, const Entry& aEntry, bool aAdding)
+{
+  for (Entry& existing : aVector) {
+    if (existing == aEntry) {
+      MOZ_RELEASE_ASSERT(!aAdding);
+      aVector.erase(&existing);
+      return;
+    }
+  }
+  MOZ_RELEASE_ASSERT(aAdding);
+  aVector.append(aEntry);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Profiling
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeCurrentTime();
+
+// Get a current timestamp, in microseconds.
+double CurrentTime();
+
+#define ForEachTimerKind(Macro)                 \
+  Macro(Default)
+
+enum class TimerKind {
+#define DefineTimerKind(aKind) aKind,
+  ForEachTimerKind(DefineTimerKind)
+#undef DefineTimerKind
+  Count
+};
+
+struct AutoTimer
+{
+  explicit AutoTimer(TimerKind aKind);
+  ~AutoTimer();
+
+private:
+  TimerKind mKind;
+  double mStart;
+};
+
+void DumpTimers();
+
+// Different kinds of untracked memory used in the system.
+namespace UntrackedMemoryKind {
+  // Note: 0 is TrackedMemoryKind, 1 is DebuggerAllocatedMemoryKind.
+  static const AllocatedMemoryKind Generic = 2;
+
+  // Memory used by untracked files.
+  static const AllocatedMemoryKind File = 3;
+
+  // Memory used for thread snapshots.
+  static const AllocatedMemoryKind ThreadSnapshot = 4;
+
+  // Memory used by various parts of the memory snapshot system.
+  static const AllocatedMemoryKind TrackedRegions = 5;
+  static const AllocatedMemoryKind FreeRegions = 6;
+  static const AllocatedMemoryKind DirtyPageSet = 7;
+  static const AllocatedMemoryKind SortedDirtyPageSet = 8;
+  static const AllocatedMemoryKind PageCopy = 9;
+
+  static const size_t Count = 10;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Redirection Bypassing
+///////////////////////////////////////////////////////////////////////////////
+
+// The functions below bypass any redirections and give access to the system
+// even if events are not passed through in the current thread. These are
+// implemented in the various platform ProcessRedirect*.cpp files, and will
+// crash on errors which can't be handled internally.
+
+// Generic typedef for a system file handle.
+typedef size_t FileHandle;
+
+// Allocate/deallocate a block of memory.
+void* DirectAllocateMemory(void* aAddress, size_t aSize);
+void DirectDeallocateMemory(void* aAddress, size_t aSize);
+
+// Give a block of memory R or RX access.
+void DirectWriteProtectMemory(void* aAddress, size_t aSize, bool aExecutable,
+                              bool aIgnoreFailures = false);
+
+// Give a block of memory RW or RWX access.
+void DirectUnprotectMemory(void* aAddress, size_t aSize, bool aExecutable,
+                           bool aIgnoreFailures = false);
+
+// Open an existing file for reading or a new file for writing, clobbering any
+// existing file.
+FileHandle DirectOpenFile(const char* aFilename, bool aWriting);
+
+// Seek to an offset within a file open for reading.
+void DirectSeekFile(FileHandle aFd, uint64_t aOffset);
+
+// Close or delete a file.
+void DirectCloseFile(FileHandle aFd);
+void DirectDeleteFile(const char* aFilename);
+
+// Append data to a file open for writing, blocking until the write completes.
+void DirectWrite(FileHandle aFd, const void* aData, size_t aSize);
+
+// Print a string directly to stderr.
+void DirectPrint(const char* aString);
+
+// Read data from a file, blocking until the read completes.
+size_t DirectRead(FileHandle aFd, void* aData, size_t aSize);
+
+// Create a new pipe.
+void DirectCreatePipe(FileHandle* aWriteFd, FileHandle* aReadFd);
+
+// Spawn a new thread.
+void DirectSpawnThread(void (*aFunction)(void*), void* aArgument);
+
+} // recordreplay
+} // mozilla
+
+#endif // mozilla_recordreplay_ProcessRecordReplay_h
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/Thread.cpp
@@ -0,0 +1,609 @@
+/* -*- Mode: C++; tab-width: 8; 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 "Thread.h"
+
+#include "ipc/ChildIPC.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/ThreadLocal.h"
+#include "ChunkAllocator.h"
+#include "MemorySnapshot.h"
+#include "ProcessRewind.h"
+#include "SpinLock.h"
+#include "ThreadSnapshot.h"
+
+namespace mozilla {
+namespace recordreplay {
+
+///////////////////////////////////////////////////////////////////////////////
+// Thread Organization
+///////////////////////////////////////////////////////////////////////////////
+
+static MOZ_THREAD_LOCAL(Thread*) gTlsThreadKey;
+
+/* static */ Monitor* Thread::gMonitor;
+
+/* static */ Thread*
+Thread::Current()
+{
+  MOZ_ASSERT(IsRecordingOrReplaying());
+  Thread* thread = gTlsThreadKey.get();
+  if (!thread && IsReplaying()) {
+    // Disable system threads when replaying.
+    WaitForeverNoIdle();
+  }
+  return thread;
+}
+
+/* static */ bool
+Thread::CurrentIsMainThread()
+{
+  Thread* thread = Current();
+  return thread && thread->IsMainThread();
+}
+
+void
+Thread::BindToCurrent()
+{
+  MOZ_ASSERT(!mStackBase);
+  gTlsThreadKey.set(this);
+
+  mNativeId = pthread_self();
+  size_t size = pthread_get_stacksize_np(mNativeId);
+  uint8_t* base = (uint8_t*)pthread_get_stackaddr_np(mNativeId) - size;
+
+  // Lock if we will be notifying later on. We don't do this for the main
+  // thread because we haven't initialized enough state yet that we can use
+  // a monitor.
+  Maybe<MonitorAutoLock> lock;
+  if (mId != MainThreadId) {
+    lock.emplace(*gMonitor);
+  }
+
+  mStackBase = base;
+  mStackSize = size;
+
+  // Notify WaitUntilInitialized if it is waiting for this thread to start.
+  if (mId != MainThreadId) {
+    gMonitor->NotifyAll();
+  }
+}
+
+// All threads, indexed by the thread ID.
+static Thread* gThreads;
+
+/* static */ Thread*
+Thread::GetById(size_t aId)
+{
+  MOZ_ASSERT(aId);
+  MOZ_ASSERT(aId <= MaxThreadId);
+  return &gThreads[aId];
+}
+
+/* static */ Thread*
+Thread::GetByNativeId(NativeThreadId aNativeId)
+{
+  for (size_t id = MainThreadId; id <= MaxRecordedThreadId; id++) {
+    Thread* thread = GetById(id);
+    if (thread->mNativeId == aNativeId) {
+      return thread;
+    }
+  }
+  return nullptr;
+}
+
+/* static */ Thread*
+Thread::GetByStackPointer(void* aSp)
+{
+  if (!gThreads) {
+    return nullptr;
+  }
+  for (size_t i = MainThreadId; i <= MaxThreadId; i++) {
+    Thread* thread = &gThreads[i];
+    if (MemoryContains(thread->mStackBase, thread->mStackSize, aSp)) {
+      return thread;
+    }
+  }
+  return nullptr;
+}
+
+/* static */ void
+Thread::InitializeThreads()
+{
+  gThreads = new Thread[MaxThreadId + 1];
+  for (size_t i = MainThreadId; i <= MaxThreadId; i++) {
+    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();
+  }
+}
+
+/* static */ void
+Thread::WaitUntilInitialized(Thread* aThread)
+{
+  MonitorAutoLock lock(*gMonitor);
+  while (!aThread->mStackBase) {
+    gMonitor->Wait();
+  }
+}
+
+/* static */ void
+Thread::ThreadMain(void* aArgument)
+{
+  MOZ_ASSERT(IsRecordingOrReplaying());
+
+  Thread* thread = (Thread*) aArgument;
+  MOZ_ASSERT(thread->mId > MainThreadId);
+
+  thread->BindToCurrent();
+
+  while (true) {
+    // Wait until this thread has been given a start routine.
+    while (true) {
+      {
+        MonitorAutoLock lock(*gMonitor);
+        if (thread->mStart) {
+          break;
+        }
+      }
+      Wait();
+    }
+
+    {
+      Maybe<AutoPassThroughThreadEvents> pt;
+      if (!thread->IsRecordedThread())
+        pt.emplace();
+      thread->mStart(thread->mStartArg);
+    }
+
+    MonitorAutoLock lock(*gMonitor);
+
+    // Clear the start routine to indicate to other threads that this one has
+    // finished executing.
+    thread->mStart = nullptr;
+    thread->mStartArg = nullptr;
+
+    // Notify any other thread waiting for this to finish in JoinThread.
+    gMonitor->NotifyAll();
+  }
+}
+
+/* static */ void
+Thread::SpawnAllThreads()
+{
+  MOZ_ASSERT(AreThreadEventsPassedThrough());
+
+  InitializeThreadSnapshots(MaxRecordedThreadId + 1);
+
+  gMonitor = new Monitor();
+
+  // All Threads are spawned up front. This allows threads to be scanned
+  // (e.g. in ReplayUnlock) without worrying about racing with other threads
+  // being spawned.
+  for (size_t i = MainThreadId + 1; i <= MaxRecordedThreadId; i++) {
+    SpawnThread(GetById(i));
+  }
+}
+
+// The number of non-recorded threads that have been spawned.
+static Atomic<size_t, SequentiallyConsistent, Behavior::DontPreserve> gNumNonRecordedThreads;
+
+/* static */ Thread*
+Thread::SpawnNonRecordedThread(Callback aStart, void* aArgument)
+{
+  if (IsMiddleman() || gInitializationFailureMessage) {
+    DirectSpawnThread(aStart, aArgument);
+    return nullptr;
+  }
+
+  size_t id = MaxRecordedThreadId + ++gNumNonRecordedThreads;
+  MOZ_RELEASE_ASSERT(id <= MaxThreadId);
+
+  Thread* thread = GetById(id);
+  thread->mStart = aStart;
+  thread->mStartArg = aArgument;
+
+  SpawnThread(thread);
+  return thread;
+}
+
+/* static */ void
+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();
+  Thread* thread = Thread::Current();
+
+  RecordReplayAssert("StartThread");
+
+  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);
+      if (!targetThread->mStart && !targetThread->mNeedsJoin) {
+        break;
+      }
+    }
+    if (id >= MaxRecordedThreadId) {
+      child::ReportFatalError("Too many threads");
+    }
+    MOZ_RELEASE_ASSERT(id <= MaxRecordedThreadId);
+  }
+  thread->Events().RecordOrReplayThreadEvent(ThreadEvent::CreateThread);
+  thread->Events().RecordOrReplayScalar(&id);
+
+  Thread* targetThread = GetById(id);
+
+  // Block until the thread is ready for a new start routine.
+  while (targetThread->mStart) {
+    MOZ_RELEASE_ASSERT(IsReplaying());
+    gMonitor->Wait();
+  }
+
+  targetThread->mStart = aStart;
+  targetThread->mStartArg = aArgument;
+  targetThread->mNeedsJoin = aNeedsJoin;
+
+  // Notify the thread in case it is waiting for a start routine under
+  // ThreadMain.
+  Notify(id);
+
+  return targetThread->mNativeId;
+}
+
+void
+Thread::Join()
+{
+  MOZ_ASSERT(!AreThreadEventsPassedThrough());
+
+  EnsureNotDivergedFromRecording();
+
+  while (true) {
+    MonitorAutoLock lock(*gMonitor);
+    if (!mStart) {
+      MOZ_RELEASE_ASSERT(mNeedsJoin);
+      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()
+{
+  MOZ_ASSERT(IsRecordingOrReplaying());
+  if (!gInitializationFailureMessage) {
+    Thread::Current()->SetPassThrough(true);
+  }
+}
+
+MOZ_EXPORT void
+RecordReplayInterface_InternalEndPassThroughThreadEvents()
+{
+  MOZ_ASSERT(IsRecordingOrReplaying());
+  if (!gInitializationFailureMessage) {
+    Thread::Current()->SetPassThrough(false);
+  }
+}
+
+MOZ_EXPORT bool
+RecordReplayInterface_InternalAreThreadEventsPassedThrough()
+{
+  MOZ_ASSERT(IsRecordingOrReplaying());
+  Thread* thread = Thread::Current();
+  return !thread || thread->PassThroughEvents();
+}
+
+MOZ_EXPORT void
+RecordReplayInterface_InternalBeginDisallowThreadEvents()
+{
+  MOZ_ASSERT(IsRecordingOrReplaying());
+  Thread::Current()->BeginDisallowEvents();
+}
+
+MOZ_EXPORT void
+RecordReplayInterface_InternalEndDisallowThreadEvents()
+{
+  MOZ_ASSERT(IsRecordingOrReplaying());
+  Thread::Current()->EndDisallowEvents();
+}
+
+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;
+
+// Whether all threads are considered to be idle.
+static Atomic<bool, SequentiallyConsistent, Behavior::DontPreserve> gThreadsAreIdle;
+
+/* static */ void
+Thread::WaitForIdleThreads()
+{
+  MOZ_RELEASE_ASSERT(CurrentIsMainThread());
+
+  MOZ_RELEASE_ASSERT(!gThreadsShouldIdle);
+  MOZ_RELEASE_ASSERT(!gThreadsAreIdle);
+  gThreadsShouldIdle = true;
+
+  MonitorAutoLock lock(*gMonitor);
+  for (size_t i = MainThreadId + 1; i <= MaxRecordedThreadId; i++) {
+    GetById(i)->mUnrecordedWaitNotified = false;
+  }
+  while (true) {
+    bool done = true;
+    for (size_t i = MainThreadId + 1; i <= MaxRecordedThreadId; i++) {
+      Thread* thread = GetById(i);
+      if (!thread->mIdle) {
+        done = false;
+        if (thread->mUnrecordedWaitCallback && !thread->mUnrecordedWaitNotified) {
+          // Set this flag before releasing the idle lock. Otherwise it's
+          // possible the thread could call NotifyUnrecordedWait while we
+          // aren't holding the lock, and we would set the flag afterwards
+          // without first invoking the callback.
+          thread->mUnrecordedWaitNotified = true;
+
+          // Release the idle lock here to avoid any risk of deadlock.
+          {
+            MonitorAutoUnlock unlock(*gMonitor);
+            AutoPassThroughThreadEvents pt;
+            thread->mUnrecordedWaitCallback();
+          }
+
+          // Releasing the global lock means that we need to start over
+          // checking whether there are any idle threads. By marking this
+          // thread as having been notified we have made progress, however.
+          done = true;
+          i = MainThreadId;
+        }
+      }
+    }
+    if (done) {
+      break;
+    }
+    MonitorAutoUnlock unlock(*gMonitor);
+    WaitNoIdle();
+  }
+
+  gThreadsAreIdle = true;
+}
+
+/* static */ void
+Thread::ResumeIdleThreads()
+{
+  MOZ_RELEASE_ASSERT(CurrentIsMainThread());
+
+  MOZ_RELEASE_ASSERT(gThreadsAreIdle);
+  gThreadsAreIdle = false;
+
+  MOZ_RELEASE_ASSERT(gThreadsShouldIdle);
+  gThreadsShouldIdle = false;
+
+  for (size_t i = MainThreadId + 1; i <= MaxRecordedThreadId; i++) {
+    Notify(i);
+  }
+}
+
+void
+Thread::NotifyUnrecordedWait(const std::function<void()>& aCallback)
+{
+  MonitorAutoLock lock(*gMonitor);
+  if (mUnrecordedWaitCallback) {
+    // Per the documentation for NotifyUnrecordedWait, we need to call the
+    // routine after a notify, even if the routine has been called already
+    // since the main thread started to wait for idle replay threads.
+    mUnrecordedWaitNotified = false;
+  } else {
+    MOZ_RELEASE_ASSERT(!mUnrecordedWaitNotified);
+  }
+
+  mUnrecordedWaitCallback = aCallback;
+
+  // The main thread might be able to make progress now by calling the routine
+  // if it is waiting for idle replay threads.
+  if (gThreadsShouldIdle) {
+    Notify(MainThreadId);
+  }
+}
+
+/* static */ void
+Thread::MaybeWaitForCheckpointSave()
+{
+  MonitorAutoLock lock(*gMonitor);
+  while (gThreadsShouldIdle) {
+    MonitorAutoUnlock unlock(*gMonitor);
+    Wait();
+  }
+}
+
+extern "C" {
+
+MOZ_EXPORT void
+RecordReplayInterface_NotifyUnrecordedWait(const std::function<void()>& aCallback)
+{
+  Thread::Current()->NotifyUnrecordedWait(aCallback);
+}
+
+MOZ_EXPORT void
+RecordReplayInterface_MaybeWaitForCheckpointSave()
+{
+  Thread::MaybeWaitForCheckpointSave();
+}
+
+} // extern "C"
+
+/* static */ void
+Thread::WaitNoIdle()
+{
+  Thread* thread = Current();
+  uint8_t data = 0;
+  size_t read = DirectRead(thread->mIdlefd, &data, 1);
+  MOZ_RELEASE_ASSERT(read == 1);
+}
+
+/* static */ void
+Thread::Wait()
+{
+  Thread* thread = Current();
+  MOZ_ASSERT(!thread->mIdle);
+  MOZ_ASSERT(thread->IsRecordedThread() && !thread->PassThroughEvents());
+
+  if (thread->IsMainThread()) {
+    WaitNoIdle();
+    return;
+  }
+
+  // The state saved for a thread needs to match up with the most recent
+  // point at which it became idle, so that when the main thread saves the
+  // stacks from all threads it saves those stacks at the right point.
+  // SaveThreadState might trigger thread events, so make sure they are
+  // passed through.
+  thread->SetPassThrough(true);
+  int stackSeparator = 0;
+  if (!SaveThreadState(thread->Id(), &stackSeparator)) {
+    // We just restored a checkpoint, notify the main thread since it is waiting
+    // for all threads to restore their stacks.
+    Notify(MainThreadId);
+  }
+
+  thread->mIdle = true;
+  if (gThreadsShouldIdle) {
+    // Notify the main thread that we just became idle.
+    Notify(MainThreadId);
+  }
+
+  do {
+    // Do the actual waiting for another thread to notify this one.
+    WaitNoIdle();
+
+    // Rewind this thread if the main thread told us to do so. The main
+    // thread is responsible for rewinding its own stack.
+    if (ShouldRestoreThreadStack(thread->Id())) {
+      RestoreThreadStack(thread->Id());
+      Unreachable();
+    }
+  } while (gThreadsShouldIdle);
+
+  thread->mIdle = false;
+  thread->SetPassThrough(false);
+}
+
+/* static */ void
+Thread::WaitForever()
+{
+  while (true) {
+    Wait();
+  }
+  Unreachable();
+}
+
+/* static */ void
+Thread::WaitForeverNoIdle()
+{
+  FileHandle writeFd, readFd;
+  DirectCreatePipe(&writeFd, &readFd);
+  while (true) {
+    uint8_t data;
+    DirectRead(readFd, &data, 1);
+  }
+}
+
+/* static */ void
+Thread::Notify(size_t aId)
+{
+  uint8_t data = 0;
+  DirectWrite(GetById(aId)->mNotifyfd, &data, 1);
+}
+
+} // namespace recordreplay
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/Thread.h
@@ -0,0 +1,322 @@
+/* -*- Mode: C++; tab-width: 8; 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 mozilla_recordreplay_Thread_h
+#define mozilla_recordreplay_Thread_h
+
+#include "mozilla/Atomics.h"
+#include "File.h"
+#include "Lock.h"
+#include "Monitor.h"
+
+#include <pthread.h>
+#include <setjmp.h>
+
+namespace mozilla {
+namespace recordreplay {
+
+// Threads Overview.
+//
+// The main thread and each thread that is spawned when thread events are not
+// passed through have their behavior recorded.
+//
+// While recording, each recorded thread has an associated Thread object which
+// can be fetched with Thread::Current and stores the thread's ID, its file for
+// storing events that occur in the thread, and some other thread local state.
+// Otherwise, threads are spawned and destroyed as usual for the process.
+//
+// While rewinding, the same Thread structure exists for each recorded thread.
+// Several additional changes are needed to facilitate rewinding and IPC:
+//
+// 1. All recorded threads are spawned early on, before any checkpoint has been
+//    reached. These threads idle until the process calls the system's thread
+//    creation API, and then they run with the start routine the process
+//    provided. After the start routine finishes they idle indefinitely,
+//    potentially running new start routines if their thread ID is reused. This
+//    allows the process to rewind itself without needing to spawn or destroy
+//    any threads.
+//
+// 2. Some additional number of threads are spawned for use by the IPC and
+//    memory snapshot mechanisms. These have associated Thread
+//    structures but are not recorded and always pass through thread events.
+//
+// 3. All recorded threads and must be able to enter a particular blocking
+//    state, under Thread::Wait, when requested by the main thread calling
+//    WaitForIdleThreads. For most recorded threads this happens when the
+//    thread attempts to take a recorded lock and blocks in Lock::Wait.
+//    The only exception is for JS helper threads, which never take recorded
+//    locks. For these threads, NotifyUnrecordedWait and
+//    MaybeWaitForCheckpointSave must be used to enter this state.
+//
+// 4. Once all recorded threads are idle, the main thread is able to record
+//    memory snapshots and thread stacks for later rewinding. Additional
+//    threads created for #2 above do not idle and do not have their state
+//    included in snapshots, but they are designed to avoid interfering with
+//    the main thread while it is taking or restoring a checkpoint.
+
+// The ID used by the process main thread.
+static const size_t MainThreadId = 1;
+
+// The maximum ID useable by recorded threads.
+static const size_t MaxRecordedThreadId = 70;
+
+// The maximum number of threads which are not recorded but need a Thread so
+// that they can participate in e.g. Wait/Notify calls.
+static const size_t MaxNumNonRecordedThreads = 12;
+
+static const size_t MaxThreadId = MaxRecordedThreadId + MaxNumNonRecordedThreads;
+
+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;
+
+  // 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* 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;
+
+  // File descriptor to notify to wake the thread up, fixed at creation.
+  FileHandle mNotifyfd;
+
+  // Whether the thread is waiting on idlefd.
+  Atomic<bool, SequentiallyConsistent, Behavior::DontPreserve> mIdle;
+
+  // Any callback which should be invoked so the thread can make progress,
+  // and whether the callback has been invoked yet while the main thread is
+  // waiting for threads to become idle. Protected by the thread monitor.
+  std::function<void()> mUnrecordedWaitCallback;
+  bool mUnrecordedWaitNotified;
+
+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(); }
+
+  // Access the flag for whether this thread is passing events through.
+  void SetPassThrough(bool aPassThrough) {
+    MOZ_RELEASE_ASSERT(mPassThroughEvents == !aPassThrough);
+    mPassThroughEvents = aPassThrough;
+  }
+  bool PassThroughEvents() {
+    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() {
+    return mDisallowEvents != 0;
+  }
+
+  // Access the counter for whether event stacks are captured while recording.
+  void BeginCaptureEventStacks() {
+    mCaptureEventStacks++;
+  }
+  void EndCaptureEventStacks() {
+    MOZ_RELEASE_ASSERT(mCaptureEventStacks);
+    mCaptureEventStacks--;
+  }
+  bool ShouldCaptureEventStacks() {
+    return mCaptureEventStacks != 0;
+  }
+
+  // Access the array of recent assertions in the thread.
+  RecentAssertInfo& RecentAssert(size_t i) {
+    MOZ_ASSERT(i < NumRecentAsserts);
+    return mRecentAsserts[i];
+  }
+
+  // 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();
+
+  // Initialize thread state.
+  static void InitializeThreads();
+
+  // Get the current thread, or null if this is a system thread.
+  static Thread* Current();
+
+  // Helper to test if this is the process main thread.
+  static bool CurrentIsMainThread();
+
+  // Lookup a Thread by various methods.
+  static Thread* GetById(size_t aId);
+  static Thread* GetByNativeId(NativeThreadId aNativeId);
+  static Thread* GetByStackPointer(void* aSp);
+
+  // Spawn all non-main recorded threads used for recording/replaying.
+  static void SpawnAllThreads();
+
+  // Spawn the specified thread.
+  static void SpawnThread(Thread* aThread);
+
+  // Spawn a non-recorded thread with the specified start routine/argument.
+  static Thread* SpawnNonRecordedThread(Callback aStart, void* aArgument);
+
+  // Wait until a thread has initialized its stack and other state.
+  static void WaitUntilInitialized(Thread* aThread);
+
+  // Start an existing thread, for use when the process has called a thread
+  // creation system API when events were not passed through. The return value
+  // is the native ID of the result.
+  static NativeThreadId StartThread(Callback aStart, void* aArgument, bool aNeedsJoin);
+
+  // Wait until this thread finishes executing its start routine.
+  void Join();
+
+///////////////////////////////////////////////////////////////////////////////
+// Thread Coordination
+///////////////////////////////////////////////////////////////////////////////
+
+  // Basic API for threads to coordinate activity with each other, for use
+  // during replay. Each Notify() on a thread ID will cause that thread to
+  // return from one call to Wait(). Thus, if a thread Wait()'s and then
+  // another thread Notify()'s its ID, the first thread will wake up afterward.
+  // Similarly, if a thread Notify()'s another thread which is not waiting,
+  // that second thread will return from its next Wait() without needing
+  // another Notify().
+  //
+  // If the main thread has called WaitForIdleThreads, then calling
+  // Wait() will put this thread in the desired idle state. WaitNoIdle() will
+  // never cause the thread to enter the idle state, and should be used
+  // carefully to avoid deadlocks with the main thread.
+  static void Wait();
+  static void WaitNoIdle();
+  static void Notify(size_t aId);
+
+  // Wait indefinitely, until the process is rewound.
+  static void WaitForever();
+
+  // Wait indefinitely, without allowing this thread to be rewound.
+  static void WaitForeverNoIdle();
+
+  // See RecordReplay.h.
+  void NotifyUnrecordedWait(const std::function<void()>& aCallback);
+  static void MaybeWaitForCheckpointSave();
+
+  // Wait for all other threads to enter the idle state necessary for saving
+  // or restoring a checkpoint. This may only be called on the main thread.
+  static void WaitForIdleThreads();
+
+  // After WaitForIdleThreads(), the main thread will call this to allow
+  // other threads to resume execution.
+  static void ResumeIdleThreads();
+};
+
+// This uses a stack pointer instead of TLS to make sure events are passed
+// through, for avoiding thorny reentrance issues.
+class AutoEnsurePassThroughThreadEventsUseStackPointer
+{
+  Thread* mThread;
+  bool mPassedThrough;
+
+public:
+  AutoEnsurePassThroughThreadEventsUseStackPointer()
+    : mThread(Thread::GetByStackPointer(this))
+    , mPassedThrough(!mThread || mThread->PassThroughEvents())
+  {
+    if (!mPassedThrough) {
+      mThread->SetPassThrough(true);
+    }
+  }
+
+  ~AutoEnsurePassThroughThreadEventsUseStackPointer()
+  {
+    if (!mPassedThrough) {
+      mThread->SetPassThrough(false);
+    }
+  }
+};
+
+} // namespace recordreplay
+} // namespace mozilla
+
+#endif // mozilla_recordreplay_Thread_h
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/Trigger.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 8; 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 "Trigger.h"
+
+#include "ipc/ChildIPC.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/RecordReplay.h"
+#include "InfallibleVector.h"
+#include "ProcessRewind.h"
+#include "Thread.h"
+#include "ValueIndex.h"
+
+namespace mozilla {
+namespace recordreplay {
+
+// Information about each trigger.
+struct TriggerInfo
+{
+  // ID of the thread which registered this trigger.
+  size_t mThreadId;
+
+  // Callback to execute when the trigger is activated.
+  std::function<void()> mCallback;
+
+  // Number of times this trigger has been activated.
+  size_t mRegisterCount;
+
+  TriggerInfo(size_t aThreadId, const std::function<void()>& aCallback)
+    : mThreadId(aThreadId), mCallback(aCallback), mRegisterCount(1)
+  {}
+};
+
+// All registered triggers.
+static ValueIndex* gTriggers;
+
+typedef std::unordered_map<void*, TriggerInfo> TriggerInfoMap;
+static TriggerInfoMap* gTriggerInfoMap;
+
+// Triggers which have been activated. This is protected by the global lock.
+static StaticInfallibleVector<size_t> gActivatedTriggers;
+
+static StaticMutexNotRecorded gTriggersMutex;
+
+void
+InitializeTriggers()
+{
+  gTriggers = new ValueIndex();
+  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()) {
+    return;
+  }
+
+  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
+
+  size_t threadId = Thread::Current()->Id();
+
+  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);
+      iter->second.mCallback = aCallback;
+      iter->second.mRegisterCount++;
+    } else {
+      id = gTriggers->Insert(aObj);
+      TriggerInfo info(threadId, aCallback);
+      gTriggerInfoMap->insert(TriggerInfoMap::value_type(aObj, info));
+    }
+  }
+
+  RecordReplayAssert("RegisterTrigger %zu", id);
+}
+
+MOZ_EXPORT void
+RecordReplayInterface_UnregisterTrigger(void* aObj)
+{
+  MOZ_ASSERT(IsRecordingOrReplaying());
+  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
+
+  StaticMutexAutoLock lock(gTriggersMutex);
+
+  TriggerInfoMap::iterator iter = gTriggerInfoMap->find(aObj);
+  MOZ_RELEASE_ASSERT(iter != gTriggerInfoMap->end());
+  if (--iter->second.mRegisterCount == 0) {
+    gTriggerInfoMap->erase(iter);
+    gTriggers->Remove(aObj);
+  }
+}
+
+MOZ_EXPORT void
+RecordReplayInterface_ActivateTrigger(void* aObj)
+{
+  if (!IsRecording()) {
+    return;
+  }
+
+  StaticMutexAutoLock lock(gTriggersMutex);
+
+  size_t id = gTriggers->GetIndex(aObj);
+  gActivatedTriggers.emplaceBack(id);
+}
+
+static void
+InvokeTriggerCallback(size_t aId)
+{
+  void* obj;
+  std::function<void()> callback;
+  {
+    StaticMutexAutoLock lock(gTriggersMutex);
+    obj = const_cast<void*>(gTriggers->GetValue(aId));
+    TriggerInfoMap::iterator iter = gTriggerInfoMap->find(obj);
+    MOZ_RELEASE_ASSERT(iter != gTriggerInfoMap->end());
+    MOZ_RELEASE_ASSERT(iter->second.mThreadId == Thread::Current()->Id());
+    MOZ_RELEASE_ASSERT(iter->second.mRegisterCount);
+    MOZ_RELEASE_ASSERT(iter->second.mCallback);
+    callback = iter->second.mCallback;
+  }
+
+  callback();
+}
+
+static Maybe<size_t>
+RemoveTriggerCallbackForThreadId(size_t aThreadId)
+{
+  StaticMutexAutoLock lock(gTriggersMutex);
+  for (size_t i = 0; i < gActivatedTriggers.length(); i++) {
+    size_t id = gActivatedTriggers[i];
+    void* obj = const_cast<void*>(gTriggers->GetValue(id));
+    TriggerInfoMap::iterator iter = gTriggerInfoMap->find(obj);
+    MOZ_RELEASE_ASSERT(iter != gTriggerInfoMap->end());
+    if (iter->second.mThreadId == aThreadId) {
+      gActivatedTriggers.erase(&gActivatedTriggers[i]);
+      return Some(id);
+    }
+  }
+  return Nothing();
+}
+
+MOZ_EXPORT void
+RecordReplayInterface_ExecuteTriggers()
+{
+  MOZ_ASSERT(IsRecordingOrReplaying());
+  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
+  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());
+
+  Thread* thread = Thread::Current();
+
+  RecordReplayAssert("ExecuteTriggers");
+
+  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(id.ref());
+      InvokeTriggerCallback(id.ref());
+    }
+    thread->Events().RecordOrReplayThreadEvent(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("ExecuteTrigger Mismatch");
+          Unreachable();
+        }
+        break;
+      }
+      size_t id = thread->Events().ReadScalar();
+      InvokeTriggerCallback(id);
+    }
+  }
+
+  RecordReplayAssert("ExecuteTriggers DONE");
+}
+
+} // extern "C"
+
+} // namespace recordreplay
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/Trigger.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; 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 mozilla_recordreplay_Trigger_h
+#define mozilla_recordreplay_Trigger_h
+
+namespace mozilla {
+namespace recordreplay {
+
+// See RecordReplay.h for a description of the record/replay trigger API.
+
+// Initialize trigger state at the beginning of recording or replaying.
+void InitializeTriggers();
+
+} // namespace recordreplay
+} // namespace mozilla
+
+#endif // mozilla_recordreplay_Trigger_h
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/WeakPointer.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; 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 "WeakPointer.h"
+
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/StaticMutex.h"
+#include "jsapi.h"
+
+#include <unordered_map>
+
+namespace mozilla {
+namespace recordreplay {
+
+typedef std::unordered_map<const void*, UniquePtr<JS::PersistentRootedObject>> WeakPointerRootMap;
+static WeakPointerRootMap* gWeakPointerRootMap;
+
+static StaticMutexNotRecorded gWeakPointerMutex;
+
+static UniquePtr<JS::PersistentRootedObject>
+NewRoot(JSObject* aJSObj)
+{
+  MOZ_RELEASE_ASSERT(aJSObj);
+  JSContext* cx = dom::danger::GetJSContext();
+  UniquePtr<JS::PersistentRootedObject> root = MakeUnique<JS::PersistentRootedObject>(cx);
+  *root = aJSObj;
+  return root;
+}
+
+extern "C" {
+
+MOZ_EXPORT void
+RecordReplayInterface_SetWeakPointerJSRoot(const void* aPtr, JSObject* aJSObj)
+{
+  MOZ_RELEASE_ASSERT(IsReplaying());
+
+  StaticMutexAutoLock lock(gWeakPointerMutex);
+
+  auto iter = gWeakPointerRootMap->find(aPtr);
+  if (iter != gWeakPointerRootMap->end()) {
+    if (aJSObj) {
+      *iter->second = aJSObj;
+    } else {
+      gWeakPointerRootMap->erase(aPtr);
+    }
+  } else if (aJSObj) {
+    gWeakPointerRootMap->insert(WeakPointerRootMap::value_type(aPtr, NewRoot(aJSObj)));
+  }
+}
+
+} // extern "C"
+
+void
+InitializeWeakPointers()
+{
+  gWeakPointerRootMap = new WeakPointerRootMap();
+}
+
+} // namespace recordreplay
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/recordreplay/WeakPointer.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; 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 mozilla_recordreplay_WeakPointer_h
+#define mozilla_recordreplay_WeakPointer_h
+
+namespace mozilla {
+namespace recordreplay {
+
+// See RecordReplay.h for a description of the record/replay weak pointer API.
+
+// Initialize weak pointer state.
+void InitializeWeakPointers();
+
+} // namespace recordreplay
+} // namespace mozilla
+
+#endif // mozilla_recordreplay_WeakPointer_h