58 GC no crashing.

Compiles link no crashing.
This commit is contained in:
win7-7 2026-01-14 05:55:42 +02:00 committed by wuggy
commit c5d270f172
12 changed files with 320 additions and 144 deletions

View file

@ -144,6 +144,12 @@ class PersistentRootedBase : public MutableWrappedPtrOperations<T, Wrapper> {};
static void* const ConstNullValue = nullptr;
template <typename T>
class FakeRooted;
template <typename T>
class FakeMutableHandle;
namespace gc {
struct Cell;
template<typename T>
@ -889,64 +895,6 @@ class HandleBase<JSObject*, Container> : public WrappedPtrOperations<JSObject*,
JS::Handle<U*> as() const;
};
/** Interface substitute for Rooted<T> which does not root the variable's memory. */
template <typename T>
class MOZ_RAII FakeRooted : public RootedBase<T, FakeRooted<T>>
{
public:
using ElementType = T;
template <typename CX>
explicit FakeRooted(CX* cx) : ptr(JS::GCPolicy<T>::initial()) {}
template <typename CX>
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<T> which is not required to point to rooted memory. */
template <typename T>
class FakeMutableHandle : public js::MutableHandleBase<T, FakeMutableHandle<T>>
{
public:
using ElementType = T;
MOZ_IMPLICIT FakeMutableHandle(T* t) {
ptr = t;
}
MOZ_IMPLICIT FakeMutableHandle(FakeRooted<T>* 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 <typename T> class MaybeRooted<T, CanGC>
}
};
template <typename T> class MaybeRooted<T, NoGC>
{
public:
typedef const T& HandleType;
typedef FakeRooted<T> RootType;
typedef FakeMutableHandle<T> MutableHandleType;
static JS::Handle<T> toHandle(HandleType v) {
MOZ_CRASH("Bad conversion");
}
static JS::MutableHandle<T> toMutableHandle(MutableHandleType v) {
MOZ_CRASH("Bad conversion");
}
template <typename T2>
static inline T2* downcastHandle(HandleType v) {
return &v->template as<T2>();
}
};
} /* 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 */

View file

@ -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<ProxyObject>(), op == proxy_ObjectMoved);
if (op) {
if (src->is<InlineTypedObject>()) {
InlineTypedObject::objectMovedDuringMinorGC(this, dst, src);
} else if (src->is<TypedArrayObject>()) {
tenuredSize += TypedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
} else if (src->is<UnboxedArrayObject>()) {
tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
} else if (src->is<ArgumentsObject>()) {
tenuredSize += ArgumentsObject::objectMovedDuringMinorGC(this, dst, src);
} else if (src->is<ProxyObject>()) {
// Objects in the nursery are never swapped so the proxy must have an
// inline ProxyValueArray.
MOZ_ASSERT(src->as<ProxyObject>().usingInlineValueArray());
dst->as<ProxyObject>().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()));

View file

@ -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*) \

View file

@ -27,9 +27,11 @@ StoreBuffer::GenericBuffer::trace(StoreBuffer* owner, JSTracer* trc)
return;
for (LifoAlloc::Enum e(*storage_); !e.empty();) {
unsigned size = *e.read<unsigned>();
BufferableRef* edge = e.read<BufferableRef>(size);
unsigned size = *e.get<unsigned>();
e.popFront<unsigned>();
BufferableRef* edge = e.get<BufferableRef>(size);
edge->trace(trc);
e.popFront(size);
}
}

View file

@ -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,

View file

@ -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 <typename... Args>
static UniquePtr<SweepAction<Args...>>
RepeatForSweepGroup(JSRuntime* rt, UniquePtr<SweepAction<Args...>> action)
{
bool ok = true;
@ -5963,14 +5964,17 @@ GCRuntime::initializeSweepActions()
return js::MakeUnique<Action>(rt, Move(action));
}
AddSweepAction(&ok, GCRuntime::sweepTypeInformation);
AddSweepAction(&ok, GCRuntime::mergeSweptObjectArenas);
template <typename... Args>
static UniquePtr<typename RemoveLastTemplateParameter<SweepAction<Args...>>::Type>
ForEachZoneInSweepGroup(JSRuntime* rt, UniquePtr<SweepAction<Args...>> 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<GCSweepGroupIter, JSRuntime*, Args...>>::Type;
return js::MakeUnique<Action>(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)));

View file

@ -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);

View file

@ -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<size_t> AutoNoteSingleThreadedRegion::count(0);
template <AllowedHelperThread Helper>
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 <AllowedHelperThread Helper>
void
CheckActiveThread<Helper>::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<Helper>())
return;
JSContext* cx = TlsContext.get();
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
#endif // XP_WIN
}
template class CheckActiveThread<AllowedHelperThread::None>;
template class CheckActiveThread<AllowedHelperThread::GCTask>;
template class CheckActiveThread<AllowedHelperThread::IonCompile>;
template <AllowedHelperThread Helper>
void
CheckZoneGroup<Helper>::check() const
{
if (OnHelperThread<Helper>())
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<AllowedHelperThread::None>;
template class CheckZoneGroup<AllowedHelperThread::GCTask>;
template class CheckZoneGroup<AllowedHelperThread::IonCompile>;
template class CheckZoneGroup<AllowedHelperThread::GCTaskOrIonCompile>;
template <GlobalLock Lock, AllowedHelperThread Helper>
void
CheckGlobalLock<Lock, Helper>::check() const
{
if (OnHelperThread<Helper>())
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<GlobalLock::GCLock, AllowedHelperThread::None>;
template class CheckGlobalLock<GlobalLock::ExclusiveAccessLock, AllowedHelperThread::None>;
template class CheckGlobalLock<GlobalLock::ExclusiveAccessLock, AllowedHelperThread::GCTask>;
template class CheckGlobalLock<GlobalLock::HelperThreadLock, AllowedHelperThread::None>;
#endif // JS_HAS_PROTECTED_DATA_CHECKS
} // namespace js

View file

@ -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<AutoClearUsedByHelperThread>& 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<AutoClearUsedByHelperThread> 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<AutoLockHelperThreadState> helperLock;
if (!HelperThreadState().isLockedByCurrentThread())
helperLock.emplace();
MOZ_ASSERT(state == NotStarted);
#endif
}

View file

@ -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);
};

View file

@ -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

View file

@ -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<size_t> 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<js::LockGuard<js::Mutex>> 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: