Bug 1337491 - Off thread parsing changes for multithreaded runtimes, r=jandem,jonco.

--HG--
extra : rebase_source : 5c8f0f3bff71caf03cddfd67057667c8454addc4
This commit is contained in:
Brian Hackett 2017-02-14 05:21:39 -07:00
Родитель 6df0361de9
Коммит 80eaee2561
24 изменённых файлов: 233 добавлений и 225 удалений

Просмотреть файл

@ -1636,8 +1636,10 @@ OutlineTypedObject::obj_trace(JSTracer* trc, JSObject* object)
newData += reinterpret_cast<uint8_t*>(owner) - reinterpret_cast<uint8_t*>(oldOwner);
typedObj.setData(newData);
Nursery& nursery = typedObj.zoneFromAnyThread()->group()->nursery();
nursery.maybeSetForwardingPointer(trc, oldData, newData, /* direct = */ false);
if (trc->isTenuringTracer()) {
Nursery& nursery = typedObj.zoneFromAnyThread()->group()->nursery();
nursery.maybeSetForwardingPointer(trc, oldData, newData, /* direct = */ false);
}
}
if (!descr.opaque() || !typedObj.isAttached())

Просмотреть файл

@ -41,6 +41,9 @@ js::Allocate(JSContext* cx, AllocKind kind, size_t nDynamicSlots, InitialHeap he
// Off-thread alloc cannot trigger GC or make runtime assertions.
if (cx->helperThread()) {
// The zone group used by the helper thread should have been created
// with a disabled nursery.
MOZ_ASSERT(!cx->nursery().isEnabled());
JSObject* obj = GCRuntime::tryNewTenuredObject<NoGC>(cx, kind, thingSize, nDynamicSlots);
if (MOZ_UNLIKELY(allowGC && !obj))
ReportOutOfMemory(cx);

Просмотреть файл

@ -2414,7 +2414,7 @@ JSObject*
js::TenuringTracer::moveToTenured(JSObject* src)
{
MOZ_ASSERT(IsInsideNursery(src));
MOZ_ASSERT(!src->zone()->usedByExclusiveThread);
MOZ_ASSERT(!src->zone()->usedByHelperThread());
AllocKind dstKind = src->allocKindForTenure(nursery());
Zone* zone = src->zone();

Просмотреть файл

@ -39,28 +39,22 @@ template <typename T>
static inline T*
AllocateObjectBuffer(JSContext* cx, uint32_t count)
{
if (!cx->helperThread()) {
size_t nbytes = JS_ROUNDUP(count * sizeof(T), sizeof(Value));
T* buffer = static_cast<T*>(cx->nursery().allocateBuffer(cx->zone(), nbytes));
if (!buffer)
ReportOutOfMemory(cx);
return buffer;
}
return cx->zone()->pod_malloc<T>(count);
size_t nbytes = JS_ROUNDUP(count * sizeof(T), sizeof(Value));
T* buffer = static_cast<T*>(cx->nursery().allocateBuffer(cx->zone(), nbytes));
if (!buffer)
ReportOutOfMemory(cx);
return buffer;
}
template <typename T>
static inline T*
AllocateObjectBuffer(JSContext* cx, JSObject* obj, uint32_t count)
{
if (!cx->helperThread()) {
size_t nbytes = JS_ROUNDUP(count * sizeof(T), sizeof(Value));
T* buffer = static_cast<T*>(cx->nursery().allocateBuffer(obj, nbytes));
if (!buffer)
ReportOutOfMemory(cx);
return buffer;
}
return obj->zone()->pod_malloc<T>(count);
size_t nbytes = JS_ROUNDUP(count * sizeof(T), sizeof(Value));
T* buffer = static_cast<T*>(cx->nursery().allocateBuffer(obj, nbytes));
if (!buffer)
ReportOutOfMemory(cx);
return buffer;
}
// If this returns null then the old buffer will be left alone.
@ -69,15 +63,12 @@ static inline T*
ReallocateObjectBuffer(JSContext* cx, JSObject* obj, T* oldBuffer,
uint32_t oldCount, uint32_t newCount)
{
if (!cx->helperThread()) {
T* buffer = static_cast<T*>(cx->nursery().reallocateBuffer(obj, oldBuffer,
oldCount * sizeof(T),
newCount * sizeof(T)));
if (!buffer)
ReportOutOfMemory(cx);
return buffer;
}
return obj->zone()->pod_realloc<T>(oldBuffer, oldCount, newCount);
T* buffer = static_cast<T*>(cx->nursery().reallocateBuffer(obj, oldBuffer,
oldCount * sizeof(T),
newCount * sizeof(T)));
if (!buffer)
ReportOutOfMemory(cx);
return buffer;
}
static inline void

Просмотреть файл

@ -298,6 +298,7 @@ js::Nursery::allocate(size_t size)
{
MOZ_ASSERT(isEnabled());
MOZ_ASSERT(!JS::CurrentThreadIsHeapBusy());
MOZ_ASSERT(CurrentThreadCanAccessRuntime(zoneGroup()->runtime));
MOZ_ASSERT_IF(currentChunk_ == currentStartChunk_, position() >= currentStartPosition_);
MOZ_ASSERT(position() % gc::CellSize == 0);
MOZ_ASSERT(size % gc::CellSize == 0);

Просмотреть файл

@ -237,6 +237,8 @@ class Nursery
return numChunks() * gc::ChunkSize;
}
size_t sizeOfMallocedBuffers(mozilla::MallocSizeOf mallocSizeOf) const {
if (!mallocedBuffers.initialized())
return 0;
size_t total = 0;
for (MallocedBuffersSet::Range r = mallocedBuffers.all(); !r.empty(); r.popFront())
total += mallocSizeOf(r.front());

Просмотреть файл

@ -179,7 +179,7 @@ gc::GCRuntime::startVerifyPreBarriers()
if (verifyPreData || isIncrementalGCInProgress())
return;
if (IsIncrementalGCUnsafe(rt) != AbortReason::None || TlsContext.get()->keepAtoms || rt->exclusiveThreadsPresent())
if (IsIncrementalGCUnsafe(rt) != AbortReason::None || TlsContext.get()->keepAtoms || rt->hasHelperThreadZones())
return;
number++;
@ -239,11 +239,10 @@ gc::GCRuntime::startVerifyPreBarriers()
marker.start();
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
MOZ_ASSERT(!zone->usedByHelperThread());
PurgeJITCaches(zone);
if (!zone->usedByExclusiveThread) {
zone->setNeedsIncrementalBarrier(true, Zone::UpdateJit);
zone->arenas.purge();
}
zone->setNeedsIncrementalBarrier(true, Zone::UpdateJit);
zone->arenas.purge();
}
return;
@ -353,7 +352,7 @@ gc::GCRuntime::endVerifyPreBarriers()
if (!compartmentCreated &&
IsIncrementalGCUnsafe(rt) == AbortReason::None &&
!TlsContext.get()->keepAtoms &&
!rt->exclusiveThreadsPresent())
!rt->hasHelperThreadZones())
{
CheckEdgeTracer cetrc(rt);

Просмотреть файл

@ -51,7 +51,6 @@ JS::Zone::Zone(JSRuntime* rt, ZoneGroup* group)
initialShapes_(group, this, InitialShapeSet()),
data(group, nullptr),
isSystem(group, false),
usedByExclusiveThread(false),
#ifdef DEBUG
gcLastZoneGroupIndex(group, 0),
#endif
@ -105,7 +104,8 @@ Zone::setNeedsIncrementalBarrier(bool needs, ShouldUpdateJit updateJit)
jitUsingBarriers_ = needs;
}
MOZ_ASSERT_IF(needs && isAtomsZone(), !runtimeFromActiveCooperatingThread()->exclusiveThreadsPresent());
MOZ_ASSERT_IF(needs && isAtomsZone(),
!runtimeFromActiveCooperatingThread()->hasHelperThreadZones());
MOZ_ASSERT_IF(needs, canCollect());
needsIncrementalBarrier_ = needs;
}
@ -295,7 +295,7 @@ 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 : runtimeFromActiveCooperatingThread()->gc.gcNumber();
return usedByHelperThread() ? 0 : runtimeFromActiveCooperatingThread()->gc.gcNumber();
}
js::jit::JitZone*
@ -324,10 +324,10 @@ bool
Zone::canCollect()
{
// Zones cannot be collected while in use by other threads.
if (usedByExclusiveThread)
if (usedByHelperThread())
return false;
JSRuntime* rt = runtimeFromAnyThread();
if (isAtomsZone() && rt->exclusiveThreadsPresent())
if (isAtomsZone() && rt->hasHelperThreadZones())
return false;
return true;
}

