author | Jon Coppeard <jcoppeard@mozilla.com> |
Mon, 19 Jul 2021 08:22:43 +0000 | |
changeset 585926 | e7e916470a4aafc5005ae2a60bab63c56466770d |
parent 585925 | 3b1f9f3413f4723951562044ad69bddb3b992418 |
child 585927 | ad93176ef80e03f18d84d41fdaab648d68f679af |
push id | 146474 |
push user | jcoppeard@mozilla.com |
push date | Mon, 19 Jul 2021 08:25:10 +0000 |
treeherder | autoland@e7e916470a4a [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | sfink |
bugs | 1720906 |
milestone | 92.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -843,19 +843,21 @@ TenuredChunk* GCRuntime::pickChunk(AutoL MOZ_ASSERT(!availableChunks(lock).contains(chunk)); availableChunks(lock).push(chunk); return chunk; } BackgroundAllocTask::BackgroundAllocTask(GCRuntime* gc, ChunkPool& pool) - : GCParallelTask(gc), + : GCParallelTask(gc, gcstats::PhaseKind::NONE), chunkPool_(pool), - enabled_(CanUseExtraThreads() && GetCPUCount() >= 2) {} + enabled_(CanUseExtraThreads() && GetCPUCount() >= 2) { + // This can occur outside GCs so doesn't have a stats phase. +} void BackgroundAllocTask::run(AutoLockHelperThreadState& lock) { AutoUnlockHelperThreadState unlock(lock); TraceLoggerThread* logger = TraceLoggerForCurrentThread(); AutoTraceLog logAllocation(logger, TraceLogger_GCAllocation); AutoLockGC gcLock(gc);
--- a/js/src/gc/GC.cpp +++ b/js/src/gc/GC.cpp @@ -3576,16 +3576,19 @@ void GCRuntime::startDecommit() { if (sweepOnBackgroundThread) { decommitTask.start(); return; } decommitTask.runFromMainThread(); } +BackgroundDecommitTask::BackgroundDecommitTask(GCRuntime* gc) + : GCParallelTask(gc, gcstats::PhaseKind::DECOMMIT) {} + void js::gc::BackgroundDecommitTask::run(AutoLockHelperThreadState& lock) { { AutoUnlockHelperThreadState unlock(lock); ChunkPool emptyChunksToFree; { AutoLockGC gcLock(gc); @@ -3744,16 +3747,19 @@ void GCRuntime::queueZonesAndStartBackgr } } if (!sweepOnBackgroundThread) { sweepTask.join(); sweepTask.runFromMainThread(); } } +BackgroundSweepTask::BackgroundSweepTask(GCRuntime* gc) + : GCParallelTask(gc, gcstats::PhaseKind::SWEEP) {} + void BackgroundSweepTask::run(AutoLockHelperThreadState& lock) { AutoTraceLog logSweeping(TraceLoggerForCurrentThread(), TraceLogger_GCSweeping); gc->sweepFromBackgroundThread(lock); } void GCRuntime::sweepFromBackgroundThread(AutoLockHelperThreadState& lock) { @@ -3809,16 +3815,21 @@ void GCRuntime::queueBuffersForFreeAfter std::swap(buffersToFreeAfterMinorGC.ref(), buffers); } void GCRuntime::startBackgroundFree() { AutoLockHelperThreadState lock; freeTask.startOrRunIfIdle(lock); } +BackgroundFreeTask::BackgroundFreeTask(GCRuntime* gc) + : GCParallelTask(gc, gcstats::PhaseKind::NONE) { + // This can occur outside GCs so doesn't have a stats phase. +} + void BackgroundFreeTask::run(AutoLockHelperThreadState& lock) { AutoTraceLog logFreeing(TraceLoggerForCurrentThread(), TraceLogger_GCFree); gc->freeFromBackgroundThread(lock); } void GCRuntime::freeFromBackgroundThread(AutoLockHelperThreadState& lock) { do { @@ -4017,27 +4028,26 @@ void ArenaLists::checkEmptyArenaList(All MOZ_ASSERT(arenaList(kind).isEmpty()); } class MOZ_RAII AutoRunParallelTask : public GCParallelTask { // This class takes a pointer to a member function of GCRuntime. using TaskFunc = JS_MEMBER_FN_PTR_TYPE(GCRuntime, void); TaskFunc func_; - gcstats::PhaseKind phase_; AutoLockHelperThreadState& lock_; public: AutoRunParallelTask(GCRuntime* gc, TaskFunc func, gcstats::PhaseKind phase, AutoLockHelperThreadState& lock) - : GCParallelTask(gc), func_(func), phase_(phase), lock_(lock) { - gc->startTask(*this, phase_, lock_); - } - - ~AutoRunParallelTask() { gc->joinTask(*this, phase_, lock_); } + : GCParallelTask(gc, phase), func_(func), lock_(lock) { + gc->startTask(*this, lock_); + } + + ~AutoRunParallelTask() { gc->joinTask(*this, lock_); } void run(AutoLockHelperThreadState& lock) override { AutoUnlockHelperThreadState unlock(lock); // The hazard analysis can't tell what the call to func_ will do but it's // not allowed to GC. JS::AutoSuppressGCAnalysis nogc; @@ -4447,16 +4457,19 @@ bool GCRuntime::beginPreparePhase(JS::GC if (!IsShutdownReason(reason) && reason != JS::GCReason::ROOTS_REMOVED && reason != JS::GCReason::XPCONNECT_SHUTDOWN) { StartHandlingCompressionsOnGC(rt); } return true; } +BackgroundUnmarkTask::BackgroundUnmarkTask(GCRuntime* gc) + : GCParallelTask(gc, gcstats::PhaseKind::UNMARK) {} + void BackgroundUnmarkTask::initZones() { MOZ_ASSERT(isIdle()); MOZ_ASSERT(zones.empty()); MOZ_ASSERT(!isCancelled()); // We can't safely iterate the zones vector from another thread so we copy the // zones to be collected into another vector. AutoEnterOOMUnsafeRegion oomUnsafe; @@ -5022,17 +5035,17 @@ void GCRuntime::getNextSweepGroup() { } for (Zone* zone = currentSweepGroup; zone; zone = zone->nextNodeInGroup()) { MOZ_ASSERT(zone->isGCMarkingBlackOnly()); MOZ_ASSERT(!zone->isQueuedForBackgroundSweep()); } if (abortSweepAfterCurrentGroup) { - joinTask(markTask, gcstats::PhaseKind::SWEEP_MARK); + markTask.join(); // Abort collection of subsequent sweep groups. for (SweepGroupZonesIter zone(this); !zone.done(); zone.next()) { MOZ_ASSERT(!zone->gcNextGraphComponent); zone->changeGCState(Zone::MarkBlackOnly, Zone::NoGC); zone->arenas.unmarkPreMarkedFreeCells(); zone->arenas.mergeNewArenasInMarkPhase(); zone->gcGrayRoots().Clear(); @@ -5423,17 +5436,19 @@ class ImmediateSweepWeakCacheTask : publ Zone* zone; JS::detail::WeakCacheBase& cache; ImmediateSweepWeakCacheTask(const ImmediateSweepWeakCacheTask&) = delete; public: ImmediateSweepWeakCacheTask(GCRuntime* gc, Zone* zone, JS::detail::WeakCacheBase& wc) - : GCParallelTask(gc), zone(zone), cache(wc) {} + : GCParallelTask(gc, gcstats::PhaseKind::SWEEP_WEAK_CACHES), + zone(zone), + cache(wc) {} ImmediateSweepWeakCacheTask(ImmediateSweepWeakCacheTask&& other) : GCParallelTask(std::move(other)), zone(other.zone), cache(other.cache) {} void run(AutoLockHelperThreadState& lock) override { AutoUnlockHelperThreadState unlock(lock); @@ -5531,56 +5546,32 @@ void GCRuntime::sweepFinalizationRegistr gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::SWEEP_FINALIZATION_REGISTRIES); AutoLockStoreBuffer lock(&storeBuffer()); for (SweepGroupZonesIter zone(this); !zone.done(); zone.next()) { sweepFinalizationRegistries(zone); } } -void GCRuntime::startTask(GCParallelTask& task, gcstats::PhaseKind phase, +void GCRuntime::startTask(GCParallelTask& task, AutoLockHelperThreadState& lock) { if (!CanUseExtraThreads()) { AutoUnlockHelperThreadState unlock(lock); task.runFromMainThread(); - stats().recordParallelPhase(phase, task.duration()); + stats().recordParallelPhase(task.phaseKind, task.duration()); return; } task.startWithLockHeld(lock); } -void GCRuntime::joinTask(GCParallelTask& task, gcstats::PhaseKind phase, +void GCRuntime::joinTask(GCParallelTask& task, AutoLockHelperThreadState& lock) { - // This is similar to GCParallelTask::joinWithLockHeld but handles recording - // execution and wait time. - - if (task.isIdle(lock)) { - return; - } - - if (task.isDispatched(lock)) { - // If the task was dispatched but has not yet started then cancel the task - // and run it from the main thread. This stops us from blocking here when - // the helper threads are busy with other tasks. - task.cancelDispatchedTask(lock); - AutoUnlockHelperThreadState unlock(lock); - task.runFromMainThread(); - } else { - // Otherwise wait for the task to complete. - gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::JOIN_PARALLEL_TASKS); - task.joinRunningOrFinishedTask(lock); - } - - stats().recordParallelPhase(phase, task.duration()); -} - -void GCRuntime::joinTask(GCParallelTask& task, gcstats::PhaseKind phase) { - AutoLockHelperThreadState lock; - joinTask(task, phase, lock); + gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::JOIN_PARALLEL_TASKS); + task.joinWithLockHeld(lock); } void GCRuntime::sweepDebuggerOnMainThread(JSFreeOp* fop) { AutoLockStoreBuffer lock(&storeBuffer()); // Detach unreachable debuggers and global objects from each other. // This can modify weakmaps and so must happen before weakmap sweeping. DebugAPI::sweepAll(fop); @@ -5794,32 +5785,32 @@ IncrementalProgress GCRuntime::beginSwee PhaseKind::SWEEP_WEAKREFS, lock); WeakCacheTaskVector sweepCacheTasks; bool canSweepWeakCachesOffThread = PrepareWeakCacheTasks(rt, &sweepCacheTasks); if (canSweepWeakCachesOffThread) { weakCachesToSweep.ref().emplace(currentSweepGroup); for (auto& task : sweepCacheTasks) { - startTask(task, PhaseKind::SWEEP_WEAK_CACHES, lock); + startTask(task, lock); } } { AutoUnlockHelperThreadState unlock(lock); sweepJitDataOnMainThread(fop); if (!canSweepWeakCachesOffThread) { MOZ_ASSERT(sweepCacheTasks.empty()); SweepAllWeakCachesOnMainThread(rt); } } for (auto& task : sweepCacheTasks) { - joinTask(task, PhaseKind::SWEEP_WEAK_CACHES, lock); + joinTask(task, lock); } } if (sweepingAtoms) { startSweepingAtomsTable(); } // FinalizationRegistry sweeping touches weak maps and so must not run in @@ -6008,31 +5999,35 @@ bool ArenaLists::foregroundFinalize(JSFr al.insertListWithCursorAtEnd(newArenasInMarkPhase(thingKind)); al.insertListWithCursorAtEnd(allocatedDuringSweep); newArenasInMarkPhase(thingKind).clear(); return true; } +BackgroundMarkTask::BackgroundMarkTask(GCRuntime* gc) + : GCParallelTask(gc, gcstats::PhaseKind::SWEEP_MARK), + budget(SliceBudget::unlimited()) {} + void js::gc::BackgroundMarkTask::run(AutoLockHelperThreadState& lock) { AutoUnlockHelperThreadState unlock(lock); // Time reporting is handled separately for parallel tasks. gc->sweepMarkResult = gc->markUntilBudgetExhausted(this->budget, GCMarker::DontReportMarkTime); } IncrementalProgress GCRuntime::joinBackgroundMarkTask() { AutoLockHelperThreadState lock; if (markTask.isIdle(lock)) { return Finished; } - joinTask(markTask, gcstats::PhaseKind::SWEEP_MARK, lock); + joinTask(markTask, lock); IncrementalProgress result = sweepMarkResult; sweepMarkResult = Finished; return result; } IncrementalProgress GCRuntime::markUntilBudgetExhausted( SliceBudget& sliceBudget, GCMarker::ShouldReportMarkTime reportTime) {
--- a/js/src/gc/GCParallelTask.cpp +++ b/js/src/gc/GCParallelTask.cpp @@ -77,27 +77,31 @@ void js::GCParallelTask::join() { } void js::GCParallelTask::joinWithLockHeld(AutoLockHelperThreadState& lock) { // Task has not been started; there's nothing to do. if (isIdle(lock)) { return; } - // If the task was dispatched but has not yet started then cancel the task and - // run it from the main thread. This stops us from blocking here when the - // helper threads are busy with other tasks. if (isDispatched(lock)) { + // If the task was dispatched but has not yet started then cancel the task + // and run it from the main thread. This stops us from blocking here when + // the helper threads are busy with other tasks. cancelDispatchedTask(lock); AutoUnlockHelperThreadState unlock(lock); runFromMainThread(); - return; + } else { + // Otherwise wait for the task to complete. + joinRunningOrFinishedTask(lock); } - joinRunningOrFinishedTask(lock); + if (phaseKind != gcstats::PhaseKind::NONE) { + gc->stats().recordParallelPhase(phaseKind, duration()); + } } void js::GCParallelTask::joinRunningOrFinishedTask( AutoLockHelperThreadState& lock) { MOZ_ASSERT(isRunning(lock) || isFinished(lock)); // Wait for the task to run to completion. while (!isFinished(lock)) {
--- a/js/src/gc/GCParallelTask.h +++ b/js/src/gc/GCParallelTask.h @@ -20,30 +20,37 @@ #define JS_MEMBER_FN_PTR_TYPE(ClassT, ReturnT, /* ArgTs */...) \ ReturnT (ClassT::*)(__VA_ARGS__) #define JS_CALL_MEMBER_FN_PTR(Receiver, Ptr, /* Args */...) \ ((Receiver)->*(Ptr))(__VA_ARGS__) namespace js { +namespace gcstats { +enum class PhaseKind : uint8_t; +} + namespace gc { class GCRuntime; } class AutoLockHelperThreadState; class HelperThread; // A generic task used to dispatch work to the helper thread system. // Users override the pure-virtual run() method. class GCParallelTask : public mozilla::LinkedListElement<GCParallelTask>, public HelperThreadTask { public: gc::GCRuntime* const gc; + // This can be PhaseKind::NONE for tasks that take place outside a GC. + const gcstats::PhaseKind phaseKind; + private: // The state of the parallel computation. enum class State { // The task is idle. Either start() has not been called or join() has // returned. Idle, // The task has been started but has not yet begun running on a helper @@ -65,20 +72,25 @@ class GCParallelTask : public mozilla::L explicit GCParallelTask(const GCParallelTask&) = delete; protected: // A flag to signal a request for early completion of the off-thread task. mozilla::Atomic<bool, mozilla::MemoryOrdering::ReleaseAcquire> cancel_; public: - explicit GCParallelTask(gc::GCRuntime* gc) - : gc(gc), state_(State::Idle), duration_(nullptr), cancel_(false) {} + explicit GCParallelTask(gc::GCRuntime* gc, gcstats::PhaseKind phaseKind) + : gc(gc), + phaseKind(phaseKind), + state_(State::Idle), + duration_(nullptr), + cancel_(false) {} GCParallelTask(GCParallelTask&& other) : gc(other.gc), + phaseKind(other.phaseKind), state_(other.state_), duration_(nullptr), cancel_(false) {} // Derived classes must override this to ensure that join() gets called // before members get destructed. virtual ~GCParallelTask();
--- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -127,46 +127,45 @@ class ChunkPool { private: TenuredChunk* current_; }; }; class BackgroundMarkTask : public GCParallelTask { public: - explicit BackgroundMarkTask(GCRuntime* gc) - : GCParallelTask(gc), budget(SliceBudget::unlimited()) {} + explicit BackgroundMarkTask(GCRuntime* gc); void setBudget(const SliceBudget& budget) { this->budget = budget; } void run(AutoLockHelperThreadState& lock) override; private: SliceBudget budget; }; class BackgroundUnmarkTask : public GCParallelTask { public: - explicit BackgroundUnmarkTask(GCRuntime* gc) : GCParallelTask(gc) {} + explicit BackgroundUnmarkTask(GCRuntime* gc); void initZones(); void run(AutoLockHelperThreadState& lock) override; private: void unmarkZones(AutoLockGC& lock); ZoneVector zones; }; class BackgroundSweepTask : public GCParallelTask { public: - explicit BackgroundSweepTask(GCRuntime* gc) : GCParallelTask(gc) {} + explicit BackgroundSweepTask(GCRuntime* gc); void run(AutoLockHelperThreadState& lock) override; }; class BackgroundFreeTask : public GCParallelTask { public: - explicit BackgroundFreeTask(GCRuntime* gc) : GCParallelTask(gc) {} + explicit BackgroundFreeTask(GCRuntime* gc); void run(AutoLockHelperThreadState& lock) override; }; // Performs extra allocation off thread so that when memory is required on the // main thread it will already be available and waiting. class BackgroundAllocTask : public GCParallelTask { // Guarded by the GC lock. GCLockData<ChunkPool&> chunkPool_; @@ -178,18 +177,17 @@ class BackgroundAllocTask : public GCPar bool enabled() const { return enabled_; } void run(AutoLockHelperThreadState& lock) override; }; // Search the provided chunks for free arenas and decommit them. class BackgroundDecommitTask : public GCParallelTask { public: - explicit BackgroundDecommitTask(GCRuntime* gc) : GCParallelTask(gc) {} - + explicit BackgroundDecommitTask(GCRuntime* gc); void run(AutoLockHelperThreadState& lock) override; }; template <typename F> struct Callback { F op; void* data; @@ -604,21 +602,18 @@ class GCRuntime { static TenuredCell* refillFreeListInGC(Zone* zone, AllocKind thingKind); void setParallelAtomsAllocEnabled(bool enabled); void setParallelUnmarkEnabled(bool enabled); /* * Concurrent sweep infrastructure. */ - void startTask(GCParallelTask& task, gcstats::PhaseKind phase, - AutoLockHelperThreadState& locked); - void joinTask(GCParallelTask& task, gcstats::PhaseKind phase, - AutoLockHelperThreadState& locked); - void joinTask(GCParallelTask& task, gcstats::PhaseKind phase); + void startTask(GCParallelTask& task, AutoLockHelperThreadState& lock); + void joinTask(GCParallelTask& task, AutoLockHelperThreadState& lock); void updateHelperThreadCount(); size_t parallelWorkerCount() const; void mergeRealms(JS::Realm* source, JS::Realm* target); // WeakRefs bool registerWeakRef(HandleObject target, HandleObject weakRef); bool unregisterWeakRefWrapper(JSObject* wrapper);
--- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -121,17 +121,19 @@ inline bool js::NurseryChunk::markPagesI } // static inline js::NurseryChunk* js::NurseryChunk::fromChunk(TenuredChunk* chunk) { return reinterpret_cast<NurseryChunk*>(chunk); } js::NurseryDecommitTask::NurseryDecommitTask(gc::GCRuntime* gc) - : GCParallelTask(gc) {} + : GCParallelTask(gc, gcstats::PhaseKind::NONE) { + // This can occur outside GCs so doesn't have a stats phase. +} bool js::NurseryDecommitTask::isEmpty( const AutoLockHelperThreadState& lock) const { return chunksToDecommit().empty() && !partialChunk; } bool js::NurseryDecommitTask::reserveSpaceForBytes(size_t nbytes) { MOZ_ASSERT(isIdle());
--- a/js/src/gc/ParallelWork.h +++ b/js/src/gc/ParallelWork.h @@ -32,19 +32,20 @@ using ParallelWorkFunc = size_t (*)(GCRu // // The WorkItemIterator class must supply done(), next() and get() methods. The // get() method must return WorkItems objects. template <typename WorkItem, typename WorkItemIterator> class ParallelWorker : public GCParallelTask { public: using WorkFunc = ParallelWorkFunc<WorkItem>; - ParallelWorker(GCRuntime* gc, WorkFunc func, WorkItemIterator& work, - const SliceBudget& budget, AutoLockHelperThreadState& lock) - : GCParallelTask(gc), + ParallelWorker(GCRuntime* gc, gcstats::PhaseKind phaseKind, WorkFunc func, + WorkItemIterator& work, const SliceBudget& budget, + AutoLockHelperThreadState& lock) + : GCParallelTask(gc, phaseKind), func_(func), work_(work), budget_(budget), item_(work.get()) { // Consume a work item on creation so that we can stop creating workers if // the number of workers exceeds the number of work items. work.next(); } @@ -105,27 +106,27 @@ class MOZ_RAII AutoRunParallelWork { const SliceBudget& budget, AutoLockHelperThreadState& lock) : gc(gc), phaseKind(phaseKind), lock(lock), tasksStarted(0) { size_t workerCount = gc->parallelWorkerCount(); MOZ_ASSERT(workerCount <= MaxParallelWorkers); MOZ_ASSERT_IF(workerCount == 0, work.done()); for (size_t i = 0; i < workerCount && !work.done(); i++) { - tasks[i].emplace(gc, func, work, budget, lock); - gc->startTask(*tasks[i], phaseKind, lock); + tasks[i].emplace(gc, phaseKind, func, work, budget, lock); + gc->startTask(*tasks[i], lock); tasksStarted++; } } ~AutoRunParallelWork() { gHelperThreadLock.assertOwnedByCurrentThread(); for (size_t i = 0; i < tasksStarted; i++) { - gc->joinTask(*tasks[i], phaseKind, lock); + gc->joinTask(*tasks[i], lock); } for (size_t i = tasksStarted; i < MaxParallelWorkers; i++) { MOZ_ASSERT(tasks[i].isNothing()); } } private: GCRuntime* gc;