diff --git a/js/public/ProfilingFrameIterator.h b/js/public/ProfilingFrameIterator.h index e4ad7453c028..47553371772b 100644 --- a/js/public/ProfilingFrameIterator.h +++ b/js/public/ProfilingFrameIterator.h @@ -46,7 +46,7 @@ struct ForEachTrackedOptimizationTypeInfoOp; // contents to become out of date. class JS_PUBLIC_API(ProfilingFrameIterator) { - JSRuntime* rt_; + JSContext* cx_; uint32_t sampleBufferGen_; js::Activation* activation_; diff --git a/js/src/jit/CompileWrappers.cpp b/js/src/jit/CompileWrappers.cpp index 68ef10babff8..2366755877ee 100644 --- a/js/src/jit/CompileWrappers.cpp +++ b/js/src/jit/CompileWrappers.cpp @@ -94,7 +94,7 @@ CompileRuntime::wellKnownSymbols() const void* CompileRuntime::addressOfActiveJSContext() { - return &runtime()->activeContext; + return runtime()->addressOfActiveContext(); } #ifdef DEBUG diff --git a/js/src/jit/JitFrameIterator.h b/js/src/jit/JitFrameIterator.h index 1c907f97339f..b5bbc4d3376c 100644 --- a/js/src/jit/JitFrameIterator.h +++ b/js/src/jit/JitFrameIterator.h @@ -284,7 +284,7 @@ class JitProfilingFrameIterator void moveToNextFrame(CommonFrameLayout* frame); public: - JitProfilingFrameIterator(JSRuntime* rt, + JitProfilingFrameIterator(JSContext* cx, const JS::ProfilingFrameIterator::RegisterState& state); explicit JitProfilingFrameIterator(void* exitFrame); diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index 395b9f9a7211..a01f6d41d933 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -2832,11 +2832,10 @@ JitFrameIterator::verifyReturnAddressUsingNativeToBytecodeMap() #endif // DEBUG JitProfilingFrameIterator::JitProfilingFrameIterator( - JSRuntime* rt, const JS::ProfilingFrameIterator::RegisterState& state) + JSContext* cx, const JS::ProfilingFrameIterator::RegisterState& state) { // If no profilingActivation is live, initialize directly to // end-of-iteration state. - JSContext* cx = rt->unsafeContextFromAnyThread(); if (!cx->profilingActivation()) { type_ = JitFrame_Entry; fp_ = nullptr; @@ -2862,7 +2861,7 @@ JitProfilingFrameIterator::JitProfilingFrameIterator( fp_ = (uint8_t*) act->lastProfilingFrame(); void* lastCallSite = act->lastProfilingCallSite(); - JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable(); + JitcodeGlobalTable* table = cx->runtime()->jitRuntime()->getJitcodeGlobalTable(); // Profiler sampling must NOT be suppressed if we are here. MOZ_ASSERT(cx->isProfilerSamplingEnabled()); @@ -2872,7 +2871,7 @@ JitProfilingFrameIterator::JitProfilingFrameIterator( return; // Try initializing with sampler pc using native=>bytecode table. - if (tryInitWithTable(table, state.pc, rt, /* forLastCallSite = */ false)) + if (tryInitWithTable(table, state.pc, cx->runtime(), /* forLastCallSite = */ false)) return; // Try initializing with lastProfilingCallSite pc @@ -2881,7 +2880,7 @@ JitProfilingFrameIterator::JitProfilingFrameIterator( return; // Try initializing with lastProfilingCallSite pc using native=>bytecode table. - if (tryInitWithTable(table, lastCallSite, rt, /* forLastCallSite = */ true)) + if (tryInitWithTable(table, lastCallSite, cx->runtime(), /* forLastCallSite = */ true)) return; } diff --git a/js/src/jit/JitcodeMap.cpp b/js/src/jit/JitcodeMap.cpp index d009c850c0bd..40a3a170cba5 100644 --- a/js/src/jit/JitcodeMap.cpp +++ b/js/src/jit/JitcodeMap.cpp @@ -25,6 +25,7 @@ #include "jsscriptinlines.h" +#include "vm/GeckoProfiler-inl.h" #include "vm/TypeInference-inl.h" using mozilla::Maybe; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index cc0b88777655..011b45d90a06 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -243,6 +243,7 @@ #include "jsscriptinlines.h" #include "gc/Heap-inl.h" +#include "vm/GeckoProfiler-inl.h" #include "vm/Stack-inl.h" #include "vm/String-inl.h" @@ -5517,7 +5518,7 @@ GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget, // middle of relocating an arena, invalid JSScript pointers may be // accessed. Suppress all sampling until a finer-grained solution can be // found. See bug 1295775. - AutoSuppressProfilerSampling suppressSampling(rt); + AutoSuppressProfilerSampling suppressSampling(TlsContext.get()); ZoneList relocatedZones; Arena* relocatedArenas = nullptr; diff --git a/js/src/threading/ProtectedData.cpp b/js/src/threading/ProtectedData.cpp index c186f95356c0..b8ff624ee286 100644 --- a/js/src/threading/ProtectedData.cpp +++ b/js/src/threading/ProtectedData.cpp @@ -47,7 +47,7 @@ CheckActiveThread::check() const return; JSContext* cx = TlsContext.get(); - MOZ_ASSERT(cx == cx->runtime()->activeContext); + MOZ_ASSERT(cx == cx->runtime()->activeContext()); #endif // XP_WIN } diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 843922596e39..d8de18526cc9 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -45,6 +45,7 @@ #include "jsopcodeinlines.h" #include "jsscriptinlines.h" +#include "vm/GeckoProfiler-inl.h" #include "vm/NativeObject-inl.h" #include "vm/Stack-inl.h" diff --git a/js/src/vm/GeckoProfiler-inl.h b/js/src/vm/GeckoProfiler-inl.h new file mode 100644 index 000000000000..5cd26276f504 --- /dev/null +++ b/js/src/vm/GeckoProfiler-inl.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef vm_GeckoProfiler_inl_h +#define vm_GeckoProfiler_inl_h + +#include "vm/GeckoProfiler.h" + +#include "vm/Runtime.h" + +namespace js { + +/* + * 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); + + ~AutoSuppressProfilerSampling(); + + private: + JSContext* cx_; + bool previouslyEnabled_; + JSRuntime::AutoProhibitActiveContextChange prohibitContextChange_; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +} // namespace js + +#endif // vm_GeckoProfiler_inl_h diff --git a/js/src/vm/GeckoProfiler.cpp b/js/src/vm/GeckoProfiler.cpp index 5f16a165dcf4..d9cc127a3675 100644 --- a/js/src/vm/GeckoProfiler.cpp +++ b/js/src/vm/GeckoProfiler.cpp @@ -4,7 +4,7 @@ * 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 "vm/GeckoProfiler.h" +#include "vm/GeckoProfiler-inl.h" #include "mozilla/DebugOnly.h" @@ -86,10 +86,13 @@ GeckoProfiler::enable(bool enabled) rt->resetProfilerSampleBufferGen(); rt->resetProfilerSampleBufferLapCount(); - // Ensure that lastProfilingFrame is null before 'enabled' becomes true. - if (rt->contextFromMainThread()->jitActivation) { - rt->contextFromMainThread()->jitActivation->setLastProfilingFrame(nullptr); - rt->contextFromMainThread()->jitActivation->setLastProfilingCallSite(nullptr); + // Ensure that lastProfilingFrame is null for all threads before 'enabled' becomes true. + for (size_t i = 0; i < rt->cooperatingContexts().length(); i++) { + JSContext* cx = rt->cooperatingContexts()[i]; + if (cx->jitActivation) { + cx->jitActivation->setLastProfilingFrame(nullptr); + cx->jitActivation->setLastProfilingCallSite(nullptr); + } } enabled_ = enabled; @@ -104,24 +107,27 @@ GeckoProfiler::enable(bool enabled) /* Update lastProfilingFrame to point to the top-most JS jit-frame currently on * stack. */ - if (rt->contextFromMainThread()->jitActivation) { - // Walk through all activations, and set their lastProfilingFrame appropriately. - if (enabled) { - void* lastProfilingFrame = GetTopProfilingJitFrame(rt->contextFromMainThread()->jitTop); - jit::JitActivation* jitActivation = rt->contextFromMainThread()->jitActivation; - while (jitActivation) { - jitActivation->setLastProfilingFrame(lastProfilingFrame); - jitActivation->setLastProfilingCallSite(nullptr); + for (size_t i = 0; i < rt->cooperatingContexts().length(); i++) { + JSContext* cx = rt->cooperatingContexts()[i]; + if (cx->jitActivation) { + // Walk through all activations, and set their lastProfilingFrame appropriately. + if (enabled) { + void* lastProfilingFrame = GetTopProfilingJitFrame(cx->jitTop); + jit::JitActivation* jitActivation = cx->jitActivation; + while (jitActivation) { + jitActivation->setLastProfilingFrame(lastProfilingFrame); + jitActivation->setLastProfilingCallSite(nullptr); - lastProfilingFrame = GetTopProfilingJitFrame(jitActivation->prevJitTop()); - jitActivation = jitActivation->prevJitActivation(); - } - } else { - jit::JitActivation* jitActivation = rt->contextFromMainThread()->jitActivation; - while (jitActivation) { - jitActivation->setLastProfilingFrame(nullptr); - jitActivation->setLastProfilingCallSite(nullptr); - jitActivation = jitActivation->prevJitActivation(); + lastProfilingFrame = GetTopProfilingJitFrame(jitActivation->prevJitTop()); + jitActivation = jitActivation->prevJitActivation(); + } + } else { + jit::JitActivation* jitActivation = cx->jitActivation; + while (jitActivation) { + jitActivation->setLastProfilingFrame(nullptr); + jitActivation->setLastProfilingCallSite(nullptr); + jitActivation = jitActivation->prevJitActivation(); + } } } } @@ -495,8 +501,12 @@ ProfileEntry::script() const volatile // If profiling is supressed then we can't trust the script pointers to be // valid as they could be in the process of being moved by a compacting GC // (although it's still OK to get the runtime from them). - JSRuntime* rt = script->zoneFromAnyThread()->runtimeFromAnyThread(); - if (!rt->unsafeContextFromAnyThread()->isProfilerSamplingEnabled()) + // + // We only need to check the active context here, as + // AutoSuppressProfilerSampling prohibits the runtime's active context from + // being changed while it exists. + JSContext* cx = script->runtimeFromAnyThread()->activeContext(); + if (!cx->isProfilerSamplingEnabled()) return nullptr; MOZ_ASSERT(!IsForwarded(script)); @@ -550,28 +560,19 @@ js::ProfilingGetPC(JSContext* cx, JSScript* script, void* ip) AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) - : rt_(cx->runtime()), - previouslyEnabled_(cx->isProfilerSamplingEnabled()) + : cx_(cx), + previouslyEnabled_(cx->isProfilerSamplingEnabled()), + prohibitContextChange_(cx->runtime()) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; if (previouslyEnabled_) - rt_->contextFromMainThread()->disableProfilerSampling(); -} - -AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSRuntime* rt - MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) - : rt_(rt), - previouslyEnabled_(rt_->contextFromMainThread()->isProfilerSamplingEnabled()) -{ - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - if (previouslyEnabled_) - rt_->contextFromMainThread()->disableProfilerSampling(); + cx_->disableProfilerSampling(); } AutoSuppressProfilerSampling::~AutoSuppressProfilerSampling() { if (previouslyEnabled_) - rt_->contextFromMainThread()->enableProfilerSampling(); + cx_->enableProfilerSampling(); } void* diff --git a/js/src/vm/GeckoProfiler.h b/js/src/vm/GeckoProfiler.h index 4bd9b1df7a08..36637ad4491b 100644 --- a/js/src/vm/GeckoProfiler.h +++ b/js/src/vm/GeckoProfiler.h @@ -217,24 +217,6 @@ class GeckoProfiler #endif }; -/* - * 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); - explicit AutoSuppressProfilerSampling(JSRuntime* rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM); - - ~AutoSuppressProfilerSampling(); - - private: - JSRuntime* rt_; - bool previouslyEnabled_; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER -}; - inline size_t GeckoProfiler::stringsCount() { diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index da6bb6e6eade..1c221d7c84c0 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -95,7 +95,8 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime) #ifdef DEBUG updateChildRuntimeCount(parentRuntime), #endif - activeContext(nullptr), + activeContext_(nullptr), + activeContextChangeProhibited_(0), profilerSampleBufferGen_(0), profilerSampleBufferLapCount_(1), telemetryCallback(nullptr), @@ -192,7 +193,9 @@ JSRuntime::init(JSContext* cx, uint32_t maxbytes, uint32_t maxNurseryBytes) if (CanUseExtraThreads() && !EnsureHelperThreadsInitialized()) return false; - activeContext = cx; + activeContext_ = cx; + if (!cooperatingContexts().append(cx)) + return false; singletonContext = cx; @@ -344,6 +347,15 @@ JSRuntime::destroyRuntime() js_delete(zoneGroupFromMainThread()); } +void +JSRuntime::setActiveContext(JSContext* cx) +{ + MOZ_ASSERT_IF(cx, isCooperatingContext(cx)); + MOZ_RELEASE_ASSERT(!activeContextChangeProhibited()); + + activeContext_ = cx; +} + void JSRuntime::addTelemetry(int id, uint32_t sample, const char* key) { diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 7b0799a8b890..7001a21768ab 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -297,10 +297,57 @@ struct JSRuntime : public js::MallocProvider AutoUpdateChildRuntimeCount updateChildRuntimeCount; #endif + private: // The context for the thread which currently has exclusive access to most // contents of the runtime. When execution on the runtime is cooperatively // scheduled, this is the thread which is currently running. - mozilla::Atomic activeContext; + mozilla::Atomic activeContext_; + + // All contexts participating in cooperative scheduling. All threads other + // than |activeContext_| are suspended. + js::ActiveThreadData> cooperatingContexts_; + + // Count of AutoProhibitActiveContextChange instances on the active context. + js::ActiveThreadData activeContextChangeProhibited_; + + public: + JSContext* activeContext() { return activeContext_; } + const void* addressOfActiveContext() { return &activeContext_; } + + void setActiveContext(JSContext* cx); + + js::Vector& cooperatingContexts() { + return cooperatingContexts_.ref(); + } + +#ifdef DEBUG + bool isCooperatingContext(JSContext* cx) { + for (size_t i = 0; i < cooperatingContexts().length(); i++) { + if (cooperatingContexts()[i] == cx) + return true; + } + return false; + } +#endif + + class MOZ_RAII AutoProhibitActiveContextChange + { + JSRuntime* rt; + + public: + explicit AutoProhibitActiveContextChange(JSRuntime* rt) + : rt(rt) + { + rt->activeContextChangeProhibited_++; + } + + ~AutoProhibitActiveContextChange() + { + rt->activeContextChangeProhibited_--; + } + }; + + bool activeContextChangeProhibited() { return activeContextChangeProhibited_; } /* * The profiler sampler generation after the latest sample. diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 3d9cc466e8e2..abd18398cf68 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -26,6 +26,7 @@ using namespace js; +using mozilla::ArrayLength; using mozilla::Maybe; using mozilla::PodCopy; @@ -1739,7 +1740,7 @@ ActivationIterator::settle() JS::ProfilingFrameIterator::ProfilingFrameIterator(JSContext* cx, const RegisterState& state, uint32_t sampleBufferGen) - : rt_(cx->runtime()), + : cx_(cx), sampleBufferGen_(sampleBufferGen), activation_(nullptr), savedPrevJitTop_(nullptr) @@ -1821,7 +1822,7 @@ JS::ProfilingFrameIterator::iteratorConstruct(const RegisterState& state) } MOZ_ASSERT(activation_->asJit()->isActive()); - new (storage_.addr()) jit::JitProfilingFrameIterator(rt_, state); + new (storage_.addr()) jit::JitProfilingFrameIterator(cx_, state); } void @@ -1899,9 +1900,9 @@ JS::ProfilingFrameIterator::getPhysicalFrameAndEntry(jit::JitcodeGlobalEntry* en // Look up an entry for the return address. void* returnAddr = jitIter().returnAddressToFp(); - jit::JitcodeGlobalTable* table = rt_->jitRuntime()->getJitcodeGlobalTable(); + jit::JitcodeGlobalTable* table = cx_->runtime()->jitRuntime()->getJitcodeGlobalTable(); if (hasSampleBufferGen()) - *entry = table->lookupForSamplerInfallible(returnAddr, rt_, sampleBufferGen_); + *entry = table->lookupForSamplerInfallible(returnAddr, cx_->runtime(), sampleBufferGen_); else *entry = table->lookupInfallible(returnAddr); @@ -1941,8 +1942,9 @@ JS::ProfilingFrameIterator::extractStack(Frame* frames, uint32_t offset, uint32_ // Extract the stack for the entry. Assume maximum inlining depth is <64 const char* labels[64]; - uint32_t depth = entry.callStackAtAddr(rt_, jitIter().returnAddressToFp(), labels, 64); - MOZ_ASSERT(depth < 64); + uint32_t depth = entry.callStackAtAddr(cx_->runtime(), jitIter().returnAddressToFp(), + labels, ArrayLength(labels)); + MOZ_ASSERT(depth < ArrayLength(labels)); for (uint32_t i = 0; i < depth; i++) { if (offset + i >= end) return i;