Bug 333236: merge sweep and free phases in GC. r=brendan

This commit is contained in:
igor@mir2.org 2007-09-19 15:04:47 -07:00
parent fe1659e34d
commit 1dd9e7fca8
8 changed files with 131 additions and 148 deletions

View File

@ -397,6 +397,7 @@ js_string_uninterner(JSDHashTable *table, JSDHashEntryHdr *hdr,
{
JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr);
JSRuntime *rt = (JSRuntime *)arg;
JSString *str;
/*
* Any string entry that remains at this point must be initialized, as the
@ -404,7 +405,10 @@ js_string_uninterner(JSDHashTable *table, JSDHashEntryHdr *hdr,
*/
JS_ASSERT(IS_STRING_TABLE(table));
JS_ASSERT(entry->keyAndFlags != 0);
js_FinalizeStringRT(rt, (JSString *)ATOM_ENTRY_KEY(entry));
str = (JSString *)ATOM_ENTRY_KEY(entry);
/* Pass null as context. */
js_FinalizeStringRT(rt, str, GCF_TYPEMASK & *js_GetGCThingFlags(str), NULL);
return JS_DHASH_NEXT;
}

View File

@ -414,7 +414,7 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode)
/*
* Free unit string storage only after the last GC has completed, so
* that js_FinalizeStringRT can detect unit strings and avoid calling
* that js_FinalizeString can detect unit strings and avoid calling
* free on their chars storage.
*/
free(rt->unitStrings);

View File

@ -2168,17 +2168,12 @@ out:
void
js_FinalizeFunction(JSContext *cx, JSFunction *fun)
{
JSScript *script;
/*
* Null-check of i.script is required since the parser sets interpreted
* very early.
*/
if (FUN_INTERPRETED(fun) && fun->u.i.script) {
script = fun->u.i.script;
fun->u.i.script = NULL;
js_DestroyScript(cx, script);
}
if (FUN_INTERPRETED(fun) && fun->u.i.script)
js_DestroyScript(cx, fun->u.i.script);
}
JSObject *

View File