Просмотреть файл

@ -468,7 +468,9 @@ struct Zone : public JS::shadow::Zone,
js::ZoneGroupData<bool> isSystem;
mozilla::Atomic<bool> usedByExclusiveThread;
bool usedByHelperThread() {
return !isAtomsZone() && group()->usedByHelperThread;
}
#ifdef DEBUG
js::ZoneGroupData<unsigned> gcLastZoneGroupIndex;
@ -598,11 +600,8 @@ struct Zone : public JS::shadow::Zone,
namespace js {
// Iterate over all zone groups except those which may be in use by parse
// threads. Pretty soon this will exclude zone groups in use by parse threads
// (as for ZonesIter), i.e. the zone groups in use by cooperating threads,
// except that right now parse threads use zones in the same zone group as
// cooperating threads (bug 1323066).
// Iterate over all zone groups except those which may be in use by helper
// thread parse tasks.
class ZoneGroupsIter
{
gc::AutoEnterIteration iterMarker;
@ -613,13 +612,18 @@ class ZoneGroupsIter
explicit ZoneGroupsIter(JSRuntime* rt) : iterMarker(&rt->gc) {
it = rt->gc.groups.ref().begin();
end = rt->gc.groups.ref().end();
if (!done() && (*it)->usedByHelperThread)
next();
}
bool done() const { return it == end; }
void next() {
MOZ_ASSERT(!done());
it++;
do {
it++;
} while (!done() && (*it)->usedByHelperThread);
}
ZoneGroup* get() const {
@ -703,7 +707,7 @@ class ZonesIter
if (zone.ref().done()) {
zone.reset();
group.next();
} else if (!zone.ref().get()->usedByExclusiveThread) {
} else {
break;
}
}

Просмотреть файл

@ -15,8 +15,9 @@ namespace js {
ZoneGroup::ZoneGroup(JSRuntime* runtime)
: runtime(runtime),
ownerContext_(TlsContext.get()),
enterCount(this, 1),
zones_(),
enterCount(1),
zones_(this),
usedByHelperThread(false),
nursery_(this, this),
storeBuffer_(this, runtime, nursery()),
blocksToFreeAfterMinorGC((size_t) JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),

Просмотреть файл

@ -22,19 +22,12 @@ class AutoKeepAtoms;
typedef Vector<JS::Zone*, 4, SystemAllocPolicy> ZoneVector;
// Zone groups encapsulate data about a group of zones that are logically
// related in some way. Currently, each runtime has a single zone group, and
// all zones except the atoms zone (which has no group) are in that group.
// This will change soon.
// related in some way.
//
// When JSRuntimes become multithreaded (also happening soon; see bug 1323066),
// zone groups will be the primary means by which threads ensure exclusive
// access to the data they are using. Most data in a zone group, its zones,
// Zone groups are the primary means by which threads ensure exclusive access
// to the data they are using. Most data in a zone group, its zones,
// compartments, GC things and so forth may only be used by the thread that has
// entered the zone group.
//
// This restriction is not quite in place yet: zones used by an parse thread
// are accessed by that thread even though it does not have exclusive access
// to the entire zone group. This will also be changing soon.
class ZoneGroup
{
@ -46,7 +39,7 @@ class ZoneGroup
UnprotectedData<CooperatingContext> ownerContext_;
// The number of times the context has entered this zone group.
ZoneGroupData<size_t> enterCount;
UnprotectedData<size_t> enterCount;
public:
CooperatingContext& ownerContext() { return ownerContext_.ref(); }
@ -58,10 +51,13 @@ class ZoneGroup
// All zones in the group.
private:
ActiveThreadOrGCTaskData<ZoneVector> zones_;
ZoneGroupOrGCTaskData<ZoneVector> zones_;
public:
ZoneVector& zones() { return zones_.ref(); }
// Whether a zone in this group is in use by a helper thread.
mozilla::Atomic<bool> usedByHelperThread;
explicit ZoneGroup(JSRuntime* runtime);
~ZoneGroup();

Просмотреть файл

@ -2216,6 +2216,7 @@ class JS_PUBLIC_API(CompartmentCreationOptions)
traceGlobal_(nullptr),
zoneSpec_(NewZoneInSystemZoneGroup),
zonePointer_(nullptr),
disableNursery_(false),
invisibleToDebugger_(false),
mergeable_(false),
preserveJitCode_(false),
@ -2251,6 +2252,14 @@ class JS_PUBLIC_API(CompartmentCreationOptions)
CompartmentCreationOptions& setNewZoneInSystemZoneGroup();
CompartmentCreationOptions& setNewZoneInExistingZoneGroup(JSObject* obj);
// If these options are creating a new zone group, prevent the use of a
// generational GC nursery by that group.
bool disableNursery() const { return disableNursery_; }
CompartmentCreationOptions& setDisableNursery(bool flag) {
disableNursery_ = flag;
return *this;
}
// Certain scopes (i.e. XBL compilation scopes) are implementation details
// of the embedding, and references to them should never leak out to script.
// This flag causes the this compartment to skip firing onNewGlobalObject
@ -2320,6 +2329,7 @@ class JS_PUBLIC_API(CompartmentCreationOptions)
JSTraceOp traceGlobal_;
ZoneSpecifier zoneSpec_;
void* zonePointer_; // Per zoneSpec_, either a Zone, ZoneGroup, or null.
bool disableNursery_;
bool invisibleToDebugger_;
bool mergeable_;
bool preserveJitCode_;

Просмотреть файл

@ -1039,8 +1039,8 @@ JSContext::JSContext(JSRuntime* runtime, const JS::ContextOptions& options)
for (size_t i = 0; i < mozilla::ArrayLength(nativeStackQuota); i++)
nativeStackQuota[i] = 0;
if (!TlsContext.get())
TlsContext.set(this);
MOZ_ASSERT(!TlsContext.get());
TlsContext.set(this);
}
JSContext::~JSContext()
@ -1070,8 +1070,8 @@ JSContext::~JSContext()
DestroyTraceLogger(traceLogger);
#endif
if (TlsContext.get() == this)
TlsContext.set(nullptr);
MOZ_ASSERT(TlsContext.get() == this);
TlsContext.set(nullptr);
}
void

Просмотреть файл

@ -1144,7 +1144,7 @@ class MOZ_RAII AutoLockForExclusiveAccess
void init(JSRuntime* rt) {
runtime = rt;
if (runtime->numExclusiveThreads) {
if (runtime->hasHelperThreadZones()) {
runtime->exclusiveAccessLock.lock();
} else {
MOZ_ASSERT(!runtime->activeThreadHasExclusiveAccess);
@ -1164,7 +1164,7 @@ class MOZ_RAII AutoLockForExclusiveAccess
init(rt);
}
~AutoLockForExclusiveAccess() {
if (runtime->numExclusiveThreads) {
if (runtime->hasHelperThreadZones()) {
runtime->exclusiveAccessLock.unlock();
} else {
MOZ_ASSERT(runtime->activeThreadHasExclusiveAccess);

Просмотреть файл

@ -443,7 +443,7 @@ JSContext::enterCompartment(
{
enterCompartmentDepth_++;
if (!c->zone()->isAtomsZone() && !c->zone()->usedByExclusiveThread)
if (!c->zone()->isAtomsZone())
enterZoneGroup(c->zone()->group());
c->enter();
@ -479,7 +479,7 @@ JSContext::leaveCompartment(
setCompartment(oldCompartment, maybeLock);
if (startingCompartment) {
startingCompartment->leave();
if (!startingCompartment->zone()->isAtomsZone() && !startingCompartment->zone()->usedByExclusiveThread)
if (!startingCompartment->zone()->isAtomsZone())
leaveZoneGroup(startingCompartment->zone()->group());
}
}
@ -488,14 +488,6 @@ inline void
JSContext::setCompartment(JSCompartment* comp,
const js::AutoLockForExclusiveAccess* maybeLock /* = nullptr */)
{
// Contexts operating on helper threads can only be in the atoms zone or in exclusive zones.
MOZ_ASSERT_IF(helperThread() && !runtime_->isAtomsCompartment(comp),
comp->zone()->usedByExclusiveThread);
// Normal JSContexts cannot enter exclusive zones.
MOZ_ASSERT_IF(this == runtime()->activeContext() && comp,
!comp->zone()->usedByExclusiveThread);
// Only one thread can be in the atoms compartment at a time.
MOZ_ASSERT_IF(runtime_->isAtomsCompartment(comp), maybeLock != nullptr);
MOZ_ASSERT_IF(runtime_->isAtomsCompartment(comp) || runtime_->isAtomsCompartment(compartment_),
@ -510,9 +502,8 @@ JSContext::setCompartment(JSCompartment* comp,
MOZ_ASSERT_IF(compartment_, compartment_->hasBeenEntered());
MOZ_ASSERT_IF(comp, comp->hasBeenEntered());
// This context must have exclusive access to the zone's group. There is an
// exception, for now, for zones used by exclusive threads.
MOZ_ASSERT_IF(comp && !comp->zone()->isAtomsZone() && !comp->zone()->usedByExclusiveThread,
// This context must have exclusive access to the zone's group.
MOZ_ASSERT_IF(comp && !comp->zone()->isAtomsZone(),
comp->zone()->group()->ownedByCurrentThread());
compartment_ = comp;

Просмотреть файл

@ -3031,9 +3031,9 @@ GCRuntime::maybeAllocTriggerZoneGC(Zone* zone, const AutoLockGC& lock)
bool
GCRuntime::triggerZoneGC(Zone* zone, JS::gcreason::Reason reason)
{
/* Zones in use by a thread with an exclusive context can't be collected. */
/* Zones in use by a helper thread can't be collected. */
if (!CurrentThreadCanAccessRuntime(rt)) {
MOZ_ASSERT(zone->usedByExclusiveThread || zone->isAtomsZone());
MOZ_ASSERT(zone->usedByHelperThread() || zone->isAtomsZone());
return false;
}
@ -3050,7 +3050,7 @@ GCRuntime::triggerZoneGC(Zone* zone, JS::gcreason::Reason reason)
if (zone->isAtomsZone()) {
/* We can't do a zone GC of the atoms compartment. */
if (TlsContext.get()->keepAtoms || rt->exclusiveThreadsPresent()) {
if (TlsContext.get()->keepAtoms || rt->hasHelperThreadZones()) {
/* Skip GC and retrigger later, since atoms zone won't be collected
* if keepAtoms is true. */
fullGCForAtomsRequested_ = true;
@ -3492,14 +3492,6 @@ Zone::sweepCompartments(FreeOp* fop, bool keepAtleastOne, bool destroyingRuntime
void
GCRuntime::sweepZones(FreeOp* fop, ZoneGroup* group, bool destroyingRuntime)
{
MOZ_ASSERT_IF(destroyingRuntime, numActiveZoneIters == 0);
MOZ_ASSERT_IF(destroyingRuntime, arenasEmptyAtShutdown);
if (rt->gc.numActiveZoneIters)
return;
assertBackgroundSweepingFinished();
JSZoneCallback callback = rt->destroyZoneCallback;
Zone** read = group->zones().begin();
@ -3547,6 +3539,14 @@ GCRuntime::sweepZones(FreeOp* fop, ZoneGroup* group, bool destroyingRuntime)
void
GCRuntime::sweepZoneGroups(FreeOp* fop, bool destroyingRuntime)
{
MOZ_ASSERT_IF(destroyingRuntime, numActiveZoneIters == 0);
MOZ_ASSERT_IF(destroyingRuntime, arenasEmptyAtShutdown);
if (rt->gc.numActiveZoneIters)
return;
assertBackgroundSweepingFinished();
ZoneGroup** read = groups.ref().begin();
ZoneGroup** end = groups.ref().end();
ZoneGroup** write = read;
@ -3853,7 +3853,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
* the other collected zones are using are marked, and we can update the
* set of atoms in use by the other collected zones at the end of the GC.
*/
if (!TlsContext.get()->keepAtoms || rt->exclusiveThreadsPresent()) {
if (!TlsContext.get()->keepAtoms || rt->hasHelperThreadZones()) {
Zone* atomsZone = rt->atomsCompartment(lock)->zone();
if (atomsZone->isGCScheduled()) {
MOZ_ASSERT(!atomsZone->isCollecting());
@ -6290,7 +6290,7 @@ GCRuntime::gcCycle(bool nonincrementalByAPI, SliceBudget& budget, JS::gcreason::
// We don't allow off-thread parsing to start while we're doing an
// incremental GC.
MOZ_ASSERT_IF(rt->activeGCInAtomsZone(), !rt->exclusiveThreadsPresent());
MOZ_ASSERT_IF(rt->activeGCInAtomsZone(), !rt->hasHelperThreadZones());
auto result = budgetIncrementalGC(nonincrementalByAPI, reason, budget, session.lock);
@ -6683,7 +6683,7 @@ ZoneGroup::minorGC(JS::gcreason::Reason reason, gcstats::Phase phase)
{
AutoLockGC lock(runtime);
for (ZonesIter zone(runtime, WithAtoms); !zone.done(); zone.next())
for (ZonesInGroupIter zone(this); !zone.done(); zone.next())
runtime->gc.maybeAllocTriggerZoneGC(zone, lock);
}
}
@ -6790,7 +6790,10 @@ js::NewCompartment(JSContext* cx, JSPrincipals* principals,
break;
}
if (!group) {
if (group) {
// Take over ownership of the group while we create the compartment/zone.
group->enter();
} else {
MOZ_ASSERT(!zone);
group = cx->new_<ZoneGroup>(rt);
if (!group)
@ -6798,7 +6801,11 @@ js::NewCompartment(JSContext* cx, JSPrincipals* principals,
groupHolder.reset(group);
if (!group->init(rt->gc.tunables.gcMaxNurseryBytes())) {
size_t nurseryBytes =
options.creationOptions().disableNursery()
? 0
: rt->gc.tunables.gcMaxNurseryBytes();
if (!group->init(nurseryBytes)) {
ReportOutOfMemory(cx);
return nullptr;
}
@ -6865,6 +6872,7 @@ js::NewCompartment(JSContext* cx, JSPrincipals* principals,
zoneHolder.forget();
groupHolder.forget();
group->leave();
return compartment.forget();
}
@ -7823,16 +7831,23 @@ js::gc::detail::CellIsMarkedGrayIfKnown(const Cell* cell)
if (!cell->isTenured())
return false;
// We ignore the gray marking state of cells and return false in two cases:
// We ignore the gray marking state of cells and return false in the
// following cases:
//
// 1) When OOM has caused us to clear the gcGrayBitsValid_ flag.
//
// 2) When we are in an incremental GC and examine a cell that is in a zone
// that is not being collected. Gray targets of CCWs that are marked black
// by a barrier will eventually be marked black in the next GC slice.
//
// 3) When we are not on the runtime's active thread. Helper threads might
// call this while parsing, and they are not allowed to inspect the
// runtime's incremental state. The objects being operated on are not able
// to be collected and will not be marked any color.
auto tc = &cell->asTenured();
auto rt = tc->runtimeFromActiveCooperatingThread();
if (!rt->gc.areGrayBitsValid() ||
auto rt = tc->runtimeFromAnyThread();
if (!CurrentThreadCanAccessRuntime(rt) ||
!rt->gc.areGrayBitsValid() ||
(rt->gc.isIncrementalGCInProgress() && !tc->zone()->wasGCStarted()))
{
return false;

Просмотреть файл

@ -47,7 +47,7 @@ CheckActiveThread<Helper>::check() const
return;
JSContext* cx = TlsContext.get();
MOZ_ASSERT(cx == cx->runtime()->activeContext());
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
#endif // XP_WIN
}
@ -63,10 +63,22 @@ CheckZoneGroup<Helper>::check() const
return;
if (group) {
// This check is disabled for now because helper thread parse tasks
// access data in the same zone group that the single active thread is
// using. This will be fixed soon (bug 1323066).
//MOZ_ASSERT(group->context && group->context == TlsContext.get());
if (group->usedByHelperThread) {
MOZ_ASSERT(group->ownedByCurrentThread());
} else {
// This check is disabled on windows for the same reason as in
// CheckActiveThread.
#ifndef XP_WIN
// In a cooperatively scheduled runtime the active thread is
// permitted access to all zone groups --- even those it has not
// entered --- for GC and similar purposes. Since all other
// cooperative threads are suspended, these accesses are threadsafe
// if the zone group is not in use by a helper thread which is not
// cooperatively scheduled.
JSContext* cx = TlsContext.get();
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
#endif
}
} else {
// |group| will be null for data in the atoms zone. This is protected
// by the exclusive access lock.

Просмотреть файл

@ -23,11 +23,6 @@ namespace js {
// associated with the data, consider using the ExclusiveData class instead.
// Otherwise, ProtectedData should be used to document whatever synchronization
// method is used.
//
// Note that several of the checks below are currently disabled
// (e.g. ThreadLocalData, ZoneGroupData). These will be fixed soon (bug 1323066),
// but for now this class is largely documenting how we would like data to be
// protected, rather than how it actually is protected.
#define DECLARE_ONE_BOOL_OPERATOR(OP, T) \
template <typename U> \
@ -179,10 +174,7 @@ class CheckThreadLocal
{}
inline void check() const {
// This check is currently disabled because JSContexts used for off
// thread parsing are created on different threads than where they run.
// This will be fixed soon (bug 1323066).
//MOZ_ASSERT(id == ThisThread::GetId());
MOZ_ASSERT(id == ThisThread::GetId());
}
#endif
};
@ -239,7 +231,8 @@ class CheckZoneGroup
};
// Data which may only be accessed by threads with exclusive access to the
// associated zone group.
// associated zone group, or by the runtime's cooperatively scheduled
// active thread for zone groups which are not in use by a helper thread.
template <typename T>
using ZoneGroupData =
ProtectedDataZoneGroupArg<CheckZoneGroup<AllowedHelperThread::None>, T>;

Просмотреть файл

@ -1484,8 +1484,10 @@ ArrayBufferViewObject::trace(JSTracer* trc, JSObject* objArg)
// We can't use a direct forwarding pointer here, as there might
// not be enough bytes available, and other views might have data
// pointers whose forwarding pointers would overlap this one.
Nursery& nursery = obj->zoneFromAnyThread()->group()->nursery();
nursery.maybeSetForwardingPointer(trc, srcData, dstData, /* direct = */ false);
if (trc->isTenuringTracer()) {
Nursery& nursery = obj->zoneFromAnyThread()->group()->nursery();
nursery.maybeSetForwardingPointer(trc, srcData, dstData, /* direct = */ false);
}
} else {
MOZ_ASSERT_IF(buf.dataPointer() == nullptr, offset == 0);

Просмотреть файл

@ -290,27 +290,27 @@ static const JSClass parseTaskGlobalClass = {
&parseTaskGlobalClassOps
};
ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, const char16_t* chars, size_t length,
ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData)
: kind(kind), cx(cx), options(initCx), chars(chars), length(length),
: kind(kind), options(cx), chars(chars), length(length),
alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
exclusiveContextGlobal(exclusiveContextGlobal),
parseGlobal(parseGlobal),
callback(callback), callbackData(callbackData),
script(nullptr), sourceObject(nullptr),
errors(cx), overRecursed(false), outOfMemory(false)
overRecursed(false), outOfMemory(false)
{
}
ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, JS::TranscodeBuffer& buffer, size_t cursor,
ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
JS::TranscodeBuffer& buffer, size_t cursor,
JS::OffThreadCompileCallback callback, void* callbackData)
: kind(kind), cx(cx), options(initCx), buffer(&buffer), cursor(cursor),
: kind(kind), options(cx), buffer(&buffer), cursor(cursor),
alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
exclusiveContextGlobal(exclusiveContextGlobal),
parseGlobal(parseGlobal),
callback(callback), callbackData(callbackData),
script(nullptr), sourceObject(nullptr),
errors(cx), overRecursed(false), outOfMemory(false)
overRecursed(false), outOfMemory(false)
{
}
@ -326,8 +326,7 @@ ParseTask::init(JSContext* cx, const ReadOnlyCompileOptions& options)
void
ParseTask::activate(JSRuntime* rt)
{
rt->setUsedByExclusiveThread(exclusiveContextGlobal->zone());
cx->enterCompartmentOf(exclusiveContextGlobal);
rt->setUsedByHelperThread(parseGlobal->zone());
}
bool
@ -344,9 +343,6 @@ ParseTask::finish(JSContext* cx)
ParseTask::~ParseTask()
{
// ParseTask takes over ownership of its input exclusive context.
js_delete(cx);
for (size_t i = 0; i < errors.length(); i++)
js_delete(errors[i]);
}
@ -354,26 +350,26 @@ ParseTask::~ParseTask()
void
ParseTask::trace(JSTracer* trc)
{
if (!cx->runtimeMatches(trc->runtime()))
if (parseGlobal->runtimeFromAnyThread() != trc->runtime())
return;
TraceManuallyBarrieredEdge(trc, &exclusiveContextGlobal, "ParseTask::exclusiveContextGlobal");
TraceManuallyBarrieredEdge(trc, &parseGlobal, "ParseTask::parseGlobal");
if (script)
TraceManuallyBarrieredEdge(trc, &script, "ParseTask::script");
if (sourceObject)
TraceManuallyBarrieredEdge(trc, &sourceObject, "ParseTask::sourceObject");
}
ScriptParseTask::ScriptParseTask(JSContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, const char16_t* chars, size_t length,
ScriptParseTask::ScriptParseTask(JSContext* cx, JSObject* parseGlobal,
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData)
: ParseTask(ParseTaskKind::Script, cx, exclusiveContextGlobal, initCx, chars, length, callback,
: ParseTask(ParseTaskKind::Script, cx, parseGlobal, chars, length, callback,
callbackData)
{
}
void
ScriptParseTask::parse()
ScriptParseTask::parse(JSContext* cx)
{
SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
script = frontend::CompileGlobalScript(cx, alloc, ScopeKind::Global,
@ -382,16 +378,16 @@ ScriptParseTask::parse()
/* sourceObjectOut = */ &sourceObject);
}
ModuleParseTask::ModuleParseTask(JSContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, const char16_t* chars, size_t length,
ModuleParseTask::ModuleParseTask(JSContext* cx, JSObject* parseGlobal,
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData)
: ParseTask(ParseTaskKind::Module, cx, exclusiveContextGlobal, initCx, chars, length, callback,
: ParseTask(ParseTaskKind::Module, cx, parseGlobal, chars, length, callback,
callbackData)
{
}
void
ModuleParseTask::parse()
ModuleParseTask::parse(JSContext* cx)
{
SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
ModuleObject* module = frontend::CompileModule(cx, options, srcBuf, alloc, &sourceObject);
@ -399,16 +395,16 @@ ModuleParseTask::parse()
script = module->script();
}
ScriptDecodeTask::ScriptDecodeTask(JSContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, JS::TranscodeBuffer& buffer, size_t cursor,
ScriptDecodeTask::ScriptDecodeTask(JSContext* cx, JSObject* parseGlobal,
JS::TranscodeBuffer& buffer, size_t cursor,
JS::OffThreadCompileCallback callback, void* callbackData)
: ParseTask(ParseTaskKind::ScriptDecode, cx, exclusiveContextGlobal, initCx,
: ParseTask(ParseTaskKind::ScriptDecode, cx, parseGlobal,
buffer, cursor, callback, callbackData)
{
}
void
ScriptDecodeTask::parse()
ScriptDecodeTask::parse(JSContext* cx)
{
RootedScript resultScript(cx);
XDROffThreadDecoder decoder(cx, alloc, &options, /* sourceObjectOut = */ &sourceObject,
@ -541,7 +537,8 @@ CreateGlobalForOffThreadParse(JSContext* cx, ParseTaskKind kind, const gc::AutoS
creationOptions.setInvisibleToDebugger(true)
.setMergeable(true)
.setNewZoneInSystemZoneGroup();
.setNewZoneInNewZoneGroup()
.setDisableNursery(true);
// Don't falsely inherit the host's global trace hook.
creationOptions.setTrace(nullptr);
@ -605,17 +602,10 @@ StartOffThreadParseTask(JSContext* cx, const ReadOnlyCompileOptions& options,
if (!global)
return false;
ScopedJSDeletePtr<JSContext> helpercx(
cx->new_<JSContext>(cx->runtime(), cx->options()));
if (!helpercx)
return false;
ScopedJSDeletePtr<ParseTask> task(taskFunctor(helpercx.get(), global));
ScopedJSDeletePtr<ParseTask> task(taskFunctor(global));
if (!task)
return false;
helpercx.forget();
if (!task->init(cx, options) || !QueueOffThreadParseTask(cx, task))
return false;
@ -630,8 +620,8 @@ js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& optio
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData)
{
auto functor = [&](JSContext* helpercx, JSObject* global) -> ScriptParseTask* {
return cx->new_<ScriptParseTask>(helpercx, global, cx, chars, length,
auto functor = [&](JSObject* global) -> ScriptParseTask* {
return cx->new_<ScriptParseTask>(cx, global, chars, length,
callback, callbackData);
};
return StartOffThreadParseTask(cx, options, ParseTaskKind::Script, functor);
@ -642,8 +632,8 @@ js::StartOffThreadParseModule(JSContext* cx, const ReadOnlyCompileOptions& optio
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData)
{
auto functor = [&](JSContext* helpercx, JSObject* global) -> ModuleParseTask* {
return cx->new_<ModuleParseTask>(helpercx, global, cx, chars, length,
auto functor = [&](JSObject* global) -> ModuleParseTask* {
return cx->new_<ModuleParseTask>(cx, global, chars, length,
callback, callbackData);
};
return StartOffThreadParseTask(cx, options, ParseTaskKind::Module, functor);
@ -654,8 +644,8 @@ js::StartOffThreadDecodeScript(JSContext* cx, const ReadOnlyCompileOptions& opti
JS::TranscodeBuffer& buffer, size_t cursor,
JS::OffThreadCompileCallback callback, void* callbackData)
{
auto functor = [&](JSContext* helpercx, JSObject* global) -> ScriptDecodeTask* {
return cx->new_<ScriptDecodeTask>(helpercx, global, cx, buffer, cursor,
auto functor = [&](JSObject* global) -> ScriptDecodeTask* {
return cx->new_<ScriptDecodeTask>(cx, global, buffer, cursor,
callback, callbackData);
};
return StartOffThreadParseTask(cx, options, ParseTaskKind::ScriptDecode, functor);
@ -674,7 +664,7 @@ js::EnqueuePendingParseTasksAfterGC(JSRuntime* rt)
for (size_t i = 0; i < waiting.length(); i++) {
ParseTask* task = waiting[i];
if (task->runtimeMatches(rt) && !task->exclusiveContextGlobal->zone()->wasGCStarted()) {
if (task->runtimeMatches(rt) && !task->parseGlobal->zone()->wasGCStarted()) {
AutoEnterOOMUnsafeRegion oomUnsafe;
if (!newTasks.append(task))
oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");
@ -1241,8 +1231,7 @@ LeaveParseTaskZone(JSRuntime* rt, ParseTask* task)
{
// Mark the zone as no longer in use by an JSContext, and available
// to be collected by the GC.
task->cx->leaveCompartment(task->cx->compartment());
rt->clearUsedByExclusiveThread(task->cx->zone());
rt->clearUsedByHelperThread(task->parseGlobal->zone());
}
ParseTask*
@ -1378,12 +1367,13 @@ GlobalHelperThreadState::mergeParseTaskCompartment(JSContext* cx, ParseTask* par
JS::AutoAssertNoGC nogc(cx);
LeaveParseTaskZone(cx->runtime(), parseTask);
AutoCompartment ac(cx, parseTask->parseGlobal);
{
// Generator functions don't have Function.prototype as prototype but a
// different function object, so the IdentifyStandardPrototype trick
// below won't work. Just special-case it.
GlobalObject* parseGlobal = &parseTask->exclusiveContextGlobal->as<GlobalObject>();
GlobalObject* parseGlobal = &parseTask->parseGlobal->as<GlobalObject>();
JSObject* parseTaskStarGenFunctionProto = parseGlobal->getStarGeneratorFunctionPrototype();
// Module objects don't have standard prototypes either.
@ -1395,7 +1385,8 @@ GlobalHelperThreadState::mergeParseTaskCompartment(JSContext* cx, ParseTask* par
// to the corresponding prototype in the new compartment. This will briefly
// create cross compartment pointers, which will be fixed by the
// MergeCompartments call below.
for (auto group = parseTask->cx->zone()->cellIter<ObjectGroup>(); !group.done(); group.next()) {
Zone* parseZone = parseTask->parseGlobal->zone();
for (auto group = parseZone->cellIter<ObjectGroup>(); !group.done(); group.next()) {
TaggedProto proto(group->proto());
if (!proto.isObject())
continue;
@ -1426,7 +1417,7 @@ GlobalHelperThreadState::mergeParseTaskCompartment(JSContext* cx, ParseTask* par
}
// Move the parsed script and all its contents into the desired compartment.
gc::MergeCompartments(parseTask->cx->compartment(), dest);
gc::MergeCompartments(parseTask->parseGlobal->compartment(), dest);
}
void
@ -1636,20 +1627,16 @@ js::PauseCurrentHelperThread()
HelperThreadState().wait(lock, GlobalHelperThreadState::PAUSE);
}
void
JSContext::setHelperThread(HelperThread* thread)
{
helperThread_ = thread;
}
bool
JSContext::addPendingCompileError(frontend::CompileError** error)
{
UniquePtr<frontend::CompileError> errorPtr(new_<frontend::CompileError>());
if (!errorPtr)
return false;
if (!helperThread()->parseTask()->errors.append(errorPtr.get()))
if (!helperThread()->parseTask()->errors.append(errorPtr.get())) {
ReportOutOfMemory(this);
return false;
}
*error = errorPtr.release();
return true;
}
@ -1677,18 +1664,16 @@ HelperThread::handleParseWorkload(AutoLockHelperThreadState& locked, uintptr_t s
currentTask.emplace(HelperThreadState().parseWorklist(locked).popCopy());
ParseTask* task = parseTask();
task->cx->setHelperThread(this);
for (size_t i = 0; i < ArrayLength(task->cx->nativeStackLimit); i++)
task->cx->nativeStackLimit[i] = stackLimit;
JSContext* oldcx = TlsContext.get();
TlsContext.set(task->cx);
{
AutoUnlockHelperThreadState unlock(locked);
task->parse();
AutoSetContextRuntime ascr(task->parseGlobal->runtimeFromAnyThread());
JSContext* cx = TlsContext.get();
AutoCompartment ac(cx, task->parseGlobal);
task->parse(cx);
}
TlsContext.set(oldcx);
// The callback is invoked while we are still off thread.
task->callback(task, task->callbackData);
@ -1891,6 +1876,12 @@ HelperThread::handleGCHelperWorkload(AutoLockHelperThreadState& locked)
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
}
void
JSContext::setHelperThread(HelperThread* thread)
{
helperThread_ = thread;
}
void
HelperThread::threadLoop()
{
@ -1900,6 +1891,7 @@ HelperThread::threadLoop()
AutoLockHelperThreadState lock;
JSContext cx(nullptr, JS::ContextOptions());
cx.setHelperThread(this);
// Compute the thread's stack limit, for over-recursed checks.
uintptr_t stackLimit = GetNativeStackBase();

Просмотреть файл

@ -568,7 +568,6 @@ class MOZ_RAII AutoUnlockHelperThreadState : public UnlockGuard<Mutex>
struct ParseTask
{
ParseTaskKind kind;
JSContext* cx;
OwningCompileOptions options;
// Anonymous union, the only correct interpretation is provided by the
// ParseTaskKind value, or from the virtual parse function.
@ -587,8 +586,8 @@ struct ParseTask
};
LifoAlloc alloc;
// Rooted pointer to the global object used by 'cx'.
JSObject* exclusiveContextGlobal;
// Rooted pointer to the global object to use while parsing.
JSObject* parseGlobal;
// Callback invoked off thread when the parse finishes.
JS::OffThreadCompileCallback callback;
@ -604,24 +603,24 @@ struct ParseTask
// Any errors or warnings produced during compilation. These are reported
// when finishing the script.
Vector<frontend::CompileError*> errors;
Vector<frontend::CompileError*, 0, SystemAllocPolicy> errors;
bool overRecursed;
bool outOfMemory;
ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, const char16_t* chars, size_t length,
ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData);
ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, JS::TranscodeBuffer& buffer, size_t cursor,
ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
JS::TranscodeBuffer& buffer, size_t cursor,
JS::OffThreadCompileCallback callback, void* callbackData);
bool init(JSContext* cx, const ReadOnlyCompileOptions& options);
void activate(JSRuntime* rt);
virtual void parse() = 0;
virtual void parse(JSContext* cx) = 0;
bool finish(JSContext* cx);
bool runtimeMatches(JSRuntime* rt) {
return cx->runtimeMatches(rt);
return parseGlobal->runtimeFromAnyThread() == rt;
}
virtual ~ParseTask();
@ -631,26 +630,26 @@ struct ParseTask
struct ScriptParseTask : public ParseTask
{
ScriptParseTask(JSContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, const char16_t* chars, size_t length,
ScriptParseTask(JSContext* cx, JSObject* parseGlobal,
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData);
void parse() override;
void parse(JSContext* cx) override;
};
struct ModuleParseTask : public ParseTask
{
ModuleParseTask(JSContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, const char16_t* chars, size_t length,
ModuleParseTask(JSContext* cx, JSObject* parseGlobal,
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData);
void parse() override;
void parse(JSContext* cx) override;
};
struct ScriptDecodeTask : public ParseTask
{
ScriptDecodeTask(JSContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, JS::TranscodeBuffer& buffer, size_t cursor,
ScriptDecodeTask(JSContext* cx, JSObject* parseGlobal,
JS::TranscodeBuffer& buffer, size_t cursor,
JS::OffThreadCompileCallback callback, void* callbackData);
void parse() override;
void parse(JSContext* cx) override;
};
// Return whether, if a new parse task was started, it would need to wait for

Просмотреть файл

@ -483,10 +483,7 @@ NativeObject::growSlotsDontReportOOM(JSContext* cx, NativeObject* obj, uint32_t
static void
FreeSlots(JSContext* cx, HeapSlot* slots)
{
// Note: off thread parse tasks do not have access to GGC nursery allocated things.
if (!cx->helperThread())
return cx->nursery().freeBuffer(slots);
js_free(slots);
return cx->nursery().freeBuffer(slots);
}
void

Просмотреть файл

@ -133,7 +133,7 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
#ifdef DEBUG
activeThreadHasExclusiveAccess(false),
#endif
numExclusiveThreads(0),
numHelperThreadZones(0),
numCompartments(0),
localeCallbacks(nullptr),
defaultLocale(nullptr),
@ -306,7 +306,7 @@ JSRuntime::destroyRuntime()
MOZ_ASSERT(ionLazyLinkListSize_ == 0);
MOZ_ASSERT(ionLazyLinkList().isEmpty());
MOZ_ASSERT(!numExclusiveThreads);
MOZ_ASSERT(!hasHelperThreadZones());
AutoLockForExclusiveAccess lock(this);
/*
@ -752,20 +752,20 @@ JSRuntime::activeGCInAtomsZone()
}
void
JSRuntime::setUsedByExclusiveThread(Zone* zone)
JSRuntime::setUsedByHelperThread(Zone* zone)
{
MOZ_ASSERT(!zone->usedByExclusiveThread);
MOZ_ASSERT(!zone->group()->usedByHelperThread);
MOZ_ASSERT(!zone->wasGCStarted());
zone->usedByExclusiveThread = true;
numExclusiveThreads++;
zone->group()->usedByHelperThread = true;
numHelperThreadZones++;
}
void
JSRuntime::clearUsedByExclusiveThread(Zone* zone)
JSRuntime::clearUsedByHelperThread(Zone* zone)
{
MOZ_ASSERT(zone->usedByExclusiveThread);
zone->usedByExclusiveThread = false;
numExclusiveThreads--;
MOZ_ASSERT(zone->group()->usedByHelperThread);
zone->group()->usedByHelperThread = false;
numHelperThreadZones--;
if (gc.fullGCForAtomsRequested() && !TlsContext.get())
gc.triggerFullGCForAtoms();
}
@ -782,10 +782,8 @@ js::CurrentThreadCanAccessZone(Zone* zone)
if (CurrentThreadCanAccessRuntime(zone->runtime_))
return true;
// Only zones in use by an exclusive thread can be used off thread.
// We don't keep track of which thread owns such zones though, so this check
// is imperfect.
return zone->usedByExclusiveThread;
// Only zones marked for use by a helper thread can be used off thread.
return zone->usedByHelperThread() && zone->group()->ownedByCurrentThread();
}
#ifdef DEBUG

Просмотреть файл

@ -571,22 +571,22 @@ struct JSRuntime : public js::MallocProvider<JSRuntime>
bool activeThreadHasExclusiveAccess;
#endif
/* Number of non-cooperating threads with exclusive access to some zone. */
js::UnprotectedData<size_t> numExclusiveThreads;
/* Number of zones which may be operated on by non-cooperating helper threads. */
js::UnprotectedData<size_t> numHelperThreadZones;
friend class js::AutoLockForExclusiveAccess;
public:
void setUsedByExclusiveThread(JS::Zone* zone);
void clearUsedByExclusiveThread(JS::Zone* zone);
void setUsedByHelperThread(JS::Zone* zone);
void clearUsedByHelperThread(JS::Zone* zone);
bool exclusiveThreadsPresent() const {
return numExclusiveThreads > 0;
bool hasHelperThreadZones() const {
return numHelperThreadZones > 0;
}
#ifdef DEBUG
bool currentThreadHasExclusiveAccess() const {
return (!exclusiveThreadsPresent() && activeThreadHasExclusiveAccess) ||
return (!hasHelperThreadZones() && activeThreadHasExclusiveAccess) ||
exclusiveAccessLock.ownedByCurrentThread();
}
#endif