Bug 1308039 - GC interrupt callbacks (r=jonco)

This commit is contained in:
Bill McCloskey 2016-10-06 16:44:58 -07:00
Родитель 8a80388d4a
Коммит 8c6a951cce
5 изменённых файлов: 127 добавлений и 13 удалений

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

@ -692,6 +692,18 @@ PokeGC(JSContext* cx);
extern JS_FRIEND_API(void)
NotifyDidPaint(JSContext* cx);
// GC Interrupt callbacks are run during GC. You should not run JS code or use
// the JS engine at all while the callback is running. Otherwise they resemble
// normal JS interrupt callbacks.
typedef bool
(* GCInterruptCallback)(JSContext* cx);
extern JS_FRIEND_API(bool)
AddGCInterruptCallback(JSContext* cx, GCInterruptCallback callback);
extern JS_FRIEND_API(void)
RequestGCInterruptCallback(JSContext* cx);
} /* namespace JS */
#endif /* js_GCAPI_h */

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

@ -7,6 +7,8 @@
#ifndef js_SliceBudget_h
#define js_SliceBudget_h
#include "mozilla/Atomics.h"
#include <stdint.h>
namespace js {
@ -36,7 +38,7 @@ class JS_PUBLIC_API(SliceBudget)
static const int64_t unlimitedDeadline = INT64_MAX;
static const intptr_t unlimitedStartCounter = INTPTR_MAX;
bool checkOverBudget();
bool checkOverBudget(JSContext* maybeCx);
SliceBudget();
@ -48,7 +50,7 @@ class JS_PUBLIC_API(SliceBudget)
WorkBudget workBudget;
int64_t deadline; /* in microseconds */
intptr_t counter;
mozilla::Atomic<intptr_t, mozilla::Relaxed> counter;
static const intptr_t CounterReset = 1000;
@ -64,19 +66,44 @@ class JS_PUBLIC_API(SliceBudget)
/* Instantiate as SliceBudget(WorkBudget(n)). */
explicit SliceBudget(WorkBudget work);
// Need an explicit copy constructor because Atomic fails to provide one.
SliceBudget(const SliceBudget& other)
: timeBudget(other.timeBudget),
workBudget(other.workBudget),
deadline(other.deadline),
counter(other.counter)
{}
// Need an explicit operator= because Atomic fails to provide one.
SliceBudget& operator=(const SliceBudget& other) {
timeBudget = other.timeBudget;
workBudget = other.workBudget;
deadline = other.deadline;
counter = intptr_t(other.counter);
return *this;
}
void makeUnlimited() {
deadline = unlimitedDeadline;
counter = unlimitedStartCounter;
}
// Request that checkOverBudget be called the next time isOverBudget is
// called.
void requestFullCheck() {
counter = 0;
}
void step(intptr_t amt = 1) {
counter -= amt;
}
bool isOverBudget() {
// Only need to pass maybeCx if the GC interrupt callback should be checked
// (and possibly invoked).
bool isOverBudget(JSContext* maybeCx = nullptr) {
if (counter > 0)
return false;
return checkOverBudget();
return checkOverBudget(maybeCx);
}
bool isWorkBudget() const { return deadline == 0; }

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

@ -24,6 +24,7 @@ namespace js {
class AutoLockGC;
class AutoLockHelperThreadState;
class SliceBudget;
class VerifyPreTracer;
namespace gc {
@ -863,6 +864,19 @@ class GCRuntime
bool isVerifyPreBarriersEnabled() const { return false; }
#endif
// GC interrupt callbacks.
bool addInterruptCallback(JS::GCInterruptCallback callback);
void requestInterruptCallback();
bool checkInterruptCallback(JSContext* cx) {
if (interruptCallbackRequested) {
invokeInterruptCallback(cx);
return true;
}
return false;
}
void invokeInterruptCallback(JSContext* cx);
// Free certain LifoAlloc blocks when it is safe to do so.
void freeUnusedLifoBlocksAfterSweeping(LifoAlloc* lifo);
void freeAllLifoBlocksAfterSweeping(LifoAlloc* lifo);
@ -1075,6 +1089,13 @@ class GCRuntime
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> numArenasFreeCommitted;
VerifyPreTracer* verifyPreData;
// GC interrupt callbacks.
using GCInterruptCallbackVector = js::Vector<JS::GCInterruptCallback, 2, js::SystemAllocPolicy>;
GCInterruptCallbackVector interruptCallbacks;
mozilla::Atomic<bool, mozilla::Relaxed> interruptCallbackRequested;
SliceBudget* currentBudget;
private:
bool chunkAllocationSinceLastGC;
int64_t lastGCTime;

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

@ -1545,13 +1545,15 @@ GCMarker::drainMarkStack(SliceBudget& budget)
auto acc = mozilla::MakeScopeExit([&] {strictCompartmentChecking = false;});
#endif
if (budget.isOverBudget())
JSContext* cx = runtime()->contextFromMainThread();
if (budget.isOverBudget(cx))
return false;
for (;;) {
while (!stack.isEmpty()) {
processMarkStackTop(budget);
if (budget.isOverBudget()) {
if (budget.isOverBudget(cx)) {
saveValueRanges();
return false;
}
@ -1626,6 +1628,8 @@ GCMarker::processMarkStackTop(SliceBudget& budget)
uintptr_t tag = addr & StackTagMask;
addr &= ~StackTagMask;
JSContext* cx = runtime()->contextFromMainThread();
// Dispatch
switch (tag) {
case ValueArrayTag: {
@ -1679,7 +1683,7 @@ GCMarker::processMarkStackTop(SliceBudget& budget)
MOZ_ASSERT(vp <= end);
while (vp != end) {
budget.step();
if (budget.isOverBudget()) {
if (budget.isOverBudget(cx)) {
pushValueArray(obj, vp, end);
return;
}
@ -1709,7 +1713,7 @@ GCMarker::processMarkStackTop(SliceBudget& budget)
AssertZoneIsMarking(obj);
budget.step();
if (budget.isOverBudget()) {
if (budget.isOverBudget(cx)) {
repush(obj);
return;
}
@ -2157,7 +2161,7 @@ GCMarker::markDelayedChildren(SliceBudget& budget)
markDelayedChildren(arena);
budget.step(150);
if (budget.isOverBudget())
if (budget.isOverBudget(runtime()->contextFromMainThread()))
return false;
} while (unmarkedArenaStackTop);
MOZ_ASSERT(!markLaterArenas);

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

@ -188,6 +188,7 @@
#include "mozilla/MacroForEach.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Move.h"
#include "mozilla/ScopeExit.h"
#include <ctype.h>
#include <string.h>
@ -506,6 +507,8 @@ FinalizeTypedArenas(FreeOp* fop,
size_t thingSize = Arena::thingSize(thingKind);
size_t thingsPerArena = Arena::thingsPerArena(thingKind);
JSContext* cx = fop->onMainThread() ? fop->runtime()->contextFromMainThread() : nullptr;
while (Arena* arena = *src) {
*src = arena->next;
size_t nmarked = arena->finalize<T>(fop, thingKind, thingSize);
@ -519,7 +522,7 @@ FinalizeTypedArenas(FreeOp* fop,
fop->runtime()->gc.releaseArena(arena, maybeLock.ref());
budget.step(thingsPerArena);
if (budget.isOverBudget())
if (budget.isOverBudget(cx))
return false;
}
@ -808,6 +811,8 @@ GCRuntime::GCRuntime(JSRuntime* rt) :
nextCellUniqueId_(LargestTaggedNullCellPointer + 1), // Ensure disjoint from null tagged pointers.
numArenasFreeCommitted(0),
verifyPreData(nullptr),
interruptCallbackRequested(false),
currentBudget(nullptr),
chunkAllocationSinceLastGC(false),
lastGCTime(PRMJ_Now()),
mode(JSGC_MODE_INCREMENTAL),
@ -2933,8 +2938,11 @@ SliceBudget::describe(char* buffer, size_t maxlen) const
}
bool
SliceBudget::checkOverBudget()
SliceBudget::checkOverBudget(JSContext* cx)
{
if (cx)
cx->gc.checkInterruptCallback(cx);
bool over = PRMJ_Now() >= deadline;
if (!over)
counter = CounterReset;
@ -5481,7 +5489,7 @@ GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
updatePointersToRelocatedCells(zone, lock);
zone->setGCState(Zone::Finished);
zonesToMaybeCompact.removeFront();
if (sliceBudget.isOverBudget())
if (sliceBudget.isOverBudget(rt->contextFromMainThread()))
break;
}
@ -5887,7 +5895,7 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
* now exhasted.
*/
beginSweepPhase(destroyingRuntime, lock);
if (budget.isOverBudget())
if (budget.isOverBudget(rt->contextFromMainThread()))
break;
/*
@ -6294,6 +6302,11 @@ GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::R
if (!checkIfGCAllowedInCurrentState(reason))
return;
currentBudget = &budget;
auto guard = mozilla::MakeScopeExit([&] {
currentBudget = nullptr;
});
AutoTraceLog logGC(TraceLoggerForMainThread(rt), TraceLogger_GC);
AutoStopVerifyingBarriers av(rt, IsShutdownGC(reason));
AutoEnqueuePendingParseTasksAfterGC aept(*this);
@ -7668,3 +7681,40 @@ js::gc::Cell::dump() const
dump(stderr);
}
#endif
bool
JS::AddGCInterruptCallback(JSContext* cx, GCInterruptCallback callback)
{
return cx->runtime()->gc.addInterruptCallback(callback);
}
void
JS::RequestGCInterruptCallback(JSContext* cx)
{
cx->runtime()->gc.requestInterruptCallback();
}
bool
GCRuntime::addInterruptCallback(JS::GCInterruptCallback callback)
{
return interruptCallbacks.append(callback);
}
void
GCRuntime::requestInterruptCallback()
{
if (currentBudget) {
interruptCallbackRequested = true;
currentBudget->requestFullCheck();
}
}
void
GCRuntime::invokeInterruptCallback(JSContext* cx)
{
JS::AutoAssertOnGC aaogc(cx);
JS::AutoAssertOnBarrier nobarrier(cx);
for (JS::GCInterruptCallback callback : interruptCallbacks) {
(*callback)(cx);
}
}