Bug 1373755 - Support profiling of cooperative threads (r=jandem)
authorBrian Hackett <bhackett1024@gmail.com>
Thu, 15 Jun 2017 15:48:05 -0700
changeset 419727 579c0c00dfd5a5b8d5e55c6aca0e799a1bb727cf
parent 419726 ba4b0030671568f31cacb429bb2465b21bb5dd60
child 419728 ab36bb2ce844d5ab7d443916ba2af38db2b610b7
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1373755
milestone56.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 1373755 - Support profiling of cooperative threads (r=jandem) MozReview-Commit-ID: E8thu8SuCYv
js/src/gc/RootMarking.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/CompileWrappers.cpp
js/src/jit/CompileWrappers.h
js/src/jit/IonInstrumentation.h
js/src/jit/arm/Trampoline-arm.cpp
js/src/jsarray.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/shell/js.cpp
js/src/vm/GeckoProfiler-inl.h
js/src/vm/GeckoProfiler.cpp
js/src/vm/GeckoProfiler.h
js/src/vm/Interpreter.cpp
js/src/vm/Probes-inl.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/SavedStacks.cpp
js/src/vm/Stack.h
js/src/vm/String.cpp
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -357,19 +357,16 @@ js::gc::GCRuntime::traceRuntimeCommon(JS
     for (const CooperatingContext& target : rt->cooperatingContexts())
         target.context()->trace(trc);
 
     // Trace all compartment roots, but not the compartment itself; it is
     // traced via the parent pointer if traceRoots actually traces anything.
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
         c->traceRoots(trc, traceOrMark);
 
-    // Trace the Gecko Profiler.
-    rt->geckoProfiler().trace(trc);
-
     // Trace helper thread roots.
     HelperThreadState().trace(trc);
 
     // Trace the embedding's black and gray roots.
     if (!JS::CurrentThreadIsHeapMinorCollecting()) {
         gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_EMBEDDING);
 
         /*
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -4803,17 +4803,18 @@ BaselineCompiler::emit_JSOP_RESUME()
 
     // If profiler instrumentation is on, update lastProfilingFrame on
     // current JitActivation
     {
         Register scratchReg = scratch2;
         Label skip;
         AbsoluteAddress addressOfEnabled(cx->runtime()->geckoProfiler().addressOfEnabled());
         masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skip);
-        masm.loadPtr(AbsoluteAddress(cx->addressOfProfilingActivation()), scratchReg);
+        masm.loadJSContext(scratchReg);
+        masm.loadPtr(Address(scratchReg, JSContext::offsetOfProfilingActivation()), scratchReg);
         masm.storePtr(masm.getStackPointer(),
                       Address(scratchReg, JitActivation::offsetOfLastProfilingFrame()));
         masm.bind(&skip);
     }
 
     // Construct BaselineFrame.
     masm.push(BaselineFrameReg);
     masm.moveStackPtrTo(BaselineFrameReg);
--- a/js/src/jit/CompileWrappers.cpp
+++ b/js/src/jit/CompileWrappers.cpp
@@ -32,17 +32,17 @@ CompileRuntime::addressOfGCZealModeBits(
 #endif
 
 const JitRuntime*
 CompileRuntime::jitRuntime()
 {
     return runtime()->jitRuntime();
 }
 
-GeckoProfiler&
+GeckoProfilerRuntime&
 CompileRuntime::geckoProfiler()
 {
     return runtime()->geckoProfiler();
 }
 
 bool
 CompileRuntime::jitSupportsFloatingPoint()
 {
--- a/js/src/jit/CompileWrappers.h
+++ b/js/src/jit/CompileWrappers.h
@@ -29,17 +29,17 @@ class CompileRuntime
 
 #ifdef JS_GC_ZEAL
     const void* addressOfGCZealModeBits();
 #endif
 
     const JitRuntime* jitRuntime();
 
     // Compilation does not occur off thread when the Gecko Profiler is enabled.
-    GeckoProfiler& geckoProfiler();
+    GeckoProfilerRuntime& geckoProfiler();
 
     bool jitSupportsFloatingPoint();
     bool hadOutOfMemory();
     bool profilingScripts();
 
     const JSAtomState& names();
     const PropertyName* emptyString();
     const StaticStrings& staticStrings();
--- a/js/src/jit/IonInstrumentation.h
+++ b/js/src/jit/IonInstrumentation.h
@@ -4,28 +4,28 @@
  * 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 jit_IonInstrumentatjit_h
 #define jit_IonInstrumentatjit_h
 
 namespace js {
 
-class GeckoProfiler;
+class GeckoProfilerRuntime;
 
 namespace jit {
 
 class MacroAssembler;
 
 typedef GeckoProfilerInstrumentation<MacroAssembler, Register> BaseInstrumentation;
 
 class IonInstrumentation : public BaseInstrumentation
 {
   public:
-    IonInstrumentation(GeckoProfiler* profiler, jsbytecode** pc)
+    IonInstrumentation(GeckoProfilerRuntime* profiler, jsbytecode** pc)
       : BaseInstrumentation(profiler)
     {
         MOZ_ASSERT(pc != nullptr);
     }
 };
 
 } // namespace jit
 } // namespace js
--- a/js/src/jit/arm/Trampoline-arm.cpp
+++ b/js/src/jit/arm/Trampoline-arm.cpp
@@ -28,17 +28,17 @@ static const FloatRegisterSet NonVolatil
                      (1ULL << FloatRegisters::d10) |
                      (1ULL << FloatRegisters::d11) |
                      (1ULL << FloatRegisters::d12) |
                      (1ULL << FloatRegisters::d13) |
                      (1ULL << FloatRegisters::d14) |
                      (1ULL << FloatRegisters::d15));
 
 static void
-GenerateReturn(MacroAssembler& masm, int returnCode, GeckoProfiler* prof)
+GenerateReturn(MacroAssembler& masm, int returnCode)
 {
     // Restore non-volatile floating point registers.
     masm.transferMultipleByRuns(NonVolatileFloatRegs, IsLoad, StackPointer, IA);
 
     // Get rid of padding word.
     masm.addPtr(Imm32(sizeof(void*)), sp);
 
     // Set up return value
@@ -372,17 +372,17 @@ JitRuntime::generateEnterJIT(JSContext* 
     // :TODO: Optimize storeValue with:
     // We're using a load-double here. In order for that to work, the data needs
     // to be stored in two consecutive registers, make sure this is the case
     //   MOZ_ASSERT(JSReturnReg_Type.code() == JSReturnReg_Data.code()+1);
     //   aasm->as_extdtr(IsStore, 64, true, Offset,
     //                   JSReturnReg_Data, EDtrAddr(r5, EDtrOffImm(0)));
 
     // Restore non-volatile registers and return.
-    GenerateReturn(masm, true, &cx->runtime()->geckoProfiler());
+    GenerateReturn(masm, true);
 
     Linker linker(masm);
     AutoFlushICache afc("EnterJIT");
     JitCode* code = linker.newCode<NoGC>(cx, OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "EnterJIT");
 #endif
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -1318,17 +1318,17 @@ ArrayJoinKernel(JSContext* cx, Separator
 // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce
 // 22.1.3.13 Array.prototype.join ( separator )
 bool
 js::array_join(JSContext* cx, unsigned argc, Value* vp)
 {
     if (!CheckRecursionLimit(cx))
         return false;
 
-    AutoGeckoProfilerEntry pseudoFrame(cx->runtime(), "Array.prototype.join");
+    AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.join");
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     AutoCycleDetector detector(cx, obj);
@@ -1578,17 +1578,17 @@ ArrayReverseDenseKernel(JSContext* cx, H
 DefineBoxedOrUnboxedFunctor3(ArrayReverseDenseKernel,
                              JSContext*, HandleObject, uint32_t);
 
 // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce
 // 22.1.3.21 Array.prototype.reverse ( )
 bool
 js::array_reverse(JSContext* cx, unsigned argc, Value* vp)
 {
-    AutoGeckoProfilerEntry pseudoFrame(cx->runtime(), "Array.prototype.reverse");
+    AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.reverse");
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     // Step 2.
@@ -2283,17 +2283,17 @@ js::NewbornArrayPush(JSContext* cx, Hand
     return true;
 }
 
 // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce
 // 22.1.3.18 Array.prototype.push ( ...items )
 bool
 js::array_push(JSContext* cx, unsigned argc, Value* vp)
 {
-    AutoGeckoProfilerEntry pseudoFrame(cx->runtime(), "Array.prototype.push");
+    AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.push");
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     // Step 2.
@@ -2340,17 +2340,17 @@ js::array_push(JSContext* cx, unsigned a
     return SetLengthProperty(cx, obj, newlength);
 }
 
 // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce
 // 22.1.3.17 Array.prototype.pop ( )
 bool
 js::array_pop(JSContext* cx, unsigned argc, Value* vp)
 {
-    AutoGeckoProfilerEntry pseudoFrame(cx->runtime(), "Array.prototype.pop");
+    AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.pop");
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     // Step 2.
@@ -2445,17 +2445,17 @@ ArrayShiftDenseKernel(JSContext* cx, Han
 DefineBoxedOrUnboxedFunctor3(ArrayShiftDenseKernel,
                              JSContext*, HandleObject, MutableHandleValue);
 
 // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce
 // 22.1.3.22 Array.prototype.shift ( )
 bool
 js::array_shift(JSContext* cx, unsigned argc, Value* vp)
 {
-    AutoGeckoProfilerEntry pseudoFrame(cx->runtime(), "Array.prototype.shift");
+    AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.shift");
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     // Step 2.
@@ -2521,17 +2521,17 @@ js::array_shift(JSContext* cx, unsigned 
     return SetLengthProperty(cx, obj, newlen);
 }
 
 // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce
 // 22.1.3.29 Array.prototype.unshift ( ...items )
 bool
 js::array_unshift(JSContext* cx, unsigned argc, Value* vp)
 {
-    AutoGeckoProfilerEntry pseudoFrame(cx->runtime(), "Array.prototype.unshift");
+    AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.unshift");
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     // Step 2.
@@ -2764,17 +2764,17 @@ CopyArrayElements(JSContext* cx, HandleO
             return false;
     }
     return true;
 }
 
 static bool
 array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUsed)
 {
-    AutoGeckoProfilerEntry pseudoFrame(cx->runtime(), "Array.prototype.splice");
+    AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.splice");
     CallArgs args = CallArgsFromVp(argc, vp);
 
     /* Step 1. */
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     /* Step 2. */
@@ -3272,17 +3272,17 @@ ArraySliceOrdinary(JSContext* cx, Handle
     rval.setObject(*narr);
     return true;
 }
 
 /* ES 2016 draft Mar 25, 2016 22.1.3.23. */
 bool
 js::array_slice(JSContext* cx, unsigned argc, Value* vp)
 {
-    AutoGeckoProfilerEntry pseudoFrame(cx->runtime(), "Array.prototype.slice");
+    AutoGeckoProfilerEntry pseudoFrame(cx, "Array.prototype.slice");
     CallArgs args = CallArgsFromVp(argc, vp);
 
     /* Step 1. */
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     /* Step 2. */
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -1585,16 +1585,17 @@ JSContext::sizeOfExcludingThis(mozilla::
      */
     return cycleDetectorVector().sizeOfExcludingThis(mallocSizeOf);
 }
 
 void
 JSContext::trace(JSTracer* trc)
 {
     cycleDetectorVector().trace(trc);
+    geckoProfiler().trace(trc);
 
     if (trc->isMarkingTracer() && compartment_)
         compartment_->mark();
 }
 
 void*
 JSContext::stackLimitAddressForJitCode(JS::StackKind kind)
 {
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -385,19 +385,16 @@ struct JSContext : public JS::RootingCon
     }
     static size_t offsetOfActivation() {
         return offsetof(JSContext, activation_);
     }
 
     js::Activation* profilingActivation() const {
         return profilingActivation_;
     }
-    void* addressOfProfilingActivation() {
-        return (void*) &profilingActivation_;
-    }
     static size_t offsetOfProfilingActivation() {
         return offsetof(JSContext, profilingActivation_);
      }
 
   private:
     /* Space for interpreter frames. */
     js::ThreadLocalData<js::InterpreterStack> interpreterStack_;
 
@@ -598,16 +595,22 @@ struct JSContext : public JS::RootingCon
     }
     void disableProfilerSampling() {
         suppressProfilerSampling = true;
     }
     void enableProfilerSampling() {
         suppressProfilerSampling = false;
     }
 
+  private:
+    /* Gecko profiling metadata */
+    js::UnprotectedData<js::GeckoProfilerThread> geckoProfiler_;
+  public:
+    js::GeckoProfilerThread& geckoProfiler() { return geckoProfiler_.ref(); }
+
 #if defined(XP_DARWIN)
     js::wasm::MachExceptionHandler wasmMachExceptionHandler;
 #endif
 
     /* Temporary arena pool used while compiling and decompiling. */
     static const size_t TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 4 * 1024;
   private:
     js::ThreadLocalData<js::LifoAlloc> tempLifoAlloc_;
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -6211,17 +6211,17 @@ AllNurseriesAreEmpty(JSRuntime* rt)
 }
 #endif
 
 /* Start a new heap session. */
 AutoTraceSession::AutoTraceSession(JSRuntime* rt, JS::HeapState heapState)
   : lock(rt),
     runtime(rt),
     prevState(TlsContext.get()->heapState),
