bug 517199 - typed GC free lists. r=brendan

This commit is contained in:
Igor Bukanov 2009-10-02 18:34:22 +04:00
parent 46afee49ed
commit 79cf339dff
4 changed files with 326 additions and 296 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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 <typename T, typename NewbornType>
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<JSObject>(cx, GCX_OBJECT, &cx->weakRoots.newbornObject);
return NewFinalizableGCThing<JSObject>(cx, FINALIZE_OBJECT,
&cx->weakRoots.newbornObject);
}
JSString *
js_NewGCString(JSContext *cx)
{
return NewGCThing<JSString>(cx, GCX_STRING, &cx->weakRoots.newbornString);
return NewFinalizableGCThing<JSString>(cx, FINALIZE_STRING,
&cx->weakRoots.newbornString);
}
JSString *
js_NewGCExternalString(JSContext *cx, uintN type)
{
JS_ASSERT(type < JS_EXTERNAL_STRING_LIMIT);
return NewGCThing<JSString>(cx, GCX_EXTERNAL_STRING + type,
&cx->weakRoots.newbornExternalString[type]);
return NewFinalizableGCThing<JSString>(cx, FINALIZE_EXTERNAL_STRING0 + type,
&cx->weakRoots.newbornExternalString[type]);
}
JSFunction *
js_NewGCFunction(JSContext *cx)
{
return NewGCThing<JSFunction>(cx, GCX_OBJECT, &cx->weakRoots.newbornObject);
return NewFinalizableGCThing<JSFunction>(cx, FINALIZE_FUNCTION,
&cx->weakRoots.newbornObject);
}
#if JS_HAS_XML_SUPPORT
JSXML *
js_NewGCXML(JSContext *cx)
{
return NewGCThing<JSXML>(cx, GCX_XML, &cx->weakRoots.newbornXML);
return NewFinalizableGCThing<JSXML>(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<typename T>
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<JSObject>(cx, FINALIZE_OBJECT, &emptyArenas);
FinalizeArenaList<JSFunction>(cx, FINALIZE_FUNCTION, &emptyArenas);
#if JS_HAS_XML_SUPPORT
case GCX_XML:
js_FinalizeXML(cx, (JSXML *) thing);
break;
FinalizeArenaList<JSXML>(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<JSString>(cx, FINALIZE_STRING, &emptyArenas);
for (unsigned i = FINALIZE_EXTERNAL_STRING0;
i <= FINALIZE_EXTERNAL_STRING_LAST;
++i) {
FinalizeArenaList<JSString>(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);

View File

@ -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;