Bug 928050 - Remove worker pausing mechanism, r=billm.

This commit is contained in:
Brian Hackett 2013-11-17 15:33:09 -07:00
parent 5c6a926e44
commit 8df8a7201a
23 changed files with 399 additions and 494 deletions

View File

@ -4788,8 +4788,6 @@ EmitFor(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top
static JS_NEVER_INLINE bool
EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
cx->maybePause();
FunctionBox *funbox = pn->pn_funbox;
RootedFunction fun(cx, funbox->function());
JS_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript());

View File

@ -402,7 +402,10 @@ Parser<ParseHandler>::Parser(ExclusiveContext *cx, LifoAlloc *alloc,
isUnexpectedEOF_(false),
handler(cx, *alloc, tokenStream, foldConstants, syntaxParser, lazyOuterFunction)
{
cx->perThreadData->activeCompilations++;
{
AutoLockForExclusiveAccess lock(cx);
cx->perThreadData->addActiveCompilation();
}
// The Mozilla specific JSOPTION_EXTRA_WARNINGS option adds extra warnings
// which are not generated if functions are parsed lazily. Note that the
@ -416,8 +419,6 @@ Parser<ParseHandler>::Parser(ExclusiveContext *cx, LifoAlloc *alloc,
template <typename ParseHandler>
Parser<ParseHandler>::~Parser()
{
context->perThreadData->activeCompilations--;
alloc.release(tempPoolMark);
/*
@ -426,6 +427,11 @@ Parser<ParseHandler>::~Parser()
* next GC) to avoid unnecessary OOMs.
*/
alloc.freeAllIfHugeAndUnused();
{
AutoLockForExclusiveAccess lock(context);
context->perThreadData->removeActiveCompilation();
}
}
template <typename ParseHandler>
@ -2245,8 +2251,6 @@ Parser<ParseHandler>::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, Fu
// function without concern for conversion to strict mode, use of lazy
// parsing and such.
context->maybePause();
Node prelude = null();
bool hasRest;
if (!functionArguments(kind, &prelude, pn, &hasRest))

View File

@ -7,6 +7,7 @@
#ifndef gc_GCInternals_h
#define gc_GCInternals_h
#include "jscntxt.h"
#include "jsworkers.h"
#include "gc/Zone.h"
@ -51,19 +52,19 @@ class AutoTraceSession
~AutoTraceSession();
protected:
AutoLockForExclusiveAccess lock;
JSRuntime *runtime;
private:
AutoTraceSession(const AutoTraceSession&) MOZ_DELETE;
void operator=(const AutoTraceSession&) MOZ_DELETE;
js::HeapState prevState;
HeapState prevState;
};
struct AutoPrepareForTracing
{
AutoFinishGC finish;
AutoPauseWorkersForTracing pause;
AutoTraceSession session;
AutoCopyFreeListToArenas copy;

View File

@ -22,7 +22,6 @@ js::TraceRuntime(JSTracer *trc)
{
JS_ASSERT(!IS_GC_MARKING_TRACER(trc));
AutoLockForExclusiveAccess lock(trc->runtime);
AutoPrepareForTracing prep(trc->runtime, WithAtoms);
MarkRuntime(trc);
}
@ -56,7 +55,6 @@ js::IterateZonesCompartmentsArenasCells(JSRuntime *rt, void *data,
IterateArenaCallback arenaCallback,
IterateCellCallback cellCallback)
{
AutoLockForExclusiveAccess lock(rt);
AutoPrepareForTracing prop(rt, WithAtoms);
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
@ -73,7 +71,6 @@ js::IterateZoneCompartmentsArenasCells(JSRuntime *rt, Zone *zone, void *data,
IterateArenaCallback arenaCallback,
IterateCellCallback cellCallback)
{
AutoLockForExclusiveAccess lock(rt);
AutoPrepareForTracing prop(rt, WithAtoms);
(*zoneCallback)(rt, data, zone);
@ -130,8 +127,6 @@ JS_IterateCompartments(JSRuntime *rt, void *data,
{
JS_ASSERT(!rt->isHeapBusy());
AutoLockForExclusiveAccess lock(rt);
AutoPauseWorkersForTracing pause(rt);
AutoTraceSession session(rt);
for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next())

View File

@ -707,15 +707,14 @@ js::gc::MarkRuntime(JSTracer *trc, bool useSavedRoots)
MarkScriptRoot(trc, &vec[i].script, "scriptAndCountsVector");
}
if (!rt->isBeingDestroyed() &&
!trc->runtime->isHeapMinorCollecting() &&
(!IS_GC_MARKING_TRACER(trc) || rt->atomsCompartment()->zone()->isCollecting()))
{
MarkAtoms(trc);
rt->staticStrings.trace(trc);
if (!rt->isBeingDestroyed() && !trc->runtime->isHeapMinorCollecting()) {
if (!IS_GC_MARKING_TRACER(trc) || rt->atomsCompartment()->zone()->isCollecting()) {
MarkAtoms(trc);
rt->staticStrings.trace(trc);
#ifdef JS_ION
jit::JitRuntime::Mark(trc);
jit::JitRuntime::Mark(trc);
#endif
}
}
for (ContextIter acx(rt); !acx.done(); acx.next())

View File

@ -448,7 +448,6 @@ gc::StartVerifyPreBarriers(JSRuntime *rt)
MinorGC(rt, JS::gcreason::API);
AutoLockForExclusiveAccess lock(rt);
AutoPrepareForTracing prep(rt, WithAtoms);
if (!IsIncrementalGCSafe(rt))
@ -814,6 +813,7 @@ MaybeVerifyPreBarriers(JSRuntime *rt, bool always)
EndVerifyPreBarriers(rt);
}
StartVerifyPreBarriers(rt);
}

View File

@ -237,6 +237,14 @@ Zone::discardJitCode(FreeOp *fop)
#endif
}
uint64_t
Zone::gcNumber()
{
// Zones in use by exclusive threads are not collected, and threads using
// them cannot access the main runtime's gcNumber without racing.
return usedByExclusiveThread ? 0 : runtimeFromMainThread()->gcNumber;
}
JS::Zone *
js::ZoneOfObject(const JSObject &obj)
{

View File

@ -193,7 +193,7 @@ struct Zone : public JS::shadow::Zone,
// Zones cannot be collected while in use by other threads.
if (usedByExclusiveThread)
return false;
JSRuntime *rt = runtimeFromMainThread();
JSRuntime *rt = runtimeFromAnyThread();
if (rt->isAtomsZone(this) && rt->exclusiveThreadsPresent())
return false;
return true;
@ -238,6 +238,12 @@ struct Zone : public JS::shadow::Zone,
/* Whether this zone is being used by a thread with an ExclusiveContext. */
bool usedByExclusiveThread;
/*
* Get a number that is incremented whenever this zone is collected, and
* possibly at other times too.
*/
uint64_t gcNumber();
/*
* These flags help us to discover if a compartment that shouldn't be alive
* manages to outlive a GC.

View File

@ -5119,7 +5119,6 @@ static AsmJSParallelTask *
GetFinishedCompilation(ModuleCompiler &m, ParallelGroupState &group)
{
AutoLockWorkerThreadState lock(*m.cx()->workerThreadState());
AutoPauseCurrentWorkerThread maybePause(m.cx());
while (!group.state.asmJSWorkerFailed()) {
if (!group.state.asmJSFinishedList.empty()) {
@ -5244,8 +5243,6 @@ CancelOutstandingJobs(ModuleCompiler &m, ParallelGroupState &group)
// Eliminate tasks that failed without adding to the finished list.
group.outstandingJobs -= group.state.harvestFailedAsmJSJobs();
AutoPauseCurrentWorkerThread maybePause(m.cx());
// Any remaining tasks are therefore undergoing active compilation.
JS_ASSERT(group.outstandingJobs >= 0);
while (group.outstandingJobs > 0) {

View File

@ -200,9 +200,9 @@ JitRuntime::~JitRuntime()
bool
JitRuntime::initialize(JSContext *cx)
{
JS_ASSERT(cx->runtime()->currentThreadHasExclusiveAccess());
JS_ASSERT(cx->runtime()->currentThreadOwnsOperationCallbackLock());
AutoLockForExclusiveAccess lock(cx);
AutoCompartment ac(cx, cx->atomsCompartment());
IonContext ictx(cx, nullptr);

View File

@ -285,11 +285,7 @@ struct ThreadSafeContext : ContextFriendFields,
size_t helperThreadCount() { return runtime_->helperThreadCount(); }
void *runtimeAddressForJit() { return runtime_; }
void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; }
// GCs cannot happen while non-main threads are running.
uint64_t gcNumber() { return runtime_->gcNumber; }
size_t gcSystemPageSize() { return runtime_->gcSystemPageSize; }
bool isHeapBusy() { return runtime_->isHeapBusy(); }
bool signalHandlersInstalled() const { return runtime_->signalHandlersInstalled(); }
bool jitSupportsFloatingPoint() const { return runtime_->jitSupportsFloatingPoint; }
@ -355,9 +351,6 @@ class ExclusiveContext : public ThreadSafeContext
void setWorkerThread(WorkerThread *workerThread);
WorkerThread *workerThread() const { return workerThread_; }
// If required, pause this thread until notified to continue by the main thread.
inline void maybePause() const;
// Threads with an ExclusiveContext may freely access any data in their
// compartment and zone.
JSCompartment *compartment() const {
@ -1021,6 +1014,61 @@ bool intrinsic_HaveSameClass(JSContext *cx, unsigned argc, Value *vp);
bool intrinsic_ShouldForceSequential(JSContext *cx, unsigned argc, Value *vp);
bool intrinsic_NewParallelArray(JSContext *cx, unsigned argc, Value *vp);
class AutoLockForExclusiveAccess
{
#ifdef JS_WORKER_THREADS
JSRuntime *runtime;
void init(JSRuntime *rt) {
runtime = rt;
if (runtime->numExclusiveThreads) {
runtime->assertCanLock(JSRuntime::ExclusiveAccessLock);
PR_Lock(runtime->exclusiveAccessLock);
runtime->exclusiveAccessOwner = PR_GetCurrentThread();
} else {
JS_ASSERT(!runtime->mainThreadHasExclusiveAccess);
runtime->mainThreadHasExclusiveAccess = true;
}
}
public:
AutoLockForExclusiveAccess(ExclusiveContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
init(cx->runtime_);
}
AutoLockForExclusiveAccess(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
init(rt);
}
~AutoLockForExclusiveAccess() {
if (runtime->numExclusiveThreads) {
JS_ASSERT(runtime->exclusiveAccessOwner == PR_GetCurrentThread());
#ifdef DEBUG
runtime->exclusiveAccessOwner = nullptr;
#endif
PR_Unlock(runtime->exclusiveAccessLock);
} else {
JS_ASSERT(runtime->mainThreadHasExclusiveAccess);
runtime->mainThreadHasExclusiveAccess = false;
}
}
#else // JS_WORKER_THREADS
public:
AutoLockForExclusiveAccess(ExclusiveContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
AutoLockForExclusiveAccess(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
~AutoLockForExclusiveAccess() {
// An empty destructor is needed to avoid warnings from clang about
// unused local variables of this type.
}
#endif // JS_WORKER_THREADS
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
} /* namespace js */
#ifdef _MSC_VER

View File

@ -135,7 +135,7 @@ class CompartmentChecker
#define START_ASSERT_SAME_COMPARTMENT() \
if (!cx->isExclusiveContext()) \
return; \
if (cx->isHeapBusy()) \
if (cx->isJSContext() && cx->asJSContext()->runtime()->isHeapBusy()) \
return; \
CompartmentChecker c(cx->asExclusiveContext())
@ -349,71 +349,6 @@ GetNativeStackLimit(ThreadSafeContext *cx)
return cx->perThreadData->nativeStackLimit[kind];
}
inline void
ExclusiveContext::maybePause() const
{
#ifdef JS_WORKER_THREADS
if (workerThread())
workerThread()->maybePause();
#endif
}
class AutoLockForExclusiveAccess
{
#ifdef JS_WORKER_THREADS
JSRuntime *runtime;
void init(JSRuntime *rt) {
runtime = rt;
if (runtime->numExclusiveThreads) {
PR_Lock(runtime->exclusiveAccessLock);
#ifdef DEBUG
runtime->exclusiveAccessOwner = PR_GetCurrentThread();
#endif
} else {
JS_ASSERT(!runtime->mainThreadHasExclusiveAccess);
runtime->mainThreadHasExclusiveAccess = true;
}
}
public:
AutoLockForExclusiveAccess(ExclusiveContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
init(cx->runtime_);
}
AutoLockForExclusiveAccess(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
init(rt);
}
~AutoLockForExclusiveAccess() {
if (runtime->numExclusiveThreads) {
JS_ASSERT(runtime->exclusiveAccessOwner == PR_GetCurrentThread());
#ifdef DEBUG
runtime->exclusiveAccessOwner = nullptr;
#endif
PR_Unlock(runtime->exclusiveAccessLock);
} else {
JS_ASSERT(runtime->mainThreadHasExclusiveAccess);
runtime->mainThreadHasExclusiveAccess = false;
}
}
#else // JS_WORKER_THREADS
public:
AutoLockForExclusiveAccess(ExclusiveContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
AutoLockForExclusiveAccess(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
~AutoLockForExclusiveAccess() {
// An empty destructor is needed to avoid warnings from clang about
// unused local variables of this type.
}
#endif // JS_WORKER_THREADS
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
inline LifoAlloc &
ExclusiveContext::typeLifoAlloc()
{

View File

@ -123,6 +123,10 @@ JSCompartment::init(JSContext *cx)
jit::JitRuntime *
JSRuntime::createJitRuntime(JSContext *cx)
{
// The shared stubs are created in the atoms compartment, which may be
// accessed by other threads with an exclusive context.
AutoLockForExclusiveAccess atomsLock(cx);
// The runtime will only be created on its owning thread, but reads of a
// runtime's jitRuntime() can occur when another thread is triggering an
// operation callback.
@ -139,8 +143,6 @@ JSRuntime::createJitRuntime(JSContext *cx)
js_delete(jitRuntime_);
jitRuntime_ = nullptr;
AutoLockForExclusiveAccess atomsLock(cx);
JSCompartment *comp = cx->runtime()->atomsCompartment();
if (comp->jitCompartment_) {
js_delete(comp->jitCompartment_);

View File

@ -782,8 +782,11 @@ Chunk::allocateArena(Zone *zone, AllocKind thingKind)
rt->gcBytes += ArenaSize;
zone->gcBytes += ArenaSize;
if (zone->gcBytes >= zone->gcTriggerBytes)
if (zone->gcBytes >= zone->gcTriggerBytes) {
AutoUnlockGC unlock(rt);
TriggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER);
}
return aheader;
}
@ -947,11 +950,6 @@ js_InitGC(JSRuntime *rt, uint32_t maxbytes)
if (!rt->gcRootsHash.init(256))
return false;
#ifdef JS_THREADSAFE
rt->gcLock = PR_NewLock();
if (!rt->gcLock)
return false;
#endif
if (!rt->gcHelperThread.init())
return false;
@ -1524,7 +1522,7 @@ template <AllowGC allowGC>
ArenaLists::refillFreeList(ThreadSafeContext *cx, AllocKind thingKind)
{
JS_ASSERT(cx->allocator()->arenas.freeLists[thingKind].isEmpty());
JS_ASSERT(!cx->isHeapBusy());
JS_ASSERT_IF(cx->isJSContext(), !cx->asJSContext()->runtime()->isHeapBusy());
Zone *zone = cx->allocator()->zone_;
@ -1532,31 +1530,53 @@ ArenaLists::refillFreeList(ThreadSafeContext *cx, AllocKind thingKind)
cx->asJSContext()->runtime()->gcIncrementalState != NO_INCREMENTAL &&
zone->gcBytes > zone->gcTriggerBytes;
#ifdef JS_THREADSAFE
JS_ASSERT_IF(cx->isJSContext() && allowGC,
!cx->asJSContext()->runtime()->currentThreadHasExclusiveAccess());
#endif
for (;;) {
if (JS_UNLIKELY(runGC)) {
if (void *thing = RunLastDitchGC(cx->asJSContext(), zone, thingKind))
return thing;
}
/*
* allocateFromArena may fail while the background finalization still
* run. If we aren't in a fork join, we want to wait for it to finish
* and restart. However, checking for that is racy as the background
* finalization could free some things after allocateFromArena decided
* to fail but at this point it may have already stopped. To avoid
* this race we always try to allocate twice.
*
* If we're in a fork join, we simply try it once and return whatever
* value we get.
*/
for (bool secondAttempt = false; ; secondAttempt = true) {
void *thing = cx->allocator()->arenas.allocateFromArenaInline(zone, thingKind);
if (JS_LIKELY(!!thing) || !cx->isJSContext())
return thing;
if (secondAttempt)
break;
if (cx->isJSContext()) {
/*
* allocateFromArena may fail while the background finalization still
* run. If we are on the main thread, we want to wait for it to finish
* and restart. However, checking for that is racy as the background
* finalization could free some things after allocateFromArena decided
* to fail but at this point it may have already stopped. To avoid
* this race we always try to allocate twice.
*/
for (bool secondAttempt = false; ; secondAttempt = true) {
void *thing = cx->allocator()->arenas.allocateFromArenaInline(zone, thingKind);
if (JS_LIKELY(!!thing))
return thing;
if (secondAttempt)
break;
cx->asJSContext()->runtime()->gcHelperThread.waitBackgroundSweepEnd();
cx->asJSContext()->runtime()->gcHelperThread.waitBackgroundSweepEnd();
}
} else {
#ifdef JS_THREADSAFE
/*
* If we're off the main thread, we try to allocate once and return
* whatever value we get. First, though, we need to ensure the main
* thread is not in a GC session.
*/
JSRuntime *rt = zone->runtimeFromAnyThread();
AutoLockWorkerThreadState lock(*rt->workerThreadState);
while (rt->isHeapBusy())
rt->workerThreadState->wait(WorkerThreadState::PRODUCER);
void *thing = cx->allocator()->arenas.allocateFromArenaInline(zone, thingKind);
if (thing)
return thing;
#else
MOZ_CRASH();
#endif
}
if (!cx->allowGC() || !allowGC)
@ -2318,6 +2338,16 @@ GCHelperThread::threadMain(void *arg)
static_cast<GCHelperThread *>(arg)->threadLoop();
}
void
GCHelperThread::wait(PRCondVar *which)
{
rt->gcLockOwner = nullptr;
PR_WaitCondVar(which, PR_INTERVAL_NO_TIMEOUT);
#ifdef DEBUG
rt->gcLockOwner = PR_GetCurrentThread();
#endif
}
void
GCHelperThread::threadLoop()
{
@ -2337,7 +2367,7 @@ GCHelperThread::threadLoop()
case SHUTDOWN:
return;
case IDLE:
PR_WaitCondVar(wakeup, PR_INTERVAL_NO_TIMEOUT);
wait(wakeup);
break;
case SWEEPING:
#if JS_TRACE_LOGGING
@ -2445,7 +2475,7 @@ GCHelperThread::waitBackgroundSweepEnd()
#ifdef JS_THREADSAFE
AutoLockGC lock(rt);
while (state == SWEEPING)
PR_WaitCondVar(done, PR_INTERVAL_NO_TIMEOUT);
wait(done);
if (rt->gcIncrementalState == NO_INCREMENTAL)
AssertBackgroundSweepingFinished(rt);
#endif /* JS_THREADSAFE */
@ -2464,7 +2494,7 @@ GCHelperThread::waitBackgroundSweepOrAllocEnd()
if (state == ALLOCATING)
state = CANCEL_ALLOCATION;
while (state == SWEEPING || state == CANCEL_ALLOCATION)
PR_WaitCondVar(done, PR_INTERVAL_NO_TIMEOUT);
wait(done);
if (rt->gcIncrementalState == NO_INCREMENTAL)
AssertBackgroundSweepingFinished(rt);
#endif /* JS_THREADSAFE */
@ -2662,10 +2692,7 @@ PurgeRuntime(JSRuntime *rt)
rt->sourceDataCache.purge();
rt->evalCache.clear();
bool activeCompilations = false;
for (ThreadDataIter iter(rt); !iter.done(); iter.next())
activeCompilations |= iter->activeCompilations;
if (!activeCompilations)
if (!rt->hasActiveCompilations())
rt->parseMapPool().purgeAll();
}
@ -2832,26 +2859,18 @@ BeginMarkPhase(JSRuntime *rt)
* zones that are not being collected, we are not allowed to collect
* atoms. Otherwise, the non-collected zones could contain pointers
* to atoms that we would miss.
*
* keepAtoms() will only change on the main thread, which we are currently
* on. If the value of keepAtoms() changes between GC slices, then we'll
* cancel the incremental GC. See IsIncrementalGCSafe.
*/
Zone *atomsZone = rt->atomsCompartment()->zone();
bool keepAtoms = false;
for (ThreadDataIter iter(rt); !iter.done(); iter.next())
keepAtoms |= iter->gcKeepAtoms;
/*
* We don't scan the stacks of exclusive threads, so we need to avoid
* collecting their objects in another way. The only GC thing pointers they
* have are to their exclusive compartment (which is not collected) or to
* the atoms compartment. Therefore, we avoid collecting the atoms
* compartment when exclusive threads are running.
*/
keepAtoms |= rt->exclusiveThreadsPresent();
if (atomsZone->isGCScheduled() && rt->gcIsFull && !keepAtoms) {
JS_ASSERT(!atomsZone->isCollecting());
atomsZone->setGCState(Zone::Mark);
any = true;
if (rt->gcIsFull && !rt->keepAtoms()) {
Zone *atomsZone = rt->atomsCompartment()->zone();
if (atomsZone->isGCScheduled()) {
JS_ASSERT(!atomsZone->isCollecting());
atomsZone->setGCState(Zone::Mark);
any = true;
}
}
/* Check that at least one zone is scheduled for collection. */
@ -4099,7 +4118,6 @@ namespace {
class AutoGCSession
{
JSRuntime *runtime;
AutoPauseWorkersForTracing pause;
AutoTraceSession session;
bool canceled;
@ -4114,24 +4132,55 @@ class AutoGCSession
/* Start a new heap session. */
AutoTraceSession::AutoTraceSession(JSRuntime *rt, js::HeapState heapState)
: runtime(rt),
: lock(rt),
runtime(rt),
prevState(rt->heapState)
{
JS_ASSERT(!rt->noGCOrAllocationCheck);
JS_ASSERT(!rt->isHeapBusy());
JS_ASSERT(heapState != Idle);
rt->heapState = heapState;
// Threads with an exclusive context can hit refillFreeList while holding
// the exclusive access lock. To avoid deadlocking when we try to acquire
// this lock during GC and the other thread is waiting, make sure we hold
// the exclusive access lock during GC sessions.
JS_ASSERT(rt->currentThreadHasExclusiveAccess());
if (rt->exclusiveThreadsPresent()) {
// Lock the worker thread state when changing the heap state in the
// presence of exclusive threads, to avoid racing with refillFreeList.
#ifdef JS_THREADSAFE
AutoLockWorkerThreadState lock(*rt->workerThreadState);
rt->heapState = heapState;
#else
MOZ_CRASH();
#endif
} else {
rt->heapState = heapState;
}
}
AutoTraceSession::~AutoTraceSession()
{
JS_ASSERT(runtime->isHeapBusy());
runtime->heapState = prevState;
if (runtime->exclusiveThreadsPresent()) {
#ifdef JS_THREADSAFE
AutoLockWorkerThreadState lock(*runtime->workerThreadState);
runtime->heapState = prevState;
// Notify any worker threads waiting for the trace session to end.
runtime->workerThreadState->notifyAll(WorkerThreadState::PRODUCER);
#else
MOZ_CRASH();
#endif
} else {
runtime->heapState = prevState;
}
}
AutoGCSession::AutoGCSession(JSRuntime *rt)
: runtime(rt),
pause(rt),
session(rt, MajorCollecting),
canceled(false)
{
@ -4140,12 +4189,9 @@ AutoGCSession::AutoGCSession(JSRuntime *rt)
runtime->gcNumber++;
#ifdef DEBUG
// Threads with an exclusive context should never pause while they are in
// the middle of a suppressGC.
for (ThreadDataIter iter(rt); !iter.done(); iter.next())
JS_ASSERT(!iter->suppressGC);
#endif
// It's ok if threads other than the main thread have suppressGC set, as
// they are operating on zones which will not be collected from here.
JS_ASSERT(!runtime->mainThread.suppressGC);
}
AutoGCSession::~AutoGCSession()
@ -4193,16 +4239,13 @@ class AutoCopyFreeListToArenasForGC
public:
AutoCopyFreeListToArenasForGC(JSRuntime *rt) : runtime(rt) {
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
//if (zone->canCollect())
zone->allocator.arenas.copyFreeListsToArenas();
}
JS_ASSERT(rt->currentThreadHasExclusiveAccess());
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next())
zone->allocator.arenas.copyFreeListsToArenas();
}
~AutoCopyFreeListToArenasForGC() {
for (ZonesIter zone(runtime, WithAtoms); !zone.done(); zone.next()) {
//if (zone->canCollect())
zone->allocator.arenas.clearFreeListsInArenas();
}
for (ZonesIter zone(runtime, WithAtoms); !zone.done(); zone.next())
zone->allocator.arenas.clearFreeListsInArenas();
}
};
@ -4359,6 +4402,8 @@ IncrementalCollectSlice(JSRuntime *rt,
JS::gcreason::Reason reason,
JSGCInvocationKind gckind)
{
JS_ASSERT(rt->currentThreadHasExclusiveAccess());
AutoCopyFreeListToArenasForGC copy(rt);
AutoGCSlice slice(rt);
@ -4485,14 +4530,8 @@ gc::IsIncrementalGCSafe(JSRuntime *rt)
{
JS_ASSERT(!rt->mainThread.suppressGC);
bool keepAtoms = false;
for (ThreadDataIter iter(rt); !iter.done(); iter.next())
keepAtoms |= iter->gcKeepAtoms;
keepAtoms |= rt->exclusiveThreadsPresent();
if (keepAtoms)
return IncrementalSafety::Unsafe("gcKeepAtoms set");
if (rt->keepAtoms())
return IncrementalSafety::Unsafe("keepAtoms set");
if (!rt->gcIncrementalEnabled)
return IncrementalSafety::Unsafe("incremental permanently disabled");
@ -4593,7 +4632,6 @@ GCCycle(JSRuntime *rt, bool incremental, int64_t budget,
}
IncrementalCollectSlice(rt, budget, reason, gckind);
return false;
}
@ -4858,7 +4896,6 @@ AutoFinishGC::AutoFinishGC(JSRuntime *rt)
AutoPrepareForTracing::AutoPrepareForTracing(JSRuntime *rt, ZoneSelector selector)
: finish(rt),
pause(rt),
session(rt),
copy(rt, selector)
{
@ -4916,6 +4953,7 @@ void
gc::MergeCompartments(JSCompartment *source, JSCompartment *target)
{
JSRuntime *rt = source->runtimeFromMainThread();
AutoPrepareForTracing prepare(rt, SkipAtoms);
// Cleanup tables and other state in the source compartment that will be

View File

@ -790,6 +790,8 @@ class GCHelperThread {
PRCondVar *done;
volatile State state;
void wait(PRCondVar *which);
bool sweepFlag;
bool shrinkFlag;

View File

@ -3746,7 +3746,7 @@ ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto_, JSFunction
TypeObjectSet::AddPtr p = newTypeObjects.lookupForAdd(TypeObjectSet::Lookup(clasp, proto_));
SkipRoot skipHash(this, &p); /* Prevent the hash from being poisoned. */
uint64_t originalGcNumber = gcNumber();
uint64_t originalGcNumber = zone()->gcNumber();
if (p) {
TypeObject *type = *p;
JS_ASSERT(type->clasp == clasp);
@ -3788,7 +3788,7 @@ ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto_, JSFunction
* If a GC has occured, then the hash we calculated may be invalid, as it
* is based on proto, which may have been moved.
*/
bool gcHappened = gcNumber() != originalGcNumber;
bool gcHappened = zone()->gcNumber() != originalGcNumber;
bool added =
gcHappened ? newTypeObjects.putNew(TypeObjectSet::Lookup(clasp, proto), type.get())
: newTypeObjects.relookupOrAdd(p, TypeObjectSet::Lookup(clasp, proto), type.get());

View File

@ -1200,7 +1200,6 @@ SourceCompressionTask::compress()
return false;
}
cont = cont && !abort_;
maybePause();
}
compressedLength = comp.outWritten();
if (abort_ || compressedLength == nbytes)
@ -1553,10 +1552,8 @@ js::SweepScriptData(JSRuntime *rt)
JS_ASSERT(rt->gcIsFull);
ScriptDataTable &table = rt->scriptDataTable();
for (ThreadDataIter iter(rt); !iter.done(); iter.next()) {
if (iter->gcKeepAtoms)
return;
}
if (rt->keepAtoms())
return;
for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) {
SharedScriptData *entry = e.front();

View File

@ -38,11 +38,11 @@ js::EnsureWorkerThreadsInitialized(ExclusiveContext *cx)
if (rt->workerThreadState)
return true;
rt->workerThreadState = rt->new_<WorkerThreadState>();
rt->workerThreadState = rt->new_<WorkerThreadState>(rt);
if (!rt->workerThreadState)
return false;
if (!rt->workerThreadState->init(rt)) {
if (!rt->workerThreadState->init()) {
js_delete(rt->workerThreadState);
rt->workerThreadState = nullptr;
return false;
@ -309,9 +309,9 @@ js::WaitForOffThreadParsingToFinish(JSRuntime *rt)
}
bool
WorkerThreadState::init(JSRuntime *rt)
WorkerThreadState::init()
{
if (!rt->useHelperThreads()) {
if (!runtime->useHelperThreads()) {
numThreads = 0;
return true;
}
@ -328,9 +328,9 @@ WorkerThreadState::init(JSRuntime *rt)
if (!producerWakeup)
return false;
numThreads = rt->helperThreadCount();
numThreads = runtime->helperThreadCount();
threads = (WorkerThread*) rt->calloc_(sizeof(WorkerThread) * numThreads);
threads = (WorkerThread*) js_pod_calloc<WorkerThread>(numThreads);
if (!threads) {
numThreads = 0;
return false;
@ -338,8 +338,8 @@ WorkerThreadState::init(JSRuntime *rt)
for (size_t i = 0; i < numThreads; i++) {
WorkerThread &helper = threads[i];
helper.runtime = rt;
helper.threadData.construct(rt);
helper.runtime = runtime;
helper.threadData.construct(runtime);
helper.threadData.ref().addToThreadList();
helper.thread = PR_CreateThread(PR_USER_THREAD,
WorkerThread::ThreadMain, &helper,
@ -359,7 +359,7 @@ WorkerThreadState::init(JSRuntime *rt)
}
void
WorkerThreadState::cleanup(JSRuntime *rt)
WorkerThreadState::cleanup()
{
// Do preparatory work for shutdown before the final GC has destroyed most
// of the GC heap.
@ -375,7 +375,7 @@ WorkerThreadState::cleanup(JSRuntime *rt)
// Clean up any parse tasks which haven't been finished yet.
while (!parseFinishedList.empty())
finishParseTask(/* maybecx = */ nullptr, rt, parseFinishedList[0]);
finishParseTask(/* maybecx = */ nullptr, runtime, parseFinishedList[0]);
}
WorkerThreadState::~WorkerThreadState()
@ -396,7 +396,7 @@ WorkerThreadState::~WorkerThreadState()
void
WorkerThreadState::lock()
{
JS_ASSERT(!isLocked());
runtime->assertCanLock(JSRuntime::WorkerThreadStateLock);
PR_Lock(workerLock);
#ifdef DEBUG
lockOwner = PR_GetCurrentThread();
@ -820,11 +820,8 @@ SourceCompressionTask::complete()
WorkerThreadState &state = *cx->workerThreadState();
AutoLockWorkerThreadState lock(state);
{
AutoPauseCurrentWorkerThread maybePause(cx);
while (state.compressionInProgress(this))
state.wait(WorkerThreadState::CONSUMER);
}
while (state.compressionInProgress(this))
state.wait(WorkerThreadState::CONSUMER);
ss->ready_ = true;
@ -897,8 +894,6 @@ WorkerThread::threadLoop()
// Block until a task is available.
while (true) {
if (state.shouldPause)
pause();
if (terminate)
return;
if (state.canStartIonCompile() ||
@ -925,112 +920,6 @@ WorkerThread::threadLoop()
}
}
AutoPauseWorkersForTracing::AutoPauseWorkersForTracing(JSRuntime *rt
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: runtime(rt), needsUnpause(false), oldExclusiveThreadsPaused(rt->exclusiveThreadsPaused)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
rt->exclusiveThreadsPaused = true;
if (!runtime->workerThreadState)
return;
JS_ASSERT(CurrentThreadCanAccessRuntime(runtime));
WorkerThreadState &state = *runtime->workerThreadState;
if (!state.numThreads)
return;
AutoLockWorkerThreadState lock(state);
// Tolerate reentrant use of AutoPauseWorkersForTracing.
if (state.shouldPause) {
JS_ASSERT(state.numPaused == state.numThreads);
return;
}
needsUnpause = true;
state.shouldPause = 1;
while (state.numPaused != state.numThreads) {
state.notifyAll(WorkerThreadState::PRODUCER);
state.wait(WorkerThreadState::CONSUMER);
}
}
AutoPauseWorkersForTracing::~AutoPauseWorkersForTracing()
{
runtime->exclusiveThreadsPaused = oldExclusiveThreadsPaused;
if (!needsUnpause)
return;
WorkerThreadState &state = *runtime->workerThreadState;
AutoLockWorkerThreadState lock(state);
state.shouldPause = 0;
// Notify all workers, to ensure that each wakes up.
state.notifyAll(WorkerThreadState::PRODUCER);
}
AutoPauseCurrentWorkerThread::AutoPauseCurrentWorkerThread(ExclusiveContext *cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: cx(cx)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
// If the current thread is a worker thread, treat it as paused while
// the caller is waiting for another worker thread to complete. Otherwise
// we will not wake up and mark this as paused due to the loop in
// AutoPauseWorkersForTracing.
if (cx->workerThread()) {
WorkerThreadState &state = *cx->workerThreadState();
JS_ASSERT(state.isLocked());
state.numPaused++;
if (state.numPaused == state.numThreads)
state.notifyAll(WorkerThreadState::CONSUMER);
}
}
AutoPauseCurrentWorkerThread::~AutoPauseCurrentWorkerThread()
{
if (cx->workerThread()) {
WorkerThreadState &state = *cx->workerThreadState();
JS_ASSERT(state.isLocked());
state.numPaused--;
// Before resuming execution of the worker thread, make sure the main
// thread does not expect worker threads to be paused.
if (state.shouldPause)
cx->workerThread()->pause();
}
}
void
WorkerThread::pause()
{
WorkerThreadState &state = *runtime->workerThreadState;
JS_ASSERT(state.isLocked());
JS_ASSERT(state.shouldPause);
JS_ASSERT(state.numPaused < state.numThreads);
state.numPaused++;
// Don't bother to notify the main thread until all workers have paused.
if (state.numPaused == state.numThreads)
state.notifyAll(WorkerThreadState::CONSUMER);
while (state.shouldPause)
state.wait(WorkerThreadState::PRODUCER);
state.numPaused--;
}
#else /* JS_WORKER_THREADS */
using namespace js;
@ -1085,26 +974,6 @@ ScriptSource::getOffThreadCompressionChars(ExclusiveContext *cx)
return nullptr;
}
AutoPauseWorkersForTracing::AutoPauseWorkersForTracing(JSRuntime *rt
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
AutoPauseWorkersForTracing::~AutoPauseWorkersForTracing()
{
}
AutoPauseCurrentWorkerThread::AutoPauseCurrentWorkerThread(ExclusiveContext *cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
AutoPauseCurrentWorkerThread::~AutoPauseCurrentWorkerThread()
{
}
frontend::CompileError &
ExclusiveContext::addPendingCompileError()
{

View File

@ -41,15 +41,6 @@ class WorkerThreadState
WorkerThread *threads;
size_t numThreads;
/*
* Whether all worker threads thread should pause their activity. This acts
* like the runtime's interrupt field and may be read without locking.
*/
volatile size_t shouldPause;
/* After shouldPause is set, the number of threads which are paused. */
uint32_t numPaused;
enum CondVar {
/* For notifying threads waiting for work that they may be able to make progress. */
CONSUMER,
@ -83,11 +74,14 @@ class WorkerThreadState
/* Worklist for source compression worker threads. */
Vector<SourceCompressionTask *, 0, SystemAllocPolicy> compressionWorklist;
WorkerThreadState() { mozilla::PodZero(this); }
WorkerThreadState(JSRuntime *rt) {
mozilla::PodZero(this);
runtime = rt;
}
~WorkerThreadState();
bool init(JSRuntime *rt);
void cleanup(JSRuntime *rt);
bool init();
void cleanup();
void lock();
void unlock();
@ -134,6 +128,8 @@ class WorkerThreadState
private:
JSRuntime *runtime;
/*
* Lock protecting all mutable shared state accessed by helper threads, and
* used by all condition variables.
@ -188,9 +184,6 @@ struct WorkerThread
return !ionBuilder && !asmData && !parseTask && !compressionTask;
}
inline void maybePause();
void pause();
void destroy();
void handleAsmJSWorkload(WorkerThreadState &state);
@ -314,59 +307,6 @@ class AutoUnlockWorkerThreadState
}
};
#ifdef JS_WORKER_THREADS
inline void
WorkerThread::maybePause()
{
if (runtime->workerThreadState->shouldPause) {
AutoLockWorkerThreadState lock(*runtime->workerThreadState);
pause();
}
}
#endif // JS_WORKER_THREADS
/* Pause any threads that are running jobs off thread during GC activity. */
class AutoPauseWorkersForTracing
{
#ifdef JS_WORKER_THREADS
JSRuntime *runtime;
bool needsUnpause;
mozilla::DebugOnly<bool> oldExclusiveThreadsPaused;
#endif
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
public:
AutoPauseWorkersForTracing(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
~AutoPauseWorkersForTracing();
};
/*
* If the current thread is a worker thread, treat it as paused during this
* class's lifetime. This should be used at any time the current thread is
* waiting for a worker to complete.
*/
class AutoPauseCurrentWorkerThread
{
#ifdef JS_WORKER_THREADS
ExclusiveContext *cx;
#endif
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
public:
AutoPauseCurrentWorkerThread(ExclusiveContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
~AutoPauseCurrentWorkerThread();
};
/* Wait for any in progress off thread parses to halt. */
void
PauseOffThreadParsing();
/* Resume any paused off thread parses. */
void
ResumeOffThreadParsing();
#ifdef JS_ION
struct AsmJSParallelTask
{
@ -464,12 +404,6 @@ struct SourceCompressionTask
complete();
}
void maybePause() {
#ifdef JS_WORKER_THREADS
workerThread->maybePause();
#endif
}
bool compress();
bool complete();
void abort() { abort_ = 1; }

View File

@ -700,7 +700,8 @@ RegExpCompartment::get(ExclusiveContext *cx, JSAtom *source, RegExpFlag flags, R
return true;
}
ScopedJSDeletePtr<RegExpShared> shared(cx->new_<RegExpShared>(source, flags, cx->gcNumber()));
uint64_t gcNumber = cx->zone()->gcNumber();
ScopedJSDeletePtr<RegExpShared> shared(cx->new_<RegExpShared>(source, flags, gcNumber));
if (!shared)
return false;

View File

@ -14,6 +14,7 @@
#include "jsproxy.h"
#include "gc/Marking.h"
#include "gc/Zone.h"
#if ENABLE_YARR_JIT
#include "yarr/YarrJIT.h"
#else
@ -189,7 +190,7 @@ class RegExpShared
/* Called when a RegExpShared is installed into a RegExpObject. */
void prepareForUse(ExclusiveContext *cx) {
gcNumberWhenUsed = cx->gcNumber();
gcNumberWhenUsed = cx->zone()->gcNumber();
}
/* Primary interface: run this regular expression on the given string. */

View File

@ -66,7 +66,6 @@ PerThreadData::PerThreadData(JSRuntime *runtime)
asmJSActivationStack_(nullptr),
dtoaState(nullptr),
suppressGC(0),
gcKeepAtoms(0),
activeCompilations(0)
{}
@ -126,7 +125,6 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
exclusiveAccessLock(nullptr),
exclusiveAccessOwner(nullptr),
mainThreadHasExclusiveAccess(false),
exclusiveThreadsPaused(false),
numExclusiveThreads(0),
#endif
systemZone(nullptr),
@ -249,6 +247,7 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
haveCreatedContext(false),
data(nullptr),
gcLock(nullptr),
gcLockOwner(nullptr),
gcHelperThread(thisFromCtor()),
signalHandlersInstalled_(false),
defaultFreeOp_(thisFromCtor(), false),
@ -265,6 +264,8 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
numGrouping(0),
#endif
mathCache_(nullptr),
activeCompilations_(0),
keepAtoms_(0),
trustedPrincipals_(nullptr),
atomsCompartment_(nullptr),
beingDestroyed_(false),
@ -333,6 +334,10 @@ JSRuntime::init(uint32_t maxbytes)
operationCallbackLock = PR_NewLock();
if (!operationCallbackLock)
return false;
gcLock = PR_NewLock();
if (!gcLock)
return false;
#endif
#ifdef JS_WORKER_THREADS
@ -414,7 +419,7 @@ JSRuntime::~JSRuntime()
#ifdef JS_WORKER_THREADS
if (workerThreadState)
workerThreadState->cleanup(this);
workerThreadState->cleanup();
#endif
/* Poison common names before final GC. */
@ -457,10 +462,9 @@ JSRuntime::~JSRuntime()
if (exclusiveAccessLock)
PR_DestroyLock(exclusiveAccessLock);
JS_ASSERT(!numExclusiveThreads);
// Avoid bogus asserts during teardown.
exclusiveThreadsPaused = true;
JS_ASSERT(!numExclusiveThreads);
mainThreadHasExclusiveAccess = true;
#endif
#ifdef JS_THREADSAFE
@ -835,3 +839,30 @@ js::CurrentThreadCanAccessZone(Zone *zone)
}
#endif
#ifdef DEBUG
void
JSRuntime::assertCanLock(RuntimeLock which)
{
#ifdef JS_THREADSAFE
// In the switch below, each case falls through to the one below it. None
// of the runtime locks are reentrant, and when multiple locks are acquired
// it must be done in the order below.
switch (which) {
case ExclusiveAccessLock:
JS_ASSERT(exclusiveAccessOwner != PR_GetCurrentThread());
case WorkerThreadStateLock:
JS_ASSERT_IF(workerThreadState, !workerThreadState->isLocked());
case OperationCallbackLock:
JS_ASSERT(!currentThreadOwnsOperationCallbackLock());
case GCLock:
JS_ASSERT(gcLockOwner != PR_GetCurrentThread());
break;
default:
MOZ_CRASH();
}
#endif // JS_THREADSAFE
}
#endif // DEBUG

View File

@ -55,6 +55,7 @@ namespace js {
class PerThreadData;
class ThreadSafeContext;
class AutoKeepAtoms;
/* Thread Local Storage slot for storing the runtime for a thread. */
extern mozilla::ThreadLocal<PerThreadData*> TlsPerThreadData;
@ -571,16 +572,7 @@ class PerThreadData : public PerThreadDataFriendFields,
*/
int32_t suppressGC;
/*
* Count of AutoKeepAtoms instances on the stack. When any instances exist,
* atoms in the runtime will not be collected.
*/
unsigned gcKeepAtoms;
/*
* Count of currently active compilations. When any compilations exist,
* the runtime's parseMapPool will not be purged.
*/
// Whether there is an active compilation on this thread.
unsigned activeCompilations;
PerThreadData(JSRuntime *runtime);
@ -593,6 +585,10 @@ class PerThreadData : public PerThreadDataFriendFields,
bool associatedWith(const JSRuntime *rt) { return runtime_ == rt; }
inline JSRuntime *runtimeFromMainThread();
inline JSRuntime *runtimeIfOnOwnerThread();
inline bool exclusiveThreadsPresent();
inline void addActiveCompilation();
inline void removeActiveCompilation();
};
template<class Client>
@ -676,8 +672,6 @@ class MarkingValidator;
typedef Vector<JS::Zone *, 4, SystemAllocPolicy> ZoneVector;
class AutoLockForExclusiveAccess;
class AutoPauseWorkersForTracing;
class ThreadDataIter;
void RecomputeStackLimit(JSRuntime *rt, StackKind kind);
@ -720,6 +714,21 @@ struct JSRuntime : public JS::shadow::Runtime,
/* Branch callback */
JSOperationCallback operationCallback;
// There are several per-runtime locks indicated by the enum below. When
// acquiring multiple of these locks, the acquisition must be done in the
// order below to avoid deadlocks.
enum RuntimeLock {
ExclusiveAccessLock,
WorkerThreadStateLock,
OperationCallbackLock,
GCLock
};
#ifdef DEBUG
void assertCanLock(RuntimeLock which);
#else
void assertCanLock(RuntimeLock which) {}
#endif
private:
/*
* Lock taken when triggering the operation callback from another thread.
@ -738,7 +747,7 @@ struct JSRuntime : public JS::shadow::Runtime,
public:
AutoLockForOperationCallback(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : rt(rt) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
JS_ASSERT(!rt->currentThreadOwnsOperationCallbackLock());
rt->assertCanLock(JSRuntime::OperationCallbackLock);
#ifdef JS_THREADSAFE
PR_Lock(rt->operationCallbackLock);
rt->operationCallbackOwner = PR_GetCurrentThread();
@ -784,14 +793,11 @@ struct JSRuntime : public JS::shadow::Runtime,
PRLock *exclusiveAccessLock;
mozilla::DebugOnly<PRThread *> exclusiveAccessOwner;
mozilla::DebugOnly<bool> mainThreadHasExclusiveAccess;
mozilla::DebugOnly<bool> exclusiveThreadsPaused;
/* Number of non-main threads with an ExclusiveContext. */
size_t numExclusiveThreads;
friend class js::AutoLockForExclusiveAccess;
friend class js::AutoPauseWorkersForTracing;
friend class js::ThreadDataIter;
public:
void setUsedByExclusiveThread(JS::Zone *zone);
@ -802,7 +808,6 @@ struct JSRuntime : public JS::shadow::Runtime,
bool currentThreadHasExclusiveAccess() {
#if defined(JS_WORKER_THREADS) && defined(DEBUG)
return (!numExclusiveThreads && mainThreadHasExclusiveAccess) ||
exclusiveThreadsPaused ||
exclusiveAccessOwner == PR_GetCurrentThread();
#else
return true;
@ -1332,8 +1337,32 @@ struct JSRuntime : public JS::shadow::Runtime,
/* Client opaque pointers */
void *data;
private:
/* Synchronize GC heap access between main thread and GCHelperThread. */
PRLock *gcLock;
PRLock *gcLock;
mozilla::DebugOnly<PRThread *> gcLockOwner;
friend class js::GCHelperThread;
public:
void lockGC() {
#ifdef JS_THREADSAFE
assertCanLock(GCLock);
PR_Lock(gcLock);
JS_ASSERT(!gcLockOwner);
#ifdef DEBUG
gcLockOwner = PR_GetCurrentThread();
#endif
#endif
}
void unlockGC() {
#ifdef JS_THREADSAFE
JS_ASSERT(gcLockOwner == PR_GetCurrentThread());
gcLockOwner = nullptr;
PR_Unlock(gcLock);
#endif
}
js::GCHelperThread gcHelperThread;
@ -1410,14 +1439,45 @@ struct JSRuntime : public JS::shadow::Runtime,
js::ConservativeGCData conservativeGC;
// Pool of maps used during parse/emit. This may be modified by threads
// with an ExclusiveContext and requires a lock.
// with an ExclusiveContext and requires a lock. Active compilations
// prevent the pool from being purged during GCs.
private:
js::frontend::ParseMapPool parseMapPool_;
unsigned activeCompilations_;
public:
js::frontend::ParseMapPool &parseMapPool() {
JS_ASSERT(currentThreadHasExclusiveAccess());
return parseMapPool_;
}
bool hasActiveCompilations() {
return activeCompilations_ != 0;
}
void addActiveCompilation() {
JS_ASSERT(currentThreadHasExclusiveAccess());
activeCompilations_++;
}
void removeActiveCompilation() {
JS_ASSERT(currentThreadHasExclusiveAccess());
activeCompilations_--;
}
// Count of AutoKeepAtoms instances on the main thread's stack. When any
// instances exist, atoms in the runtime will not be collected. Threads
// with an ExclusiveContext do not increment this value, but the presence
// of any such threads also inhibits collection of atoms. We don't scan the
// stacks of exclusive threads, so we need to avoid collecting their
// objects in another way. The only GC thing pointers they have are to
// their exclusive compartment (which is not collected) or to the atoms
// compartment. Therefore, we avoid collecting the atoms compartment when
// exclusive threads are running.
private:
unsigned keepAtoms_;
friend class js::AutoKeepAtoms;
public:
bool keepAtoms() {
JS_ASSERT(CurrentThreadCanAccessRuntime(this));
return keepAtoms_ != 0 || exclusiveThreadsPresent();
}
private:
const JSPrincipals *trustedPrincipals_;
@ -1722,14 +1782,6 @@ FreeOp::free_(void *p)
js_free(p);
}
#ifdef JS_THREADSAFE
# define JS_LOCK_GC(rt) PR_Lock((rt)->gcLock)
# define JS_UNLOCK_GC(rt) PR_Unlock((rt)->gcLock)
#else
# define JS_LOCK_GC(rt) do { } while (0)
# define JS_UNLOCK_GC(rt) do { } while (0)
#endif
class AutoLockGC
{
public:
@ -1740,13 +1792,13 @@ class AutoLockGC
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
// Avoid MSVC warning C4390 for non-threadsafe builds.
if (rt)
JS_LOCK_GC(rt);
rt->lockGC();
}
~AutoLockGC()
{
if (runtime)
JS_UNLOCK_GC(runtime);
runtime->unlockGC();
}
bool locked() const {
@ -1757,7 +1809,7 @@ class AutoLockGC
JS_ASSERT(rt);
JS_ASSERT(!runtime);
runtime = rt;
JS_LOCK_GC(rt);
rt->lockGC();
}
private:
@ -1768,22 +1820,18 @@ class AutoLockGC
class AutoUnlockGC
{
private:
#ifdef JS_THREADSAFE
JSRuntime *rt;
#endif
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
public:
explicit AutoUnlockGC(JSRuntime *rt
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
#ifdef JS_THREADSAFE
: rt(rt)
#endif
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
JS_UNLOCK_GC(rt);
rt->unlockGC();
}
~AutoUnlockGC() { JS_LOCK_GC(rt); }
~AutoUnlockGC() { rt->lockGC(); }
};
class MOZ_STACK_CLASS AutoKeepAtoms
@ -1797,10 +1845,19 @@ class MOZ_STACK_CLASS AutoKeepAtoms
: pt(pt)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
pt->gcKeepAtoms++;
if (JSRuntime *rt = pt->runtimeIfOnOwnerThread()) {
rt->keepAtoms_++;
} else {
// This should be a thread with an exclusive context, which will
// always inhibit collection of atoms.
JS_ASSERT(pt->exclusiveThreadsPresent());
}
}
~AutoKeepAtoms() {
pt->gcKeepAtoms--;
if (JSRuntime *rt = pt->runtimeIfOnOwnerThread()) {
JS_ASSERT(rt->keepAtoms_);
rt->keepAtoms_--;
}
}
};
@ -1814,14 +1871,35 @@ PerThreadData::setIonStackLimit(uintptr_t limit)
inline JSRuntime *
PerThreadData::runtimeFromMainThread()
{
JS_ASSERT(js::CurrentThreadCanAccessRuntime(runtime_));
JS_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
return runtime_;
}
inline JSRuntime *
PerThreadData::runtimeIfOnOwnerThread()
{
return js::CurrentThreadCanAccessRuntime(runtime_) ? runtime_ : nullptr;
return CurrentThreadCanAccessRuntime(runtime_) ? runtime_ : nullptr;
}
inline bool
PerThreadData::exclusiveThreadsPresent()
{
return runtime_->exclusiveThreadsPresent();
}
inline void
PerThreadData::addActiveCompilation()
{
activeCompilations++;
runtime_->addActiveCompilation();
}
inline void
PerThreadData::removeActiveCompilation()
{
JS_ASSERT(activeCompilations);
activeCompilations--;
runtime_->removeActiveCompilation();
}
/************************************************************************/
@ -1913,45 +1991,6 @@ class RuntimeAllocPolicy
void reportAllocOverflow() const {}
};
/*
* Enumerate all the per thread data in a runtime.
*/
class ThreadDataIter {
PerThreadData *iter;
public:
explicit ThreadDataIter(JSRuntime *rt) {
#ifdef JS_WORKER_THREADS
// Only allow iteration over a runtime's threads when those threads are
// paused, to avoid racing when reading data from the PerThreadData.
JS_ASSERT(rt->exclusiveThreadsPaused);
#endif
iter = rt->threadList.getFirst();
}
bool done() const {
return !iter;
}
void next() {
JS_ASSERT(!done());
iter = iter->getNext();
}
PerThreadData *get() const {
JS_ASSERT(!done());
return iter;
}
operator PerThreadData *() const {
return get();
}
PerThreadData *operator ->() const {
return get();
}
};
extern const JSSecurityCallbacks NullSecurityCallbacks;
} /* namespace js */