diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 3c5fc8f572a..649a7efb70a 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -858,22 +858,18 @@ PrintWinCodebase(nsGlobalWindow *win) } #endif -// Don't GC for the first 10s (startup). -PRIntervalTime startTime = -1; - static void MaybeGC(JSContext *cx) { - if (startTime) { - if (startTime == -1) { - startTime = PR_IntervalNow(); - return; - } - if (PR_IntervalToMilliseconds(PR_IntervalNow() - startTime) < 10000) - return; - startTime = 0; + 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); } - JS_MaybeGC(cx); } static already_AddRefed @@ -3883,10 +3879,30 @@ SetMemoryHighWaterMarkPrefChangedCallback(const char* aPrefName, void* aClosure) { PRInt32 highwatermark = nsContentUtils::GetIntPref(aPrefName, 32); - JS_SetGCParameter(nsJSRuntime::sRuntime, JSGC_MAX_BYTES, - (highwatermark >= 32) - ? 0xffffffff - : highwatermark * 1024L * 1024L); + 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); return 0; } @@ -3979,6 +3995,12 @@ 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 977731b09fc..f81e5ff00ef 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -1980,7 +1980,7 @@ case "$target" in DSO_LDOPTS=-SUBSYSTEM:WINDOWS 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 psapi.lib" + LIBS="$LIBS kernel32.lib user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.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 279a890de3d..ccade84c220 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -2447,7 +2447,72 @@ JS_GC(JSContext *cx) JS_PUBLIC_API(void) JS_MaybeGC(JSContext *cx) { - js_MaybeGC(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_PUBLIC_API(JSGCCallback) @@ -2481,10 +2546,17 @@ JS_SetGCParameter(JSRuntime *rt, JSGCParamKey key, uint32 value) case JSGC_MAX_BYTES: rt->gcMaxBytes = value; break; - default: - JS_ASSERT(key == JSGC_STACKPOOL_LIFESPAN); + case JSGC_MAX_MALLOC_BYTES: + rt->gcMaxMallocBytes = value; + break; + case JSGC_STACKPOOL_LIFESPAN: rt->gcEmptyArenaPoolLifespan = value; break; + default: + JS_ASSERT(key == JSGC_TRIGGER_FACTOR); + JS_ASSERT(value >= 100); + rt->setGCTriggerFactor(value); + return; } } @@ -2494,8 +2566,12 @@ 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 dbc2f043423..2a6a422331d 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1259,17 +1259,30 @@ 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 = 1, + 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, /* Amount of bytes allocated by the GC. */ - JSGC_BYTES = 2, + JSGC_BYTES = 4, /* Number of times when GC was invoked. */ - JSGC_NUMBER = 3, + JSGC_NUMBER = 5, /* Max size of the code cache in bytes. */ - JSGC_MAX_CODE_CACHE_BYTES = 4 + JSGC_MAX_CODE_CACHE_BYTES = 6 } JSGCParamKey; extern JS_PUBLIC_API(void) diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 12ed56168e5..42b579b363b 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -1779,7 +1779,6 @@ 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 92c6c714183..ddfdebe3030 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -251,6 +251,12 @@ 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. @@ -370,14 +376,16 @@ 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; - size_t gcBytes; - size_t gcMaxBytes; - size_t gcLastRSS; - size_t gcFreed; + uint32 gcTriggerFactor; + size_t gcTriggerBytes; volatile JSBool gcIsNeeded; /* @@ -406,6 +414,7 @@ struct JSRuntime { #endif JSGCCallback gcCallback; + size_t gcMallocBytes; JSGCArenaInfo *gcUntracedArenaStackTop; #ifdef DEBUG size_t gcTraceLaterCount; @@ -694,6 +703,9 @@ 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); } @@ -1065,15 +1077,15 @@ struct JSContext { #endif #ifdef JS_THREADSAFE - inline void createDeallocatorTask(size_t *bytesp) { - JSThreadData *tls = JS_THREAD_DATA(this); + inline void createDeallocatorTask() { + JSThreadData* tls = JS_THREAD_DATA(this); JS_ASSERT(!tls->deallocatorTask); if (runtime->deallocatorThread && !runtime->deallocatorThread->busy()) - tls->deallocatorTask = new JSFreePointerListTask(bytesp); + tls->deallocatorTask = new JSFreePointerListTask(); } 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; @@ -1081,32 +1093,46 @@ struct JSContext { } #endif - inline void *malloc(size_t bytes) { + /* 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) { 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) { + inline void* realloc(void* p, size_t bytes) { + void *orig = p; 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 59700fe3763..d4a31a249df 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1298,13 +1298,24 @@ js_InitGC(JSRuntime *rt, uint32 maxbytes) } rt->gcLocksHash = NULL; /* create lazily */ - rt->gcMaxBytes = maxbytes; + /* + * Separate gcMaxMallocBytes from gcMaxBytes but initialize to maxbytes + * for default backward API compatibility. + */ + rt->gcMaxBytes = rt->gcMaxMallocBytes = maxbytes; rt->gcEmptyArenaPoolLifespan = 30000; /* - * Sample the current resident set size (RSS). + * By default the trigger factor gets maximum possible value. This + * means that GC will not be triggered by growth of GC memory (gcBytes). */ - rt->gcLastRSS = js_GetRSS(); + rt->setGCTriggerFactor((uint32) -1); + + /* + * The assigned value prevents GC from running when GC memory is too low + * (during JS engine start). + */ + rt->setGCLastBytes(8192); METER(memset(&rt->gcStats, 0, sizeof rt->gcStats)); return JS_TRUE; @@ -1713,6 +1724,42 @@ 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) { @@ -1729,6 +1776,7 @@ NewGCThing(JSContext *cx, uintN flags) #endif #ifdef JS_THREADSAFE JSBool gcLocked; + uintN localMallocBytes; JSGCThing **lastptr; JSGCThing *tmpthing; uint8 *tmpflagp; @@ -1751,7 +1799,8 @@ NewGCThing(JSContext *cx, uintN flags) JSGCThing *&freeList = cx->thread->gcFreeLists[flindex]; thing = freeList; - if (thing) { + localMallocBytes = JS_THREAD_DATA(cx)->gcMallocBytes; + if (thing && rt->gcMaxMallocBytes - rt->gcMallocBytes > localMallocBytes) { flagp = thing->flagp; freeList = thing->next; METER(astats->localalloc++); @@ -1761,6 +1810,14 @@ 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) { @@ -1775,7 +1832,7 @@ NewGCThing(JSContext *cx, uintN flags) #endif arenaList = &rt->gcArenaList[flindex]; - doGC = rt->gcIsNeeded; + doGC = IsGCThresholdReached(rt); for (;;) { if (doGC #ifdef JS_TRACER @@ -1804,10 +1861,13 @@ 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. - * This is caused by allocating new things in gcCallback(cx, JSGC_END). + * 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. */ - if (freeList) + if (freeList || rt->gcMallocBytes >= rt->gcMaxMallocBytes) break; tmpthing = arenaList->freeList; @@ -1877,7 +1937,7 @@ testReservedObjects: * arena. Prefer to order free things by ascending address in the * (unscientific) hope of better cache locality. */ - if (freeList) + if (freeList || rt->gcMallocBytes >= rt->gcMaxMallocBytes) break; lastptr = &freeList; maxFreeThings = thingsLimit - arenaList->lastCount; @@ -2004,7 +2064,7 @@ RefillDoubleFreeList(JSContext *cx) return NULL; } - if (rt->gcIsNeeded) + if (IsGCThresholdReached(rt)) goto do_gc; /* @@ -2191,6 +2251,45 @@ 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 @@ -3399,6 +3498,9 @@ 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); @@ -3459,14 +3561,7 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) rt->gcMarkingTracer = NULL; #ifdef JS_THREADSAFE - /* - * 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); + cx->createDeallocatorTask(); #endif /* @@ -3647,16 +3742,7 @@ 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 @@ -3714,6 +3800,7 @@ out: goto restart; } + rt->setGCLastBytes(rt->gcBytes); done_running: rt->gcLevel = 0; rt->gcRunning = rt->gcRegenShapes = false; @@ -3765,129 +3852,3 @@ 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; - if (js_GetRSS() > (lastRSS + lastRSS/4)) // 25% growth? - 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[1024]; - if (!statfd) { - sprintf(buf, "/proc/%d/stat", getpid()); - statfd = open(buf, O_RDONLY); - } - if (statfd == -1) - return 0; - lseek(statfd, 0, SEEK_SET); - int n = read(statfd, buf, sizeof(buf)-1); - buf[n] = 0; - const char* p = strrchr(buf, ')') + 2; - if (!p) - return 0; - p = skipFields(p, 20); - if (!p) - return 0; - return atol(p); -} -#endif diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 705ea4c59fe..52ec7982fd6 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -284,9 +284,6 @@ 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; @@ -329,15 +326,41 @@ 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(size_t *bytesp) : head(NULL), bytesp(bytesp) {} + JSFreePointerListTask() : head(NULL) {} - void add(void* ptr); - void run(); + 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; + } + } }; #endif @@ -351,9 +374,6 @@ 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 e19abc90c68..bb7ba260807 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -4989,6 +4989,11 @@ 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); @@ -5077,6 +5082,7 @@ 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 1bfb6aa06c3..02791e40cff 100644 --- a/js/src/jsscope.cpp +++ b/js/src/jsscope.cpp @@ -172,6 +172,7 @@ 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) { @@ -410,6 +411,9 @@ 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 4512ee43a9c..6e0895b2fb9 100644 --- a/js/src/jsutil.h +++ b/js/src/jsutil.h @@ -202,26 +202,6 @@ 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 0ee1cc478ba..3eaecc34bf6 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -308,8 +308,6 @@ GetContextData(JSContext *cx) static JSBool ShellOperationCallback(JSContext *cx) { - JS_MaybeGC(cx); - if (!gCanceled) return JS_TRUE; @@ -1119,12 +1117,16 @@ 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, " @@ -1151,6 +1153,11 @@ 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/memory/jemalloc/jemalloc.c b/memory/jemalloc/jemalloc.c index fd8f799ea07..a3952dee01c 100644 --- a/memory/jemalloc/jemalloc.c +++ b/memory/jemalloc/jemalloc.c @@ -6397,11 +6397,7 @@ 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 0b46ecabf44..4419bd0ff56 100644 --- a/memory/jemalloc/jemalloc.h +++ b/memory/jemalloc/jemalloc.h @@ -92,11 +92,7 @@ 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. */