Merge backout.

This commit is contained in:
Robert Sayre 2010-09-01 11:25:02 -07:00
commit da659f8bfe
10 changed files with 429 additions and 297 deletions

View File

@ -163,6 +163,7 @@ CPPSRCS = \
jsscope.cpp \
jsscript.cpp \
jsstr.cpp \
jstask.cpp \
jstypedarray.cpp \
jsutil.cpp \
jswrapper.cpp \
@ -230,6 +231,7 @@ INSTALLED_HEADERS = \
jsstaticcheck.h \
jsstdint.h \
jsstr.h \
jstask.h \
jstracer.h \
jstypedarray.h \
jstypes.h \

View File

@ -80,6 +80,7 @@
#include "jsscope.h"
#include "jsscript.h"
#include "jsstr.h"
#include "jstask.h"
#include "jstracer.h"
#include "jsdbgapi.h"
#include "prmjtime.h"
@ -606,6 +607,15 @@ JSRuntime::init(uint32 maxbytes)
wrapObjectCallback = js::TransparentObjectWrapper;
#ifdef JS_THREADSAFE
gcLock = JS_NEW_LOCK();
if (!gcLock)
return false;
gcDone = JS_NEW_CONDVAR(gcLock);
if (!gcDone)
return false;
requestDone = JS_NEW_CONDVAR(gcLock);
if (!requestDone)
return false;
/* this is asymmetric with JS_ShutDown: */
if (!js_SetupLocks(8, 16))
return false;
@ -1836,7 +1846,7 @@ JS_free(JSContext *cx, void *p)
JS_PUBLIC_API(void)
JS_updateMallocCounter(JSContext *cx, size_t nbytes)
{
return cx->runtime->updateMallocCounter(nbytes);
return cx->updateMallocCounter(nbytes);
}
JS_PUBLIC_API(char *)
@ -2604,7 +2614,7 @@ JS_NewExternalString(JSContext *cx, jschar *chars, size_t length, intN type)
if (!str)
return NULL;
str->initFlat(chars, length);
cx->runtime->updateMallocCounter((length + 1) * sizeof(jschar));
cx->updateMallocCounter((length + 1) * sizeof(jschar));
return str;
}
@ -4861,10 +4871,7 @@ JS_TriggerOperationCallback(JSContext *cx)
JS_PUBLIC_API(void)
JS_TriggerAllOperationCallbacks(JSRuntime *rt)
{
#ifdef JS_THREADSAFE
AutoLockGC lock(rt);
#endif
TriggerAllOperationCallbacks(rt);
js_TriggerAllOperationCallbacks(rt, JS_FALSE);
}
JS_PUBLIC_API(JSBool)

View File

@ -707,6 +707,7 @@ js_PurgeThreads(JSContext *cx)
e.removeFront();
} else {
thread->data.purge(cx);
thread->gcThreadMallocBytes = JS_GC_THREAD_MALLOC_LIMIT;
}
}
#else
@ -1931,17 +1932,16 @@ js_HandleExecutionInterrupt(JSContext *cx)
return result;
}
namespace js {
void
TriggerAllOperationCallbacks(JSRuntime *rt)
js_TriggerAllOperationCallbacks(JSRuntime *rt, JSBool gcLocked)
{
#ifdef JS_THREADSAFE
Conditionally<AutoLockGC> lockIf(!gcLocked, rt);
#endif
for (ThreadDataIter i(rt); !i.empty(); i.popFront())
i.threadData()->triggerOperationCallback();
}
} /* namespace js */
JSStackFrame *
js_GetScriptedCaller(JSContext *cx, JSStackFrame *fp)
{
@ -2154,38 +2154,45 @@ JSContext::containingSegment(const JSStackFrame *target)
return NULL;
}
JS_FRIEND_API(void)
JSRuntime::onTooMuchMalloc()
void
JSContext::checkMallocGCPressure(void *p)
{
if (!p) {
js_ReportOutOfMemory(this);
return;
}
#ifdef JS_THREADSAFE
AutoLockGC lock(this);
JS_ASSERT(thread);
JS_ASSERT(thread->gcThreadMallocBytes <= 0);
ptrdiff_t n = JS_GC_THREAD_MALLOC_LIMIT - thread->gcThreadMallocBytes;
thread->gcThreadMallocBytes = JS_GC_THREAD_MALLOC_LIMIT;
AutoLockGC lock(runtime);
runtime->gcMallocBytes -= n;
/*
* We can be called outside a request and can race against a GC that
* mutates the JSThread set during the sweeping phase.
* Trigger the GC on memory pressure but only if we are inside a request
* and not inside a GC.
*/
js_WaitForGC(this);
if (runtime->isGCMallocLimitReached() && thread->requestDepth != 0)
#endif
TriggerGC(this);
}
{
if (!runtime->gcRunning) {
JS_ASSERT(runtime->isGCMallocLimitReached());
runtime->gcMallocBytes = -1;
JS_FRIEND_API(void *)
JSRuntime::onOutOfMemory(void *p, size_t nbytes, JSContext *cx)
{
#ifdef JS_THREADSAFE
gcHelperThread.waitBackgroundSweepEnd(this);
if (!p)
p = ::js_malloc(nbytes);
else if (p == reinterpret_cast<void *>(1))
p = ::js_calloc(nbytes);
else
p = ::js_realloc(p, nbytes);
if (p)
return p;
#endif
if (cx)
js_ReportOutOfMemory(cx);
return NULL;
/*
* Empty the GC free lists to trigger a last-ditch GC when any GC
* thing is allocated later on this thread. This makes unnecessary
* to check for the memory pressure on the fast path of the GC
* allocator. We cannot touch the free lists on other threads as
* their manipulation is not thread-safe.
*/
JS_THREAD_DATA(this)->gcFreeLists.purge();
js_TriggerGC(this, true);
}
}
}
bool