@ -816,37 +816,6 @@ js_IsAboutToBeFinalized(JSContext *cx, void *thing)
return !(flags & (GCF_MARK | GCF_LOCK | GCF_FINAL));
}
typedef void (*GCFinalizeOp)(JSContext *cx, JSGCThing *thing);
#ifndef DEBUG
# define js_FinalizeDouble NULL
#endif
#if !JS_HAS_XML_SUPPORT
# define js_FinalizeXMLNamespace NULL
# define js_FinalizeXMLQName NULL
# define js_FinalizeXML NULL
#endif
static GCFinalizeOp gc_finalizers[GCX_NTYPES] = {
(GCFinalizeOp) js_FinalizeObject, /* GCX_OBJECT */
(GCFinalizeOp) js_FinalizeString, /* GCX_STRING */
(GCFinalizeOp) js_FinalizeDouble, /* GCX_DOUBLE */
(GCFinalizeOp) js_FinalizeFunction, /* GCX_FUNCTION */
(GCFinalizeOp) js_FinalizeXMLNamespace, /* GCX_NAMESPACE */
(GCFinalizeOp) js_FinalizeXMLQName, /* GCX_QNAME */
(GCFinalizeOp) js_FinalizeXML, /* GCX_XML */
NULL, /* unused */
NULL, /* GCX_EXTERNAL_STRING */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
#ifdef DEBUG
static const char newborn_external_string[] = "newborn external string";
@ -870,21 +839,6 @@ static const char *gc_typenames[GCX_NTYPES] = {
};
#endif
intN
js_ChangeExternalStringFinalizer(JSStringFinalizeOp oldop,
JSStringFinalizeOp newop)
{
uintN i;
for (i = GCX_EXTERNAL_STRING; i < GCX_NTYPES; i++) {
if (gc_finalizers[i] == (GCFinalizeOp) oldop) {
gc_finalizers[i] = (GCFinalizeOp) newop;
return (intN) i;
}
}
return -1;
}
/* This is compatible with JSDHashEntryStub. */
typedef struct JSGCRootHashEntry {
JSDHashEntryHdr hdr;
@ -2390,11 +2344,10 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind)
uintN i, type;
JSTracer trc;
uint32 thingSize, indexLimit;
JSGCArenaInfo *a, **ap;
JSGCArenaInfo *a, **ap, *emptyArenas;
uint8 flags, *flagp;
JSGCThing *thing, *freeList;
JSGCArenaList *arenaList;
GCFinalizeOp finalizer;
JSBool allClear;
#ifdef JS_THREADSAFE
uint32 requestDebit;
@ -2647,45 +2600,98 @@ restart:
* of the file guarantees that JSString and jsdouble instances are
* allocated from a different list.
*/
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];
a = arenaList->last;
if (!a)
ap = &arenaList->last;
if (!(a = *ap))
continue;
JS_ASSERT(arenaList->lastCount > 0);
arenaList->freeList = NULL;
freeList = NULL;
METER(arenaList->stats.nthings = 0);
METER(arenaList->stats.freelen = 0);
thingSize = arenaList->thingSize;
indexLimit = THINGS_PER_ARENA(thingSize);
JS_ASSERT(arenaList->lastCount > 0);
flagp = THING_FLAGP(a, arenaList->lastCount - 1);
for (;;) {
METER(size_t nfree = 0);
JS_ASSERT(a->prevUntracedPage == 0);
JS_ASSERT(a->untracedThings == 0);
allClear = JS_TRUE;
do {
flags = *flagp;
if (flags & GCF_MARK) {
if (flags & (GCF_MARK | GCF_LOCK)) {
*flagp &= ~GCF_MARK;
} else if (!(flags & (GCF_LOCK | GCF_FINAL))) {
allClear = JS_FALSE;
METER(++arenaList->stats.nthings);
} else if (!(flags & GCF_FINAL)) {
/* Call the finalizer with GCF_FINAL ORed into flags. */
thing = (JSGCThing *) FLAGP_TO_THING(flagp, thingSize);
*flagp = (uint8)(flags | GCF_FINAL);
type = flags & GCF_TYPEMASK;
finalizer = gc_finalizers[type];
if (finalizer) {
thing = (JSGCThing *) FLAGP_TO_THING(flagp, thingSize);
*flagp = (uint8)(flags | GCF_FINAL);
if (type >= GCX_EXTERNAL_STRING)
js_PurgeDeflatedStringCache(rt, (JSString *)thing);
finalizer(cx, thing);
switch (type) {
case GCX_OBJECT:
js_FinalizeObject(cx, (JSObject *) thing);
break;
case GCX_DOUBLE:
/* Do nothing. */
break;
case GCX_FUNCTION:
js_FinalizeFunction(cx, (JSFunction *) thing);
break;
#if JS_HAS_XML_SUPPORT
case GCX_NAMESPACE:
js_FinalizeXMLNamespace(cx, (JSXMLNamespace *) thing);
break;
case GCX_QNAME:
js_FinalizeXMLQName(cx, (JSXMLQName *) thing);
break;
case GCX_XML:
js_FinalizeXML(cx, (JSXML *) thing);
break;
#endif
default:
JS_ASSERT(type == GCX_STRING ||
type - GCX_EXTERNAL_STRING <
GCX_NTYPES - GCX_EXTERNAL_STRING);
js_FinalizeStringRT(rt, (JSString *) thing, type, cx);
break;
}
/* Set flags to GCF_FINAL, signifying that thing is free. */
*flagp = GCF_FINAL;
thing->flagp = flagp;
thing->next = freeList;
freeList = thing;
METER(++nfree);
}
} while (++flagp != THING_FLAGS_END(a));
a = a->prev;
if (!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;
JS_ASSERT(rt->gcBytes >= GC_ARENA_SIZE);
rt->gcBytes -= GC_ARENA_SIZE;
a->prev = emptyArenas;
emptyArenas = a;
} else {
arenaList->freeList = freeList;
ap = &a->prev;
METER(arenaList->stats.freelen += nfree);
METER(arenaList->stats.totalfreelen += nfree);
METER(++arenaList->stats.totalarenas);
}
if (!(a = *ap))
break;
flagp = THING_FLAGP(a, indexLimit - 1);
}
@ -2706,63 +2712,13 @@ restart:
js_SweepScriptFilenames(rt);
/*
* Free phase.
* Free any unused arenas and rebuild the JSGCThing freelist.
* Destroy arenas after we finished the sweeping sofinalizers can safely
* use js_IsAboutToBeFinalized().
*/
for (i = 0; i < GC_NUM_FREELISTS; i++) {
arenaList = &rt->gcArenaList[i];
ap = &arenaList->last;
if (!(a = *ap))
continue;
allClear = JS_TRUE;
arenaList->freeList = NULL;
freeList = NULL;
thingSize = arenaList->thingSize;
indexLimit = THINGS_PER_ARENA(thingSize);
JS_ASSERT(arenaList->lastCount > 0);
flagp = THING_FLAGP(a, arenaList->lastCount - 1);
METER(arenaList->stats.nthings = 0);
METER(arenaList->stats.freelen = 0);
for (;;) {
METER(size_t nfree = 0);
do {
if (*flagp != GCF_FINAL) {
allClear = JS_FALSE;
METER(++arenaList->stats.nthings);
} else {
thing = (JSGCThing *) FLAGP_TO_THING(flagp, thingSize);
thing->flagp = flagp;
thing->next = freeList;
freeList = thing;
METER(++nfree);
}
} while (++flagp != THING_FLAGS_END(a));
if (allClear) {
/*
* Forget just assembled free list head for the arena and
* destroy the arena itself.
*/
freeList = arenaList->freeList;
if (a == arenaList->last)
arenaList->lastCount = indexLimit;
*ap = a->prev;
JS_ASSERT(rt->gcBytes >= GC_ARENA_SIZE);
rt->gcBytes -= GC_ARENA_SIZE;
DestroyGCArena(rt, a);
} else {
allClear = JS_TRUE;
arenaList->freeList = freeList;
ap = &a->prev;
METER(arenaList->stats.freelen += nfree);
METER(arenaList->stats.totalfreelen += nfree);
METER(++arenaList->stats.totalarenas);
}
if (!(a = *ap))
break;
flagp = THING_FLAGP(a, indexLimit - 1);
}
while (emptyArenas) {
a = emptyArenas;
emptyArenas = emptyArenas->prev;
DestroyGCArena(rt, a);
}
if (rt->gcCallback)

View File

@ -91,16 +91,16 @@ js_GetGCStringRuntime(JSString *str);
#define GC_POKE(cx, oldval) ((cx)->runtime->gcPoke = JSVAL_IS_GCTHING(oldval))
#endif
extern intN
js_ChangeExternalStringFinalizer(JSStringFinalizeOp oldop,
JSStringFinalizeOp newop);
extern JSBool
js_InitGC(JSRuntime *rt, uint32 maxbytes);
extern void
js_FinishGC(JSRuntime *rt);
extern intN
js_ChangeExternalStringFinalizer(JSStringFinalizeOp oldop,
JSStringFinalizeOp newop);
extern JSBool
js_AddRoot(JSContext *cx, void *rp, const char *name);

View File

@ -2796,7 +2796,6 @@ js_FinalizeObject(JSContext *cx, JSObject *obj)
/* Drop map and free slots. */
js_DropObjectMap(cx, map, obj);
obj->map = NULL;
FreeSlots(cx, obj);
}

View File

@ -2619,19 +2619,38 @@ js_PurgeDeflatedStringCache(JSRuntime *rt, JSString *str)
JS_RELEASE_LOCK(rt->deflatedStringCacheLock);
}
void
js_FinalizeString(JSContext *cx, JSString *str)
static JSStringFinalizeOp str_finalizers[GCX_NTYPES - GCX_EXTERNAL_STRING] = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
intN
js_ChangeExternalStringFinalizer(JSStringFinalizeOp oldop,
JSStringFinalizeOp newop)
{
js_FinalizeStringRT(cx->runtime, str);
uintN i;
for (i = 0; i != JS_ARRAY_LENGTH(str_finalizers); i++) {
if (str_finalizers[i] == oldop) {
str_finalizers[i] = newop;
return (intN) i + GCX_EXTERNAL_STRING;
}
}
return -1;
}
/*
* 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)
js_FinalizeStringRT(JSRuntime *rt, JSString *str, uintN gctype, JSContext *cx)
{
JSBool valid;
JSStringFinalizeOp finalizer;
JS_RUNTIME_UNMETER(rt, liveStrings);
if (JSSTRING_IS_DEPENDENT(str)) {
JS_ASSERT(gctype == GCX_STRING);
/* If JSSTRFLAG_DEPENDENT is set, this string must be valid. */
JS_ASSERT(JSSTRDEP_BASE(str));
JS_RUNTIME_UNMETER(rt, liveDependentStrings);
@ -2642,17 +2661,26 @@ js_FinalizeStringRT(JSRuntime *rt, JSString *str)
if (valid) {
if (IN_UNIT_STRING_SPACE_RT(rt, str->u.chars)) {
JS_ASSERT(rt->unitStrings[*str->u.chars] == str);
JS_ASSERT(gctype == GCX_STRING);
rt->unitStrings[*str->u.chars] = NULL;
} else {
} else if (gctype == GCX_STRING) {
free(str->u.chars);
} else {
JS_ASSERT(gctype - GCX_EXTERNAL_STRING <
JS_ARRAY_LENGTH(str_finalizers));
finalizer = str_finalizers[gctype - GCX_EXTERNAL_STRING];
if (finalizer) {
/*
* Assume that the finalizer for the permanently interned
* string knows how to deal with null context.
*/
finalizer(cx, str);
}
}
}
}
if (valid)
js_PurgeDeflatedStringCache(rt, str);
#ifdef DEBUG
memset(str, JS_FREE_PATTERN, sizeof *str);
#endif
}
JS_FRIEND_API(const char *)

View File

@ -406,12 +406,13 @@ js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n);
extern JSString *
js_NewStringCopyZ(JSContext *cx, const jschar *s);
/* Free the chars held by str when it is finalized by the GC. */
/*
* Free the chars held by str when it is finalized by the GC.
*
* This function always needs rt but can live with null cx.
*/
extern void
js_FinalizeString(JSContext *cx, JSString *str);
extern void
js_FinalizeStringRT(JSRuntime *rt, JSString *str);
js_FinalizeStringRT(JSRuntime *rt, JSString *str, uintN gctype, JSContext *cx);
/*
* Convert a value to a printable C string.