903519 - Implement AutoSuppressNurseryCellAlloc to avoid nursery allocation just before offthread parse startup

903519 - Implement AutoSuppressNurseryCellAlloc to avoid nursery allocation just before offthread parse startup
This commit is contained in:
win7-7 2026-01-05 20:02:44 +02:00 committed by wuggy
commit 6079c8fdb4
4 changed files with 64 additions and 12 deletions

View file

@ -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<NoGC>(cx, kind, thingSize, nDynamicSlots);
if (MOZ_UNLIKELY(allowGC && !obj))
ReportOutOfMemory(cx);
@ -81,8 +82,9 @@ template <AllowGC allowGC>
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<StringAllocT, NoGC>(cx, kind, size);
if (MOZ_UNLIKELY(allowGC && !str))
ReportOutOfMemory(cx);

View file

@ -33,6 +33,10 @@ class JitContext;
class DebugModeOSRVolatileJitFrameIterator;
} // namespace jit
namespace gc {
class AutoSuppressNurseryCellAlloc;
}
typedef HashSet<Shape*> 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>
{
JSContext(JSRuntime* runtime, const JS::ContextOptions& options);
~JSContext();
bool init(js::ContextKind kind);
private:
js::UnprotectedData<JSRuntime*> runtime_;
js::WriteOnceData<js::ContextKind> kind_;
// System handle for the thread this context is associated with.
js::WriteOnceData<size_t> threadNative_;
// The thread on which this context is running, if this is performing a parse task.
js::ThreadLocalData<js::HelperThread*> helperThread_;
friend class js::gc::AutoSuppressNurseryCellAlloc;
js::ThreadLocalData<size_t> nurserySuppressions_;
js::ThreadLocalData<JS::ContextOptions> options_;
js::ThreadLocalData<js::gc::ArenaLists*> 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_;
}

View file

@ -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<AutoClearUsedByHelperThread> 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()
{

View file

@ -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<Latin1Char>(wholeLength)
: JSInlineString::lengthFits<char16_t>(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