View File

@ -70,6 +70,7 @@
#include "jsregexp.h"
#include "jsutil.h"
#include "jsarray.h"
#include "jstask.h"
#include "jsvector.h"
#include "prmjtime.h"
@ -186,7 +187,7 @@ class ContextAllocPolicy
};
/* Holds the execution state during trace execution. */
struct TracerState
struct TracerState
{
JSContext* cx; // current VM context handle
double* stackBase; // native stack base
@ -1120,7 +1121,7 @@ struct JSThreadData {
/* State used by dtoa.c. */
DtoaState *dtoaState;
/*
/*
* State used to cache some double-to-string conversions. A stupid
* optimization aimed directly at v8-splay.js, which stupidly converts
* many doubles multiple times in a row.
@ -1184,6 +1185,12 @@ struct JSThread {
/* Indicates that the thread is waiting in ClaimTitle from jslock.cpp. */
JSTitle *titleToShare;
/*
* Thread-local version of JSRuntime.gcMallocBytes to avoid taking
* locks on each JS_malloc.
*/
ptrdiff_t gcThreadMallocBytes;
/*
* This thread is inside js_GC, either waiting until it can start GC, or
* waiting for GC to finish on another thread. This thread holds no locks;
@ -1201,7 +1208,7 @@ struct JSThread {
# ifdef DEBUG
unsigned checkRequestDepth;
# endif
# endif
/* Weak ref, for low-cost sealed title locking */
JSTitle *lockedSealedTitle;
@ -1210,6 +1217,13 @@ struct JSThread {
JSThreadData data;
};
/*
* Only when JSThread::gcThreadMallocBytes exhausts the following limit we
* update JSRuntime::gcMallocBytes.
* .
*/
const size_t JS_GC_THREAD_MALLOC_LIMIT = 1 << 19;
#define JS_THREAD_DATA(cx) (&(cx)->thread->data)
extern JSThread *
@ -1256,7 +1270,7 @@ namespace js {
struct GCPtrHasher
{
typedef void *Lookup;
static HashNumber hash(void *key) {
return HashNumber(uintptr_t(key) >> JS_GCTHING_ZEROBITS);
}
@ -1282,7 +1296,7 @@ typedef js::HashMap<void *,
/* If HashNumber grows, need to change WrapperHasher. */
JS_STATIC_ASSERT(sizeof(HashNumber) == 4);
struct WrapperHasher
{
typedef Value Lookup;
@ -1427,16 +1441,18 @@ struct JSRuntime {
JSGCCallback gcCallback;
private:
/*
* Malloc counter to measure memory pressure for GC scheduling. It runs
* from gcMaxMallocBytes down to zero.
*/
volatile ptrdiff_t gcMallocBytes;
ptrdiff_t gcMallocBytes;
#ifdef JS_THREADSAFE
JSBackgroundThread gcHelperThread;
#endif
public:
js::GCChunkAllocator *gcChunkAllocator;
void setCustomGCChunkAllocator(js::GCChunkAllocator *allocator) {
JS_ASSERT(allocator);
JS_ASSERT(state == JSRTS_DOWN);
@ -1488,8 +1504,6 @@ struct JSRuntime {
uint32 requestCount;
JSThread *gcThread;
js::GCHelperThread gcHelperThread;
/* Lock and owning thread pointer for JS_LOCK_RUNTIME. */
PRLock *rtLock;
#ifdef DEBUG
@ -1740,36 +1754,11 @@ struct JSRuntime {
void setGCTriggerFactor(uint32 factor);
void setGCLastBytes(size_t lastBytes);
/*
* Call the system malloc while checking for GC memory pressure and
* reporting OOM error when cx is not null.
*/
void* malloc(size_t bytes, JSContext *cx = NULL) {
updateMallocCounter(bytes);
void *p = ::js_malloc(bytes);
return JS_LIKELY(!!p) ? p : onOutOfMemory(NULL, bytes, cx);
}
void* malloc(size_t bytes) { return ::js_malloc(bytes); }
/*
* Call the system calloc while checking for GC memory pressure and
* reporting OOM error when cx is not null.
*/
void* calloc(size_t bytes, JSContext *cx = NULL) {
updateMallocCounter(bytes);
void *p = ::js_calloc(bytes);
return JS_LIKELY(!!p) ? p : onOutOfMemory(reinterpret_cast<void *>(1), bytes, cx);
}
void* calloc(size_t bytes) { return ::js_calloc(bytes); }
void* realloc(void* p, size_t bytes, JSContext *cx = NULL) {
/*
* For compatibility we do not account for realloc that increases
* previously allocated memory.
*/
if (!p)
updateMallocCounter(bytes);
void *p2 = ::js_realloc(p, bytes);
return JS_LIKELY(!!p2) ? p2 : onOutOfMemory(p, bytes, cx);
}
void* realloc(void* p, size_t bytes) { return ::js_realloc(p, bytes); }
void free(void* p) { ::js_free(p); }
@ -1785,38 +1774,6 @@ struct JSRuntime {
gcMaxMallocBytes = (ptrdiff_t(value) >= 0) ? value : size_t(-1) >> 1;
resetGCMallocBytes();
}
/*
* Call this after allocating memory held by GC things, to update memory
* pressure counters or report the OOM error if necessary. If oomError and
* cx is not null the function also reports OOM error.
*
* The function must be called outside the GC lock and in case of OOM error
* the caller must ensure that no deadlock possible during OOM reporting.
*/
void updateMallocCounter(size_t nbytes) {
/* We tolerate any thread races when updating gcMallocBytes. */
ptrdiff_t newCount = gcMallocBytes - ptrdiff_t(nbytes);
gcMallocBytes = newCount;
if (JS_UNLIKELY(newCount <= 0))
onTooMuchMalloc();
}
private:
/*
* The function must be called outside the GC lock.
*/
JS_FRIEND_API(void) onTooMuchMalloc();
/*
* This should be called after system malloc/realloc returns NULL to try
* to recove some memory or to report an error. Failures in malloc and
* calloc are signaled by p == null and p == reinterpret_cast<void *>(1).
* Other values of p mean a realloc failure.
*
* The function must be called outside the GC lock.
*/
JS_FRIEND_API(void *) onOutOfMemory(void *p, size_t nbytes, JSContext *cx);
};
/* Common macros to access thread-local caches in JSThread or JSRuntime. */
@ -2172,7 +2129,7 @@ struct JSContext
}
return fp;
}
#ifdef JS_THREADSAFE
JSThread *thread;
unsigned outstandingRequests;/* number of JS_BeginRequest calls
@ -2279,34 +2236,87 @@ struct JSContext
#ifdef JS_THREADSAFE
/*
* When non-null JSContext::free delegates the job to the background
* thread.
* The sweep task for this context.
*/
js::GCHelperThread *gcBackgroundFree;
js::BackgroundSweepTask *gcSweepTask;
#endif
ptrdiff_t &getMallocCounter() {
#ifdef JS_THREADSAFE
return thread->gcThreadMallocBytes;
#else
return runtime->gcMallocBytes;
#endif
}
/*
* Call this after allocating memory held by GC things, to update memory
* pressure counters or report the OOM error if necessary.
*/
inline void updateMallocCounter(void *p, size_t nbytes) {
JS_ASSERT(ptrdiff_t(nbytes) >= 0);
ptrdiff_t &counter = getMallocCounter();
counter -= ptrdiff_t(nbytes);
if (!p || counter <= 0)
checkMallocGCPressure(p);
}
/*
* Call this after successfully allocating memory held by GC things, to
* update memory pressure counters.
*/
inline void updateMallocCounter(size_t nbytes) {
JS_ASSERT(ptrdiff_t(nbytes) >= 0);
ptrdiff_t &counter = getMallocCounter();
counter -= ptrdiff_t(nbytes);
if (counter <= 0) {
/*
* Use 1 as an arbitrary non-null pointer indicating successful
* allocation.
*/
checkMallocGCPressure(reinterpret_cast<void *>(jsuword(1)));
}
}
inline void* malloc(size_t bytes) {
return runtime->malloc(bytes, this);
JS_ASSERT(bytes != 0);
void *p = runtime->malloc(bytes);
updateMallocCounter(p, bytes);
return p;
}
inline void* mallocNoReport(size_t bytes) {
JS_ASSERT(bytes != 0);
return runtime->malloc(bytes, NULL);
void *p = runtime->malloc(bytes);
if (!p)
return NULL;
updateMallocCounter(bytes);
return p;
}
inline void* calloc(size_t bytes) {
JS_ASSERT(bytes != 0);
return runtime->calloc(bytes, this);
void *p = runtime->calloc(bytes);
updateMallocCounter(p, bytes);
return p;
}
inline void* realloc(void* p, size_t bytes) {
return runtime->realloc(p, bytes, this);
void *orig = p;
p = runtime->realloc(p, bytes);
/*
* For compatibility we do not account for realloc that increases
* previously allocated memory.
*/
updateMallocCounter(p, orig ? 0 : bytes);
return p;
}
inline void free(void* p) {
#ifdef JS_THREADSAFE
if (gcBackgroundFree) {
gcBackgroundFree->freeLater(p);
if (gcSweepTask) {
gcSweepTask->freeLater(p);
return;
}
#endif
@ -3249,13 +3259,14 @@ js_InvokeOperationCallback(JSContext *cx);
extern JSBool
js_HandleExecutionInterrupt(JSContext *cx);
namespace js {
/* Must be called with GC lock taken. */
#ifndef JS_THREADSAFE
# define js_TriggerAllOperationCallbacks(rt, gcLocked) \
js_TriggerAllOperationCallbacks (rt)
#endif
void
TriggerAllOperationCallbacks(JSRuntime *rt);
} /* namespace js */
js_TriggerAllOperationCallbacks(JSRuntime *rt, JSBool gcLocked);
extern JSStackFrame *
js_GetScriptedCaller(JSContext *cx, JSStackFrame *fp);
@ -3434,7 +3445,7 @@ class AutoValueVector : private AutoGCRooter
const Value &back() const { return vector.back(); }
friend void AutoGCRooter::trace(JSTracer *trc);
private:
Vector<Value, 8> vector;
JS_DECL_USE_GUARD_OBJECT_NOTIFIER
@ -3494,7 +3505,7 @@ class AutoIdVector : private AutoGCRooter
const jsid &back() const { return vector.back(); }
friend void AutoGCRooter::trace(JSTracer *trc);
private:
Vector<jsid, 8> vector;
JS_DECL_USE_GUARD_OBJECT_NOTIFIER

View File

@ -78,6 +78,7 @@
#include "jsscript.h"
#include "jsstaticcheck.h"
#include "jsstr.h"
#include "jstask.h"
#include "jstracer.h"
#if JS_HAS_XML_SUPPORT
@ -642,7 +643,7 @@ NewGCArena(JSContext *cx)
*/
if (!JS_ON_TRACE(cx))
return NULL;
TriggerGC(rt);
js_TriggerGC(cx, true);
}
if (rt->gcFreeArenaChunks.empty()) {
@ -802,7 +803,7 @@ GetFinalizableTraceKind(size_t thingKind)
JSTRACE_OBJECT, /* FINALIZE_FUNCTION */
#if JS_HAS_XML_SUPPORT /* FINALIZE_XML */
JSTRACE_XML,
#endif
#endif
JSTRACE_STRING, /* FINALIZE_SHORT_STRING */
JSTRACE_STRING, /* FINALIZE_STRING */
JSTRACE_STRING, /* FINALIZE_EXTERNAL_STRING0 */
@ -929,16 +930,7 @@ js_InitGC(JSRuntime *rt, uint32 maxbytes)
return false;
#ifdef JS_THREADSAFE
rt->gcLock = JS_NEW_LOCK();
if (!rt->gcLock)
return false;
rt->gcDone = JS_NEW_CONDVAR(rt->gcLock);
if (!rt->gcDone)
return false;
rt->requestDone = JS_NEW_CONDVAR(rt->gcLock);
if (!rt->requestDone)
return false;
if (!rt->gcHelperThread.init(rt))
if (!rt->gcHelperThread.init())
return false;
#endif
@ -1109,7 +1101,7 @@ MarkWordConservatively(JSTracer *trc, jsuword w)
}
#endif
}
#if defined JS_DUMP_CONSERVATIVE_GC_ROOTS || defined JS_GCMETER
if (IS_GC_MARKING_TRACER(trc))
static_cast<GCMarker *>(trc)->conservativeStats.counter[test]++;
@ -1179,7 +1171,7 @@ MarkConservativeStackRoots(JSTracer *trc)
}
#else
MarkThreadDataConservatively(trc, &trc->context->runtime->threadData);
#endif
#endif
}
JS_NEVER_INLINE void
@ -1205,7 +1197,7 @@ static inline void
RecordNativeStackTopForGC(JSContext *cx)
{
ConservativeGCThreadData *ctd = &JS_THREAD_DATA(cx)->conservativeGC;
#ifdef JS_THREADSAFE
/* Record the stack top here only if we are called from a request. */
JS_ASSERT(cx->thread->requestDepth >= ctd->requestThreshold);
@ -1234,7 +1226,7 @@ js_FinishGC(JSRuntime *rt)
#endif
#ifdef JS_THREADSAFE
rt->gcHelperThread.finish(rt);
rt->gcHelperThread.cancel();
#endif
FinishGCArenaLists(rt);
@ -1930,7 +1922,7 @@ Mark(JSTracer *trc, void *thing, uint32 kind)
}
str = iter.next();
} while (str);
} else if (MarkIfUnmarkedGCThing(thing, gcmarker->getMarkColor())) {
/*
* With JS_GC_ASSUME_LOW_C_STACK defined the mark phase of GC
@ -1960,7 +1952,7 @@ void
MarkGCThing(JSTracer *trc, void *thing)
{
JS_ASSERT(size_t(thing) % JS_GCTHING_ALIGN == 0);
if (!thing)
return;
@ -2307,9 +2299,16 @@ MarkRuntime(JSTracer *trc)
#endif
}
} /* namespace js */
void
TriggerGC(JSRuntime *rt)
js_TriggerGC(JSContext *cx, JSBool gcLocked)
{
JSRuntime *rt = cx->runtime;
#ifdef JS_THREADSAFE
JS_ASSERT(cx->thread->requestDepth > 0);
#endif
JS_ASSERT(!rt->gcRunning);
if (rt->gcIsNeeded)
return;
@ -2318,12 +2317,10 @@ TriggerGC(JSRuntime *rt)
* Trigger the GC when it is safe to call an operation callback on any
* thread.
*/
rt->gcIsNeeded = true;
TriggerAllOperationCallbacks(rt);
rt->gcIsNeeded = JS_TRUE;
js_TriggerAllOperationCallbacks(rt, gcLocked);
}
} /* namespace js */
void
js_DestroyScriptsToGC(JSContext *cx, JSThreadData *data)
{
@ -2593,84 +2590,8 @@ FinalizeArenaList(JSContext *cx, unsigned thingKind)
namespace js {
bool
GCHelperThread::init(JSRuntime *rt)
{
if (!(wakeup = PR_NewCondVar(rt->gcLock)))
return false;
if (!(sweepingDone = PR_NewCondVar(rt->gcLock)))
return false;
thread = PR_CreateThread(PR_USER_THREAD, threadMain, rt, PR_PRIORITY_NORMAL,
PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
return !!thread;
}
void
GCHelperThread::finish(JSRuntime *rt)
{
PRThread *join = NULL;
{
AutoLockGC lock(rt);
if (thread && !shutdown) {
shutdown = true;
PR_NotifyCondVar(wakeup);
join = thread;
}
}
if (join) {
/* PR_DestroyThread is not necessary. */
PR_JoinThread(join);
}
if (wakeup)
PR_DestroyCondVar(wakeup);
if (sweepingDone)
PR_DestroyCondVar(sweepingDone);
}
/* static */
void
GCHelperThread::threadMain(void *arg)
{
JSRuntime *rt = static_cast<JSRuntime *>(arg);
rt->gcHelperThread.threadLoop(rt);
}
void
GCHelperThread::threadLoop(JSRuntime *rt)
{
AutoLockGC lock(rt);
while (!shutdown) {
PR_WaitCondVar(wakeup, PR_INTERVAL_NO_TIMEOUT);
if (sweeping) {
AutoUnlockGC unlock(rt);
doSweep();
}
sweeping = false;
PR_NotifyAllCondVar(sweepingDone);
}
}
void
GCHelperThread::startBackgroundSweep(JSRuntime *rt)
{
/* The caller takes the GC lock. */
JS_ASSERT(!sweeping);
sweeping = true;
PR_NotifyCondVar(wakeup);
}
void
GCHelperThread::waitBackgroundSweepEnd(JSRuntime *rt)
{
AutoLockGC lock(rt);
while (sweeping)
PR_WaitCondVar(sweepingDone, PR_INTERVAL_NO_TIMEOUT);
}
JS_FRIEND_API(void)
GCHelperThread::replenishAndFreeLater(void *ptr)
BackgroundSweepTask::replenishAndFreeLater(void *ptr)
{
JS_ASSERT(freeCursor == freeCursorEnd);
do {
@ -2689,7 +2610,7 @@ GCHelperThread::replenishAndFreeLater(void *ptr)
}
void
GCHelperThread::doSweep()
BackgroundSweepTask::run()
{
if (freeCursor) {
void **array = freeCursorEnd - FREE_ARRAY_LENGTH;
@ -2702,7 +2623,6 @@ GCHelperThread::doSweep()
void **array = *iter;
freeElementsAndArray(array, array + FREE_ARRAY_LENGTH);
}
freeVector.resize(0);
}
}
@ -2717,10 +2637,10 @@ SweepCompartments(JSContext *cx)
JSCompartment **read = rt->compartments.begin();
JSCompartment **end = rt->compartments.end();
JSCompartment **write = read;
/* Delete defaultCompartment only during runtime shutdown */
rt->defaultCompartment->marked = true;
while (read < end) {
JSCompartment *compartment = (*read++);
if (compartment->marked) {
@ -2804,10 +2724,10 @@ MarkAndSweep(JSContext *cx GCTIMER_PARAM)
JS_ASSERT(IS_GC_MARKING_TRACER(&gcmarker));
JS_ASSERT(gcmarker.getMarkColor() == BLACK);
rt->gcMarkingTracer = &gcmarker;
for (GCChunkSet::Range r(rt->gcChunkSet.all()); !r.empty(); r.popFront())
GCChunkInfo::fromChunk(r.front())->clearMarkBitmap();
MarkRuntime(&gcmarker);
js_MarkScriptFilenames(rt);
@ -2823,15 +2743,9 @@ MarkAndSweep(JSContext *cx GCTIMER_PARAM)
(void) rt->gcCallback(cx, JSGC_MARK_END);
#ifdef JS_THREADSAFE
/*
* cx->gcBackgroundFree is set if we need several mark-and-sweep loops to
* finish the GC.
*/
if(!cx->gcBackgroundFree) {
/* Wait until the sweeping from the previois GC finishes. */
rt->gcHelperThread.waitBackgroundSweepEnd(rt);
cx->gcBackgroundFree = &rt->gcHelperThread;
}
JS_ASSERT(!cx->gcSweepTask);
if (!rt->gcHelperThread.busy())
cx->gcSweepTask = new js::BackgroundSweepTask();
#endif
/*
@ -2919,6 +2833,13 @@ MarkAndSweep(JSContext *cx GCTIMER_PARAM)
FreeGCChunks(rt);
TIMESTAMP(sweepDestroyEnd);
#ifdef JS_THREADSAFE
if (cx->gcSweepTask) {
rt->gcHelperThread.schedule(cx->gcSweepTask);
cx->gcSweepTask = NULL;
}
#endif
if (rt->gcCallback)
(void) rt->gcCallback(cx, JSGC_FINALIZE_END);
#ifdef DEBUG_srcnotesize
@ -3140,16 +3061,13 @@ GCUntilDone(JSContext *cx, JSGCInvocationKind gckind GCTIMER_PARAM)
#endif
return;
}
AutoGCSession gcsession(cx);
METER(rt->gcStats.poke++);
bool firstRun = true;
rt->gcMarkAndSweep = true;
#ifdef JS_THREADSAFE
JS_ASSERT(!cx->gcBackgroundFree);
#endif
do {
rt->gcPoke = false;
@ -3167,12 +3085,6 @@ GCUntilDone(JSContext *cx, JSGCInvocationKind gckind GCTIMER_PARAM)
// - a finalizer called js_RemoveRoot or js_UnlockGCThingRT.
} while (rt->gcPoke);
#ifdef JS_THREADSAFE
JS_ASSERT(cx->gcBackgroundFree == &rt->gcHelperThread);
cx->gcBackgroundFree = NULL;
rt->gcHelperThread.startBackgroundSweep(rt);
#endif
rt->gcMarkAndSweep = false;
rt->gcRegenShapes = false;
rt->setGCLastBytes(rt->gcBytes);
@ -3286,7 +3198,7 @@ TraceRuntime(JSTracer *trc)
JSContext *cx = trc->context;
JSRuntime *rt = cx->runtime;
AutoLockGC lock(rt);
if (rt->gcThread != cx->thread) {
AutoGCSession gcsession(cx);
AutoUnlockGC unlock(rt);

View File

@ -51,6 +51,7 @@
#include "jsbit.h"
#include "jsgcchunk.h"
#include "jsutil.h"
#include "jstask.h"
#include "jsvector.h"
#include "jsversion.h"
#include "jsobj.h"
@ -187,12 +188,18 @@ TraceRuntime(JSTracer *trc);
extern JS_REQUIRES_STACK JS_FRIEND_API(void)
MarkContext(JSTracer *trc, JSContext *acx);
/* Must be called with GC lock taken. */
extern void
TriggerGC(JSRuntime *rt);
} /* namespace js */
/*
* Schedule the GC call at a later safe point.
*/
#ifndef JS_THREADSAFE
# define js_TriggerGC(cx, gcLocked) js_TriggerGC (cx)
#endif
extern void
js_TriggerGC(JSContext *cx, JSBool gcLocked);
/*
* Kinds of js_GC invocation.
*/
@ -358,24 +365,18 @@ namespace js {
/*
* During the finalization we do not free immediately. Rather we add the
* corresponding pointers to a buffer which we later release on a separated
* thread.
* corresponding pointers to a buffer which we later release on the
* background thread.
*
* The buffer is implemented as a vector of 64K arrays of pointers, not as a
* simple vector, to avoid realloc calls during the vector growth and to not
* bloat the binary size of the inlined freeLater method. Any OOM during
* buffer growth results in the pointer being freed immediately.
*/
class GCHelperThread {
class BackgroundSweepTask : public JSBackgroundTask {
static const size_t FREE_ARRAY_SIZE = size_t(1) << 16;
static const size_t FREE_ARRAY_LENGTH = FREE_ARRAY_SIZE / sizeof(void *);
PRThread* thread;
PRCondVar* wakeup;
PRCondVar* sweepingDone;
bool shutdown;
bool sweeping;
Vector<void **, 16, js::SystemAllocPolicy> freeVector;
void **freeCursor;
void **freeCursorEnd;
@ -390,37 +391,18 @@ class GCHelperThread {
js_free(array);
}
static void threadMain(void* arg);
void threadLoop(JSRuntime *rt);
void doSweep();
public:
GCHelperThread()
: thread(NULL),
wakeup(NULL),
sweepingDone(NULL),
shutdown(false),
sweeping(false),
freeCursor(NULL),
freeCursorEnd(NULL) { }
bool init(JSRuntime *rt);
void finish(JSRuntime *rt);
/* Must be called with GC lock taken. */
void startBackgroundSweep(JSRuntime *rt);
/* Must be called outside the GC lock. */
void waitBackgroundSweepEnd(JSRuntime *rt);
BackgroundSweepTask()
: freeCursor(NULL), freeCursorEnd(NULL) { }
void freeLater(void *ptr) {
JS_ASSERT(!sweeping);
if (freeCursor != freeCursorEnd)
*freeCursor++ = ptr;
else
replenishAndFreeLater(ptr);
}
virtual void run();
};
#endif /* JS_THREADSAFE */
@ -480,7 +462,7 @@ struct ConservativeGCThreadData {
nativeStackTop = NULL;
}
#endif
bool hasStackToScan() const {
return !!nativeStackTop;
}
@ -501,7 +483,7 @@ struct GCMarker : public JSTracer {
#if defined(JS_DUMP_CONSERVATIVE_GC_ROOTS) || defined(JS_GCMETER)
ConservativeGCStats conservativeStats;
#endif
#ifdef JS_DUMP_CONSERVATIVE_GC_ROOTS
struct ConservativeRoot { void *thing; uint32 traceKind; };
Vector<ConservativeRoot, 0, SystemAllocPolicy> conservativeRoots;

View File

@ -515,7 +515,7 @@ obj_toSource(JSContext *cx, uintN argc, Value *vp)
if (!chars) {
/* If outermost, allocate 4 + 1 for "({})" and the terminator. */
chars = (jschar *) cx->runtime->malloc(((outermost ? 4 : 2) + 1) * sizeof(jschar));
chars = (jschar *) js_malloc(((outermost ? 4 : 2) + 1) * sizeof(jschar));
nchars = 0;
if (!chars)
goto error;

View File

@ -86,11 +86,7 @@ js_GenerateShape(JSContext *cx, bool gcLocked)
*/
rt->shapeGen = SHAPE_OVERFLOW_BIT;
shape = SHAPE_OVERFLOW_BIT;
#ifdef JS_THREADSAFE
Conditionally<AutoLockGC> lockIf(!gcLocked, rt);
#endif
TriggerGC(rt);
js_TriggerGC(cx, gcLocked);
}
return shape;
}
@ -162,7 +158,7 @@ PropertyTable::init(JSContext *cx, Shape *lastProp)
METER(tableAllocFails);
return false;
}
cx->runtime->updateMallocCounter(JS_BIT(sizeLog2) * sizeof(Shape *));
cx->updateMallocCounter(JS_BIT(sizeLog2) * sizeof(Shape *));
hashShift = JS_DHASH_BITS - sizeLog2;
for (Shape::Range r = lastProp->all(); !r.empty(); r.popFront()) {
@ -406,7 +402,7 @@ PropertyTable::change(JSContext *cx, int change)
entries = newTable;
/* Treat the above calloc as a JS_malloc, to match CreateScopeTable. */
cx->runtime->updateMallocCounter(nbytes);
cx->updateMallocCounter(nbytes);
/* Copy only live entries, leaving removed and free ones behind. */
for (oldspp = oldTable; oldsize != 0; oldspp++) {

130
js/src/jstask.cpp Normal file
View File

@ -0,0 +1,130 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=4 sw=4 et tw=99 ft=cpp:
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla SpiderMonkey JavaScript 1.9.1 code, released
* June 30, 2009.
*
* The Initial Developer of the Original Code is
* Andreas Gal <gal@mozilla.com>
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "jstask.h"
#ifdef JS_THREADSAFE
static void start(void* arg) {
((JSBackgroundThread*)arg)->work();
}
JSBackgroundThread::JSBackgroundThread()
: thread(NULL), stack(NULL), lock(NULL), wakeup(NULL), shutdown(false)
{
}
JSBackgroundThread::~JSBackgroundThread()
{
if (wakeup)
PR_DestroyCondVar(wakeup);
if (lock)
PR_DestroyLock(lock);
/* PR_DestroyThread is not necessary. */
}
bool
JSBackgroundThread::init()
{
if (!(lock = PR_NewLock()))
return false;
if (!(wakeup = PR_NewCondVar(lock)))
return false;
thread = PR_CreateThread(PR_USER_THREAD, start, this, PR_PRIORITY_LOW,
PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
return !!thread;
}
void
JSBackgroundThread::cancel()
{
/* We allow to call the cancel method after failed init. */
if (!thread)
return;
PR_Lock(lock);
if (shutdown) {
PR_Unlock(lock);
return;
}
shutdown = true;
PR_NotifyCondVar(wakeup);
PR_Unlock(lock);
PR_JoinThread(thread);
}
void
JSBackgroundThread::work()
{
PR_Lock(lock);
while (!shutdown) {
PR_WaitCondVar(wakeup, PR_INTERVAL_NO_TIMEOUT);
JSBackgroundTask* t;
while ((t = stack) != NULL) {
stack = t->next;
PR_Unlock(lock);
t->run();
delete t;
PR_Lock(lock);
}
}
PR_Unlock(lock);
}
bool
JSBackgroundThread::busy()
{
return !!stack; // we tolerate some racing here
}
void
JSBackgroundThread::schedule(JSBackgroundTask* task)
{
PR_Lock(lock);
if (shutdown) {
PR_Unlock(lock);
task->run();
delete task;
return;
}
task->next = stack;
stack = task;
PR_NotifyCondVar(wakeup);
PR_Unlock(lock);
}
#endif

85
js/src/jstask.h Normal file
View File

@ -0,0 +1,85 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=4 sw=4 et tw=99 ft=cpp:
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released
* June 30, 2009.
*
* The Initial Developer of the Original Code is
* Andreas Gal <gal@mozilla.com>
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef jstask_h___
#define jstask_h___
class JSBackgroundTask {
friend class JSBackgroundThread;
JSBackgroundTask* next;
public:
virtual ~JSBackgroundTask() {}
virtual void run() = 0;
};
#ifdef JS_THREADSAFE
#include "prthread.h"
#include "prlock.h"
#include "prcvar.h"
class JSBackgroundThread {
PRThread* thread;
JSBackgroundTask* stack;
PRLock* lock;
PRCondVar* wakeup;
bool shutdown;
public:
JSBackgroundThread();
~JSBackgroundThread();
bool init();
void cancel();
void work();
bool busy();
void schedule(JSBackgroundTask* task);
};
#else
class JSBackgroundThread {
public:
void schedule(JSBackgroundTask* task) {
task->run();
}
};
#endif
#endif /* jstask_h___ */