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 static JS_NEVER_INLINE bool
EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{ {
cx->maybePause();
FunctionBox *funbox = pn->pn_funbox; FunctionBox *funbox = pn->pn_funbox;
RootedFunction fun(cx, funbox->function()); RootedFunction fun(cx, funbox->function());
JS_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript()); JS_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript());

View File

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

View File

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

View File

@ -22,7 +22,6 @@ js::TraceRuntime(JSTracer *trc)
{ {
JS_ASSERT(!IS_GC_MARKING_TRACER(trc)); JS_ASSERT(!IS_GC_MARKING_TRACER(trc));
AutoLockForExclusiveAccess lock(trc->runtime);
AutoPrepareForTracing prep(trc->runtime, WithAtoms); AutoPrepareForTracing prep(trc->runtime, WithAtoms);
MarkRuntime(trc); MarkRuntime(trc);
} }
@ -56,7 +55,6 @@ js::IterateZonesCompartmentsArenasCells(JSRuntime *rt, void *data,
IterateArenaCallback arenaCallback, IterateArenaCallback arenaCallback,
IterateCellCallback cellCallback) IterateCellCallback cellCallback)
{ {
AutoLockForExclusiveAccess lock(rt);
AutoPrepareForTracing prop(rt, WithAtoms); AutoPrepareForTracing prop(rt, WithAtoms);
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) { for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
@ -73,7 +71,6 @@ js::IterateZoneCompartmentsArenasCells(JSRuntime *rt, Zone *zone, void *data,
IterateArenaCallback arenaCallback, IterateArenaCallback arenaCallback,
IterateCellCallback cellCallback) IterateCellCallback cellCallback)
{ {
AutoLockForExclusiveAccess lock(rt);
AutoPrepareForTracing prop(rt, WithAtoms); AutoPrepareForTracing prop(rt, WithAtoms);
(*zoneCallback)(rt, data, zone); (*zoneCallback)(rt, data, zone);
@ -130,8 +127,6 @@ JS_IterateCompartments(JSRuntime *rt, void *data,
{ {
JS_ASSERT(!rt->isHeapBusy()); JS_ASSERT(!rt->isHeapBusy());
AutoLockForExclusiveAccess lock(rt);
AutoPauseWorkersForTracing pause(rt);
AutoTraceSession session(rt); AutoTraceSession session(rt);
for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next()) 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"); MarkScriptRoot(trc, &vec[i].script, "scriptAndCountsVector");
} }
if (!rt->isBeingDestroyed() && if (!rt->isBeingDestroyed() && !trc->runtime->isHeapMinorCollecting()) {
!trc->runtime->isHeapMinorCollecting() && if (!IS_GC_MARKING_TRACER(trc) || rt->atomsCompartment()->zone()->isCollecting()) {
(!IS_GC_MARKING_TRACER(trc) || rt->atomsCompartment()->zone()->isCollecting())) MarkAtoms(trc);
{ rt->staticStrings.trace(trc);
MarkAtoms(trc);
rt->staticStrings.trace(trc);
#ifdef JS_ION #ifdef JS_ION
jit::JitRuntime::Mark(trc); jit::JitRuntime::Mark(trc);
#endif #endif
}
} }
for (ContextIter acx(rt); !acx.done(); acx.next()) for (ContextIter acx(rt); !acx.done(); acx.next())

View File

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

View File

@ -237,6 +237,14 @@ Zone::discardJitCode(FreeOp *fop)
#endif #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::Zone *
js::ZoneOfObject(const JSObject &obj) 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. // Zones cannot be collected while in use by other threads.
if (usedByExclusiveThread) if (usedByExclusiveThread)
return false; return false;
JSRuntime *rt = runtimeFromMainThread(); JSRuntime *rt = runtimeFromAnyThread();
if (rt->isAtomsZone(this) && rt->exclusiveThreadsPresent()) if (rt->isAtomsZone(this) && rt->exclusiveThreadsPresent())
return false; return false;
return true; return true;
@ -238,6 +238,12 @@ struct Zone : public JS::shadow::Zone,
/* Whether this zone is being used by a thread with an ExclusiveContext. */ /* Whether this zone is being used by a thread with an ExclusiveContext. */
bool usedByExclusiveThread; 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 * These flags help us to discover if a compartment that shouldn't be alive
* manages to outlive a GC. * manages to outlive a GC.

View File

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

View File

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

View File

@ -285,11 +285,7 @@ struct ThreadSafeContext : ContextFriendFields,
size_t helperThreadCount() { return runtime_->helperThreadCount(); } size_t helperThreadCount() { return runtime_->helperThreadCount(); }
void *runtimeAddressForJit() { return runtime_; } void *runtimeAddressForJit() { return runtime_; }
void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; } 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; } size_t gcSystemPageSize() { return runtime_->gcSystemPageSize; }
bool isHeapBusy() { return runtime_->isHeapBusy(); }
bool signalHandlersInstalled() const { return runtime_->signalHandlersInstalled(); } bool signalHandlersInstalled() const { return runtime_->signalHandlersInstalled(); }
bool jitSupportsFloatingPoint() const { return runtime_->jitSupportsFloatingPoint; } bool jitSupportsFloatingPoint() const { return runtime_->jitSupportsFloatingPoint; }
@ -355,9 +351,6 @@ class ExclusiveContext : public ThreadSafeContext
void setWorkerThread(WorkerThread *workerThread); void setWorkerThread(WorkerThread *workerThread);
WorkerThread *workerThread() const { return 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 // Threads with an ExclusiveContext may freely access any data in their
// compartment and zone. // compartment and zone.
JSCompartment *compartment() const { 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_ShouldForceSequential(JSContext *cx, unsigned argc, Value *vp);
bool intrinsic_NewParallelArray(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 */ } /* namespace js */
#ifdef _MSC_VER #ifdef _MSC_VER

View File

@ -135,7 +135,7 @@ class CompartmentChecker
#define START_ASSERT_SAME_COMPARTMENT() \ #define START_ASSERT_SAME_COMPARTMENT() \
if (!cx->isExclusiveContext()) \ if (!cx->isExclusiveContext()) \
return; \ return; \
if (cx->isHeapBusy()) \ if (cx->isJSContext() && cx->asJSContext()->runtime()->isHeapBusy()) \
return; \ return; \
CompartmentChecker c(cx->asExclusiveContext()) CompartmentChecker c(cx->asExclusiveContext())
@ -349,71 +349,6 @@ GetNativeStackLimit(ThreadSafeContext *cx)
return cx->perThreadData->nativeStackLimit[kind]; 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 & inline LifoAlloc &
ExclusiveContext::typeLifoAlloc() ExclusiveContext::typeLifoAlloc()
{ {

View File

@ -123,6 +123,10 @@ JSCompartment::init(JSContext *cx)
jit::JitRuntime * jit::JitRuntime *
JSRuntime::createJitRuntime(JSContext *cx) 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 // 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 // runtime's jitRuntime() can occur when another thread is triggering an
// operation callback. // operation callback.
@ -139,8 +143,6 @@ JSRuntime::createJitRuntime(JSContext *cx)
js_delete(jitRuntime_); js_delete(jitRuntime_);
jitRuntime_ = nullptr; jitRuntime_ = nullptr;
AutoLockForExclusiveAccess atomsLock(cx);
JSCompartment *comp = cx->runtime()->atomsCompartment(); JSCompartment *comp = cx->runtime()->atomsCompartment();
if (comp->jitCompartment_) { if (comp->jitCompartment_) {
js_delete(comp->jitCompartment_); js_delete(comp->jitCompartment_);

View File

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

View File

@ -790,6 +790,8 @@ class GCHelperThread {
PRCondVar *done; PRCondVar *done;
volatile State state; volatile State state;
void wait(PRCondVar *which);
bool sweepFlag; bool sweepFlag;
bool shrinkFlag; 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_)); TypeObjectSet::AddPtr p = newTypeObjects.lookupForAdd(TypeObjectSet::Lookup(clasp, proto_));
SkipRoot skipHash(this, &p); /* Prevent the hash from being poisoned. */ SkipRoot skipHash(this, &p); /* Prevent the hash from being poisoned. */
uint64_t originalGcNumber = gcNumber(); uint64_t originalGcNumber = zone()->gcNumber();
if (p) { if (p) {
TypeObject *type = *p; TypeObject *type = *p;
JS_ASSERT(type->clasp == clasp); 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 * If a GC has occured, then the hash we calculated may be invalid, as it
* is based on proto, which may have been moved. * is based on proto, which may have been moved.
*/ */
bool gcHappened = gcNumber() != originalGcNumber; bool gcHappened = zone()->gcNumber() != originalGcNumber;
bool added = bool added =
gcHappened ? newTypeObjects.putNew(TypeObjectSet::Lookup(clasp, proto), type.get()) gcHappened ? newTypeObjects.putNew(TypeObjectSet::Lookup(clasp, proto), type.get())
: newTypeObjects.relookupOrAdd(p, 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; return false;
} }
cont = cont && !abort_; cont = cont && !abort_;
maybePause();
} }
compressedLength = comp.outWritten(); compressedLength = comp.outWritten();
if (abort_ || compressedLength == nbytes) if (abort_ || compressedLength == nbytes)
@ -1553,10 +1552,8 @@ js::SweepScriptData(JSRuntime *rt)
JS_ASSERT(rt->gcIsFull); JS_ASSERT(rt->gcIsFull);
ScriptDataTable &table = rt->scriptDataTable(); ScriptDataTable &table = rt->scriptDataTable();
for (ThreadDataIter iter(rt); !iter.done(); iter.next()) { if (rt->keepAtoms())
if (iter->gcKeepAtoms) return;
return;
}
for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) { for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) {
SharedScriptData *entry = e.front(); SharedScriptData *entry = e.front();

View File

@ -38,11 +38,11 @@ js::EnsureWorkerThreadsInitialized(ExclusiveContext *cx)
if (rt->workerThreadState) if (rt->workerThreadState)
return true; return true;
rt->workerThreadState = rt->new_<WorkerThreadState>(); rt->workerThreadState = rt->new_<WorkerThreadState>(rt);
if (!rt->workerThreadState) if (!rt->workerThreadState)
return false; return false;
if (!rt->workerThreadState->init(rt)) { if (!rt->workerThreadState->init()) {
js_delete(rt->workerThreadState); js_delete(rt->workerThreadState);
rt->workerThreadState = nullptr; rt->workerThreadState = nullptr;
return false; return false;
@ -309,9 +309,9 @@ js::WaitForOffThreadParsingToFinish(JSRuntime *rt)
} }
bool bool
WorkerThreadState::init(JSRuntime *rt) WorkerThreadState::init()
{ {
if (!rt->useHelperThreads()) { if (!runtime->useHelperThreads()) {
numThreads = 0; numThreads = 0;
return true; return true;
} }
@ -328,9 +328,9 @@ WorkerThreadState::init(JSRuntime *rt)
if (!producerWakeup) if (!producerWakeup)
return false; return false;
numThreads = rt->helperThreadCount(); numThreads = runtime->helperThreadCount();
threads = (WorkerThread*) rt->calloc_(sizeof(WorkerThread) * numThreads); threads = (WorkerThread*) js_pod_calloc<WorkerThread>(numThreads);
if (!threads) { if (!threads) {
numThreads = 0; numThreads = 0;
return false; return false;
@ -338,8 +338,8 @@ WorkerThreadState::init(JSRuntime *rt)
for (size_t i = 0; i < numThreads; i++) { for (size_t i = 0; i < numThreads; i++) {
WorkerThread &helper = threads[i]; WorkerThread &helper = threads[i];
helper.runtime = rt; helper.runtime = runtime;
helper.threadData.construct(rt); helper.threadData.construct(runtime);
helper.threadData.ref().addToThreadList(); helper.threadData.ref().addToThreadList();
helper.thread = PR_CreateThread(PR_USER_THREAD, helper.thread = PR_CreateThread(PR_USER_THREAD,
WorkerThread::ThreadMain, &helper, WorkerThread::ThreadMain, &helper,
@ -359,7 +359,7 @@ WorkerThreadState::init(JSRuntime *rt)
} }
void void
WorkerThreadState::cleanup(JSRuntime *rt) WorkerThreadState::cleanup()
{ {
// Do preparatory work for shutdown before the final GC has destroyed most // Do preparatory work for shutdown before the final GC has destroyed most
// of the GC heap. // of the GC heap.
@ -375,7 +375,7 @@ WorkerThreadState::cleanup(JSRuntime *rt)
// Clean up any parse tasks which haven't been finished yet. // Clean up any parse tasks which haven't been finished yet.
while (!parseFinishedList.empty()) while (!parseFinishedList.empty())
finishParseTask(/* maybecx = */ nullptr, rt, parseFinishedList[0]); finishParseTask(/* maybecx = */ nullptr, runtime, parseFinishedList[0]);
} }
WorkerThreadState::~WorkerThreadState() WorkerThreadState::~WorkerThreadState()
@ -396,7 +396,7 @@ WorkerThreadState::~WorkerThreadState()
void void
WorkerThreadState::lock() WorkerThreadState::lock()
{ {
JS_ASSERT(!isLocked()); runtime->assertCanLock(JSRuntime::WorkerThreadStateLock);
PR_Lock(workerLock); PR_Lock(workerLock);
#ifdef DEBUG #ifdef DEBUG
lockOwner = PR_GetCurrentThread(); lockOwner = PR_GetCurrentThread();
@ -820,11 +820,8 @@ SourceCompressionTask::complete()
WorkerThreadState &state = *cx->workerThreadState(); WorkerThreadState &state = *cx->workerThreadState();
AutoLockWorkerThreadState lock(state); AutoLockWorkerThreadState lock(state);
{ while (state.compressionInProgress(this))
AutoPauseCurrentWorkerThread maybePause(cx); state.wait(WorkerThreadState::CONSUMER);
while (state.compressionInProgress(this))
state.wait(WorkerThreadState::CONSUMER);
}
ss->ready_ = true; ss->ready_ = true;
@ -897,8 +894,6 @@ WorkerThread::threadLoop()
// Block until a task is available. // Block until a task is available.
while (true) { while (true) {
if (state.shouldPause)
pause();
if (terminate) if (terminate)
return; return;
if (state.canStartIonCompile() || 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 */ #else /* JS_WORKER_THREADS */
using namespace js; using namespace js;
@ -1085,26 +974,6 @@ ScriptSource::getOffThreadCompressionChars(ExclusiveContext *cx)
return nullptr; 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 & frontend::CompileError &
ExclusiveContext::addPendingCompileError() ExclusiveContext::addPendingCompileError()
{ {

View File

@ -41,15 +41,6 @@ class WorkerThreadState
WorkerThread *threads; WorkerThread *threads;
size_t numThreads; 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 { enum CondVar {
/* For notifying threads waiting for work that they may be able to make progress. */ /* For notifying threads waiting for work that they may be able to make progress. */
CONSUMER, CONSUMER,
@ -83,11 +74,14 @@ class WorkerThreadState
/* Worklist for source compression worker threads. */ /* Worklist for source compression worker threads. */
Vector<SourceCompressionTask *, 0, SystemAllocPolicy> compressionWorklist; Vector<SourceCompressionTask *, 0, SystemAllocPolicy> compressionWorklist;
WorkerThreadState() { mozilla::PodZero(this); } WorkerThreadState(JSRuntime *rt) {
mozilla::PodZero(this);
runtime = rt;
}
~WorkerThreadState(); ~WorkerThreadState();
bool init(JSRuntime *rt); bool init();
void cleanup(JSRuntime *rt); void cleanup();
void lock(); void lock();
void unlock(); void unlock();
@ -134,6 +128,8 @@ class WorkerThreadState
private: private:
JSRuntime *runtime;
/* /*
* Lock protecting all mutable shared state accessed by helper threads, and * Lock protecting all mutable shared state accessed by helper threads, and
* used by all condition variables. * used by all condition variables.
@ -188,9 +184,6 @@ struct WorkerThread
return !ionBuilder && !asmData && !parseTask && !compressionTask; return !ionBuilder && !asmData && !parseTask && !compressionTask;
} }
inline void maybePause();
void pause();
void destroy(); void destroy();
void handleAsmJSWorkload(WorkerThreadState &state); 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 #ifdef JS_ION
struct AsmJSParallelTask struct AsmJSParallelTask
{ {
@ -464,12 +404,6 @@ struct SourceCompressionTask
complete(); complete();
} }
void maybePause() {
#ifdef JS_WORKER_THREADS
workerThread->maybePause();
#endif
}
bool compress(); bool compress();
bool complete(); bool complete();
void abort() { abort_ = 1; } void abort() { abort_ = 1; }

View File

@ -700,7 +700,8 @@ RegExpCompartment::get(ExclusiveContext *cx, JSAtom *source, RegExpFlag flags, R
return true; 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) if (!shared)
return false; return false;

View File

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

View File

@ -66,7 +66,6 @@ PerThreadData::PerThreadData(JSRuntime *runtime)
asmJSActivationStack_(nullptr), asmJSActivationStack_(nullptr),
dtoaState(nullptr), dtoaState(nullptr),
suppressGC(0), suppressGC(0),
gcKeepAtoms(0),
activeCompilations(0) activeCompilations(0)
{} {}
@ -126,7 +125,6 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
exclusiveAccessLock(nullptr), exclusiveAccessLock(nullptr),
exclusiveAccessOwner(nullptr), exclusiveAccessOwner(nullptr),
mainThreadHasExclusiveAccess(false), mainThreadHasExclusiveAccess(false),
exclusiveThreadsPaused(false),
numExclusiveThreads(0), numExclusiveThreads(0),
#endif #endif
systemZone(nullptr), systemZone(nullptr),
@ -249,6 +247,7 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
haveCreatedContext(false), haveCreatedContext(false),
data(nullptr), data(nullptr),
gcLock(nullptr), gcLock(nullptr),
gcLockOwner(nullptr),
gcHelperThread(thisFromCtor()), gcHelperThread(thisFromCtor()),
signalHandlersInstalled_(false), signalHandlersInstalled_(false),
defaultFreeOp_(thisFromCtor(), false), defaultFreeOp_(thisFromCtor(), false),
@ -265,6 +264,8 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
numGrouping(0), numGrouping(0),
#endif #endif
mathCache_(nullptr), mathCache_(nullptr),
activeCompilations_(0),
keepAtoms_(0),
trustedPrincipals_(nullptr), trustedPrincipals_(nullptr),
atomsCompartment_(nullptr), atomsCompartment_(nullptr),
beingDestroyed_(false), beingDestroyed_(false),
@ -333,6 +334,10 @@ JSRuntime::init(uint32_t maxbytes)
operationCallbackLock = PR_NewLock(); operationCallbackLock = PR_NewLock();
if (!operationCallbackLock) if (!operationCallbackLock)
return false; return false;
gcLock = PR_NewLock();
if (!gcLock)
return false;
#endif #endif
#ifdef JS_WORKER_THREADS #ifdef JS_WORKER_THREADS
@ -414,7 +419,7 @@ JSRuntime::~JSRuntime()
#ifdef JS_WORKER_THREADS #ifdef JS_WORKER_THREADS
if (workerThreadState) if (workerThreadState)
workerThreadState->cleanup(this); workerThreadState->cleanup();
#endif #endif
/* Poison common names before final GC. */ /* Poison common names before final GC. */
@ -457,10 +462,9 @@ JSRuntime::~JSRuntime()
if (exclusiveAccessLock) if (exclusiveAccessLock)
PR_DestroyLock(exclusiveAccessLock); PR_DestroyLock(exclusiveAccessLock);
JS_ASSERT(!numExclusiveThreads);
// Avoid bogus asserts during teardown. // Avoid bogus asserts during teardown.
exclusiveThreadsPaused = true; JS_ASSERT(!numExclusiveThreads);
mainThreadHasExclusiveAccess = true;
#endif #endif
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
@ -835,3 +839,30 @@ js::CurrentThreadCanAccessZone(Zone *zone)
} }
#endif #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 PerThreadData;
class ThreadSafeContext; class ThreadSafeContext;
class AutoKeepAtoms;
/* Thread Local Storage slot for storing the runtime for a thread. */ /* Thread Local Storage slot for storing the runtime for a thread. */
extern mozilla::ThreadLocal<PerThreadData*> TlsPerThreadData; extern mozilla::ThreadLocal<PerThreadData*> TlsPerThreadData;
@ -571,16 +572,7 @@ class PerThreadData : public PerThreadDataFriendFields,
*/ */
int32_t suppressGC; int32_t suppressGC;
/* // Whether there is an active compilation on this thread.
* 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.
*/
unsigned activeCompilations; unsigned activeCompilations;
PerThreadData(JSRuntime *runtime); PerThreadData(JSRuntime *runtime);
@ -593,6 +585,10 @@ class PerThreadData : public PerThreadDataFriendFields,
bool associatedWith(const JSRuntime *rt) { return runtime_ == rt; } bool associatedWith(const JSRuntime *rt) { return runtime_ == rt; }
inline JSRuntime *runtimeFromMainThread(); inline JSRuntime *runtimeFromMainThread();
inline JSRuntime *runtimeIfOnOwnerThread(); inline JSRuntime *runtimeIfOnOwnerThread();
inline bool exclusiveThreadsPresent();
inline void addActiveCompilation();
inline void removeActiveCompilation();
}; };
template<class Client> template<class Client>
@ -676,8 +672,6 @@ class MarkingValidator;
typedef Vector<JS::Zone *, 4, SystemAllocPolicy> ZoneVector; typedef Vector<JS::Zone *, 4, SystemAllocPolicy> ZoneVector;
class AutoLockForExclusiveAccess; class AutoLockForExclusiveAccess;
class AutoPauseWorkersForTracing;
class ThreadDataIter;
void RecomputeStackLimit(JSRuntime *rt, StackKind kind); void RecomputeStackLimit(JSRuntime *rt, StackKind kind);
@ -720,6 +714,21 @@ struct JSRuntime : public JS::shadow::Runtime,
/* Branch callback */ /* Branch callback */
JSOperationCallback operationCallback; 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: private:
/* /*
* Lock taken when triggering the operation callback from another thread. * Lock taken when triggering the operation callback from another thread.
@ -738,7 +747,7 @@ struct JSRuntime : public JS::shadow::Runtime,
public: public:
AutoLockForOperationCallback(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : rt(rt) { AutoLockForOperationCallback(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : rt(rt) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT; MOZ_GUARD_OBJECT_NOTIFIER_INIT;
JS_ASSERT(!rt->currentThreadOwnsOperationCallbackLock()); rt->assertCanLock(JSRuntime::OperationCallbackLock);
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
PR_Lock(rt->operationCallbackLock); PR_Lock(rt->operationCallbackLock);
rt->operationCallbackOwner = PR_GetCurrentThread(); rt->operationCallbackOwner = PR_GetCurrentThread();
@ -784,14 +793,11 @@ struct JSRuntime : public JS::shadow::Runtime,
PRLock *exclusiveAccessLock; PRLock *exclusiveAccessLock;
mozilla::DebugOnly<PRThread *> exclusiveAccessOwner; mozilla::DebugOnly<PRThread *> exclusiveAccessOwner;
mozilla::DebugOnly<bool> mainThreadHasExclusiveAccess; mozilla::DebugOnly<bool> mainThreadHasExclusiveAccess;
mozilla::DebugOnly<bool> exclusiveThreadsPaused;
/* Number of non-main threads with an ExclusiveContext. */ /* Number of non-main threads with an ExclusiveContext. */
size_t numExclusiveThreads; size_t numExclusiveThreads;
friend class js::AutoLockForExclusiveAccess; friend class js::AutoLockForExclusiveAccess;
friend class js::AutoPauseWorkersForTracing;
friend class js::ThreadDataIter;
public: public:
void setUsedByExclusiveThread(JS::Zone *zone); void setUsedByExclusiveThread(JS::Zone *zone);
@ -802,7 +808,6 @@ struct JSRuntime : public JS::shadow::Runtime,
bool currentThreadHasExclusiveAccess() { bool currentThreadHasExclusiveAccess() {
#if defined(JS_WORKER_THREADS) && defined(DEBUG) #if defined(JS_WORKER_THREADS) && defined(DEBUG)
return (!numExclusiveThreads && mainThreadHasExclusiveAccess) || return (!numExclusiveThreads && mainThreadHasExclusiveAccess) ||
exclusiveThreadsPaused ||
exclusiveAccessOwner == PR_GetCurrentThread(); exclusiveAccessOwner == PR_GetCurrentThread();
#else #else
return true; return true;
@ -1332,8 +1337,32 @@ struct JSRuntime : public JS::shadow::Runtime,
/* Client opaque pointers */ /* Client opaque pointers */
void *data; void *data;
private:
/* Synchronize GC heap access between main thread and GCHelperThread. */ /* 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; js::GCHelperThread gcHelperThread;
@ -1410,14 +1439,45 @@ struct JSRuntime : public JS::shadow::Runtime,
js::ConservativeGCData conservativeGC; js::ConservativeGCData conservativeGC;
// Pool of maps used during parse/emit. This may be modified by threads // 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: private:
js::frontend::ParseMapPool parseMapPool_; js::frontend::ParseMapPool parseMapPool_;
unsigned activeCompilations_;
public: public:
js::frontend::ParseMapPool &parseMapPool() { js::frontend::ParseMapPool &parseMapPool() {
JS_ASSERT(currentThreadHasExclusiveAccess()); JS_ASSERT(currentThreadHasExclusiveAccess());
return parseMapPool_; 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: private:
const JSPrincipals *trustedPrincipals_; const JSPrincipals *trustedPrincipals_;
@ -1722,14 +1782,6 @@ FreeOp::free_(void *p)
js_free(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 class AutoLockGC
{ {
public: public:
@ -1740,13 +1792,13 @@ class AutoLockGC
MOZ_GUARD_OBJECT_NOTIFIER_INIT; MOZ_GUARD_OBJECT_NOTIFIER_INIT;
// Avoid MSVC warning C4390 for non-threadsafe builds. // Avoid MSVC warning C4390 for non-threadsafe builds.
if (rt) if (rt)
JS_LOCK_GC(rt); rt->lockGC();
} }
~AutoLockGC() ~AutoLockGC()
{ {
if (runtime) if (runtime)
JS_UNLOCK_GC(runtime); runtime->unlockGC();
} }
bool locked() const { bool locked() const {
@ -1757,7 +1809,7 @@ class AutoLockGC
JS_ASSERT(rt); JS_ASSERT(rt);
JS_ASSERT(!runtime); JS_ASSERT(!runtime);
runtime = rt; runtime = rt;
JS_LOCK_GC(rt); rt->lockGC();
} }
private: private:
@ -1768,22 +1820,18 @@ class AutoLockGC
class AutoUnlockGC class AutoUnlockGC
{ {
private: private:
#ifdef JS_THREADSAFE
JSRuntime *rt; JSRuntime *rt;
#endif
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
public: public:
explicit AutoUnlockGC(JSRuntime *rt explicit AutoUnlockGC(JSRuntime *rt
MOZ_GUARD_OBJECT_NOTIFIER_PARAM) MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
#ifdef JS_THREADSAFE
: rt(rt) : rt(rt)
#endif
{ {
MOZ_GUARD_OBJECT_NOTIFIER_INIT; MOZ_GUARD_OBJECT_NOTIFIER_INIT;
JS_UNLOCK_GC(rt); rt->unlockGC();
} }
~AutoUnlockGC() { JS_LOCK_GC(rt); } ~AutoUnlockGC() { rt->lockGC(); }
}; };
class MOZ_STACK_CLASS AutoKeepAtoms class MOZ_STACK_CLASS AutoKeepAtoms
@ -1797,10 +1845,19 @@ class MOZ_STACK_CLASS AutoKeepAtoms
: pt(pt) : pt(pt)
{ {
MOZ_GUARD_OBJECT_NOTIFIER_INIT; 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() { ~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 * inline JSRuntime *
PerThreadData::runtimeFromMainThread() PerThreadData::runtimeFromMainThread()
{ {
JS_ASSERT(js::CurrentThreadCanAccessRuntime(runtime_)); JS_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
return runtime_; return runtime_;
} }
inline JSRuntime * inline JSRuntime *
PerThreadData::runtimeIfOnOwnerThread() 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 {} 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; extern const JSSecurityCallbacks NullSecurityCallbacks;
} /* namespace js */ } /* namespace js */