From 79cf339dff7e5d18ebbe9319a6a828834786c34c Mon Sep 17 00:00:00 2001 From: Igor Bukanov Date: Fri, 2 Oct 2009 18:34:22 +0400 Subject: [PATCH] bug 517199 - typed GC free lists. r=brendan --- js/src/jsatom.cpp | 3 +- js/src/jscntxt.h | 4 +- js/src/jsgc.cpp | 535 +++++++++++++++++++++++++--------------------- js/src/jsgc.h | 80 +++---- 4 files changed, 326 insertions(+), 296 deletions(-) diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index 5f6a3e6b727e..050479da6cbb 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -454,8 +454,7 @@ js_string_uninterner(JSDHashTable *table, JSDHashEntryHdr *hdr, JS_ASSERT(entry->keyAndFlags != 0); str = (JSString *)ATOM_ENTRY_KEY(entry); - /* Pass null as context. */ - js_FinalizeStringRT(rt, str, js_GetExternalStringGCType(str), NULL); + js_FinalizeStringRT(rt, str); return JS_DHASH_NEXT; } diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 80cf29f8a84e..c16977e14c4f 100755 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -363,7 +363,7 @@ struct JSThread { /* Indicates that the thread is waiting in ClaimTitle from jslock.cpp. */ JSTitle *titleToShare; - JSGCThing *gcFreeLists[GC_NUM_FREELISTS]; + JSGCThing *gcFreeLists[FINALIZE_LIMIT]; /* Factored out of JSThread for !JS_THREADSAFE embedding in JSRuntime. */ JSThreadData data; @@ -450,7 +450,7 @@ struct JSRuntime { /* Garbage collector state, used by jsgc.c. */ JSGCChunkInfo *gcChunkList; - JSGCArenaList gcArenaList[GC_NUM_FREELISTS]; + JSGCArenaList gcArenaList[FINALIZE_LIMIT]; JSGCDoubleArenaList gcDoubleArenaList; JSDHashTable gcRootsHash; JSDHashTable *gcLocksHash; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 34701c4bfa98..55709e2be2ee 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -168,12 +168,6 @@ JS_STATIC_ASSERT(JSTRACE_XML == 3); */ JS_STATIC_ASSERT(JSTRACE_STRING + 1 == JSTRACE_XML); -/* - * The number of used GCX-types must stay within GCX_LIMIT. - */ -JS_STATIC_ASSERT(GCX_NTYPES <= GCX_LIMIT); - - /* * Check that we can reinterpret double as JSGCDoubleCell. */ @@ -184,6 +178,11 @@ JS_STATIC_ASSERT(sizeof(JSGCDoubleCell) == sizeof(double)); */ JS_STATIC_ASSERT(JSVAL_NULL == 0); +/* + * Check consistency of external string constants from JSFinalizeGCThingKind. + */ +JS_STATIC_ASSERT(FINALIZE_EXTERNAL_STRING_LAST - FINALIZE_EXTERNAL_STRING0 == + JS_EXTERNAL_STRING_LIMIT - 1); /* * A GC arena contains a fixed number of flag bits for each thing in its heap, @@ -343,6 +342,19 @@ struct JSGCArenaInfo { #endif }; +/* GC flag definitions, must fit in 8 bits (type index goes in the low bits). */ +const uint8 GCF_MARK = JS_BIT(0); +const uint8 GCF_FINAL = JS_BIT(1); +const uint8 GCF_LOCK = JS_BIT(2); /* lock request bit in API */ + +/* + * The private JSGCThing struct, which describes a JSRuntime.gcFreeList element. + */ +struct JSGCThing { + JSGCThing *next; + uint8 *flagp; +}; + /* * Verify that the bit fields are indeed shared and JSGCArenaInfo is as small * as possible. The code does not rely on this check so if on a particular @@ -665,16 +677,6 @@ JS_STATIC_ASSERT(sizeof(JSGCThing) >= sizeof(jsdouble)); /* We want to use all the available GC thing space for object's slots. */ JS_STATIC_ASSERT(sizeof(JSObject) % sizeof(JSGCThing) == 0); -/* - * Ensure that JSObject is allocated from a different GC-list rather than - * jsdouble and JSString so we can easily finalize JSObject before these 2 - * types of GC things. See comments in js_GC. - */ -JS_STATIC_ASSERT(GC_FREELIST_INDEX(sizeof(JSString)) != - GC_FREELIST_INDEX(sizeof(JSObject))); -JS_STATIC_ASSERT(GC_FREELIST_INDEX(sizeof(jsdouble)) != - GC_FREELIST_INDEX(sizeof(JSObject))); - /* * JSPtrTable capacity growth descriptor. The table grows by powers of two * starting from capacity JSPtrTableInfo.minCapacity, but switching to linear @@ -1084,18 +1086,68 @@ DestroyGCArenas(JSRuntime *rt, JSGCArenaInfo *last) } } +static inline size_t +GetFinalizableThingSize(unsigned thingKind) +{ + JS_STATIC_ASSERT(JS_EXTERNAL_STRING_LIMIT == 8); + + static const uint8 map[FINALIZE_LIMIT] = { + sizeof(JSObject), /* FINALIZE_OBJECT */ + sizeof(JSFunction), /* FINALIZE_FUNCTION */ +#if JS_HAS_XML_SUPPORT + sizeof(JSXML), /* FINALIZE_XML */ +#endif + sizeof(JSString), /* FINALIZE_STRING */ + sizeof(JSString), /* FINALIZE_EXTERNAL_STRING0 */ + sizeof(JSString), /* FINALIZE_EXTERNAL_STRING1 */ + sizeof(JSString), /* FINALIZE_EXTERNAL_STRING2 */ + sizeof(JSString), /* FINALIZE_EXTERNAL_STRING3 */ + sizeof(JSString), /* FINALIZE_EXTERNAL_STRING4 */ + sizeof(JSString), /* FINALIZE_EXTERNAL_STRING5 */ + sizeof(JSString), /* FINALIZE_EXTERNAL_STRING6 */ + sizeof(JSString), /* FINALIZE_EXTERNAL_STRING7 */ + }; + + JS_ASSERT(thingKind < FINALIZE_LIMIT); + return map[thingKind]; +} + +static inline size_t +GetFinalizableArenaTraceKind(JSGCArenaInfo *a) +{ + JS_STATIC_ASSERT(JS_EXTERNAL_STRING_LIMIT == 8); + JS_ASSERT(a->list); + + static const uint8 map[FINALIZE_LIMIT] = { + JSTRACE_OBJECT, /* FINALIZE_OBJECT */ + JSTRACE_OBJECT, /* FINALIZE_FUNCTION */ +#if JS_HAS_XML_SUPPORT /* FINALIZE_XML */ + JSTRACE_XML, +#endif /* FINALIZE_STRING */ + JSTRACE_STRING, + JSTRACE_STRING, /* FINALIZE_EXTERNAL_STRING0 */ + JSTRACE_STRING, /* FINALIZE_EXTERNAL_STRING1 */ + JSTRACE_STRING, /* FINALIZE_EXTERNAL_STRING2 */ + JSTRACE_STRING, /* FINALIZE_EXTERNAL_STRING3 */ + JSTRACE_STRING, /* FINALIZE_EXTERNAL_STRING4 */ + JSTRACE_STRING, /* FINALIZE_EXTERNAL_STRING5 */ + JSTRACE_STRING, /* FINALIZE_EXTERNAL_STRING6 */ + JSTRACE_STRING, /* FINALIZE_EXTERNAL_STRING7 */ + }; + + JS_ASSERT(a->list->thingKind < FINALIZE_LIMIT); + return map[a->list->thingKind]; +} + static void InitGCArenaLists(JSRuntime *rt) { - uintN i, thingSize; - JSGCArenaList *arenaList; - - for (i = 0; i < GC_NUM_FREELISTS; i++) { - arenaList = &rt->gcArenaList[i]; - thingSize = GC_FREELIST_NBYTES(i); + for (unsigned i = 0; i != FINALIZE_LIMIT; ++i) { + JSGCArenaList *arenaList = &rt->gcArenaList[i]; + arenaList->thingSize = GetFinalizableThingSize(i); arenaList->last = NULL; - arenaList->lastCount = THINGS_PER_ARENA(thingSize); - arenaList->thingSize = thingSize; + arenaList->lastCount = THINGS_PER_ARENA(arenaList->thingSize); + arenaList->thingKind = i; arenaList->freeList = NULL; } rt->gcDoubleArenaList.first = NULL; @@ -1105,11 +1157,8 @@ InitGCArenaLists(JSRuntime *rt) static void FinishGCArenaLists(JSRuntime *rt) { - uintN i; - JSGCArenaList *arenaList; - - for (i = 0; i < GC_NUM_FREELISTS; i++) { - arenaList = &rt->gcArenaList[i]; + for (unsigned i = 0; i < FINALIZE_LIMIT; i++) { + JSGCArenaList *arenaList = &rt->gcArenaList[i]; DestroyGCArenas(rt, arenaList->last); arenaList->last = NULL; arenaList->lastCount = THINGS_PER_ARENA(arenaList->thingSize); @@ -1137,73 +1186,41 @@ GetGCThingFlags(void *thing) return THING_FLAGP(a, index); } -/* - * This function returns null when thing is jsdouble. - */ -static uint8 * -GetGCThingFlagsOrNull(void *thing) -{ - JSGCArenaInfo *a; - uint32 index; - - if (JSString::isStatic(thing)) - return NULL; - a = THING_TO_ARENA(thing); - if (!a->list) - return NULL; - index = THING_TO_INDEX(thing, a->list->thingSize); - return THING_FLAGP(a, index); -} - intN js_GetExternalStringGCType(JSString *str) { + JS_STATIC_ASSERT(FINALIZE_STRING + 1 == FINALIZE_EXTERNAL_STRING0); JS_ASSERT(!JSString::isStatic(str)); - uintN type = (uintN) *GetGCThingFlags(str) & GCF_TYPEMASK; - JS_ASSERT(type == GCX_STRING || type >= GCX_EXTERNAL_STRING); - return (type == GCX_STRING) ? -1 : (intN) (type - GCX_EXTERNAL_STRING); -} - -static uint32 -MapGCFlagsToTraceKind(uintN flags) -{ - uint32 type; - - type = flags & GCF_TYPEMASK; - JS_ASSERT(type != GCX_DOUBLE); - JS_ASSERT(type < GCX_NTYPES); - return (type < GCX_EXTERNAL_STRING) ? type : JSTRACE_STRING; + unsigned thingKind = THING_TO_ARENA(str)->list->thingKind; + JS_ASSERT(IsFinalizableStringKind(thingKind)); + return intN(thingKind) - intN(FINALIZE_EXTERNAL_STRING0); } JS_FRIEND_API(uint32) js_GetGCThingTraceKind(void *thing) { - JSGCArenaInfo *a; - uint32 index; - if (JSString::isStatic(thing)) return JSTRACE_STRING; - a = THING_TO_ARENA(thing); + JSGCArenaInfo *a = THING_TO_ARENA(thing); if (!a->list) return JSTRACE_DOUBLE; - - index = THING_TO_INDEX(thing, a->list->thingSize); - return MapGCFlagsToTraceKind(*THING_FLAGP(a, index)); + return GetFinalizableArenaTraceKind(a); } JSRuntime* js_GetGCStringRuntime(JSString *str) { - JSGCArenaList *list; + JSGCArenaList *list = THING_TO_ARENA(str)->list; + JS_ASSERT(list->thingSize == sizeof(JSString)); - list = THING_TO_ARENA(str)->list; - - JS_ASSERT(list->thingSize == sizeof(JSGCThing)); - JS_ASSERT(GC_FREELIST_INDEX(sizeof(JSGCThing)) == 0); - - return (JSRuntime *)((uint8 *)list - offsetof(JSRuntime, gcArenaList)); + unsigned i = list->thingKind; + JS_ASSERT(i == FINALIZE_STRING || + (FINALIZE_EXTERNAL_STRING0 <= i && + i < FINALIZE_EXTERNAL_STRING0 + JS_EXTERNAL_STRING_LIMIT)); + return (JSRuntime *)((uint8 *)(list - i) - + offsetof(JSRuntime, gcArenaList)); } JSBool @@ -1766,8 +1783,9 @@ IsGCThresholdReached(JSRuntime *rt) } template -static JS_INLINE T* -NewGCThing(JSContext *cx, uintN flags, NewbornType** newbornRoot) +static inline T* +NewFinalizableGCThing(JSContext *cx, unsigned thingKind, + NewbornType** newbornRoot) { JSRuntime *rt; bool doGC; @@ -1789,11 +1807,11 @@ NewGCThing(JSContext *cx, uintN flags, NewbornType** newbornRoot) uintN maxFreeThings; /* max to take from the global free list */ #endif - JS_ASSERT((flags & GCF_TYPEMASK) != GCX_DOUBLE); rt = cx->runtime; + size_t nbytes = sizeof(T); + JS_ASSERT(nbytes == GetFinalizableThingSize(thingKind)); JS_ASSERT(JS_ROUNDUP(nbytes, sizeof(JSGCThing)) == nbytes); - uintN flindex = GC_FREELIST_INDEX(nbytes); /* Updates of metering counters here may not be thread-safe. */ METER(astats = &cx->runtime->gcStats.arenaStats[flindex]); @@ -1803,7 +1821,7 @@ NewGCThing(JSContext *cx, uintN flags, NewbornType** newbornRoot) gcLocked = JS_FALSE; JS_ASSERT(cx->thread); - JSGCThing *&freeList = cx->thread->gcFreeLists[flindex]; + JSGCThing *&freeList = cx->thread->gcFreeLists[thingKind]; thing = freeList; localMallocBytes = JS_THREAD_DATA(cx)->gcMallocBytes; if (thing && rt->gcMaxMallocBytes - rt->gcMallocBytes > localMallocBytes) { @@ -1837,7 +1855,7 @@ NewGCThing(JSContext *cx, uintN flags, NewbornType** newbornRoot) goto testReservedObjects; #endif - arenaList = &rt->gcArenaList[flindex]; + arenaList = &rt->gcArenaList[thingKind]; doGC = IsGCThresholdReached(rt); for (;;) { if (doGC @@ -2000,11 +2018,11 @@ testReservedObjects: } /* We can't fail now, so update flags. */ - *flagp = (uint8)flags; + *flagp = uint8(0); #ifdef DEBUG_gchist gchist[gchpos].lastDitch = doGC; - gchist[gchpos].freeList = rt->gcArenaList[flindex].freeList; + gchist[gchpos].freeList = rt->gcArenaList[thingKind].freeList; if (++gchpos == NGCHIST) gchpos = 0; #endif @@ -2031,34 +2049,38 @@ fail: JSObject * js_NewGCObject(JSContext *cx) { - return NewGCThing(cx, GCX_OBJECT, &cx->weakRoots.newbornObject); + return NewFinalizableGCThing(cx, FINALIZE_OBJECT, + &cx->weakRoots.newbornObject); } JSString * js_NewGCString(JSContext *cx) { - return NewGCThing(cx, GCX_STRING, &cx->weakRoots.newbornString); + return NewFinalizableGCThing(cx, FINALIZE_STRING, + &cx->weakRoots.newbornString); } JSString * js_NewGCExternalString(JSContext *cx, uintN type) { JS_ASSERT(type < JS_EXTERNAL_STRING_LIMIT); - return NewGCThing(cx, GCX_EXTERNAL_STRING + type, - &cx->weakRoots.newbornExternalString[type]); + return NewFinalizableGCThing(cx, FINALIZE_EXTERNAL_STRING0 + type, + &cx->weakRoots.newbornExternalString[type]); } JSFunction * js_NewGCFunction(JSContext *cx) { - return NewGCThing(cx, GCX_OBJECT, &cx->weakRoots.newbornObject); + return NewFinalizableGCThing(cx, FINALIZE_FUNCTION, + &cx->weakRoots.newbornObject); } #if JS_HAS_XML_SUPPORT JSXML * js_NewGCXML(JSContext *cx) { - return NewGCThing(cx, GCX_XML, &cx->weakRoots.newbornXML); + return NewFinalizableGCThing(cx,FINALIZE_XML, + &cx->weakRoots.newbornXML); } #endif @@ -2315,11 +2337,23 @@ js_RemoveAsGCBytes(JSRuntime *rt, size_t sz) * they have no descendants to mark during the GC. Currently the optimization * is only used for non-dependant strings. */ -#define GC_THING_IS_SHALLOW(flagp, thing) \ - ((flagp) && \ - ((*(flagp) & GCF_TYPEMASK) >= GCX_EXTERNAL_STRING || \ - ((*(flagp) & GCF_TYPEMASK) == GCX_STRING && \ - !((JSString *) (thing))->isDependent()))) +static uint8 * +GetShallowGCThingFlag(void *thing) +{ + if (JSString::isStatic(thing)) + return NULL; + JSGCArenaInfo *a = THING_TO_ARENA(thing); + if (!a->list || !IsFinalizableStringKind(a->list->thingKind)) + return NULL; + + JS_ASSERT(sizeof(JSString) == a->list->thingSize); + JSString *str = (JSString *) thing; + if (str->isDependent()) + return NULL; + + uint32 index = THING_TO_INDEX(thing, sizeof(JSString)); + return THING_FLAGP(a, index); +} /* This is compatible with JSDHashEntryStub. */ typedef struct JSGCLockHashEntry { @@ -2331,22 +2365,19 @@ typedef struct JSGCLockHashEntry { JSBool js_LockGCThingRT(JSRuntime *rt, void *thing) { - JSBool shallow, ok; - uint8 *flagp; - JSGCLockHashEntry *lhe; - if (!thing) return JS_TRUE; - flagp = GetGCThingFlagsOrNull(thing); JS_LOCK_GC(rt); - shallow = GC_THING_IS_SHALLOW(flagp, thing); /* * Avoid adding a rt->gcLocksHash entry for shallow things until someone * nests a lock. */ - if (shallow && !(*flagp & GCF_LOCK)) { + uint8 *flagp = GetShallowGCThingFlag(thing); + JSBool ok; + JSGCLockHashEntry *lhe; + if (flagp && !(*flagp & GCF_LOCK)) { *flagp |= GCF_LOCK; METER(rt->gcStats.lock++); ok = JS_TRUE; @@ -2387,18 +2418,14 @@ js_LockGCThingRT(JSRuntime *rt, void *thing) JSBool js_UnlockGCThingRT(JSRuntime *rt, void *thing) { - uint8 *flagp; - JSBool shallow; - JSGCLockHashEntry *lhe; - if (!thing) return JS_TRUE; - flagp = GetGCThingFlagsOrNull(thing); JS_LOCK_GC(rt); - shallow = GC_THING_IS_SHALLOW(flagp, thing); - if (shallow && !(*flagp & GCF_LOCK)) + uint8 *flagp = GetShallowGCThingFlag(thing); + JSGCLockHashEntry *lhe; + if (flagp && !(*flagp & GCF_LOCK)) goto out; if (!rt->gcLocksHash || (lhe = (JSGCLockHashEntry *) @@ -2406,7 +2433,7 @@ js_UnlockGCThingRT(JSRuntime *rt, void *thing) JS_DHASH_LOOKUP), JS_DHASH_ENTRY_IS_FREE(&lhe->hdr))) { /* Shallow entry is not in the hash -> clear its lock bit. */ - if (shallow) + if (flagp) *flagp &= ~GCF_LOCK; else goto out; @@ -2523,7 +2550,7 @@ TraceDelayedChildren(JSTracer *trc) { JSRuntime *rt; JSGCArenaInfo *a, *aprev; - uint32 thingSize; + uint32 thingSize, traceKind; uint32 thingsPerUntracedBit; uint32 untracedBitIndex, thingIndex, indexLimit, endIndex; JSGCThing *thing; @@ -2545,6 +2572,7 @@ TraceDelayedChildren(JSTracer *trc) JS_ASSERT(a->prevUntracedPage != 0); JS_ASSERT(rt->gcUntracedArenaStackTop->prevUntracedPage != 0); thingSize = a->list->thingSize; + traceKind = GetFinalizableArenaTraceKind(a); indexLimit = (a == a->list->last) ? a->list->lastCount : THINGS_PER_ARENA(thingSize); @@ -2583,7 +2611,7 @@ TraceDelayedChildren(JSTracer *trc) --rt->gcTraceLaterCount; #endif thing = FLAGP_TO_THING(flagp, thingSize); - JS_TraceChildren(trc, thing, MapGCFlagsToTraceKind(*flagp)); + JS_TraceChildren(trc, thing, traceKind); } while (++thingIndex != endIndex); } @@ -2660,9 +2688,10 @@ JS_CallTracer(JSTracer *trc, void *thing, uint32 kind) for (;;) { if (JSString::isStatic(thing)) goto out; - flagp = THING_TO_FLAGP(thing, sizeof(JSGCThing)); + a = THING_TO_ARENA(thing); + flagp = THING_FLAGP(a, THING_TO_INDEX(thing, sizeof(JSString))); JS_ASSERT((*flagp & GCF_FINAL) == 0); - JS_ASSERT(kind == MapGCFlagsToTraceKind(*flagp)); + JS_ASSERT(kind == GetFinalizableArenaTraceKind(a)); if (!((JSString *) thing)->isDependent()) { *flagp |= GCF_MARK; goto out; @@ -2675,8 +2704,8 @@ JS_CallTracer(JSTracer *trc, void *thing, uint32 kind) /* NOTREACHED */ } + JS_ASSERT(kind == GetFinalizableArenaTraceKind(THING_TO_ARENA(thing))); flagp = GetGCThingFlags(thing); - JS_ASSERT(kind == MapGCFlagsToTraceKind(*flagp)); if (*flagp & GCF_MARK) goto out; @@ -2766,33 +2795,26 @@ gc_root_traversal(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 num, JSVAL_IS_GCTHING(v) && !JSString::isStatic(JSVAL_TO_GCTHING(v))) { #ifdef DEBUG - JSBool root_points_to_gcArenaList = JS_FALSE; + bool root_points_to_gcArenaList = false; jsuword thing = (jsuword) JSVAL_TO_GCTHING(v); - JSRuntime *rt; - uintN i; - JSGCArenaList *arenaList; - uint32 thingSize; - JSGCArenaInfo *a; - size_t limit; - - rt = trc->context->runtime; - for (i = 0; i < GC_NUM_FREELISTS; i++) { - arenaList = &rt->gcArenaList[i]; - thingSize = arenaList->thingSize; - limit = (size_t) arenaList->lastCount * thingSize; - for (a = arenaList->last; a; a = a->prev) { + JSRuntime *rt = trc->context->runtime; + for (unsigned i = 0; i != FINALIZE_LIMIT; i++) { + JSGCArenaList *arenaList = &rt->gcArenaList[i]; + size_t thingSize = arenaList->thingSize; + size_t limit = arenaList->lastCount * thingSize; + for (JSGCArenaInfo *a = arenaList->last; a; a = a->prev) { if (thing - ARENA_INFO_TO_START(a) < limit) { - root_points_to_gcArenaList = JS_TRUE; + root_points_to_gcArenaList = true; break; } - limit = (size_t) THINGS_PER_ARENA(thingSize) * thingSize; + limit = THINGS_PER_ARENA(thingSize) * thingSize; } } if (!root_points_to_gcArenaList) { - for (a = rt->gcDoubleArenaList.first; a; a = a->prev) { + for (JSGCArenaInfo *a = rt->gcDoubleArenaList.first; a; a = a->prev) { if (thing - ARENA_INFO_TO_START(a) < DOUBLES_PER_ARENA * sizeof(jsdouble)) { - root_points_to_gcArenaList = JS_TRUE; + root_points_to_gcArenaList = true; break; } } @@ -3069,8 +3091,9 @@ MarkReservedObjects(JSTraceMonitor *tm) { /* Keep the reserved objects. */ for (JSObject *obj = tm->reservedObjects; obj; obj = JSVAL_TO_OBJECT(obj->fslots[0])) { + JS_ASSERT(js_GetGCThingTraceKind(obj) == JSTRACE_OBJECT); + uint8 *flagp = GetGCThingFlags(obj); - JS_ASSERT((*flagp & GCF_TYPEMASK) == GCX_OBJECT); JS_ASSERT(*flagp != GCF_FINAL); *flagp |= GCF_MARK; } @@ -3189,9 +3212,11 @@ js_DestroyScriptsToGC(JSContext *cx, JSThreadData *data) } } -static void -FinalizeObject(JSContext *cx, JSObject *obj) +static inline void +FinalizeGCThing(JSContext *cx, JSObject *obj, unsigned thingKind) { + JS_ASSERT(thingKind == FINALIZE_FUNCTION || thingKind == FINALIZE_OBJECT); + /* Cope with stillborn objects that have no map. */ if (!obj->map) return; @@ -3216,6 +3241,21 @@ FinalizeObject(JSContext *cx, JSObject *obj) js_FreeSlots(cx, obj); } +static inline void +FinalizeGCThing(JSContext *cx, JSFunction *fun, unsigned thingKind) +{ + JS_ASSERT(thingKind == FINALIZE_FUNCTION); + FinalizeGCThing(cx, FUN_OBJECT(fun), thingKind); +} + +#if JS_HAS_XML_SUPPORT +static inline void +FinalizeGCThing(JSContext *cx, JSXML *xml, unsigned thingKind) +{ + js_FinalizeXML(cx, xml); +} +#endif + JS_STATIC_ASSERT(JS_EXTERNAL_STRING_LIMIT == 8); static JSStringFinalizeOp str_finalizers[JS_EXTERNAL_STRING_LIMIT] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL @@ -3238,8 +3278,8 @@ js_ChangeExternalStringFinalizer(JSStringFinalizeOp oldop, * cx is NULL when we are called from js_FinishAtomState to force the * finalization of the permanently interned strings. */ -void -js_FinalizeStringRT(JSRuntime *rt, JSString *str, intN type, JSContext *cx) +static void +FinalizeString(JSRuntime *rt, JSString *str, unsigned thingKind, JSContext *cx) { jschar *chars; JSBool valid; @@ -3247,9 +3287,10 @@ js_FinalizeStringRT(JSRuntime *rt, JSString *str, intN type, JSContext *cx) JS_RUNTIME_UNMETER(rt, liveStrings); JS_ASSERT(!JSString::isStatic(str)); + JS_ASSERT(IsFinalizableStringKind(thingKind)); if (str->isDependent()) { /* A dependent string can not be external and must be valid. */ - JS_ASSERT(type < 0); + JS_ASSERT(thingKind == FINALIZE_STRING); JS_ASSERT(str->dependentBase()); JS_RUNTIME_UNMETER(rt, liveDependentStrings); valid = JS_TRUE; @@ -3258,13 +3299,14 @@ js_FinalizeStringRT(JSRuntime *rt, JSString *str, intN type, JSContext *cx) chars = str->flatChars(); valid = (chars != NULL); if (valid) { - if (type < 0) { + if (thingKind == FINALIZE_STRING) { if (cx) cx->free(chars); else rt->free(chars); } else { - JS_ASSERT((uintN) type < JS_ARRAY_LENGTH(str_finalizers)); + unsigned type = thingKind - FINALIZE_EXTERNAL_STRING0; + JS_ASSERT(type < JS_ARRAY_LENGTH(str_finalizers)); finalizer = str_finalizers[type]; if (finalizer) { /* @@ -3280,6 +3322,97 @@ js_FinalizeStringRT(JSRuntime *rt, JSString *str, intN type, JSContext *cx) js_PurgeDeflatedStringCache(rt, str); } +static inline void +FinalizeGCThing(JSContext *cx, JSString *str, unsigned thingKind) +{ + return FinalizeString(cx->runtime, str, thingKind, cx); +} + +void +js_FinalizeStringRT(JSRuntime *rt, JSString *str) +{ + JS_ASSERT(!JSString::isStatic(str)); + + unsigned thingKind = THING_TO_ARENA(str)->list->thingKind; + FinalizeString(rt, str, thingKind, NULL); +} + +template +static void +FinalizeArenaList(JSContext *cx, unsigned thingKind, + JSGCArenaInfo **emptyArenas) +{ + JSGCArenaList *arenaList = &cx->runtime->gcArenaList[thingKind]; + JS_ASSERT(sizeof(T) == arenaList->thingSize); + + JSGCArenaInfo **ap = &arenaList->last; + JSGCArenaInfo *a = *ap; + if (!a) + return; + + JS_ASSERT(arenaList->lastCount > 0); + + JSGCThing *freeList = NULL; + arenaList->freeList = NULL; + + uint32 indexLimit = THINGS_PER_ARENA(sizeof(T)); + uint8 *flagp = THING_FLAGP(a, arenaList->lastCount - 1); + +#ifdef JS_GCMETER + uint32 nlivearenas = 0, nkilledarenas = 0, nthings = 0; +#endif + for (;;) { + JS_ASSERT(a->prevUntracedPage == 0); + JS_ASSERT(a->u.untracedThings == 0); + + bool allClear = true; + do { + if (*flagp & (GCF_MARK | GCF_LOCK)) { + *flagp &= ~GCF_MARK; + allClear = false; + METER(nthings++); + } else { + JSGCThing *thing = FLAGP_TO_THING(flagp, sizeof(T)); + if (!(*flagp & GCF_FINAL)) { + /* Call the finalizer with GCF_FINAL ORed into flags. */ + *flagp |= GCF_FINAL; + FinalizeGCThing(cx, (T *) thing, thingKind); +#ifdef DEBUG + memset(thing, JS_FREE_PATTERN, sizeof(T)); +#endif + } + thing->flagp = flagp; + thing->next = freeList; + freeList = thing; + } + } while (++flagp != THING_FLAGS_END(a)); + + if (allClear) { + /* + * Forget just assembled free list head for the arena and + * add the arena itself to the destroy list. + */ + freeList = arenaList->freeList; + if (a == arenaList->last) + arenaList->lastCount = indexLimit; + *ap = a->prev; + a->prev = *emptyArenas; + *emptyArenas = a; + METER(nkilledarenas++); + } else { + arenaList->freeList = freeList; + ap = &a->prev; + METER(nlivearenas++); + } + if (!(a = *ap)) + break; + flagp = THING_FLAGP(a, indexLimit - 1); + } + + METER(UpdateArenaStats(&rt->gcStats.arenaStats[thingKind], + nlivearenas, nkilledarenas, nthings)); +} + /* * The gckind flag bit GC_LOCK_HELD indicates a call from js_NewGCThing with * rt->gcLock already held, so the lock should be kept on return. @@ -3290,14 +3423,8 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) JSRuntime *rt; JSBool keepAtoms; JSGCCallback callback; - uintN i, type; JSTracer trc; - uint32 thingSize, indexLimit; - JSGCArenaInfo *a, **ap, *emptyArenas; - uint8 flags, *flagp; - JSGCThing *thing, *freeList; - JSGCArenaList *arenaList; - JSBool allClear; + JSGCArenaInfo *emptyArenas, *a, **ap; #ifdef JS_THREADSAFE uint32 requestDebit; #endif @@ -3586,107 +3713,25 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) #endif /* - * Here we need to ensure that JSObject instances are finalized before GC- - * allocated JSString and jsdouble instances so object's finalizer can - * access them even if they will be freed. For that we simply finalize the - * list containing JSObject first since the static assert at the beginning - * of the file guarantees that JSString and jsdouble instances are - * allocated from a different list. + * We finalize JSObject instances before JSString, double and other GC + * things to ensure that object's finalizer can access them even if they + * will be freed. + * + * To minimize the number of checks per each to be freed object and + * function we use separated list finalizers when a debug hook is + * installed. */ emptyArenas = NULL; - for (i = 0; i < GC_NUM_FREELISTS; i++) { - arenaList = &rt->gcArenaList[i == 0 - ? GC_FREELIST_INDEX(sizeof(JSObject)) - : i == GC_FREELIST_INDEX(sizeof(JSObject)) - ? 0 - : i]; - ap = &arenaList->last; - if (!(a = *ap)) - continue; - - JS_ASSERT(arenaList->lastCount > 0); - arenaList->freeList = NULL; - freeList = NULL; - thingSize = arenaList->thingSize; - indexLimit = THINGS_PER_ARENA(thingSize); - flagp = THING_FLAGP(a, arenaList->lastCount - 1); - METER((nlivearenas = 0, nkilledarenas = 0, nthings = 0)); - for (;;) { - JS_ASSERT(a->prevUntracedPage == 0); - JS_ASSERT(a->u.untracedThings == 0); - allClear = JS_TRUE; - do { - flags = *flagp; - if (flags & (GCF_MARK | GCF_LOCK)) { - *flagp &= ~GCF_MARK; - allClear = JS_FALSE; - METER(nthings++); - } else { - thing = FLAGP_TO_THING(flagp, thingSize); - if (!(flags & GCF_FINAL)) { - /* - * Call the finalizer with GCF_FINAL ORed into flags. - */ - *flagp = (uint8)(flags | GCF_FINAL); - type = flags & GCF_TYPEMASK; - switch (type) { - case GCX_OBJECT: - FinalizeObject(cx, (JSObject *) thing); - break; + FinalizeArenaList(cx, FINALIZE_OBJECT, &emptyArenas); + FinalizeArenaList(cx, FINALIZE_FUNCTION, &emptyArenas); #if JS_HAS_XML_SUPPORT - case GCX_XML: - js_FinalizeXML(cx, (JSXML *) thing); - break; + FinalizeArenaList(cx, FINALIZE_XML, &emptyArenas); #endif - default: - JS_ASSERT(type == GCX_STRING || - type - GCX_EXTERNAL_STRING < - GCX_NTYPES - GCX_EXTERNAL_STRING); - js_FinalizeStringRT(rt, (JSString *) thing, - (intN) (type - - GCX_EXTERNAL_STRING), - cx); - break; - } -#ifdef DEBUG - memset(thing, JS_FREE_PATTERN, thingSize); -#endif - } - thing->flagp = flagp; - thing->next = freeList; - freeList = thing; - } - } while (++flagp != THING_FLAGS_END(a)); - - if (allClear) { - /* - * Forget just assembled free list head for the arena and - * add the arena itself to the destroy list. - */ - freeList = arenaList->freeList; - if (a == arenaList->last) - arenaList->lastCount = indexLimit; - *ap = a->prev; - a->prev = emptyArenas; - emptyArenas = a; - METER(nkilledarenas++); - } else { - arenaList->freeList = freeList; - ap = &a->prev; - METER(nlivearenas++); - } - if (!(a = *ap)) - break; - flagp = THING_FLAGP(a, indexLimit - 1); - } - - /* - * We use arenaList - &rt->gcArenaList[0], not i, as the stat index - * due to the enumeration reorder at the beginning of the loop. - */ - METER(UpdateArenaStats(&rt->gcStats.arenaStats[arenaList - - &rt->gcArenaList[0]], - nlivearenas, nkilledarenas, nthings)); + FinalizeArenaList(cx, FINALIZE_STRING, &emptyArenas); + for (unsigned i = FINALIZE_EXTERNAL_STRING0; + i <= FINALIZE_EXTERNAL_STRING_LAST; + ++i) { + FinalizeArenaList(cx, i, &emptyArenas); } ap = &rt->gcDoubleArenaList.first; @@ -3731,7 +3776,7 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) js_SweepScriptFilenames(rt); /* - * Destroy arenas after we finished the sweeping sofinalizers can safely + * Destroy arenas after we finished the sweeping so finalizers can safely * use js_IsAboutToBeFinalized(). */ DestroyGCArenas(rt, emptyArenas); diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 2cbc8d4b4a56..d5a2d1e00bc6 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -58,33 +58,8 @@ JS_BEGIN_EXTERN_C */ #define JSTRACE_LIMIT 4 -/* - * We use the trace kinds as the types for all GC things except external - * strings. - */ -#define GCX_OBJECT JSTRACE_OBJECT /* JSObject */ -#define GCX_DOUBLE JSTRACE_DOUBLE /* jsdouble */ -#define GCX_STRING JSTRACE_STRING /* JSString */ -#define GCX_XML JSTRACE_XML /* JSXML */ -#define GCX_EXTERNAL_STRING JSTRACE_LIMIT /* JSString with external - chars */ const uintN JS_EXTERNAL_STRING_LIMIT = 8; -/* - * The number of defined GC types and the maximum limit for the number of - * possible GC types. - */ -#define GCX_NTYPES (GCX_EXTERNAL_STRING + JS_EXTERNAL_STRING_LIMIT) -#define GCX_LIMIT_LOG2 4 /* type index bits */ -#define GCX_LIMIT JS_BIT(GCX_LIMIT_LOG2) - -/* GC flag definitions, must fit in 8 bits (type index goes in the low bits). */ -#define GCF_TYPEMASK JS_BITMASK(GCX_LIMIT_LOG2) -#define GCF_MARK JS_BIT(GCX_LIMIT_LOG2) -#define GCF_FINAL JS_BIT(GCX_LIMIT_LOG2 + 1) -#define GCF_LOCKSHIFT (GCX_LIMIT_LOG2 + 2) /* lock bit shift */ -#define GCF_LOCK JS_BIT(GCF_LOCKSHIFT) /* lock request bit in API */ - /* * Get the type of the external string or -1 if the string was not created * with JS_NewExternalString. @@ -151,19 +126,6 @@ typedef struct JSPtrTable { extern JSBool js_RegisterCloseableIterator(JSContext *cx, JSObject *obj); -/* - * The private JSGCThing struct, which describes a gcFreeList element. - */ -struct JSGCThing { - JSGCThing *next; - uint8 *flagp; -}; - -#define GC_NBYTES_MAX (10 * sizeof(JSGCThing)) -#define GC_NUM_FREELISTS (GC_NBYTES_MAX / sizeof(JSGCThing)) -#define GC_FREELIST_NBYTES(i) (((i) + 1) * sizeof(JSGCThing)) -#define GC_FREELIST_INDEX(n) (((n) / sizeof(JSGCThing)) - 1) - /* * Allocates a new GC thing of the given size. After a successful allocation * the caller must fully initialize the thing before calling any function that @@ -289,6 +251,36 @@ typedef enum JSGCInvocationKind { extern void js_GC(JSContext *cx, JSGCInvocationKind gckind); +/* + * The kind of GC thing with a finalizer. The external strings follow the + * ordinary string to simplify js_GetExternalStringGCType. + */ +enum JSFinalizeGCThingKind { + FINALIZE_OBJECT, + FINALIZE_FUNCTION, +#if JS_HAS_XML_SUPPORT + FINALIZE_XML, +#endif + FINALIZE_STRING, + FINALIZE_EXTERNAL_STRING0, + FINALIZE_EXTERNAL_STRING1, + FINALIZE_EXTERNAL_STRING2, + FINALIZE_EXTERNAL_STRING3, + FINALIZE_EXTERNAL_STRING4, + FINALIZE_EXTERNAL_STRING5, + FINALIZE_EXTERNAL_STRING6, + FINALIZE_EXTERNAL_STRING7, + FINALIZE_EXTERNAL_STRING_LAST = FINALIZE_EXTERNAL_STRING7, + FINALIZE_LIMIT +}; + +static inline bool +IsFinalizableStringKind(unsigned thingKind) +{ + return unsigned(FINALIZE_STRING) <= thingKind && + thingKind <= unsigned(FINALIZE_EXTERNAL_STRING_LAST); +} + typedef struct JSGCArenaInfo JSGCArenaInfo; typedef struct JSGCArenaList JSGCArenaList; typedef struct JSGCChunkInfo JSGCChunkInfo; @@ -297,6 +289,7 @@ struct JSGCArenaList { JSGCArenaInfo *last; /* last allocated GC arena */ uint32 lastCount; /* number of allocated things in the last arena */ + uint32 thingKind; /* one of JSFinalizeGCThingKind */ uint32 thingSize; /* size of things to allocate on this list */ JSGCThing *freeList; /* list of free GC things */ @@ -359,15 +352,8 @@ class JSFreePointerListTask : public JSBackgroundTask { }; #endif -/* - * Free the chars held by str when it is finalized by the GC. When type is - * less then zero, it denotes an internal string. Otherwise it denotes the - * type of the external string allocated with JS_NewExternalString. - * - * This function always needs rt but can live with null cx. - */ extern void -js_FinalizeStringRT(JSRuntime *rt, JSString *str, intN type, JSContext *cx); +js_FinalizeStringRT(JSRuntime *rt, JSString *str); #ifdef DEBUG_notme #define JS_GCMETER 1 @@ -416,7 +402,7 @@ typedef struct JSGCStats { uint32 closelater; /* number of close hooks scheduled to run */ uint32 maxcloselater; /* max number of close hooks scheduled to run */ - JSGCArenaStats arenaStats[GC_NUM_FREELISTS]; + JSGCArenaStats arenaStats[FINALIZE_LIST_LIMIT]; JSGCArenaStats doubleArenaStats; } JSGCStats;