-    pseudoFrame(rt, HeapStateToLabel(heapState), ProfileEntry::Category::GC)
+    pseudoFrame(TlsContext.get(), HeapStateToLabel(heapState), ProfileEntry::Category::GC)
 {
     MOZ_ASSERT(prevState == JS::HeapState::Idle);
     MOZ_ASSERT(heapState != JS::HeapState::Idle);
     MOZ_ASSERT_IF(heapState == JS::HeapState::MajorCollecting, AllNurseriesAreEmpty(rt));
     TlsContext.get()->heapState = heapState;
 }
 
 AutoTraceSession::~AutoTraceSession()
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3327,16 +3327,35 @@ CooperativeBeginSingleThreadedExecution(
 
 static void
 CooperativeEndSingleThreadedExecution(JSContext* cx)
 {
     if (cooperationState)
         cooperationState->singleThreaded = false;
 }
 
+static bool
+EnsureGeckoProfilingStackInstalled(JSContext* cx, ShellContext* sc)
+{
+    if (cx->geckoProfiler().installed()) {
+        MOZ_ASSERT(sc->geckoProfilingStack);
+        return true;
+    }
+
+    MOZ_ASSERT(!sc->geckoProfilingStack);
+    sc->geckoProfilingStack = MakeUnique<PseudoStack>();
+    if (!sc->geckoProfilingStack) {
+        JS_ReportOutOfMemory(cx);
+        return false;
+    }
+
+    SetContextProfilingStack(cx, sc->geckoProfilingStack.get());
+    return true;
+}
+
 struct WorkerInput
 {
     JSRuntime* parentRuntime;
     JSContext* siblingContext;
     char16_t* chars;
     size_t length;
 
     WorkerInput(JSRuntime* parentRuntime, char16_t* chars, size_t length)
@@ -3395,16 +3414,20 @@ WorkerMain(void* arg)
         js::UseInternalJobQueues(cx);
 
         if (!JS::InitSelfHostedCode(cx))
             return;
 
         environmentPreparer.emplace(cx);
     } else {
         JS_AddInterruptCallback(cx, ShellInterruptCallback);
+
+        // The Gecko Profiler requires that all cooperating contexts have
+        // profiling stacks installed.
+        MOZ_ALWAYS_TRUE(EnsureGeckoProfilingStackInstalled(cx, sc.get()));
     }
 
     do {
         JSAutoRequest ar(cx);
 
         JS::CompartmentOptions compartmentOptions;
         SetStandardCompartmentOptions(compartmentOptions);
         if (input->siblingContext)
@@ -3425,21 +3448,16 @@ WorkerMain(void* arg)
         if (!JS::Compile(cx, options, input->chars, input->length, &script))
             break;
         RootedValue result(cx);
         JS_ExecuteScript(cx, script, &result);
     } while (0);
 
     KillWatchdog(cx);
     JS_SetGrayGCRootsTracer(cx, nullptr, nullptr);
-
-    if (sc->geckoProfilingStack) {
-        MOZ_ALWAYS_TRUE(cx->runtime()->geckoProfiler().enable(false));
-        SetContextProfilingStack(cx, nullptr);
-    }
 }
 
 // Workers can spawn other workers, so we need a lock to access workerThreads.
 static Mutex* workerThreadsLock = nullptr;
 static Vector<js::Thread*, 0, SystemAllocPolicy> workerThreads;
 
 class MOZ_RAII AutoLockWorkerThreads : public LockGuard<Mutex>
 {
@@ -5205,116 +5223,80 @@ static bool
 IsLatin1(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     bool isLatin1 = args.get(0).isString() && args[0].toString()->hasLatin1Chars();
     args.rval().setBoolean(isLatin1);
     return true;
 }
 
-static bool
-EnsureGeckoProfilingStackInstalled(JSContext* cx, ShellContext* sc)
-{
-    if (cx->runtime()->geckoProfiler().installed()) {
-        if (!sc->geckoProfilingStack) {
-            JS_ReportErrorASCII(cx, "Profiler already installed by another context");
-            return false;
-        }
-
-        return true;
-    }
-
-    MOZ_ASSERT(!sc->geckoProfilingStack);
-    sc->geckoProfilingStack = MakeUnique<PseudoStack>();
-    if (!sc->geckoProfilingStack) {
-        JS_ReportOutOfMemory(cx);
-        return false;
-    }
-
-    SetContextProfilingStack(cx, sc->geckoProfilingStack.get());
+// Set the profiling stack for each cooperating context in a runtime.
+static bool
+EnsureAllContextProfilingStacks(JSContext* cx)
+{
+    for (const CooperatingContext& target : cx->runtime()->cooperatingContexts()) {
+        ShellContext* sc = GetShellContext(target.context());
+        if (!EnsureGeckoProfilingStackInstalled(target.context(), sc))
+            return false;
+    }
+
     return true;
 }
 
 static bool
 EnableGeckoProfiling(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    ShellContext* sc = GetShellContext(cx);
-
-    if (!EnsureGeckoProfilingStackInstalled(cx, sc))
-        return false;
-
-    // Disable before re-enabling; see the assertion in
-    // |GeckoProfiler::setProfilingStack|.
-    if (cx->runtime()->geckoProfiler().installed())
-        MOZ_ALWAYS_TRUE(cx->runtime()->geckoProfiler().enable(false));
+    if (!EnsureAllContextProfilingStacks(cx))
+        return false;
 
     cx->runtime()->geckoProfiler().enableSlowAssertions(false);
-    if (!cx->runtime()->geckoProfiler().enable(true)) {
-        JS_ReportErrorASCII(cx, "Cannot ensure single threaded execution in profiler");
-        return false;
-    }
+    cx->runtime()->geckoProfiler().enable(true);
 
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 EnableGeckoProfilingWithSlowAssertions(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setUndefined();
 
-    ShellContext* sc = GetShellContext(cx);
-
-    if (!EnsureGeckoProfilingStackInstalled(cx, sc))
-        return false;
-
     if (cx->runtime()->geckoProfiler().enabled()) {
         // If profiling already enabled with slow assertions disabled,
         // this is a no-op.
         if (cx->runtime()->geckoProfiler().slowAssertionsEnabled())
             return true;
 
         // Slow assertions are off.  Disable profiling before re-enabling
         // with slow assertions on.
-        MOZ_ALWAYS_TRUE(cx->runtime()->geckoProfiler().enable(false));
-    }
-
-    // Disable before re-enabling; see the assertion in |GeckoProfiler::setProfilingStack|.
-    if (cx->runtime()->geckoProfiler().installed())
-        MOZ_ALWAYS_TRUE(cx->runtime()->geckoProfiler().enable(false));
+        cx->runtime()->geckoProfiler().enable(false);
+    }
+
+    if (!EnsureAllContextProfilingStacks(cx))
+        return false;
 
     cx->runtime()->geckoProfiler().enableSlowAssertions(true);
-    if (!cx->runtime()->geckoProfiler().enable(true)) {
-        JS_ReportErrorASCII(cx, "Cannot ensure single threaded execution in profiler");
-        return false;
-    }
+    cx->runtime()->geckoProfiler().enable(true);
 
     return true;
 }
 
 static bool
 DisableGeckoProfiling(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setUndefined();
 
-    ShellContext* sc = GetShellContext(cx);
-
-    if (!cx->runtime()->geckoProfiler().installed())
+    if (!cx->runtime()->geckoProfiler().enabled())
         return true;
 
-    if (!sc->geckoProfilingStack) {
-        JS_ReportErrorASCII(cx, "Profiler was not installed by this context");
-        return false;
-    }
-
-    MOZ_ALWAYS_TRUE(cx->runtime()->geckoProfiler().enable(false));
+    cx->runtime()->geckoProfiler().enable(false);
     return true;
 }
 
 // Global mailbox that is used to communicate a SharedArrayBuffer
 // value from one worker to another.
 //
 // For simplicity we store only the SharedArrayRawBuffer; retaining
 // the SAB object would require per-runtime storage, and would have no
--- a/js/src/vm/GeckoProfiler-inl.h
+++ b/js/src/vm/GeckoProfiler-inl.h
@@ -4,20 +4,36 @@
  * 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 vm_GeckoProfiler_inl_h
 #define vm_GeckoProfiler_inl_h
 
 #include "vm/GeckoProfiler.h"
 
+#include "jscntxt.h"
+
 #include "vm/Runtime.h"
 
 namespace js {
 
+inline void
+GeckoProfilerThread::updatePC(JSContext* cx, JSScript* script, jsbytecode* pc)
+{
+    if (!cx->runtime()->geckoProfiler().enabled())
+        return;
+
+    uint32_t sp = pseudoStack_->stackPointer;
+    if (sp - 1 < PseudoStack::MaxEntries) {
+        MOZ_ASSERT(sp > 0);
+        MOZ_ASSERT(pseudoStack_->entries[sp - 1].rawScript() == script);
+        pseudoStack_->entries[sp - 1].setPC(pc);
+    }
+}
+
 /*
  * This class is used to suppress profiler sampling during
  * critical sections where stack state is not valid.
  */
 class MOZ_RAII AutoSuppressProfilerSampling
 {
   public:
     explicit AutoSuppressProfilerSampling(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
--- a/js/src/vm/GeckoProfiler.cpp
+++ b/js/src/vm/GeckoProfiler.cpp
@@ -20,48 +20,49 @@
 #include "vm/StringBuffer.h"
 
 #include "jsgcinlines.h"
 
 using namespace js;
 
 using mozilla::DebugOnly;
 
-GeckoProfiler::GeckoProfiler(JSRuntime* rt)
+GeckoProfilerThread::GeckoProfilerThread()
+  : pseudoStack_(nullptr)
+{
+}
+
+GeckoProfilerRuntime::GeckoProfilerRuntime(JSRuntime* rt)
   : rt(rt),
     strings(mutexid::GeckoProfilerStrings),
-    pseudoStack_(nullptr),
     slowAssertions(false),
     enabled_(false),
     eventMarker_(nullptr)
 {
     MOZ_ASSERT(rt != nullptr);
 }
 
 bool
-GeckoProfiler::init()
+GeckoProfilerRuntime::init()
 {
     auto locked = strings.lock();
     if (!locked->init())
         return false;
 
     return true;
 }
 
 void
-GeckoProfiler::setProfilingStack(PseudoStack* pseudoStack)
+GeckoProfilerThread::setProfilingStack(PseudoStack* pseudoStack)
 {
-    MOZ_ASSERT_IF(pseudoStack_, !enabled());
-    MOZ_ASSERT(strings.lock()->initialized());
-
     pseudoStack_ = pseudoStack;
 }
 
 void
-GeckoProfiler::setEventMarker(void (*fn)(const char*))
+GeckoProfilerRuntime::setEventMarker(void (*fn)(const char*))
 {
     eventMarker_ = fn;
 }
 
 /* Get a pointer to the top-most profiling frame, given the exit frame pointer. */
 static void*
 GetTopProfilingJitFrame(Activation* act)
 {
@@ -73,34 +74,29 @@ GetTopProfilingJitFrame(Activation* act)
     if (!exitFP)
         return nullptr;
 
     jit::JitProfilingFrameIterator iter(exitFP);
     MOZ_ASSERT(!iter.done());
     return iter.fp();
 }
 
-bool
-GeckoProfiler::enable(bool enabled)
+void
+GeckoProfilerRuntime::enable(bool enabled)
 {
-    MOZ_ASSERT(installed());
+#ifdef DEBUG
+    // All cooperating contexts must have profile stacks installed before the
+    // profiler can be enabled. Cooperating threads created while the profiler
+    // is enabled must have stacks set before they execute any JS.
+    for (const CooperatingContext& target : rt->cooperatingContexts())
+        MOZ_ASSERT(target.context()->geckoProfiler().installed());
+#endif
 
     if (enabled_ == enabled)
-        return true;
-
-    // Execution in the runtime must be single threaded if the Gecko profiler
-    // is enabled. There is only a single profiler stack in the runtime, from
-    // which entries must be added/removed in a LIFO fashion.
-    JSContext* cx = rt->activeContextFromOwnThread();
-    if (enabled) {
-        if (!rt->beginSingleThreadedExecution(cx))
-            return false;
-    } else {
-        rt->endSingleThreadedExecution(cx);
-    }
+        return;
 
     /*
      * Ensure all future generated code will be instrumented, or that all
      * currently instrumented code is discarded
      */
     ReleaseAllJITCode(rt->defaultFreeOp());
 
     // This function is called when the Gecko profiler makes a new Sampler
@@ -158,69 +154,67 @@ GeckoProfiler::enable(bool enabled)
         }
     }
 
     // WebAssembly code does not need to be released, but profiling string
     // labels have to be generated so that they are available during async
     // profiling stack iteration.
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
         c->wasm.ensureProfilingLabels(enabled);
-
-    return true;
 }
 
 /* Lookup the string for the function/script, creating one if necessary */
 const char*
-GeckoProfiler::profileString(JSScript* script, JSFunction* maybeFun)
+GeckoProfilerRuntime::profileString(JSScript* script, JSFunction* maybeFun)
 {
     auto locked = strings.lock();
     MOZ_ASSERT(locked->initialized());
 
     ProfileStringMap::AddPtr s = locked->lookupForAdd(script);
 
     if (!s) {
         auto str = allocProfileString(script, maybeFun);
         if (!str || !locked->add(s, script, mozilla::Move(str)))
             return nullptr;
     }
 
     return s->value().get();
 }
 
 void
-GeckoProfiler::onScriptFinalized(JSScript* script)
+GeckoProfilerRuntime::onScriptFinalized(JSScript* script)
 {
     /*
      * This function is called whenever a script is destroyed, regardless of
      * whether profiling has been turned on, so don't invoke a function on an
      * invalid hash set. Also, even if profiling was enabled but then turned
      * off, we still want to remove the string, so no check of enabled() is
      * done.
      */
     auto locked = strings.lock();
     if (!locked->initialized())
         return;
     if (ProfileStringMap::Ptr entry = locked->lookup(script))
         locked->remove(entry);
 }
 
 void
-GeckoProfiler::markEvent(const char* event)
+GeckoProfilerRuntime::markEvent(const char* event)
 {
     MOZ_ASSERT(enabled());
     if (eventMarker_) {
         JS::AutoSuppressGCAnalysis nogc;
         eventMarker_(event);
     }
 }
 
 bool
-GeckoProfiler::enter(JSContext* cx, JSScript* script, JSFunction* maybeFun)
+GeckoProfilerThread::enter(JSContext* cx, JSScript* script, JSFunction* maybeFun)
 {
-    const char* dynamicString = profileString(script, maybeFun);
+    const char* dynamicString = cx->runtime()->geckoProfiler().profileString(script, maybeFun);
     if (dynamicString == nullptr) {
         ReportOutOfMemory(cx);
         return false;
     }
 
 #ifdef DEBUG
     // In debug builds, assert the JS pseudo frames already on the stack
     // have a non-null pc. Only look at the top frames to avoid quadratic
@@ -233,25 +227,26 @@ GeckoProfiler::enter(JSContext* cx, JSSc
     }
 #endif
 
     pseudoStack_->pushJsFrame("", dynamicString, script, script->code());
     return true;
 }
 
 void
-GeckoProfiler::exit(JSScript* script, JSFunction* maybeFun)
+GeckoProfilerThread::exit(JSScript* script, JSFunction* maybeFun)
 {
     pseudoStack_->pop();
 
 #ifdef DEBUG
     /* Sanity check to make sure push/pop balanced */
     uint32_t sp = pseudoStack_->stackPointer;
     if (sp < PseudoStack::MaxEntries) {
-        const char* dynamicString = profileString(script, maybeFun);
+        JSRuntime* rt = script->runtimeFromActiveCooperatingThread();
+        const char* dynamicString = rt->geckoProfiler().profileString(script, maybeFun);
         /* Can't fail lookup because we should already be in the set */
         MOZ_ASSERT(dynamicString);
 
         // Bug 822041
         if (!pseudoStack_->entries[sp].isJs()) {
             fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n");
             fprintf(stderr, " entries=%p size=%u/%u\n",
                             (void*) pseudoStack_->entries,
@@ -276,17 +271,17 @@ GeckoProfiler::exit(JSScript* script, JS
 
 /*
  * Serializes the script/function pair into a "descriptive string" which is
  * allowed to fail. This function cannot trigger a GC because it could finalize
  * some scripts, resize the hash table of profile strings, and invalidate the
  * AddPtr held while invoking allocProfileString.
  */
 UniqueChars
-GeckoProfiler::allocProfileString(JSScript* script, JSFunction* maybeFun)
+GeckoProfilerRuntime::allocProfileString(JSScript* script, JSFunction* maybeFun)
 {
     // Note: this profiler string is regexp-matched by
     // devtools/client/profiler/cleopatra/js/parserWorker.js.
 
     // Get the function name, if any.
     JSAtom* atom = maybeFun ? maybeFun->displayAtom() : nullptr;
 
     // Get the script filename, if any, and its length.
@@ -324,44 +319,44 @@ GeckoProfiler::allocProfileString(JSScri
     }
 
     MOZ_ASSERT(ret == len, "Computed length should match actual length!");
 
     return cstr;
 }
 
 void
-GeckoProfiler::trace(JSTracer* trc)
+GeckoProfilerThread::trace(JSTracer* trc)
 {
     if (pseudoStack_) {
         size_t size = pseudoStack_->stackSize();
         for (size_t i = 0; i < size; i++)
             pseudoStack_->entries[i].trace(trc);
     }
 }
 
 void
-GeckoProfiler::fixupStringsMapAfterMovingGC()
+GeckoProfilerRuntime::fixupStringsMapAfterMovingGC()
 {
     auto locked = strings.lock();
     if (!locked->initialized())
         return;
 
     for (ProfileStringMap::Enum e(locked.get()); !e.empty(); e.popFront()) {
         JSScript* script = e.front().key();
         if (IsForwarded(script)) {
             script = Forwarded(script);
             e.rekeyFront(script);
         }
     }
 }
 
 #ifdef JSGC_HASH_TABLE_CHECKS
 void
-GeckoProfiler::checkStringsMapAfterMovingGC()
+GeckoProfilerRuntime::checkStringsMapAfterMovingGC()
 {
     auto locked = strings.lock();
     if (!locked->initialized())
         return;
 
     for (auto r = locked->all(); !r.empty(); r.popFront()) {
         JSScript* script = r.front().key();
         CheckGCThingAfterMovingGC(script);
@@ -376,20 +371,20 @@ ProfileEntry::trace(JSTracer* trc)
 {
     if (isJs()) {
         JSScript* s = rawScript();
         TraceNullableRoot(trc, &s, "ProfileEntry script");
         spOrScript = s;
     }
 }
 
-GeckoProfilerEntryMarker::GeckoProfilerEntryMarker(JSRuntime* rt,
+GeckoProfilerEntryMarker::GeckoProfilerEntryMarker(JSContext* cx,
                                                    JSScript* script
                                                    MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
-    : profiler(&rt->geckoProfiler())
+    : profiler(&cx->geckoProfiler())
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     if (!profiler->installed()) {
         profiler = nullptr;
         return;
     }
     spBefore_ = profiler->stackPointer();
 
@@ -408,20 +403,20 @@ GeckoProfilerEntryMarker::~GeckoProfiler
     if (profiler == nullptr)
         return;
 
     profiler->pseudoStack_->pop();    // the JS frame
     profiler->pseudoStack_->pop();    // the BEGIN_PSEUDO_JS frame
     MOZ_ASSERT(spBefore_ == profiler->stackPointer());
 }
 
-AutoGeckoProfilerEntry::AutoGeckoProfilerEntry(JSRuntime* rt, const char* label,
+AutoGeckoProfilerEntry::AutoGeckoProfilerEntry(JSContext* cx, const char* label,
                                                ProfileEntry::Category category
                                                MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
-    : profiler_(&rt->geckoProfiler())
+    : profiler_(&cx->geckoProfiler())
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     if (!profiler_->installed()) {
         profiler_ = nullptr;
         return;
     }
     spBefore_ = profiler_->stackPointer();
 
@@ -434,22 +429,22 @@ AutoGeckoProfilerEntry::~AutoGeckoProfil
 {
     if (!profiler_)
         return;
 
     profiler_->pseudoStack_->pop();
     MOZ_ASSERT(spBefore_ == profiler_->stackPointer());
 }
 
-GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(JSRuntime* rt, bool hasProfilerFrame
+GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(JSContext* cx, bool hasProfilerFrame
                                                                MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
-    : profiler(&rt->geckoProfiler())
+    : profiler(&cx->geckoProfiler())
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    if (!hasProfilerFrame || !profiler->enabled()) {
+    if (!hasProfilerFrame || !cx->runtime()->geckoProfiler().enabled()) {
         profiler = nullptr;
         return;
     }
 
     uint32_t sp = profiler->pseudoStack_->stackPointer;
     if (sp >= PseudoStack::MaxEntries) {
         profiler = nullptr;
         return;
@@ -525,24 +520,23 @@ ProfileEntry::setPC(jsbytecode* pc)
     JSScript* script = this->script();
     MOZ_ASSERT(script); // This should not be called while profiling is suppressed.
     lineOrPcOffset = pcToOffset(script, pc);
 }
 
 JS_FRIEND_API(void)
 js::SetContextProfilingStack(JSContext* cx, PseudoStack* pseudoStack)
 {
-    cx->runtime()->geckoProfiler().setProfilingStack(pseudoStack);
+    cx->geckoProfiler().setProfilingStack(pseudoStack);
 }
 
 JS_FRIEND_API(void)
 js::EnableContextProfilingStack(JSContext* cx, bool enabled)
 {
-    if (!cx->runtime()->geckoProfiler().enable(enabled))
-        MOZ_CRASH("Execution in this runtime should already be single threaded");
+    cx->runtime()->geckoProfiler().enable(enabled);
 }
 
 JS_FRIEND_API(void)
 js::RegisterContextProfilingEventMarker(JSContext* cx, void (*fn)(const char*))
 {
     MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled());
     cx->runtime()->geckoProfiler().setEventMarker(fn);
 }
--- a/js/src/vm/GeckoProfiler.h
+++ b/js/src/vm/GeckoProfiler.h
@@ -109,155 +109,155 @@ using ProfileStringMap = HashMap<JSScrip
                                  UniqueChars,
                                  DefaultHasher<JSScript*>,
                                  SystemAllocPolicy>;
 
 class AutoGeckoProfilerEntry;
 class GeckoProfilerEntryMarker;
 class GeckoProfilerBaselineOSRMarker;
 
-class GeckoProfiler
+class GeckoProfilerThread
 {
     friend class AutoGeckoProfilerEntry;
     friend class GeckoProfilerEntryMarker;
     friend class GeckoProfilerBaselineOSRMarker;
 
-    JSRuntime*           rt;
-    ExclusiveData<ProfileStringMap> strings;
     PseudoStack*         pseudoStack_;
-    bool                 slowAssertions;
-    uint32_t             enabled_;
-    void                (*eventMarker_)(const char*);
-
-    UniqueChars allocProfileString(JSScript* script, JSFunction* function);
 
   public:
-    explicit GeckoProfiler(JSRuntime* rt);
-
-    bool init();
+    GeckoProfilerThread();
 
     uint32_t stackPointer() { MOZ_ASSERT(installed()); return pseudoStack_->stackPointer; }
     ProfileEntry* stack() { return pseudoStack_->entries; }
 
     /* management of whether instrumentation is on or off */
-    bool enabled() { MOZ_ASSERT_IF(enabled_, installed()); return enabled_; }
     bool installed() { return pseudoStack_ != nullptr; }
-    MOZ_MUST_USE bool enable(bool enabled);
-    void enableSlowAssertions(bool enabled) { slowAssertions = enabled; }
-    bool slowAssertionsEnabled() { return slowAssertions; }
+
+    void setProfilingStack(PseudoStack* pseudoStack);
+    void trace(JSTracer* trc);
 
     /*
      * Functions which are the actual instrumentation to track run information
      *
      *   - enter: a function has started to execute
      *   - updatePC: updates the pc information about where a function
      *               is currently executing
      *   - exit: this function has ceased execution, and no further
      *           entries/exits will be made
      */
     bool enter(JSContext* cx, JSScript* script, JSFunction* maybeFun);
     void exit(JSScript* script, JSFunction* maybeFun);
-    void updatePC(JSScript* script, jsbytecode* pc) {
-        if (!enabled())
-            return;
+    inline void updatePC(JSContext* cx, JSScript* script, jsbytecode* pc);
+};
+
+class GeckoProfilerRuntime
+{
+    JSRuntime*           rt;
+    ExclusiveData<ProfileStringMap> strings;
+    bool                 slowAssertions;
+    uint32_t             enabled_;
+    void                (*eventMarker_)(const char*);
 
-        uint32_t sp = pseudoStack_->stackPointer;
-        if (sp - 1 < PseudoStack::MaxEntries) {
-            MOZ_ASSERT(sp > 0);
-            MOZ_ASSERT(pseudoStack_->entries[sp - 1].rawScript() == script);
-            pseudoStack_->entries[sp - 1].setPC(pc);
-        }
-    }
+    UniqueChars allocProfileString(JSScript* script, JSFunction* function);
+
+  public:
+    explicit GeckoProfilerRuntime(JSRuntime* rt);
+
+    bool init();
 
-    void setProfilingStack(PseudoStack* pseudoStack);
+    /* management of whether instrumentation is on or off */
+    bool enabled() { return enabled_; }
+    void enable(bool enabled);
+    void enableSlowAssertions(bool enabled) { slowAssertions = enabled; }
+    bool slowAssertionsEnabled() { return slowAssertions; }
+
     void setEventMarker(void (*fn)(const char*));
     const char* profileString(JSScript* script, JSFunction* maybeFun);
     void onScriptFinalized(JSScript* script);
 
     void markEvent(const char* event);
 
     /* meant to be used for testing, not recommended to call in normal code */
     size_t stringsCount();
     void stringsReset();
 
     uint32_t* addressOfEnabled() {
         return &enabled_;
     }
 
-    void trace(JSTracer* trc);
     void fixupStringsMapAfterMovingGC();
 #ifdef JSGC_HASH_TABLE_CHECKS
     void checkStringsMapAfterMovingGC();
 #endif
 };
 
 inline size_t
-GeckoProfiler::stringsCount()
+GeckoProfilerRuntime::stringsCount()
 {
     return strings.lock()->count();
 }
 
 inline void
-GeckoProfiler::stringsReset()
+GeckoProfilerRuntime::stringsReset()
 {
     strings.lock()->clear();
 }
 
 /*
  * This class is used in RunScript() to push the marker onto the sampling stack
  * that we're about to enter JS function calls. This is the only time in which a
  * valid stack pointer is pushed to the sampling stack.
  */
 class MOZ_RAII GeckoProfilerEntryMarker
 {
   public:
-    explicit GeckoProfilerEntryMarker(JSRuntime* rt,
+    explicit GeckoProfilerEntryMarker(JSContext* cx,
                                       JSScript* script
                                       MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
     ~GeckoProfilerEntryMarker();
 
   private:
-    GeckoProfiler* profiler;
+    GeckoProfilerThread* profiler;
     mozilla::DebugOnly<uint32_t> spBefore_;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 /*
  * RAII class to automatically add Gecko Profiler pseudo frame entries.
  *
  * NB: The `label` string must be statically allocated.
  */
 class MOZ_NONHEAP_CLASS AutoGeckoProfilerEntry
 {
   public:
-    explicit AutoGeckoProfilerEntry(JSRuntime* rt, const char* label,
+    explicit AutoGeckoProfilerEntry(JSContext* cx, const char* label,
                                     ProfileEntry::Category category = ProfileEntry::Category::JS
                                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
     ~AutoGeckoProfilerEntry();
 
   private:
-    GeckoProfiler* profiler_;
+    GeckoProfilerThread* profiler_;
     mozilla::DebugOnly<uint32_t> spBefore_;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 /*
  * This class is used in the interpreter to bound regions where the baseline JIT
  * being entered via OSR.  It marks the current top pseudostack entry as
  * OSR-ed
  */
 class MOZ_RAII GeckoProfilerBaselineOSRMarker
 {
   public:
-    explicit GeckoProfilerBaselineOSRMarker(JSRuntime* rt, bool hasProfilerFrame
+    explicit GeckoProfilerBaselineOSRMarker(JSContext* cx, bool hasProfilerFrame
                                             MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
     ~GeckoProfilerBaselineOSRMarker();
 
   private:
-    GeckoProfiler* profiler;
+    GeckoProfilerThread* profiler;
     mozilla::DebugOnly<uint32_t> spBefore_;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 /*
  * This class manages the instrumentation portion of the profiling for JIT
  * code.
  *
@@ -267,26 +267,26 @@ class MOZ_RAII GeckoProfilerBaselineOSRM
  * manages the instrumentation which needs to be attached to them as well.
  *
  * The basic methods which emit instrumentation are at the end of this class,
  * and the management functions are all described in the middle.
  */
 template<class Assembler, class Register>
 class GeckoProfilerInstrumentation
 {
-    GeckoProfiler* profiler_; // Instrumentation location management
+    GeckoProfilerRuntime* profiler_; // Instrumentation location management
 
   public:
     /*
      * Creates instrumentation which writes information out the the specified
      * profiler's stack and constituent fields.
      */
-    explicit GeckoProfilerInstrumentation(GeckoProfiler* profiler) : profiler_(profiler) {}
+    explicit GeckoProfilerInstrumentation(GeckoProfilerRuntime* profiler) : profiler_(profiler) {}
 
     /* Small proxies around GeckoProfiler */
     bool enabled() { return profiler_ && profiler_->enabled(); }
-    GeckoProfiler* profiler() { MOZ_ASSERT(enabled()); return profiler_; }
+    GeckoProfilerRuntime* profiler() { MOZ_ASSERT(enabled()); return profiler_; }
     void disable() { profiler_ = nullptr; }
 };
 
 } /* namespace js */
 
 #endif /* vm_GeckoProfiler_h */
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -51,16 +51,17 @@
 #include "jsatominlines.h"
 #include "jsboolinlines.h"
 #include "jsfuninlines.h"
 #include "jsscriptinlines.h"
 
 #include "jit/JitFrames-inl.h"
 #include "vm/Debugger-inl.h"
 #include "vm/EnvironmentObject-inl.h"
+#include "vm/GeckoProfiler-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/Probes-inl.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::ArrayLength;
@@ -373,17 +374,17 @@ js::RunScript(JSContext* cx, RunState& s
 
     if (!Debugger::checkNoExecute(cx, state.script()))
         return false;
 
 #if defined(MOZ_HAVE_RDTSC)
     js::AutoStopwatch stopwatch(cx);
 #endif // defined(MOZ_HAVE_RDTSC)
 
-    GeckoProfilerEntryMarker marker(cx->runtime(), state.script());
+    GeckoProfilerEntryMarker marker(cx, state.script());
 
     state.script()->ensureNonLazyCanonicalFunction();
 
     if (jit::IsIonEnabled(cx)) {
         jit::MethodStatus status = jit::CanEnter(cx, state);
         if (status == jit::Method_Error)
             return false;
         if (status == jit::Method_Compiled) {
@@ -2012,31 +2013,31 @@ CASE(JSOP_LOOPENTRY)
         jit::MethodStatus status = jit::CanEnterBaselineAtBranch(cx, REGS.fp(), false);
         if (status == jit::Method_Error)
             goto error;
         if (status == jit::Method_Compiled) {
             bool wasProfiler = REGS.fp()->hasPushedGeckoProfilerFrame();
 
             jit::JitExecStatus maybeOsr;
             {
-                GeckoProfilerBaselineOSRMarker osr(cx->runtime(), wasProfiler);
+                GeckoProfilerBaselineOSRMarker osr(cx, wasProfiler);
                 maybeOsr = jit::EnterBaselineAtBranch(cx, REGS.fp(), REGS.pc);
             }
 
             // We failed to call into baseline at all, so treat as an error.
             if (maybeOsr == jit::JitExec_Aborted)
                 goto error;
 
             interpReturnOK = (maybeOsr == jit::JitExec_Ok);
 
             // Pop the profiler frame pushed by the interpreter.  (The compiled
             // version of the function popped a copy of the frame pushed by the
             // OSR trampoline.)
             if (wasProfiler)
-                cx->runtime()->geckoProfiler().exit(script, script->functionNonDelazifying());
+                cx->geckoProfiler().exit(script, script->functionNonDelazifying());
 
             if (activation.entryFrame() != REGS.fp())
                 goto jit_return_pop_frame;
             goto leave_on_safe_point;
         }
     }
 END_CASE(JSOP_LOOPENTRY)
 
@@ -2987,17 +2988,17 @@ CASE(JSOP_STRICTEVAL)
     TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]);
 }
 END_CASE(JSOP_EVAL)
 
 CASE(JSOP_SPREADNEW)
 CASE(JSOP_SPREADCALL)
 CASE(JSOP_SPREADSUPERCALL)
     if (REGS.fp()->hasPushedGeckoProfilerFrame())
-        cx->runtime()->geckoProfiler().updatePC(script, REGS.pc);
+        cx->geckoProfiler().updatePC(cx, script, REGS.pc);
     /* FALL THROUGH */
 
 CASE(JSOP_SPREADEVAL)
 CASE(JSOP_STRICTSPREADEVAL)
 {
     static_assert(JSOP_SPREADEVAL_LENGTH == JSOP_STRICTSPREADEVAL_LENGTH,
                   "spreadeval and strictspreadeval must be the same size");
     bool construct = JSOp(*REGS.pc) == JSOP_SPREADNEW || JSOp(*REGS.pc) == JSOP_SPREADSUPERCALL;;
@@ -3033,17 +3034,17 @@ CASE(JSOP_FUNAPPLY)
 CASE(JSOP_NEW)
 CASE(JSOP_CALL)
 CASE(JSOP_CALL_IGNORES_RV)
 CASE(JSOP_CALLITER)
 CASE(JSOP_SUPERCALL)
 CASE(JSOP_FUNCALL)
 {
     if (REGS.fp()->hasPushedGeckoProfilerFrame())
-        cx->runtime()->geckoProfiler().updatePC(script, REGS.pc);
+        cx->geckoProfiler().updatePC(cx, script, REGS.pc);
 
     MaybeConstruct construct = MaybeConstruct(*REGS.pc == JSOP_NEW || *REGS.pc == JSOP_SUPERCALL);
     bool ignoresReturnValue = *REGS.pc == JSOP_CALL_IGNORES_RV;
     unsigned argStackSlots = GET_ARGC(REGS.pc) + construct;
 
     MOZ_ASSERT(REGS.stackDepth() >= 2u + GET_ARGC(REGS.pc));
     CallArgs args = CallArgsFromSp(argStackSlots, REGS.sp, construct, ignoresReturnValue);
 
--- a/js/src/vm/Probes-inl.h
+++ b/js/src/vm/Probes-inl.h
@@ -36,17 +36,17 @@ probes::EnterScript(JSContext* cx, JSScr
 {
 #ifdef INCLUDE_MOZILLA_DTRACE
     if (JAVASCRIPT_FUNCTION_ENTRY_ENABLED())
         DTraceEnterJSFun(cx, maybeFun, script);
 #endif
 
     JSRuntime* rt = cx->runtime();
     if (rt->geckoProfiler().enabled()) {
-        if (!rt->geckoProfiler().enter(cx, script, maybeFun))
+        if (!cx->geckoProfiler().enter(cx, script, maybeFun))
             return false;
         MOZ_ASSERT_IF(!fp->script()->isStarGenerator() &&
                       !fp->script()->isLegacyGenerator() &&
                       !fp->script()->isAsync(),
                       !fp->hasPushedGeckoProfilerFrame());
         fp->setPushedGeckoProfilerFrame();
     }
 
@@ -57,17 +57,17 @@ inline void
 probes::ExitScript(JSContext* cx, JSScript* script, JSFunction* maybeFun, bool popProfilerFrame)
 {
 #ifdef INCLUDE_MOZILLA_DTRACE
     if (JAVASCRIPT_FUNCTION_RETURN_ENABLED())
         DTraceExitJSFun(cx, maybeFun, script);
 #endif
 
     if (popProfilerFrame)
-        cx->runtime()->geckoProfiler().exit(script, maybeFun);
+        cx->geckoProfiler().exit(script, maybeFun);
 }
 
 inline bool
 probes::StartExecution(JSScript* script)
 {
     bool ok = true;
 
 #ifdef INCLUDE_MOZILLA_DTRACE
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -312,18 +312,16 @@ JSRuntime::destroyRuntime()
         profilerSampleBufferGen_ = UINT32_MAX;
 
         JS::PrepareForFullGC(cx);
         gc.gc(GC_NORMAL, JS::gcreason::DESTROY_RUNTIME);
     }
 
     AutoNoteSingleThreadedRegion anstr;
 
-    MOZ_ASSERT_IF(!geckoProfiler().enabled(), !singleThreadedExecutionRequired_);
-
     MOZ_ASSERT(!hasHelperThreadZones());
     AutoLockForExclusiveAccess lock(this);
 
     /*
      * Even though all objects in the compartment are dead, we may have keep
      * some filenames around because of gcKeepAtoms.
      */
     FreeScriptData(this, lock);
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -504,19 +504,19 @@ struct JSRuntime : public js::MallocProv
     js::ActiveThreadData<JSDestroyPrincipalsOp> destroyPrincipals;
     js::ActiveThreadData<JSReadPrincipalsOp> readPrincipals;
 
     /* Optional warning reporter. */
     js::ActiveThreadData<JS::WarningReporter> warningReporter;
 
   private:
     /* Gecko profiling metadata */
-    js::UnprotectedData<js::GeckoProfiler> geckoProfiler_;
+    js::UnprotectedData<js::GeckoProfilerRuntime> geckoProfiler_;
   public:
-    js::GeckoProfiler& geckoProfiler() { return geckoProfiler_.ref(); }
+    js::GeckoProfilerRuntime& geckoProfiler() { return geckoProfiler_.ref(); }
 
     // Heap GC roots for PersistentRooted pointers.
     js::ActiveThreadData<mozilla::EnumeratedArray<JS::RootKind, JS::RootKind::Limit,
                                                  mozilla::LinkedList<JS::PersistentRooted<void*>>>> heapRoots;
 
     void tracePersistentRoots(JSTracer* trc);
     void finishPersistentRoots();
 
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -1168,17 +1168,17 @@ SavedStacks::saveCurrentStack(JSContext*
         cx->isExceptionPending() ||
         !cx->global() ||
         !cx->global()->isStandardClassResolved(JSProto_Object))
     {
         frame.set(nullptr);
         return true;
     }
 
-    AutoGeckoProfilerEntry psuedoFrame(cx->runtime(), "js::SavedStacks::saveCurrentStack");
+    AutoGeckoProfilerEntry psuedoFrame(cx, "js::SavedStacks::saveCurrentStack");
     FrameIter iter(cx);
     return insertFrames(cx, iter, frame, mozilla::Move(capture));
 }
 
 bool
 SavedStacks::copyAsyncStack(JSContext* cx, HandleObject asyncStack, HandleString asyncCause,
                             MutableHandleSavedFrame adoptedStack, uint32_t maxFrameCount)
 {
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -47,17 +47,17 @@ class JS_PUBLIC_API(AutoEntryMonitor);
 
 namespace js {
 
 class InterpreterRegs;
 class CallObject;
 class FrameIter;
 class EnvironmentObject;
 class ScriptFrameIter;
-class GeckoProfiler;
+class GeckoProfilerRuntime;
 class InterpreterFrame;
 class LexicalEnvironmentObject;
 class EnvironmentIter;
 class EnvironmentCoordinate;
 
 class SavedFrame;
 
 namespace jit {
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -580,17 +580,17 @@ JSRope::flattenInternal(JSContext* maybe
     return flattenInternal<b, Latin1Char>(maybecx);
 }
 
 JSFlatString*
 JSRope::flatten(JSContext* maybecx)
 {
     mozilla::Maybe<AutoGeckoProfilerEntry> entry;
     if (maybecx && !maybecx->helperThread())
-        entry.emplace(maybecx->runtime(), "JSRope::flatten");
+        entry.emplace(maybecx, "JSRope::flatten");
 
     if (zone()->needsIncrementalBarrier())
         return flattenInternal<WithIncrementalBarrier>(maybecx);
     return flattenInternal<NoBarrier>(maybecx);
 }
 
 template <AllowGC allowGC>
 static JSLinearString*