From c5d270f17298063eb7a315d34bf3dd6b03eba046 Mon Sep 17 00:00:00 2001 From: win7-7 Date: Wed, 14 Jan 2026 05:55:42 +0200 Subject: [PATCH] 58 GC no crashing. Compiles link no crashing. --- js/public/RootingAPI.h | 87 ++---------------- js/src/gc/Marking.cpp | 23 +++-- js/src/gc/Policy.h | 2 - js/src/gc/StoreBuffer.cpp | 6 +- js/src/gc/Zone.h | 3 + js/src/jsgc.cpp | 34 +++---- js/src/jsgc.h | 9 +- js/src/threading/ProtectedData.cpp | 139 +++++++++++++++++++++++++++++ js/src/vm/HelperThreads.cpp | 75 ++++++++++++---- js/src/vm/RegExpShared.h | 4 + js/src/vm/Runtime.cpp | 22 +++-- js/src/vm/Runtime.h | 60 +++++++++++-- 12 files changed, 320 insertions(+), 144 deletions(-) create mode 100644 js/src/threading/ProtectedData.cpp diff --git a/js/public/RootingAPI.h b/js/public/RootingAPI.h index 137a86e48f..37f67caa14 100644 --- a/js/public/RootingAPI.h +++ b/js/public/RootingAPI.h @@ -144,6 +144,12 @@ class PersistentRootedBase : public MutableWrappedPtrOperations {}; static void* const ConstNullValue = nullptr; +template +class FakeRooted; + +template +class FakeMutableHandle; + namespace gc { struct Cell; template @@ -889,64 +895,6 @@ class HandleBase : public WrappedPtrOperations as() const; }; -/** Interface substitute for Rooted which does not root the variable's memory. */ -template -class MOZ_RAII FakeRooted : public RootedBase> -{ - public: - using ElementType = T; - - template - explicit FakeRooted(CX* cx) : ptr(JS::GCPolicy::initial()) {} - - template - FakeRooted(CX* cx, T initial) : ptr(initial) {} - - DECLARE_POINTER_CONSTREF_OPS(T); - DECLARE_POINTER_ASSIGN_OPS(FakeRooted, T); - DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr); - DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(ptr); - - private: - T ptr; - - void set(const T& value) { - ptr = value; - } - - FakeRooted(const FakeRooted&) = delete; -}; - -/** Interface substitute for MutableHandle which is not required to point to rooted memory. */ -template -class FakeMutableHandle : public js::MutableHandleBase> -{ - public: - using ElementType = T; - - MOZ_IMPLICIT FakeMutableHandle(T* t) { - ptr = t; - } - - MOZ_IMPLICIT FakeMutableHandle(FakeRooted* root) { - ptr = root->address(); - } - - void set(const T& v) { - *ptr = v; - } - - DECLARE_POINTER_CONSTREF_OPS(T); - DECLARE_NONPOINTER_ACCESSOR_METHODS(*ptr); - DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(*ptr); - - private: - FakeMutableHandle() {} - DELETE_ASSIGNMENT_OPS(FakeMutableHandle, T); - - T* ptr; -}; - /** * Types for a variable that either should or shouldn't be rooted, depending on * the template parameter allowGC. Used for implementing functions that can @@ -986,27 +934,6 @@ template class MaybeRooted } }; -template class MaybeRooted -{ - public: - typedef const T& HandleType; - typedef FakeRooted RootType; - typedef FakeMutableHandle MutableHandleType; - - static JS::Handle toHandle(HandleType v) { - MOZ_CRASH("Bad conversion"); - } - - static JS::MutableHandle toMutableHandle(MutableHandleType v) { - MOZ_CRASH("Bad conversion"); - } - - template - static inline T2* downcastHandle(HandleType v) { - return &v->template as(); - } -}; - } /* namespace js */ namespace JS { @@ -1519,6 +1446,4 @@ operator!=(const T& a, std::nullptr_t b) { return !(a == b); } -#undef DELETE_ASSIGNMENT_OPS - #endif /* js_RootingAPI_h */ diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index a7670e59ac..cd82ff9a8e 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -2161,7 +2161,7 @@ GCMarker::enterWeakMarkingMode() if (weakMapAction() == ExpandWeakMaps) { tag_ = TracerKindTag::WeakMarking; - for (SweepGroupZonesIter zone(runtime()); !zone.done(); zone.next()) { + for (GCSweepGroupIter zone(runtime()); !zone.done(); zone.next()) { for (WeakMapBase* m : zone->gcWeakMapList()) { if (m->marked) (void) m->traceEntries(this); @@ -2610,12 +2610,25 @@ js::TenuringTracer::moveToTenuredSlow(JSObject* src) // shape list. This is updated in Nursery::sweepDictionaryModeObjects(). } - JSObjectMovedOp op = dst->getClass()->extObjectMovedOp(); - MOZ_ASSERT_IF(src->is(), op == proxy_ObjectMoved); - if (op) { + if (src->is()) { + InlineTypedObject::objectMovedDuringMinorGC(this, dst, src); + } else if (src->is()) { + tenuredSize += TypedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind); + } else if (src->is()) { + tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind); + } else if (src->is()) { + tenuredSize += ArgumentsObject::objectMovedDuringMinorGC(this, dst, src); + } else if (src->is()) { + // Objects in the nursery are never swapped so the proxy must have an + // inline ProxyValueArray. + MOZ_ASSERT(src->as().usingInlineValueArray()); + dst->as().setInlineValueArray(); + if (JSObjectMovedOp op = dst->getClass()->extObjectMovedOp()) + op(dst, src); + } else if (JSObjectMovedOp op = dst->getClass()->extObjectMovedOp()) { // Tell the hazard analysis that the object moved hook can't GC. JS::AutoSuppressGCAnalysis nogc; - tenuredSize += op(dst, src); + op(dst, src); } else { MOZ_ASSERT_IF(src->getClass()->hasFinalize(), CanNurseryAllocateFinalizedClass(src->getClass())); diff --git a/js/src/gc/Policy.h b/js/src/gc/Policy.h index 2800542564..53bf1dc1a5 100644 --- a/js/src/gc/Policy.h +++ b/js/src/gc/Policy.h @@ -47,7 +47,6 @@ class RegExpObject; class SavedFrame; class Scope; class EnvironmentObject; -class RequestedModuleObject; class ScriptSourceObject; class Shape; class SharedArrayBufferObject; @@ -88,7 +87,6 @@ class JitCode; D(js::PropertyName*) \ D(js::RegExpObject*) \ D(js::RegExpShared*) \ - D(js::RequestedModuleObject*) \ D(js::SavedFrame*) \ D(js::Scope*) \ D(js::ScriptSourceObject*) \ diff --git a/js/src/gc/StoreBuffer.cpp b/js/src/gc/StoreBuffer.cpp index 1005372893..7ae85debee 100644 --- a/js/src/gc/StoreBuffer.cpp +++ b/js/src/gc/StoreBuffer.cpp @@ -27,9 +27,11 @@ StoreBuffer::GenericBuffer::trace(StoreBuffer* owner, JSTracer* trc) return; for (LifoAlloc::Enum e(*storage_); !e.empty();) { - unsigned size = *e.read(); - BufferableRef* edge = e.read(size); + unsigned size = *e.get(); + e.popFront(); + BufferableRef* edge = e.get(size); edge->trace(trc); + e.popFront(size); } } diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index ccf6e4fa76..97306ff6f6 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -45,6 +45,9 @@ class ZoneHeapThreshold double gcHeapGrowthFactor() const { return gcHeapGrowthFactor_; } size_t gcTriggerBytes() const { return gcTriggerBytes_; } + size_t AllocThresholdFactorTriggerBytes(GCSchedulingTunables& tunables) const { + return gcTriggerBytes_ * tunables.allocThresholdFactor(); + } double eagerAllocTrigger(bool highFrequencyGC) const; void updateAfterGC(size_t lastBytes, JSGCInvocationKind gckind, diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index cbce3584fd..d296b971ed 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -925,7 +925,7 @@ GCRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes) return false; { - AutoLockGC lock(rt); + AutoLockGCBgAlloc lock(rt); /* * Separate gcMaxMallocBytes from gcMaxBytes but initialize to maxbytes @@ -5954,8 +5954,9 @@ AddSweepAction(bool* ok, SweepAction::Func func, AllocKind kind = AllocKind::LIM *ok = SweepPhases.back().emplaceBack(func, kind); } -/* static */ bool -GCRuntime::initializeSweepActions() +template +static UniquePtr> +RepeatForSweepGroup(JSRuntime* rt, UniquePtr> action) { bool ok = true; @@ -5963,14 +5964,17 @@ GCRuntime::initializeSweepActions() return js::MakeUnique(rt, Move(action)); } - AddSweepAction(&ok, GCRuntime::sweepTypeInformation); - AddSweepAction(&ok, GCRuntime::mergeSweptObjectArenas); +template +static UniquePtr>::Type> +ForEachZoneInSweepGroup(JSRuntime* rt, UniquePtr> action) +{ + if (!action) + return nullptr; - for (const auto& finalizePhase : IncrementalFinalizePhases) { - AddSweepPhase(&ok); - for (auto kind : finalizePhase.kinds) - AddSweepAction(&ok, GCRuntime::finalizeAllocKind, kind); - } + using Action = typename RemoveLastTemplateParameter< + SweepActionForEach>::Type; + return js::MakeUnique(rt, Move(action)); +} AddSweepPhase(&ok); AddSweepAction(&ok, GCRuntime::sweepShapeTree); @@ -5989,7 +5993,7 @@ GCRuntime::initSweepActions() using sweepaction::Call; sweepActions.ref() = - RepeatForZoneGroup(rt, + RepeatForSweepGroup(rt, Sequence( Call(&GCRuntime::endMarkingSweepGroup), Call(&GCRuntime::beginSweepingSweepGroup), @@ -5998,17 +6002,17 @@ GCRuntime::initSweepActions() #endif Call(&GCRuntime::sweepAtomsTable), Call(&GCRuntime::sweepWeakCaches), - ForEachZoneInZoneGroup(rt, + ForEachZoneInSweepGroup(rt, ForEachAllocKind(ForegroundObjectFinalizePhase.kinds, Call(&GCRuntime::finalizeAllocKind))), - ForEachZoneInZoneGroup(rt, + ForEachZoneInSweepGroup(rt, Sequence( Call(&GCRuntime::sweepTypeInformation), Call(&GCRuntime::mergeSweptObjectArenas))), - ForEachZoneInZoneGroup(rt, + ForEachZoneInSweepGroup(rt, ForEachAllocKind(ForegroundNonObjectFinalizePhase.kinds, Call(&GCRuntime::finalizeAllocKind))), - ForEachZoneInZoneGroup(rt, + ForEachZoneInSweepGroup(rt, Call(&GCRuntime::sweepShapeTree)), Call(&GCRuntime::endSweepingSweepGroup))); diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 521dea05c6..c463eb6237 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -306,12 +306,6 @@ GetGCKindBytes(AllocKind thingKind) return sizeof(JSObject_Slots0) + GetGCKindSlots(thingKind) * sizeof(Value); } -// Class to assist in triggering background chunk allocation. This cannot be done -// while holding the GC or worker thread state lock due to lock ordering issues. -// As a result, the triggering is delayed using this class until neither of the -// above locks is held. -class AutoMaybeStartBackgroundAllocation; - /* * A single segment of a SortedArenaList. Each segment has a head and a tail, * which track the start and end of a segment for O(1) append and concatenation. @@ -827,8 +821,7 @@ class ArenaLists inline void mergeSweptArenas(AllocKind thingKind); TenuredCell* allocateFromArena(JS::Zone* zone, AllocKind thingKind, - ShouldCheckThresholds checkThresholds, - AutoMaybeStartBackgroundAllocation& maybeStartBGAlloc); + ShouldCheckThresholds checkThresholds); inline TenuredCell* allocateFromArenaInner(JS::Zone* zone, Arena* arena, AllocKind kind); inline void normalizeBackgroundFinalizeState(AllocKind thingKind); diff --git a/js/src/threading/ProtectedData.cpp b/js/src/threading/ProtectedData.cpp new file mode 100644 index 0000000000..f33bf123dd --- /dev/null +++ b/js/src/threading/ProtectedData.cpp @@ -0,0 +1,139 @@ +/* -*- 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/. */ + +#include "threading/ProtectedData.h" + +#include "jscntxt.h" + +#include "gc/Heap.h" +#include "vm/HelperThreads.h" + +namespace js { + +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + +/* static */ mozilla::Atomic AutoNoteSingleThreadedRegion::count(0); + +template +static inline bool +OnHelperThread() +{ + if (Helper == AllowedHelperThread::IonCompile || Helper == AllowedHelperThread::GCTaskOrIonCompile) { + if (CurrentThreadIsIonCompiling()) + return true; + } + + if (Helper == AllowedHelperThread::GCTask || Helper == AllowedHelperThread::GCTaskOrIonCompile) { + if (TlsContext.get()->performingGC || TlsContext.get()->runtime()->gc.onBackgroundThread()) + return true; + } + + return false; +} + +void +CheckThreadLocal::check() const +{ + JSContext* cx = TlsContext.get(); + MOZ_ASSERT(cx); + + // As for CheckZoneGroup, in a cooperatively scheduled runtime the active + // thread is permitted access to thread local state for other suspended + // threads in the same runtime. + if (cx->isCooperativelyScheduled()) + MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); + else + MOZ_ASSERT(id == ThisThread::GetId()); +} + +template +void +CheckActiveThread::check() const +{ + // When interrupting a thread on Windows, changes are made to the runtime + // and active thread's state from another thread while the active thread is + // suspended. We need a way to mark these accesses as being tantamount to + // accesses by the active thread. See bug 1323066. +#ifndef XP_WIN + if (OnHelperThread()) + return; + + JSContext* cx = TlsContext.get(); + MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); +#endif // XP_WIN +} + +template class CheckActiveThread; +template class CheckActiveThread; +template class CheckActiveThread; + +template +void +CheckZoneGroup::check() const +{ + if (OnHelperThread()) + return; + + JSContext* cx = TlsContext.get(); + if (group) { + if (group->usedByHelperThread()) { + MOZ_ASSERT(group->ownedByCurrentThread()); + } else { + // This check is disabled on windows for the same reason as in + // CheckActiveThread. +#ifndef XP_WIN + // In a cooperatively scheduled runtime the active thread is + // permitted access to all zone groups --- even those it has not + // entered --- for GC and similar purposes. Since all other + // cooperative threads are suspended, these accesses are threadsafe + // if the zone group is not in use by a helper thread. + // + // A corollary to this is that suspended cooperative threads may + // not access anything in a zone group, even zone groups they own, + // because they're not allowed to interact with the JS API. + MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); +#endif + } + } else { + // |group| will be null for data in the atoms zone. This is protected + // by the exclusive access lock. + MOZ_ASSERT(cx->runtime()->currentThreadHasExclusiveAccess()); + } +} + +template class CheckZoneGroup; +template class CheckZoneGroup; +template class CheckZoneGroup; +template class CheckZoneGroup; + +template +void +CheckGlobalLock::check() const +{ + if (OnHelperThread()) + return; + + switch (Lock) { + case GlobalLock::GCLock: + MOZ_ASSERT(TlsContext.get()->runtime()->gc.currentThreadHasLockedGC()); + break; + case GlobalLock::ExclusiveAccessLock: + MOZ_ASSERT(TlsContext.get()->runtime()->currentThreadHasExclusiveAccess()); + break; + case GlobalLock::HelperThreadLock: + MOZ_ASSERT(HelperThreadState().isLockedByCurrentThread()); + break; + } +} + +template class CheckGlobalLock; +template class CheckGlobalLock; +template class CheckGlobalLock; +template class CheckGlobalLock; + +#endif // JS_HAS_PROTECTED_DATA_CHECKS + +} // namespace js \ No newline at end of file diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index 431a3fa133..b8329160e6 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -31,6 +31,7 @@ using namespace js; using mozilla::ArrayLength; using mozilla::DebugOnly; +using mozilla::Maybe; using mozilla::Unused; using mozilla::TimeDuration; @@ -483,6 +484,14 @@ js::CancelOffThreadParses(JSRuntime* rt) if (!found) break; } + +#ifdef DEBUG + GlobalHelperThreadState::ParseTaskVector& worklist = HelperThreadState().parseWorklist(lock); + for (size_t i = 0; i < worklist.length(); i++) { + ParseTask* task = worklist[i]; + MOZ_ASSERT(!task->runtimeMatches(rt)); + } +#endif } bool @@ -536,8 +545,29 @@ EnsureParserCreatedClasses(JSContext* cx, ParseTaskKind kind) return true; } +class AutoClearUsedByHelperThread +{ + ZoneGroup* group; + + public: + AutoClearUsedByHelperThread(JSObject* global) + : group(global->zone()->group()) + {} + + void forget() { + group = nullptr; + } + + ~AutoClearUsedByHelperThread() { + if (group) + group->clearUsedByHelperThread(); + } +}; + static JSObject* -CreateGlobalForOffThreadParse(JSContext* cx, ParseTaskKind kind, const gc::AutoSuppressGC& nogc) +CreateGlobalForOffThreadParse(JSContext* cx, ParseTaskKind kind, + Maybe& clearUseGuard, + const gc::AutoSuppressGC& nogc) { JSCompartment* currentCompartment = cx->compartment(); @@ -560,7 +590,13 @@ CreateGlobalForOffThreadParse(JSContext* cx, ParseTaskKind kind, const gc::AutoS JS_SetCompartmentPrincipals(global->compartment(), currentCompartment->principals()); - // Initialize all classes required for parsing while still on the main + // Mark this zone group as created for a helper thread. This prevents it + // from being collected until clearUsedByHelperThread() is called. + ZoneGroup* group = global->zone()->group(); + group->setCreatedForHelperThread(); + clearUseGuard.emplace(global); + + // Initialize all classes required for parsing while still on the active // thread, for both the target and the new global so that prototype // pointers can be changed infallibly after parsing finishes. if (!EnsureParserCreatedClasses(cx, kind)) @@ -577,19 +613,18 @@ CreateGlobalForOffThreadParse(JSContext* cx, ParseTaskKind kind, const gc::AutoS static bool QueueOffThreadParseTask(JSContext* cx, ParseTask* task) { - if (OffThreadParsingMustWaitForGC(cx->runtime())) { - AutoLockHelperThreadState lock; - if (!HelperThreadState().parseWaitingOnGC(lock).append(task)) { - ReportOutOfMemory(cx); - return false; - } - } else { - AutoLockHelperThreadState lock; - if (!HelperThreadState().parseWorklist(lock).append(task)) { - ReportOutOfMemory(cx); - return false; - } + AutoLockHelperThreadState lock; + bool mustWait = OffThreadParsingMustWaitForGC(cx->runtime()); + + auto& queue = mustWait ? HelperThreadState().parseWaitingOnGC(lock) + : HelperThreadState().parseWorklist(lock); + if (!queue.append(task)) { + ReportOutOfMemory(cx); + return false; + } + + if (!mustWait) { task->activate(cx->runtime()); HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock); } @@ -608,7 +643,8 @@ StartOffThreadParseTask(JSContext* cx, const ReadOnlyCompileOptions& options, gc::AutoAssertNoNurseryAlloc noNurseryAlloc(cx->runtime()); AutoSuppressAllocationMetadataBuilder suppressMetadata(cx); - JSObject* global = CreateGlobalForOffThreadParse(cx, kind, nogc); + Maybe clearUseGuard; + JSObject* global = CreateGlobalForOffThreadParse(cx, kind, clearUseGuard, nogc); if (!global) return false; @@ -628,6 +664,7 @@ StartOffThreadParseTask(JSContext* cx, const ReadOnlyCompileOptions& options, return false; task.forget(); + clearUseGuard->forget(); return true; } @@ -693,8 +730,8 @@ js::EnqueuePendingParseTasksAfterGC(JSRuntime* rt) if (newTasks.empty()) return; - // This logic should mirror the contents of the !activeGCInAtomsZone() - // branch in StartOffThreadParseScript: + // This logic should mirror the contents of the + // !OffThreadParsingMustWaitForGC() branch in QueueOffThreadParseTask: for (size_t i = 0; i < newTasks.length(); i++) newTasks[i]->activate(rt); @@ -1130,7 +1167,9 @@ js::GCParallelTask::~GCParallelTask() // base class can't ensure that the task is done using the members. All we // can do now is check that someone has previously stopped the task. #ifdef DEBUG - AutoLockHelperThreadState helperLock; + Maybe helperLock; + if (!HelperThreadState().isLockedByCurrentThread()) + helperLock.emplace(); MOZ_ASSERT(state == NotStarted); #endif } diff --git a/js/src/vm/RegExpShared.h b/js/src/vm/RegExpShared.h index e1e6dad802..941dca7f45 100644 --- a/js/src/vm/RegExpShared.h +++ b/js/src/vm/RegExpShared.h @@ -297,6 +297,10 @@ class RegExpZone bool get(JSContext* cx, HandleAtom source, JSString* maybeOpt, MutableHandleRegExpShared shared); +#ifdef DEBUG + void clear() { set_.clear(); } +#endif + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); }; diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index a80359f282..d42e5e6ab2 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -166,7 +166,7 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime) #ifdef DEBUG mainThreadHasExclusiveAccess(false), #endif - numExclusiveThreads(0), + numActiveHelperThreadZones(0), numCompartments(0), localeCallbacks(nullptr), defaultLocale(nullptr), @@ -868,11 +868,21 @@ JSRuntime::setUsedByExclusiveThread(Zone* zone) void JSRuntime::clearUsedByExclusiveThread(Zone* zone) { - MOZ_ASSERT(zone->usedByExclusiveThread); - zone->usedByExclusiveThread = false; - numExclusiveThreads--; - if (gc.fullGCForAtomsRequested() && !keepAtoms()) - gc.triggerFullGCForAtoms(); + MOZ_ASSERT(!zone->group()->usedByHelperThread()); + MOZ_ASSERT(!zone->wasGCStarted()); + zone->group()->setUsedByHelperThread(); + numActiveHelperThreadZones++; +} + +void +JSRuntime::clearUsedByHelperThread(Zone* zone) +{ + MOZ_ASSERT(zone->group()->usedByHelperThread()); + zone->group()->clearUsedByHelperThread(); + numActiveHelperThreadZones--; + JSContext* cx = TlsContext.get(); + if (gc.fullGCForAtomsRequested() && cx->canCollectAtoms()) + gc.triggerFullGCForAtoms(cx); } bool diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 02df80e18c..cc8c7c47ca 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -676,8 +676,8 @@ struct JSRuntime : public JS::shadow::Runtime, bool mainThreadHasExclusiveAccess; #endif - /* Number of non-main threads with an ExclusiveContext. */ - size_t numExclusiveThreads; + /* Number of zones which may be operated on by non-cooperating helper threads. */ + js::UnprotectedData numActiveHelperThreadZones; friend class js::AutoLockForExclusiveAccess; @@ -685,8 +685,8 @@ struct JSRuntime : public JS::shadow::Runtime, void setUsedByExclusiveThread(JS::Zone* zone); void clearUsedByExclusiveThread(JS::Zone* zone); - bool exclusiveThreadsPresent() const { - return numExclusiveThreads > 0; + bool hasHelperThreadZones() const { + return numActiveHelperThreadZones > 0; } // How many compartments there are across all zones. This number includes @@ -1414,8 +1414,10 @@ FreeOp::appendJitPoisonRange(const jit::JitPoisonRange& range) /* * RAII class that takes the GC lock while it is live. * - * Note that the lock may be temporarily released by use of AutoUnlockGC when - * passed a non-const reference to this class. + * Usually functions will pass const references of this class. However + * non-const references can be used to either temporarily release the lock by + * use of AutoUnlockGC or to start background allocation when the lock is + * released. */ class MOZ_RAII AutoLockGC { @@ -1429,7 +1431,7 @@ class MOZ_RAII AutoLockGC } ~AutoLockGC() { - unlock(); + lockGuard_.reset(); } void lock() { @@ -1446,6 +1448,9 @@ class MOZ_RAII AutoLockGC return lockGuard_.ref(); } + protected: + JSRuntime* runtime() const { return runtime_; } + private: JSRuntime* runtime_; mozilla::Maybe> lockGuard_; @@ -1455,6 +1460,47 @@ class MOZ_RAII AutoLockGC AutoLockGC& operator=(const AutoLockGC&) = delete; }; +/* + * Same as AutoLockGC except it can optionally start a background chunk + * allocation task when the lock is released. + */ +class MOZ_RAII AutoLockGCBgAlloc : public AutoLockGC +{ + public: + explicit AutoLockGCBgAlloc(JSRuntime* rt) + : AutoLockGC(rt) + , startBgAlloc(false) + {} + + ~AutoLockGCBgAlloc() { + unlock(); + + /* + * We have to do this after releasing the lock because it may acquire + * the helper lock which could cause lock inversion if we still held + * the GC lock. + */ + if (startBgAlloc) + runtime()->gc.startBackgroundAllocTaskIfIdle(); + } + + /* + * This can be used to start a background allocation task (if one isn't + * already running) that allocates chunks and makes them available in the + * free chunks list. This happens after the lock is released in order to + * avoid lock inversion. + */ + void tryToStartBackgroundAllocation() { + startBgAlloc = true; + } + + private: + + // true if we should start a background chunk allocation task after the + // lock is released. + bool startBgAlloc; +}; + class MOZ_RAII AutoUnlockGC { public: