From 6079c8fdb46396eb5ff0ba9e7aaa1c5b606e1fcd Mon Sep 17 00:00:00 2001 From: win7-7 Date: Mon, 5 Jan 2026 20:02:44 +0200 Subject: [PATCH] 903519 - Implement AutoSuppressNurseryCellAlloc to avoid nursery allocation just before offthread parse startup 903519 - Implement AutoSuppressNurseryCellAlloc to avoid nursery allocation just before offthread parse startup --- js/src/gc/Allocator.cpp | 15 ++++++++----- js/src/jscntxt.h | 44 +++++++++++++++++++++++++++++++++---- js/src/vm/HelperThreads.cpp | 14 +++++++++++- js/src/vm/String.cpp | 3 ++- 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/js/src/gc/Allocator.cpp b/js/src/gc/Allocator.cpp index 25ff3b0230..5dc8caeea6 100644 --- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -39,8 +39,9 @@ js::Allocate(ExclusiveContext* cx, AllocKind kind, size_t nDynamicSlots, Initial MOZ_ASSERT_IF(nDynamicSlots != 0, clasp->isNative() || clasp->isProxy()); - // Off-main-thread alloc cannot trigger GC or make runtime assertions. - if (!cx->isJSContext()) { + // We cannot trigger GC or make runtime assertions when nursery allocation + // is suppressed, either explicitly or because we are off-thread. + if (cx->isNurseryAllocSuppressed()) { JSObject* obj = GCRuntime::tryNewTenuredObject(cx, kind, thingSize, nDynamicSlots); if (MOZ_UNLIKELY(allowGC && !obj)) ReportOutOfMemory(cx); @@ -81,8 +82,9 @@ template JSObject* GCRuntime::tryNewNurseryObject(JSContext* cx, size_t thingSize, size_t nDynamicSlots, const Class* clasp) { - MOZ_ASSERT(isNurseryAllocAllowed()); - MOZ_ASSERT(!cx->zone()->usedByExclusiveThread); + MOZ_ASSERT(cx->isNurseryAllocAllowed()); + MOZ_ASSERT(!cx->helperThread()); + MOZ_ASSERT(!cx->isNurseryAllocSuppressed()); MOZ_ASSERT(!IsAtomsCompartment(cx->compartment())); JSObject* obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp); if (obj) @@ -133,7 +135,8 @@ GCRuntime::tryNewNurseryString(JSContext* cx, size_t thingSize, AllocKind kind) { MOZ_ASSERT(IsNurseryAllocable(kind)); MOZ_ASSERT(cx->isNurseryAllocAllowed()); - MOZ_ASSERT(!cx->isJSContext()); + MOZ_ASSERT(!cx->helperThread()); + MOZ_ASSERT(!cx->isNurseryAllocSuppressed()); MOZ_ASSERT(!IsAtomsCompartment(cx->compartment())); Cell* cell = cx->nursery().allocateString(cx, cx->zone(), thingSize, kind); @@ -162,7 +165,7 @@ js::AllocateString(JSContext* cx, InitialHeap heap) MOZ_ASSERT(size == sizeof(JSString) || size == sizeof(JSFatInlineString)); // Off-thread alloc cannot trigger GC or make runtime assertions. - if (cx->isJSContext()) { + if (cx->isNurseryAllocSuppressed()) { StringAllocT* str = GCRuntime::tryNewTenuredThing(cx, kind, size); if (MOZ_UNLIKELY(allowGC && !str)) ReportOutOfMemory(cx); diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 5c762bd032..4a04857040 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -33,6 +33,10 @@ class JitContext; class DebugModeOSRVolatileJitFrameIterator; } // namespace jit +namespace gc { +class AutoSuppressNurseryCellAlloc; +} + typedef HashSet ShapeSet; /* Detects cycles when traversing an object graph. */ @@ -113,8 +117,34 @@ enum class ContextKind // JSRuntime, so it's more efficient to use the base class. JSRuntime* const runtime_; - // The thread on which this context is running, if this is not a JSContext. - HelperThread* helperThread_; +/* + * A JSContext encapsulates the thread local state used when using the JS + * runtime. + */ +struct JSContext : public JS::RootingContext, + public js::MallocProvider +{ + JSContext(JSRuntime* runtime, const JS::ContextOptions& options); + ~JSContext(); + + bool init(js::ContextKind kind); + + private: + js::UnprotectedData runtime_; + js::WriteOnceData kind_; + + // System handle for the thread this context is associated with. + js::WriteOnceData threadNative_; + + // The thread on which this context is running, if this is performing a parse task. + js::ThreadLocalData helperThread_; + + friend class js::gc::AutoSuppressNurseryCellAlloc; + js::ThreadLocalData nurserySuppressions_; + + js::ThreadLocalData options_; + + js::ThreadLocalData arenas_; public: enum ContextKind { @@ -286,8 +316,14 @@ enum class ContextKind void setHelperThread(HelperThread* helperThread); HelperThread* helperThread() const { return helperThread_; } - // Threads with an ExclusiveContext may freely access any data in their - // compartment and zone. + void setHelperThread(js::HelperThread* helperThread); + js::HelperThread* helperThread() const { return helperThread_; } + + bool isNurseryAllocSuppressed() const { + return nurserySuppressions_; + } + + // Threads may freely access any data in their compartment and zone. JSCompartment* compartment() const { return compartment_; } diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index e8e1409d36..5ea4f941ea 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -643,7 +643,7 @@ StartOffThreadParseTask(JSContext* cx, const ReadOnlyCompileOptions& options, // Suppress GC so that calls below do not trigger a new incremental GC // which could require barriers on the atoms compartment. gc::AutoSuppressGC nogc(cx); - gc::AutoAssertNoNurseryAlloc noNurseryAlloc(cx->runtime()); + gc::AutoSuppressNurseryCellAlloc noNurseryAlloc(cx); AutoSuppressAllocationMetadataBuilder suppressMetadata(cx); Maybe clearUseGuard; @@ -1931,6 +1931,18 @@ HelperThread::handleGCHelperWorkload(AutoLockHelperThreadState& locked) HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked); } +void +JSContext::setHelperThread(HelperThread* thread) +{ + if (helperThread_) + nurserySuppressions_--; + + helperThread_ = thread; + + if (helperThread_) + nurserySuppressions_++; +} + void HelperThread::threadLoop() { diff --git a/js/src/vm/String.cpp b/js/src/vm/String.cpp index 036ebba770..b49863ec85 100644 --- a/js/src/vm/String.cpp +++ b/js/src/vm/String.cpp @@ -21,6 +21,7 @@ #include "jscntxtinlines.h" #include "jscompartmentinlines.h" + using namespace js; using mozilla::IsSame; @@ -636,7 +637,7 @@ js::ConcatStrings(ExclusiveContext* cx, bool canUseInline = isLatin1 ? JSInlineString::lengthFits(wholeLength) : JSInlineString::lengthFits(wholeLength); - if (canUseInline && cx->isJSContext()) { + if (canUseInline) { Latin1Char* latin1Buf = nullptr; // initialize to silence GCC warning char16_t* twoByteBuf = nullptr; // initialize to silence GCC warning JSInlineString* str = isLatin1