diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 649a7efb70a5..3c5fc8f572ab 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -858,18 +858,22 @@ PrintWinCodebase(nsGlobalWindow *win) } #endif +// Don't GC for the first 10s (startup). +PRIntervalTime startTime = -1; + static void MaybeGC(JSContext *cx) { - size_t bytes = cx->runtime->gcBytes; - size_t lastBytes = cx->runtime->gcLastBytes; - if ((bytes > 8192 && bytes > lastBytes * 16) -#ifdef DEBUG - || cx->runtime->gcZeal > 0 -#endif - ) { - JS_GC(cx); + if (startTime) { + if (startTime == -1) { + startTime = PR_IntervalNow(); + return; + } + if (PR_IntervalToMilliseconds(PR_IntervalNow() - startTime) < 10000) + return; + startTime = 0; } + JS_MaybeGC(cx); } static already_AddRefed @@ -3879,30 +3883,10 @@ SetMemoryHighWaterMarkPrefChangedCallback(const char* aPrefName, void* aClosure) { PRInt32 highwatermark = nsContentUtils::GetIntPref(aPrefName, 32); - if (highwatermark >= 32) { - // There are two options of memory usage in tracemonkey. One is - // to use malloc() and the other is to use memory for GC. (E.g. - // js_NewGCThing()/RefillDoubleFreeList()). - // Let's limit the high water mark for the first one to 32MB, - // and second one to 0xffffffff. - JS_SetGCParameter(nsJSRuntime::sRuntime, JSGC_MAX_MALLOC_BYTES, - 32L * 1024L * 1024L); - JS_SetGCParameter(nsJSRuntime::sRuntime, JSGC_MAX_BYTES, - 0xffffffff); - } else { - JS_SetGCParameter(nsJSRuntime::sRuntime, JSGC_MAX_MALLOC_BYTES, - highwatermark * 1024L * 1024L); - JS_SetGCParameter(nsJSRuntime::sRuntime, JSGC_MAX_BYTES, - highwatermark * 1024L * 1024L); - } - return 0; -} - -static int -SetMemoryGCFrequencyPrefChangedCallback(const char* aPrefName, void* aClosure) -{ - PRInt32 triggerFactor = nsContentUtils::GetIntPref(aPrefName, 1600); - JS_SetGCParameter(nsJSRuntime::sRuntime, JSGC_TRIGGER_FACTOR, triggerFactor); + JS_SetGCParameter(nsJSRuntime::sRuntime, JSGC_MAX_BYTES, + (highwatermark >= 32) + ? 0xffffffff + : highwatermark * 1024L * 1024L); return 0; } @@ -3995,12 +3979,6 @@ nsJSRuntime::Init() SetMemoryHighWaterMarkPrefChangedCallback("javascript.options.mem.high_water_mark", nsnull); - nsContentUtils::RegisterPrefCallback("javascript.options.mem.gc_frequency", - SetMemoryGCFrequencyPrefChangedCallback, - nsnull); - SetMemoryGCFrequencyPrefChangedCallback("javascript.options.mem.gc_frequency", - nsnull); - nsCOMPtr obs = do_GetService("@mozilla.org/observer-service;1", &rv); NS_ENSURE_SUCCESS(rv, rv); diff --git a/js/src/configure.in b/js/src/configure.in index 8ec17dafc892..5ba716a14ee3 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -1952,7 +1952,7 @@ case "$target" in _DEFINES_CXXFLAGS='-FI $(DEPTH)/js-confdefs.h -DMOZILLA_CLIENT' CFLAGS="$CFLAGS -W3 -Gy -Fd\$(COMPILE_PDBFILE)" CXXFLAGS="$CXXFLAGS -W3 -Gy -Fd\$(COMPILE_PDBFILE)" - LIBS="$LIBS kernel32.lib user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.lib" + LIBS="$LIBS kernel32.lib user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.lib psapi.lib" MOZ_DEBUG_FLAGS='-Zi' MOZ_DEBUG_LDFLAGS='-DEBUG -DEBUGTYPE:CV' WARNINGS_AS_ERRORS='-WX' diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 00d9efed1b30..ab6cdf45851d 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -2452,72 +2452,7 @@ JS_GC(JSContext *cx) JS_PUBLIC_API(void) JS_MaybeGC(JSContext *cx) { - JSRuntime *rt; - uint32 bytes, lastBytes; - - rt = cx->runtime; - -#ifdef JS_GC_ZEAL - if (rt->gcZeal > 0) { - JS_GC(cx); - return; - } -#endif - - bytes = rt->gcBytes; - lastBytes = rt->gcLastBytes; - - /* - * We run the GC if we used all available free GC cells and had to - * allocate extra 1/3 of GC arenas since the last run of GC, or if - * we have malloc'd more bytes through JS_malloc than we were told - * to allocate by JS_NewRuntime. - * - * The reason for - * bytes > 4/3 lastBytes - * condition is the following. Bug 312238 changed bytes and lastBytes - * to mean the total amount of memory that the GC uses now and right - * after the last GC. - * - * Before the bug the variables meant the size of allocated GC things - * now and right after the last GC. That size did not include the - * memory taken by free GC cells and the condition was - * bytes > 3/2 lastBytes. - * That is, we run the GC if we have half again as many bytes of - * GC-things as the last time we GC'd. To be compatible we need to - * express that condition through the new meaning of bytes and - * lastBytes. - * - * We write the original condition as - * B*(1-F) > 3/2 Bl*(1-Fl) - * where B is the total memory size allocated by GC and F is the free - * cell density currently and Sl and Fl are the size and the density - * right after GC. The density by definition is memory taken by free - * cells divided by total amount of memory. In other words, B and Bl - * are bytes and lastBytes with the new meaning and B*(1-F) and - * Bl*(1-Fl) are bytes and lastBytes with the original meaning. - * - * Our task is to exclude F and Fl from the last statement. According - * to the stats from bug 331966 comment 23, Fl is about 10-25% for a - * typical run of the browser. It means that the original condition - * implied that we did not run GC unless we exhausted the pool of - * free cells. Indeed if we still have free cells, then B == Bl since - * we did not yet allocated any new arenas and the condition means - * 1 - F > 3/2 (1-Fl) or 3/2Fl > 1/2 + F - * That implies 3/2 Fl > 1/2 or Fl > 1/3. That cannot be fulfilled - * for the state described by the stats. So we can write the original - * condition as: - * F == 0 && B > 3/2 Bl(1-Fl) - * Again using the stats we see that Fl is about 11% when the browser - * starts up and when we are far from hitting rt->gcMaxBytes. With - * this F we have - * F == 0 && B > 3/2 Bl(1-0.11) - * or approximately F == 0 && B > 4/3 Bl. - */ - if ((bytes > 8192 && bytes > lastBytes + lastBytes / 3) || - rt->gcMallocBytes >= rt->gcMaxMallocBytes) { - JS_GC(cx); - } + js_MaybeGC(cx); } JS_PUBLIC_API(JSGCCallback) @@ -2551,17 +2486,10 @@ JS_SetGCParameter(JSRuntime *rt, JSGCParamKey key, uint32 value) case JSGC_MAX_BYTES: rt->gcMaxBytes = value; break; - case JSGC_MAX_MALLOC_BYTES: - rt->gcMaxMallocBytes = value; - break; - case JSGC_STACKPOOL_LIFESPAN: + default: + JS_ASSERT(key == JSGC_STACKPOOL_LIFESPAN); rt->gcEmptyArenaPoolLifespan = value; break; - default: - JS_ASSERT(key == JSGC_TRIGGER_FACTOR); - JS_ASSERT(value >= 100); - rt->setGCTriggerFactor(value); - return; } } @@ -2571,12 +2499,8 @@ JS_GetGCParameter(JSRuntime *rt, JSGCParamKey key) switch (key) { case JSGC_MAX_BYTES: return rt->gcMaxBytes; - case JSGC_MAX_MALLOC_BYTES: - return rt->gcMaxMallocBytes; case JSGC_STACKPOOL_LIFESPAN: return rt->gcEmptyArenaPoolLifespan; - case JSGC_TRIGGER_FACTOR: - return rt->gcTriggerFactor; case JSGC_BYTES: return rt->gcBytes; default: diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 23f5b44f5528..40e3d547baae 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1271,30 +1271,17 @@ typedef enum JSGCParamKey { /* Maximum nominal heap before last ditch GC. */ JSGC_MAX_BYTES = 0, - /* Number of JS_malloc bytes before last ditch GC. */ - JSGC_MAX_MALLOC_BYTES = 1, - /* Hoard stackPools for this long, in ms, default is 30 seconds. */ - JSGC_STACKPOOL_LIFESPAN = 2, - - /* - * The factor that defines when the GC is invoked. The factor is a - * percent of the memory allocated by the GC after the last run of - * the GC. When the current memory allocated by the GC is more than - * this percent then the GC is invoked. The factor cannot be less - * than 100 since the current memory allocated by the GC cannot be less - * than the memory allocated after the last run of the GC. - */ - JSGC_TRIGGER_FACTOR = 3, + JSGC_STACKPOOL_LIFESPAN = 1, /* Amount of bytes allocated by the GC. */ - JSGC_BYTES = 4, + JSGC_BYTES = 2, /* Number of times when GC was invoked. */ - JSGC_NUMBER = 5, + JSGC_NUMBER = 3, /* Max size of the code cache in bytes. */ - JSGC_MAX_CODE_CACHE_BYTES = 6 + JSGC_MAX_CODE_CACHE_BYTES = 4 } JSGCParamKey; extern JS_PUBLIC_API(void) diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 97b5d60abfd0..d0c86c8a1d57 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -1797,6 +1797,7 @@ js_TriggerAllOperationCallbacks(JSRuntime *rt, JSBool gcLocked) if (!gcLocked) JS_LOCK_GC(rt); #endif + iter = NULL; while ((acx = js_ContextIterator(rt, JS_FALSE, &iter))) JS_TriggerOperationCallback(acx); diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index af79b64bd3e9..1c344d131466 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -262,12 +262,6 @@ struct JSThreadData { JSEvalCacheMeter evalCacheMeter; #endif - /* - * Thread-local version of JSRuntime.gcMallocBytes to avoid taking - * locks on each JS_malloc. - */ - size_t gcMallocBytes; - #ifdef JS_THREADSAFE /* * Deallocator task for this thread. @@ -390,16 +384,14 @@ struct JSRuntime { JSDHashTable gcRootsHash; JSDHashTable *gcLocksHash; jsrefcount gcKeepAtoms; - size_t gcBytes; - size_t gcLastBytes; - size_t gcMaxBytes; - size_t gcMaxMallocBytes; uint32 gcEmptyArenaPoolLifespan; uint32 gcLevel; uint32 gcNumber; JSTracer *gcMarkingTracer; - uint32 gcTriggerFactor; - size_t gcTriggerBytes; + size_t gcBytes; + size_t gcMaxBytes; + size_t gcLastRSS; + size_t gcFreed; volatile JSBool gcIsNeeded; volatile JSBool gcFlushCodeCaches; @@ -429,7 +421,6 @@ struct JSRuntime { #endif JSGCCallback gcCallback; - size_t gcMallocBytes; JSGCArenaInfo *gcUntracedArenaStackTop; #ifdef DEBUG size_t gcTraceLaterCount; @@ -718,9 +709,6 @@ struct JSRuntime { char lastScriptFilename[1024]; #endif - void setGCTriggerFactor(uint32 factor); - void setGCLastBytes(size_t lastBytes); - inline void* malloc(size_t bytes) { return ::js_malloc(bytes); } @@ -1080,15 +1068,15 @@ struct JSContext { #endif #ifdef JS_THREADSAFE - inline void createDeallocatorTask() { - JSThreadData* tls = JS_THREAD_DATA(this); + inline void createDeallocatorTask(size_t *bytesp) { + JSThreadData *tls = JS_THREAD_DATA(this); JS_ASSERT(!tls->deallocatorTask); if (runtime->deallocatorThread && !runtime->deallocatorThread->busy()) - tls->deallocatorTask = new JSFreePointerListTask(); + tls->deallocatorTask = new JSFreePointerListTask(bytesp); } inline void submitDeallocatorTask() { - JSThreadData* tls = JS_THREAD_DATA(this); + JSThreadData *tls = JS_THREAD_DATA(this); if (tls->deallocatorTask) { runtime->deallocatorThread->schedule(tls->deallocatorTask); tls->deallocatorTask = NULL; @@ -1096,46 +1084,32 @@ struct JSContext { } #endif - /* Call this after succesful malloc of memory for GC-related things. */ - inline void updateMallocCounter(size_t nbytes) { - size_t *pbytes, bytes; - - pbytes = &JS_THREAD_DATA(this)->gcMallocBytes; - bytes = *pbytes; - *pbytes = (size_t(-1) - bytes <= nbytes) ? size_t(-1) : bytes + nbytes; - } - - inline void* malloc(size_t bytes) { + inline void *malloc(size_t bytes) { JS_ASSERT(bytes != 0); void *p = runtime->malloc(bytes); if (!p) { JS_ReportOutOfMemory(this); return NULL; } - updateMallocCounter(bytes); return p; } - inline void* calloc(size_t bytes) { + inline void *calloc(size_t bytes) { JS_ASSERT(bytes != 0); void *p = runtime->calloc(bytes); if (!p) { JS_ReportOutOfMemory(this); return NULL; } - updateMallocCounter(bytes); return p; } - inline void* realloc(void* p, size_t bytes) { - void *orig = p; + inline void *realloc(void* p, size_t bytes) { p = runtime->realloc(p, bytes); if (!p) { JS_ReportOutOfMemory(this); return NULL; } - if (!orig) - updateMallocCounter(bytes); return p; } diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 9132c491fec9..8eeddad0cb39 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1298,24 +1298,13 @@ js_InitGC(JSRuntime *rt, uint32 maxbytes) } rt->gcLocksHash = NULL; /* create lazily */ - /* - * Separate gcMaxMallocBytes from gcMaxBytes but initialize to maxbytes - * for default backward API compatibility. - */ - rt->gcMaxBytes = rt->gcMaxMallocBytes = maxbytes; + rt->gcMaxBytes = maxbytes; rt->gcEmptyArenaPoolLifespan = 30000; /* - * By default the trigger factor gets maximum possible value. This - * means that GC will not be triggered by growth of GC memory (gcBytes). + * Sample the current resident set size (RSS). */ - rt->setGCTriggerFactor((uint32) -1); - - /* - * The assigned value prevents GC from running when GC memory is too low - * (during JS engine start). - */ - rt->setGCLastBytes(8192); + rt->gcLastRSS = js_GetRSS(); METER(memset(&rt->gcStats, 0, sizeof rt->gcStats)); return JS_TRUE; @@ -1724,42 +1713,6 @@ static struct GCHist { unsigned gchpos = 0; #endif -void -JSRuntime::setGCTriggerFactor(uint32 factor) -{ - JS_ASSERT(factor >= 100); - - gcTriggerFactor = factor; - setGCLastBytes(gcLastBytes); -} - -void -JSRuntime::setGCLastBytes(size_t lastBytes) -{ - gcLastBytes = lastBytes; - uint64 triggerBytes = uint64(lastBytes) * uint64(gcTriggerFactor / 100); - if (triggerBytes != size_t(triggerBytes)) - triggerBytes = size_t(-1); - gcTriggerBytes = size_t(triggerBytes); -} - -static JS_INLINE bool -IsGCThresholdReached(JSRuntime *rt) -{ -#ifdef JS_GC_ZEAL - if (rt->gcZeal >= 1) - return true; -#endif - - /* - * Since the initial value of the gcLastBytes parameter is not equal to - * zero (see the js_InitGC function) the return value is false when - * the gcBytes value is close to zero at the JS engine start. - */ - return rt->gcMallocBytes >= rt->gcMaxMallocBytes || - rt->gcBytes >= rt->gcTriggerBytes; -} - template static JS_INLINE T* NewGCThing(JSContext *cx, uintN flags) { @@ -1776,7 +1729,6 @@ NewGCThing(JSContext *cx, uintN flags) #endif #ifdef JS_THREADSAFE JSBool gcLocked; - uintN localMallocBytes; JSGCThing **lastptr; JSGCThing *tmpthing; uint8 *tmpflagp; @@ -1799,8 +1751,7 @@ NewGCThing(JSContext *cx, uintN flags) JSGCThing *&freeList = cx->thread->gcFreeLists[flindex]; thing = freeList; - localMallocBytes = JS_THREAD_DATA(cx)->gcMallocBytes; - if (thing && rt->gcMaxMallocBytes - rt->gcMallocBytes > localMallocBytes) { + if (thing) { flagp = thing->flagp; freeList = thing->next; METER(astats->localalloc++); @@ -1810,14 +1761,6 @@ NewGCThing(JSContext *cx, uintN flags) JS_LOCK_GC(rt); gcLocked = JS_TRUE; - /* Transfer thread-local counter to global one. */ - if (localMallocBytes != 0) { - JS_THREAD_DATA(cx)->gcMallocBytes = 0; - if (rt->gcMaxMallocBytes - rt->gcMallocBytes < localMallocBytes) - rt->gcMallocBytes = rt->gcMaxMallocBytes; - else - rt->gcMallocBytes += localMallocBytes; - } #endif JS_ASSERT(!rt->gcRunning); if (rt->gcRunning) { @@ -1832,7 +1775,7 @@ NewGCThing(JSContext *cx, uintN flags) #endif arenaList = &rt->gcArenaList[flindex]; - doGC = IsGCThresholdReached(rt); + doGC = rt->gcIsNeeded; for (;;) { if (doGC #ifdef JS_TRACER @@ -1861,13 +1804,10 @@ NewGCThing(JSContext *cx, uintN flags) #ifdef JS_THREADSAFE /* * Refill the local free list by taking several things from the - * global free list unless the free list is already populated or - * we are still at rt->gcMaxMallocBytes barrier. The former is - * caused via allocating new things in gcCallback(cx, JSGC_END). - * The latter happens when GC is canceled due to - * gcCallback(cx, JSGC_BEGIN) returning false. + * global free list unless the free list is already populated. + * This is caused by allocating new things in gcCallback(cx, JSGC_END). */ - if (freeList || rt->gcMallocBytes >= rt->gcMaxMallocBytes) + if (freeList) break; tmpthing = arenaList->freeList; @@ -1937,7 +1877,7 @@ testReservedObjects: * arena. Prefer to order free things by ascending address in the * (unscientific) hope of better cache locality. */ - if (freeList || rt->gcMallocBytes >= rt->gcMaxMallocBytes) + if (freeList) break; lastptr = &freeList; maxFreeThings = thingsLimit - arenaList->lastCount; @@ -2064,7 +2004,7 @@ RefillDoubleFreeList(JSContext *cx) return NULL; } - if (IsGCThresholdReached(rt)) + if (rt->gcIsNeeded) goto do_gc; /* @@ -2251,45 +2191,6 @@ js_ReserveObjects(JSContext *cx, size_t nobjects) } #endif -JSBool -js_AddAsGCBytes(JSContext *cx, size_t sz) -{ - JSRuntime *rt; - - rt = cx->runtime; - if (rt->gcBytes >= rt->gcMaxBytes || - sz > (size_t) (rt->gcMaxBytes - rt->gcBytes) || - IsGCThresholdReached(rt)) { - if (JS_ON_TRACE(cx)) { - /* - * If we can't leave the trace, signal OOM condition, otherwise - * exit from trace and proceed with GC. - */ - if (!js_CanLeaveTrace(cx)) { - JS_UNLOCK_GC(rt); - return JS_FALSE; - } - js_LeaveTrace(cx); - } - js_GC(cx, GC_LAST_DITCH); - if (rt->gcBytes >= rt->gcMaxBytes || - sz > (size_t) (rt->gcMaxBytes - rt->gcBytes)) { - JS_UNLOCK_GC(rt); - JS_ReportOutOfMemory(cx); - return JS_FALSE; - } - } - rt->gcBytes += (uint32) sz; - return JS_TRUE; -} - -void -js_RemoveAsGCBytes(JSRuntime *rt, size_t sz) -{ - JS_ASSERT((size_t) rt->gcBytes >= sz); - rt->gcBytes -= (uint32) sz; -} - /* * Shallow GC-things can be locked just by setting the GCF_LOCK bit, because * they have no descendants to mark during the GC. Currently the optimization @@ -3496,9 +3397,6 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) /* Clear gcIsNeeded now, when we are about to start a normal GC cycle. */ rt->gcIsNeeded = JS_FALSE; - /* Reset malloc counter. */ - rt->gcMallocBytes = 0; - #ifdef JS_DUMP_SCOPE_METERS { extern void js_DumpScopeMeters(JSRuntime *rt); js_DumpScopeMeters(rt); @@ -3559,7 +3457,14 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) rt->gcMarkingTracer = NULL; #ifdef JS_THREADSAFE - cx->createDeallocatorTask(); + /* + * Deallocations occur in the background. The background thread will set + * gcFreed once it is done. At the next GC we will substract the amount + * of data we freed in the background from the previous GC cycle's + * RSS sample. + */ + rt->gcFreed = 0; + cx->createDeallocatorTask(&rt->gcFreed); #endif /* @@ -3740,7 +3645,16 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) */ DestroyGCArenas(rt, emptyArenas); + /* Sample Resident Set Size (RSS). */ + rt->gcLastRSS = js_GetRSS(); + #ifdef JS_THREADSAFE + /* + * It is important that we sample rt->gcLastRSS before submitting the + * deallocator task. This way we know that the RSS we sample ddoes not + * contain the bytes we will free in the background, which we will + * account for separately in rt->gcFreed. + */ cx->submitDeallocatorTask(); #endif @@ -3798,7 +3712,6 @@ out: goto restart; } - rt->setGCLastBytes(rt->gcBytes); done_running: rt->gcLevel = 0; rt->gcRunning = rt->gcRegenShapes = false; @@ -3850,3 +3763,134 @@ out: } } } + +void +js_MaybeGC(JSContext *cx) +{ + JSRuntime *rt; + + rt = cx->runtime; + +#ifdef JS_GC_ZEAL + if (rt->gcZeal > 0) { + JS_GC(cx); + return; + } +#endif + + size_t lastRSS = rt->gcLastRSS; + size_t freed = rt->gcFreed; + if (lastRSS > freed) + lastRSS -= freed; + size_t rss = js_GetRSS(); + /* Trigger a GC if the working set grew by more than 32MB and at least 25%. */ + if (rss > lastRSS + 32*1024*1024 && rss > lastRSS + lastRSS/4) + JS_GC(cx); +} + +#ifdef JS_THREADSAFE +void +JSFreePointerListTask::add(void *ptr) +{ +#ifdef DEBUG + memset(ptr, 0xcd, js_malloc_size(ptr)); +#endif + *(void**)ptr = head; + head = ptr; +} + +void +JSFreePointerListTask::run() +{ + size_t bytes = 0; + void *ptr = head; + while (ptr) { + void* next = *(void **)ptr; + bytes += js_malloc_size(ptr); + js_free(ptr); + ptr = next; + } + JS_ATOMIC_ADD(bytesp, bytes); +} +#endif + +#ifdef __APPLE__ +#include +#include +size_t +js_GetRSS() +{ + task_t task; + task_basic_info ti; + mach_msg_type_number_t count; + + task_for_pid(mach_task_self (), getpid(), &task); + count = TASK_BASIC_INFO_COUNT; + task_info(task, TASK_BASIC_INFO, (task_info_t)&ti, &count); + return ti.resident_size; +} +#endif + +#ifdef WINCE +#include "windows.h" + +size_t +js_GetRSS() +{ + MEMORYSTATUS ms; + GlobalMemoryStatus(&ms); + return ms.dwTotalVirtual - ms.dwAvailVirtual; +} +#else +#ifdef WIN32 +#include "windows.h" +#include "psapi.h" +size_t +js_GetRSS() +{ + PROCESS_MEMORY_COUNTERS pmc; + GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)); + return pmc.WorkingSetSize; +} +#endif +#endif + +#ifdef __linux__ +#include +#include +static const char* +skipFields(const char* p, unsigned n) +{ + while (*++p) { + if ((*p == ' ') && (--n == 0)) + return p+1; + } + return NULL; +} + +static int statfd = 0; + +size_t +js_GetRSS() +{ + char buf[128]; + if (!statfd) { + snprintf(buf, 100, "/proc/%d/stat", getpid()); + statfd = open(buf, O_RDONLY); + } + if (statfd < 0) + return 0; + lseek(statfd, 0, SEEK_SET); + int n = read(statfd, buf, sizeof(buf)-1); + if (n < 0) + return 0; + buf[n] = 0; + const char* p = strrchr(buf, ')'); + if (!p) + return 0; + p = skipFields(p + 2, 20); + if (!p) + return 0; + return atol(p); +} +#endif diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 52ec7982fd68..705ea4c59fe4 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -284,6 +284,9 @@ typedef enum JSGCInvocationKind { extern void js_GC(JSContext *cx, JSGCInvocationKind gckind); +extern void +js_MaybeGC(JSContext *cx); + typedef struct JSGCArenaInfo JSGCArenaInfo; typedef struct JSGCArenaList JSGCArenaList; typedef struct JSGCChunkInfo JSGCChunkInfo; @@ -326,41 +329,15 @@ struct JSWeakRoots { #define JS_CLEAR_WEAK_ROOTS(wr) (memset((wr), 0, sizeof(JSWeakRoots))) -/* - * Increase runtime->gcBytes by sz bytes to account for an allocation outside - * the GC that will be freed only after the GC is run. The function may run - * the last ditch GC to ensure that gcBytes does not exceed gcMaxBytes. It will - * fail if the latter is not possible. - * - * This function requires that runtime->gcLock is held on entry. On successful - * return the lock is still held and on failure it will be released with - * the error reported. - */ -extern JSBool -js_AddAsGCBytes(JSContext *cx, size_t sz); - -extern void -js_RemoveAsGCBytes(JSRuntime* rt, size_t sz); - #ifdef JS_THREADSAFE class JSFreePointerListTask : public JSBackgroundTask { void *head; + size_t *bytesp; public: - JSFreePointerListTask() : head(NULL) {} + JSFreePointerListTask(size_t *bytesp) : head(NULL), bytesp(bytesp) {} - void add(void* ptr) { - *(void**)ptr = head; - head = ptr; - } - - void run() { - void *ptr = head; - while (ptr) { - void *next = *(void **)ptr; - js_free(ptr); - ptr = next; - } - } + void add(void* ptr); + void run(); }; #endif @@ -374,6 +351,9 @@ class JSFreePointerListTask : public JSBackgroundTask { extern void js_FinalizeStringRT(JSRuntime *rt, JSString *str, intN type, JSContext *cx); +extern size_t +js_GetRSS(); + #ifdef DEBUG_notme #define JS_GCMETER 1 #endif diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 58eb0787b5a6..4917b3b09db7 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -4971,11 +4971,6 @@ js_Enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op, JS_ASSERT(ne->cursor == (jsword) length); if (allocated != 0) { JS_LOCK_GC(cx->runtime); - if (!js_AddAsGCBytes(cx, allocated)) { - /* js_AddAsGCBytes releases the GC lock on failures. */ - cx->free(ne); - return JS_FALSE; - } ne->next = cx->runtime->nativeEnumerators; cx->runtime->nativeEnumerators = ne; JS_ASSERT(((jsuword) ne & (jsuword) 1) == (jsuword) 0); @@ -5064,7 +5059,6 @@ js_TraceNativeEnumerators(JSTracer *trc) js_TraceId(trc, *cursor); } while (++cursor != end); } else if (doGC) { - js_RemoveAsGCBytes(rt, NativeEnumeratorSize(ne->length)); *nep = ne->next; trc->context->free(ne); continue; diff --git a/js/src/jsscope.cpp b/js/src/jsscope.cpp index 02791e40cff0..1bfb6aa06c38 100644 --- a/js/src/jsscope.cpp +++ b/js/src/jsscope.cpp @@ -172,7 +172,6 @@ JSScope::createTable(JSContext *cx, bool report) JS_ReportOutOfMemory(cx); return false; } - cx->updateMallocCounter(JS_BIT(sizeLog2) * sizeof(JSScopeProperty *)); hashShift = JS_DHASH_BITS - sizeLog2; for (sprop = lastProp; sprop; sprop = sprop->parent) { @@ -411,9 +410,6 @@ JSScope::changeTable(JSContext *cx, int change) oldtable = table; table = newtable; - /* Treat the above calloc as a JS_malloc, to match CreateScopeTable. */ - cx->runtime->gcMallocBytes += nbytes; - /* Copy only live entries, leaving removed and free ones behind. */ for (oldspp = oldtable; oldsize != 0; oldspp++) { sprop = SPROP_FETCH(oldspp); diff --git a/js/src/jsutil.h b/js/src/jsutil.h index 6e0895b2fb9d..4512ee43a9ca 100644 --- a/js/src/jsutil.h +++ b/js/src/jsutil.h @@ -202,6 +202,26 @@ static JS_INLINE void js_free(void* p) { free(p); } +#ifdef XP_UNIX +#ifdef __APPLE__ +#include +static JS_INLINE size_t js_malloc_size(void* p) { + return malloc_size(p); +} +#else +#include +static JS_INLINE size_t js_malloc_size(void* p) { + return malloc_usable_size(p); +} +#endif +#endif +#ifdef XP_WIN +#include +static JS_INLINE size_t js_malloc_size(void* p) { + return _msize(p); +} +#endif + JS_END_EXTERN_C #endif /* jsutil_h___ */ diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 8a5dc5007304..c353938b6c0a 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -1144,16 +1144,12 @@ GCParameter(JSContext *cx, uintN argc, jsval *vp) return JS_FALSE; if (strcmp(paramName, "maxBytes") == 0) { param = JSGC_MAX_BYTES; - } else if (strcmp(paramName, "maxMallocBytes") == 0) { - param = JSGC_MAX_MALLOC_BYTES; } else if (strcmp(paramName, "gcStackpoolLifespan") == 0) { param = JSGC_STACKPOOL_LIFESPAN; } else if (strcmp(paramName, "gcBytes") == 0) { param = JSGC_BYTES; } else if (strcmp(paramName, "gcNumber") == 0) { param = JSGC_NUMBER; - } else if (strcmp(paramName, "gcTriggerFactor") == 0) { - param = JSGC_TRIGGER_FACTOR; } else { JS_ReportError(cx, "the first argument argument must be maxBytes, " @@ -1180,11 +1176,6 @@ GCParameter(JSContext *cx, uintN argc, jsval *vp) "with non-zero value"); return JS_FALSE; } - if (param == JSGC_TRIGGER_FACTOR && value < 100) { - JS_ReportError(cx, - "the gcTriggerFactor value must be >= 100"); - return JS_FALSE; - } JS_SetGCParameter(cx->runtime, param, value); *vp = JSVAL_VOID; return JS_TRUE; diff --git a/js/src/xpconnect/shell/xpcshell.cpp b/js/src/xpconnect/shell/xpcshell.cpp index 7c7b21a9621d..cfc9fa94825d 100644 --- a/js/src/xpconnect/shell/xpcshell.cpp +++ b/js/src/xpconnect/shell/xpcshell.cpp @@ -150,6 +150,86 @@ static JSBool compileOnly = JS_FALSE; JSPrincipals *gJSPrincipals = nsnull; nsAutoString *gWorkingDirectory = nsnull; +static JSContext *gWatchdogContext = nsnull; + +#ifdef XP_WIN +static HANDLE gTimerHandle = 0; + +VOID CALLBACK +TimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired) +{ + JS_TriggerOperationCallback(gWatchdogContext); +} + +static void +EnableWatchdog(JSContext *cx) +{ + gWatchdogContext = cx; + + if (gTimerHandle) + return; + + if (!CreateTimerQueueTimer(&gTimerHandle, + NULL, + (WAITORTIMERCALLBACK)TimerCallback, + rt, + DWORD(1000), + 0, + WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE)) + gTimerHandle = 0; +} + +static void +DisableWatchdog() +{ + if (gTimerHandle) { + DeleteTimerQueueTimer(NULL, gTimerHandle, NULL); + gTimerHandle = 0; + } +} +#else +static void +AlarmHandler(int sig) +{ + JS_TriggerOperationCallback(gWatchdogContext); +} + +static void +EnableWatchdog(JSContext *cx) +{ + gWatchdogContext = cx; + + signal(SIGALRM, AlarmHandler); /* set the Alarm signal capture */ + alarm(1); +} + +static void +DisableWatchdog() +{ + alarm(0); + signal(SIGALRM, NULL); +} +#endif + +class Watchdog { +public: + Watchdog(JSContext *cx) { + EnableWatchdog(cx); + } + + ~Watchdog() { + DisableWatchdog(); + } +}; + +static JSBool +ShellOperationCallback(JSContext *cx) +{ + JS_MaybeGC(cx); + + return JS_TRUE; +} + static JSBool GetLocationProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { @@ -1518,6 +1598,7 @@ ContextCallback(JSContext *cx, uintN contextOp) if (contextOp == JSCONTEXT_NEW) { JS_SetErrorReporter(cx, my_ErrorReporter); JS_SetVersion(cx, JSVERSION_LATEST); + JS_SetOperationCallback(cx, ShellOperationCallback); } return JS_TRUE; } @@ -1645,6 +1726,8 @@ main(int argc, char **argv, char **envp) return 1; } + Watchdog watchdog(cx); + nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID()); if (!xpc) { printf("failed to get nsXPConnect service!\n"); diff --git a/memory/jemalloc/jemalloc.c b/memory/jemalloc/jemalloc.c index a3952dee01c4..fd8f799ea07c 100644 --- a/memory/jemalloc/jemalloc.c +++ b/memory/jemalloc/jemalloc.c @@ -6397,7 +6397,11 @@ free(void *ptr) */ size_t +#ifdef __linux__ +malloc_usable_size(void *ptr) +#else malloc_usable_size(const void *ptr) +#endif { #ifdef MALLOC_VALIDATE diff --git a/memory/jemalloc/jemalloc.h b/memory/jemalloc/jemalloc.h index 4419bd0ff56c..0b46ecabf44e 100644 --- a/memory/jemalloc/jemalloc.h +++ b/memory/jemalloc/jemalloc.h @@ -92,7 +92,11 @@ void free(void *ptr); int posix_memalign(void **memptr, size_t alignment, size_t size); void *memalign(size_t alignment, size_t size); +#ifdef __linux +size_t malloc_usable_size(void *ptr); +#else size_t malloc_usable_size(const void *ptr); +#endif void jemalloc_stats(jemalloc_stats_t *stats); /* The x*() functions never return NULL. */