зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset 2a613f5a5866 (bug 1119537) for hazard failures
MozReview-Commit-ID: HjSuYxycsVu
This commit is contained in:
Родитель
bdd8f0ca83
Коммит
2f82f5bc02
|
@ -798,8 +798,6 @@ GCState(JSContext* cx, unsigned argc, Value* vp)
|
||||||
state = "finalize";
|
state = "finalize";
|
||||||
else if (globalState == gc::COMPACT)
|
else if (globalState == gc::COMPACT)
|
||||||
state = "compact";
|
state = "compact";
|
||||||
else if (globalState == gc::DECOMMIT)
|
|
||||||
state = "decommit";
|
|
||||||
else
|
else
|
||||||
MOZ_CRASH("Unobserveable global GC state");
|
MOZ_CRASH("Unobserveable global GC state");
|
||||||
|
|
||||||
|
|
|
@ -81,24 +81,7 @@ class BackgroundAllocTask : public GCParallelTask
|
||||||
bool enabled() const { return enabled_; }
|
bool enabled() const { return enabled_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void run() override;
|
virtual void run() override;
|
||||||
};
|
|
||||||
|
|
||||||
// Search the provided Chunks for free arenas and decommit them.
|
|
||||||
class BackgroundDecommitTask : public GCParallelTask
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using ChunkVector = mozilla::Vector<Chunk*>;
|
|
||||||
|
|
||||||
explicit BackgroundDecommitTask(JSRuntime *rt) : runtime(rt) {}
|
|
||||||
void setChunksToScan(ChunkVector &chunks);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void run() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
JSRuntime* runtime;
|
|
||||||
ChunkVector toDecommit;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -967,7 +950,7 @@ class GCRuntime
|
||||||
void endSweepPhase(bool lastGC);
|
void endSweepPhase(bool lastGC);
|
||||||
void sweepZones(FreeOp* fop, bool lastGC);
|
void sweepZones(FreeOp* fop, bool lastGC);
|
||||||
void decommitAllWithoutUnlocking(const AutoLockGC& lock);
|
void decommitAllWithoutUnlocking(const AutoLockGC& lock);
|
||||||
void startDecommit();
|
void decommitArenas(AutoLockGC& lock);
|
||||||
void expireChunksAndArenas(bool shouldShrink, AutoLockGC& lock);
|
void expireChunksAndArenas(bool shouldShrink, AutoLockGC& lock);
|
||||||
void queueZonesForBackgroundSweep(ZoneList& zones);
|
void queueZonesForBackgroundSweep(ZoneList& zones);
|
||||||
void sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks, ThreadType threadType);
|
void sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks, ThreadType threadType);
|
||||||
|
@ -1344,7 +1327,6 @@ class GCRuntime
|
||||||
mozilla::DebugOnly<mozilla::Atomic<PRThread*>> lockOwner;
|
mozilla::DebugOnly<mozilla::Atomic<PRThread*>> lockOwner;
|
||||||
|
|
||||||
BackgroundAllocTask allocTask;
|
BackgroundAllocTask allocTask;
|
||||||
BackgroundDecommitTask decommitTask;
|
|
||||||
GCHelperState helperState;
|
GCHelperState helperState;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -593,7 +593,7 @@ js::Nursery::FreeMallocedBuffersTask::transferBuffersToFree(MallocedBuffersSet&
|
||||||
{
|
{
|
||||||
// Transfer the contents of the source set to the task's buffers_ member by
|
// Transfer the contents of the source set to the task's buffers_ member by
|
||||||
// swapping the sets, which also clears the source.
|
// swapping the sets, which also clears the source.
|
||||||
MOZ_ASSERT(!isRunningWithLockHeld());
|
MOZ_ASSERT(!isRunning());
|
||||||
MOZ_ASSERT(buffers_.empty());
|
MOZ_ASSERT(buffers_.empty());
|
||||||
mozilla::Swap(buffers_, buffersToFree);
|
mozilla::Swap(buffers_, buffersToFree);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ assertEq(gcstate(), "none");
|
||||||
// sized slices while background finalization is on-going, so we need to loop.
|
// sized slices while background finalization is on-going, so we need to loop.
|
||||||
gcslice(1000000);
|
gcslice(1000000);
|
||||||
while (gcstate() == "finalize") { gcslice(1); }
|
while (gcstate() == "finalize") { gcslice(1); }
|
||||||
while (gcstate() == "decommit") { gcslice(1); }
|
|
||||||
assertEq(gcstate(), "none");
|
assertEq(gcstate(), "none");
|
||||||
|
|
||||||
// Incremental GC in multiple slices: if marking takes more than one slice,
|
// Incremental GC in multiple slices: if marking takes more than one slice,
|
||||||
|
@ -24,7 +23,6 @@ gcslice(1000000);
|
||||||
assertEq(gcstate(), "mark");
|
assertEq(gcstate(), "mark");
|
||||||
gcslice(1000000);
|
gcslice(1000000);
|
||||||
while (gcstate() == "finalize") { gcslice(1); }
|
while (gcstate() == "finalize") { gcslice(1); }
|
||||||
while (gcstate() == "decommit") { gcslice(1); }
|
|
||||||
assertEq(gcstate(), "none");
|
assertEq(gcstate(), "none");
|
||||||
|
|
||||||
// Zeal mode 8: Incremental GC in two main slices:
|
// Zeal mode 8: Incremental GC in two main slices:
|
||||||
|
@ -36,7 +34,6 @@ gcslice(1);
|
||||||
assertEq(gcstate(), "mark");
|
assertEq(gcstate(), "mark");
|
||||||
gcslice(1);
|
gcslice(1);
|
||||||
while (gcstate() == "finalize") { gcslice(1); }
|
while (gcstate() == "finalize") { gcslice(1); }
|
||||||
while (gcstate() == "decommit") { gcslice(1); }
|
|
||||||
assertEq(gcstate(), "none");
|
assertEq(gcstate(), "none");
|
||||||
|
|
||||||
// Zeal mode 9: Incremental GC in two main slices:
|
// Zeal mode 9: Incremental GC in two main slices:
|
||||||
|
@ -48,7 +45,6 @@ gcslice(1);
|
||||||
assertEq(gcstate(), "mark");
|
assertEq(gcstate(), "mark");
|
||||||
gcslice(1);
|
gcslice(1);
|
||||||
while (gcstate() == "finalize") { gcslice(1); }
|
while (gcstate() == "finalize") { gcslice(1); }
|
||||||
while (gcstate() == "decommit") { gcslice(1); }
|
|
||||||
assertEq(gcstate(), "none");
|
assertEq(gcstate(), "none");
|
||||||
|
|
||||||
// Zeal mode 10: Incremental GC in multiple slices (always yeilds before
|
// Zeal mode 10: Incremental GC in multiple slices (always yeilds before
|
||||||
|
@ -59,5 +55,4 @@ gcslice(1000000);
|
||||||
assertEq(gcstate(), "sweep");
|
assertEq(gcstate(), "sweep");
|
||||||
gcslice(1000000);
|
gcslice(1000000);
|
||||||
while (gcstate() == "finalize") { gcslice(1); }
|
while (gcstate() == "finalize") { gcslice(1); }
|
||||||
while (gcstate() == "decommit") { gcslice(1); }
|
|
||||||
assertEq(gcstate(), "none");
|
assertEq(gcstate(), "none");
|
||||||
|
|
129
js/src/jsgc.cpp
129
js/src/jsgc.cpp
|
@ -1031,7 +1031,7 @@ void
|
||||||
GCRuntime::startBackgroundAllocTaskIfIdle()
|
GCRuntime::startBackgroundAllocTaskIfIdle()
|
||||||
{
|
{
|
||||||
AutoLockHelperThreadState helperLock;
|
AutoLockHelperThreadState helperLock;
|
||||||
if (allocTask.isRunningWithLockHeld())
|
if (allocTask.isRunning())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Join the previous invocation of the task. This will return immediately
|
// Join the previous invocation of the task. This will return immediately
|
||||||
|
@ -1178,7 +1178,6 @@ GCRuntime::GCRuntime(JSRuntime* rt) :
|
||||||
#endif
|
#endif
|
||||||
lock(nullptr),
|
lock(nullptr),
|
||||||
allocTask(rt, emptyChunks_),
|
allocTask(rt, emptyChunks_),
|
||||||
decommitTask(rt),
|
|
||||||
helperState(rt)
|
helperState(rt)
|
||||||
{
|
{
|
||||||
setGCMode(JSGC_MODE_GLOBAL);
|
setGCMode(JSGC_MODE_GLOBAL);
|
||||||
|
@ -1362,7 +1361,6 @@ GCRuntime::finish()
|
||||||
*/
|
*/
|
||||||
helperState.finish();
|
helperState.finish();
|
||||||
allocTask.cancel(GCParallelTask::CancelAndWait);
|
allocTask.cancel(GCParallelTask::CancelAndWait);
|
||||||
decommitTask.cancel(GCParallelTask::CancelAndWait);
|
|
||||||
|
|
||||||
#ifdef JS_GC_ZEAL
|
#ifdef JS_GC_ZEAL
|
||||||
/* Free memory associated with GC verification. */
|
/* Free memory associated with GC verification. */
|
||||||
|
@ -3341,67 +3339,43 @@ GCRuntime::decommitAllWithoutUnlocking(const AutoLockGC& lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
GCRuntime::startDecommit()
|
GCRuntime::decommitArenas(AutoLockGC& lock)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
|
// Verify that all entries in the empty chunks pool are decommitted.
|
||||||
MOZ_ASSERT(!decommitTask.isRunning());
|
for (ChunkPool::Iter chunk(emptyChunks(lock)); !chunk.done(); chunk.next())
|
||||||
|
MOZ_ASSERT(!chunk->info.numArenasFreeCommitted);
|
||||||
|
|
||||||
BackgroundDecommitTask::ChunkVector toDecommit;
|
// Build a Vector of all current available Chunks. Since we release the
|
||||||
{
|
// gc lock while doing the decommit syscall, it is dangerous to iterate
|
||||||
AutoLockGC lock(rt);
|
// the available list directly, as concurrent operations can modify it.
|
||||||
|
mozilla::Vector<Chunk*> toDecommit;
|
||||||
// Verify that all entries in the empty chunks pool are already decommitted.
|
MOZ_ASSERT(availableChunks(lock).verify());
|
||||||
for (ChunkPool::Iter chunk(emptyChunks(lock)); !chunk.done(); chunk.next())
|
for (ChunkPool::Iter iter(availableChunks(lock)); !iter.done(); iter.next()) {
|
||||||
MOZ_ASSERT(!chunk->info.numArenasFreeCommitted);
|
if (!toDecommit.append(iter.get())) {
|
||||||
|
// The OOM handler does a full, immediate decommit, so there is
|
||||||
// Since we release the GC lock while doing the decommit syscall below,
|
// nothing more to do here in any case.
|
||||||
// it is dangerous to iterate the available list directly, as the main
|
return onOutOfMallocMemory(lock);
|
||||||
// thread could modify it concurrently. Instead, we build and pass an
|
|
||||||
// explicit Vector containing the Chunks we want to visit.
|
|
||||||
MOZ_ASSERT(availableChunks(lock).verify());
|
|
||||||
for (ChunkPool::Iter iter(availableChunks(lock)); !iter.done(); iter.next()) {
|
|
||||||
if (!toDecommit.append(iter.get())) {
|
|
||||||
// The OOM handler does a full, immediate decommit.
|
|
||||||
return onOutOfMallocMemory(lock);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
decommitTask.setChunksToScan(toDecommit);
|
|
||||||
|
|
||||||
if (sweepOnBackgroundThread && decommitTask.start())
|
// Start at the tail and stop before the first chunk: we allocate from the
|
||||||
return;
|
// head and don't want to thrash with the mutator.
|
||||||
|
for (size_t i = toDecommit.length(); i > 1; --i) {
|
||||||
|
Chunk* chunk = toDecommit[i - 1];
|
||||||
|
MOZ_ASSERT(chunk);
|
||||||
|
|
||||||
decommitTask.runFromMainThread(rt);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
js::gc::BackgroundDecommitTask::setChunksToScan(ChunkVector &chunks)
|
|
||||||
{
|
|
||||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime));
|
|
||||||
MOZ_ASSERT(!isRunning());
|
|
||||||
MOZ_ASSERT(toDecommit.empty());
|
|
||||||
Swap(toDecommit, chunks);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* virtual */ void
|
|
||||||
js::gc::BackgroundDecommitTask::run()
|
|
||||||
{
|
|
||||||
AutoLockGC lock(runtime);
|
|
||||||
|
|
||||||
for (Chunk* chunk : toDecommit) {
|
|
||||||
// The arena list is not doubly-linked, so we have to work in the free
|
// The arena list is not doubly-linked, so we have to work in the free
|
||||||
// list order and not in the natural order.
|
// list order and not in the natural order.
|
||||||
while (chunk->info.numArenasFreeCommitted) {
|
while (chunk->info.numArenasFreeCommitted) {
|
||||||
bool ok = chunk->decommitOneFreeArena(runtime, lock);
|
bool ok = chunk->decommitOneFreeArena(rt, lock);
|
||||||
|
|
||||||
// If we are low enough on memory that we can't update the page
|
// FIXME Bug 1095620: add cancellation support when this becomes
|
||||||
// tables, or if we need to return for any other reason, break out
|
// a ParallelTask.
|
||||||
// of the loop.
|
if (/* cancel_ || */ !ok)
|
||||||
if (cancel_ || !ok)
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toDecommit.clearAndFree();
|
MOZ_ASSERT(availableChunks(lock).verify());
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -3412,6 +3386,9 @@ GCRuntime::expireChunksAndArenas(bool shouldShrink, AutoLockGC& lock)
|
||||||
AutoUnlockGC unlock(lock);
|
AutoUnlockGC unlock(lock);
|
||||||
FreeChunkPool(rt, toFree);
|
FreeChunkPool(rt, toFree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldShrink)
|
||||||
|
decommitArenas(lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -5721,7 +5698,6 @@ GCRuntime::endCompactPhase(JS::gcreason::Reason reason)
|
||||||
void
|
void
|
||||||
GCRuntime::finishCollection(JS::gcreason::Reason reason)
|
GCRuntime::finishCollection(JS::gcreason::Reason reason)
|
||||||
{
|
{
|
||||||
assertBackgroundSweepingFinished();
|
|
||||||
MOZ_ASSERT(marker.isDrained());
|
MOZ_ASSERT(marker.isDrained());
|
||||||
marker.stop();
|
marker.stop();
|
||||||
clearBufferedGrayRoots();
|
clearBufferedGrayRoots();
|
||||||
|
@ -5750,6 +5726,15 @@ GCRuntime::finishCollection(JS::gcreason::Reason reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
lastGCTime = currentTime;
|
lastGCTime = currentTime;
|
||||||
|
|
||||||
|
// If this is an OOM GC reason, wait on the background sweeping thread
|
||||||
|
// before returning to ensure that we free as much as possible. If this is
|
||||||
|
// a zeal-triggered GC, we want to ensure that the mutator can continue
|
||||||
|
// allocating on the same pages to reduce fragmentation.
|
||||||
|
if (IsOOMReason(reason) || reason == JS::gcreason::DEBUG_GC) {
|
||||||
|
gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
|
||||||
|
rt->gc.waitBackgroundSweepOrAllocEnd();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char*
|
static const char*
|
||||||
|
@ -5898,12 +5883,6 @@ GCRuntime::resetIncrementalGC(const char* reason)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case DECOMMIT: {
|
|
||||||
auto unlimited = SliceBudget::unlimited();
|
|
||||||
incrementalCollectSlice(unlimited, JS::gcreason::RESET);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
MOZ_CRASH("Invalid incremental GC state");
|
MOZ_CRASH("Invalid incremental GC state");
|
||||||
}
|
}
|
||||||
|
@ -6165,28 +6144,13 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
|
||||||
endCompactPhase(reason);
|
endCompactPhase(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
startDecommit();
|
|
||||||
incrementalState = DECOMMIT;
|
|
||||||
|
|
||||||
MOZ_FALLTHROUGH;
|
|
||||||
|
|
||||||
case DECOMMIT:
|
|
||||||
{
|
|
||||||
gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
|
|
||||||
|
|
||||||
// Yield until background decommit is done.
|
|
||||||
if (isIncremental && decommitTask.isRunning())
|
|
||||||
break;
|
|
||||||
|
|
||||||
decommitTask.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
finishCollection(reason);
|
finishCollection(reason);
|
||||||
|
|
||||||
incrementalState = NO_INCREMENTAL;
|
incrementalState = NO_INCREMENTAL;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
MOZ_CRASH("unexpected GC incrementalState");
|
MOZ_ASSERT(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6315,12 +6279,10 @@ GCRuntime::gcCycle(bool nonincrementalByAPI, SliceBudget& budget, JS::gcreason::
|
||||||
{
|
{
|
||||||
gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
|
gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
|
||||||
|
|
||||||
// Background finalization and decommit are finished by defininition
|
// As we are about to clear the mark bits, wait for background
|
||||||
// before we can start a new GC session.
|
// finalization to finish. We only need to wait on the first slice.
|
||||||
if (!isIncrementalGCInProgress()) {
|
if (!isIncrementalGCInProgress())
|
||||||
assertBackgroundSweepingFinished();
|
waitBackgroundSweepEnd();
|
||||||
MOZ_ASSERT(!decommitTask.isRunning());
|
|
||||||
}
|
|
||||||
|
|
||||||
// We must also wait for background allocation to finish so we can
|
// We must also wait for background allocation to finish so we can
|
||||||
// avoid taking the GC lock when manipulating the chunks during the GC.
|
// avoid taking the GC lock when manipulating the chunks during the GC.
|
||||||
|
@ -6657,9 +6619,6 @@ GCRuntime::onOutOfMallocMemory()
|
||||||
// Stop allocating new chunks.
|
// Stop allocating new chunks.
|
||||||
allocTask.cancel(GCParallelTask::CancelAndWait);
|
allocTask.cancel(GCParallelTask::CancelAndWait);
|
||||||
|
|
||||||
// Make sure we release anything queued for release.
|
|
||||||
decommitTask.join();
|
|
||||||
|
|
||||||
// Wait for background free of nursery huge slots to finish.
|
// Wait for background free of nursery huge slots to finish.
|
||||||
nursery.waitBackgroundFreeEnd();
|
nursery.waitBackgroundFreeEnd();
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,7 @@ enum State {
|
||||||
MARK,
|
MARK,
|
||||||
SWEEP,
|
SWEEP,
|
||||||
FINALIZE,
|
FINALIZE,
|
||||||
COMPACT,
|
COMPACT
|
||||||
DECOMMIT
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Map from C++ type to alloc kind. JSObject does not have a 1:1 mapping, so must use Arena::thingSize. */
|
/* Map from C++ type to alloc kind. JSObject does not have a 1:1 mapping, so must use Arena::thingSize. */
|
||||||
|
@ -962,7 +961,6 @@ class GCParallelTask
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a task is actively running.
|
// Check if a task is actively running.
|
||||||
bool isRunningWithLockHeld() const;
|
|
||||||
bool isRunning() const;
|
bool isRunning() const;
|
||||||
|
|
||||||
// This should be friended to HelperThread, but cannot be because it
|
// This should be friended to HelperThread, but cannot be because it
|
||||||
|
|
|
@ -1089,19 +1089,12 @@ js::GCParallelTask::runFromHelperThread()
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
js::GCParallelTask::isRunningWithLockHeld() const
|
js::GCParallelTask::isRunning() const
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(HelperThreadState().isLocked());
|
MOZ_ASSERT(HelperThreadState().isLocked());
|
||||||
return state == Dispatched;
|
return state == Dispatched;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
js::GCParallelTask::isRunning() const
|
|
||||||
{
|
|
||||||
AutoLockHelperThreadState helperLock;
|
|
||||||
return isRunningWithLockHeld();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
HelperThread::handleGCParallelWorkload()
|
HelperThread::handleGCParallelWorkload()
|
||||||
{
|
{
|
||||||
|
|
Загрузка…
Ссылка в новой задаче