diff --git a/js/src/jsapi.c b/js/src/jsapi.c index 169448a62e5f..9f1e14ce485d 100644 --- a/js/src/jsapi.c +++ b/js/src/jsapi.c @@ -691,6 +691,7 @@ JS_DestroyRuntime(JSRuntime *rt) #endif #endif + js_FinishAtomState(&rt->atomState); js_FinishGC(rt); #ifdef JS_THREADSAFE if (rt->gcLock) @@ -3392,7 +3393,7 @@ JS_InternString(JSContext *cx, const char *s) JSAtom *atom; CHECK_REQUEST(cx); - atom = js_Atomize(cx, s, strlen(s), ATOM_PINNED); + atom = js_Atomize(cx, s, strlen(s), ATOM_INTERNED); if (!atom) return NULL; return ATOM_TO_STRING(atom); @@ -3427,7 +3428,7 @@ JS_InternUCStringN(JSContext *cx, const jschar *s, size_t length) JSAtom *atom; CHECK_REQUEST(cx); - atom = js_AtomizeChars(cx, s, length, ATOM_PINNED); + atom = js_AtomizeChars(cx, s, length, ATOM_INTERNED); if (!atom) return NULL; return ATOM_TO_STRING(atom); diff --git a/js/src/jsatom.c b/js/src/jsatom.c index 98b2706efabe..68e4001ccac6 100644 --- a/js/src/jsatom.c +++ b/js/src/jsatom.c @@ -293,9 +293,12 @@ bad: return JS_FALSE; } +/* NB: cx unused; js_FinishAtomState calls us with null cx. */ void js_FreeAtomState(JSContext *cx, JSAtomState *state) { + if (state->interns != 0) + return; state->runtime = NULL; JS_HashTableDestroy(state->table); state->table = NULL; @@ -305,6 +308,29 @@ js_FreeAtomState(JSContext *cx, JSAtomState *state) #endif } +JS_STATIC_DLL_CALLBACK(intN) +js_atom_uninterner(JSHashEntry *he, intN i, void *arg) +{ + JSAtom *atom; + + atom = (JSAtom *)he; + JS_ASSERT(atom->flags & ATOM_INTERNED); + JS_ASSERT(ATOM_IS_STRING(atom)); + js_FinalizeStringRT(arg, ATOM_TO_STRING(atom)); + return HT_ENUMERATE_NEXT; +} + +void +js_FinishAtomState(JSAtomState *state) +{ + if (state->interns == 0) + return; + JS_HashTableEnumerateEntries(state->table, js_atom_uninterner, + state->runtime); + state->interns = 0; + js_FreeAtomState(NULL, state); +} + typedef struct MarkArgs { uintN gcflags; JSGCThingMarker mark; @@ -320,7 +346,8 @@ js_atom_marker(JSHashEntry *he, intN i, void *arg) atom = (JSAtom *)he; args = (MarkArgs *) arg; - if ((atom->flags & ATOM_PINNED) || (args->gcflags & GC_KEEP_ATOMS)) { + if ((atom->flags & (ATOM_PINNED | ATOM_INTERNED)) || + (args->gcflags & GC_KEEP_ATOMS)) { atom->flags |= ATOM_MARK; key = ATOM_KEY(atom); if (JSVAL_IS_GCTHING(key)) { @@ -352,7 +379,7 @@ js_atom_sweeper(JSHashEntry *he, intN i, void *arg) atom->flags &= ~ATOM_MARK; return HT_ENUMERATE_NEXT; } - JS_ASSERT((atom->flags & ATOM_PINNED) == 0); + JS_ASSERT((atom->flags & (ATOM_PINNED | ATOM_INTERNED)) == 0); atom->entry.key = NULL; atom->flags = 0; return HT_ENUMERATE_REMOVE; @@ -548,7 +575,9 @@ js_AtomizeString(JSContext *cx, JSString *str, uintN flags) } atom = (JSAtom *)he; - atom->flags |= flags & ATOM_PINNED; + atom->flags |= flags & (ATOM_PINNED | ATOM_INTERNED); + if (flags & ATOM_INTERNED) + state->interns++; out: JS_UNLOCK(&state->lock,cx); return atom; diff --git a/js/src/jsatom.h b/js/src/jsatom.h index 024ea3e47333..abf5cfff0fc1 100644 --- a/js/src/jsatom.h +++ b/js/src/jsatom.h @@ -50,14 +50,15 @@ JS_BEGIN_EXTERN_C -#define ATOM_NOCOPY 0x01 /* don't copy atom string bytes */ -#define ATOM_TMPSTR 0x02 /* internal, to avoid extra string */ +#define ATOM_PINNED 0x01 /* atom is pinned against GC */ +#define ATOM_INTERNED 0x02 /* pinned variant for JS_Intern* API */ #define ATOM_MARK 0x04 /* atom is reachable via GC */ -#define ATOM_PINNED 0x08 /* atom is pinned against GC */ +#define ATOM_NOCOPY 0x40 /* don't copy atom string bytes */ +#define ATOM_TMPSTR 0x80 /* internal, to avoid extra string */ struct JSAtom { JSHashEntry entry; /* key is jsval, value keyword info */ - uint8 flags; /* flags, PINNED and/or MARK for now */ + uint8 flags; /* pinned, interned, and mark flags */ int8 kwindex; /* keyword index, -1 if not keyword */ jsatomid number; /* atom serial number and hash code */ }; @@ -134,6 +135,7 @@ struct JSAtomState { JSRuntime *runtime; /* runtime that owns us */ JSHashTable *table; /* hash table containing all atoms */ jsatomid number; /* one beyond greatest atom number */ + jsatomid interns; /* number of interned strings */ /* Type names and value literals. */ JSAtom *typeAtoms[JSTYPE_LIMIT]; @@ -232,11 +234,27 @@ extern JSBool js_InitAtomState(JSContext *cx, JSAtomState *state); /* - * Free and clear atom state. + * Free and clear atom state (except for any interned string atoms). */ extern void js_FreeAtomState(JSContext *cx, JSAtomState *state); +/* + * Interned strings are atoms that live until state's runtime is destroyed. + * This function frees all interned string atoms, and then frees and clears + * state's members (just as js_FreeAtomState does), unless there aren't any + * interned strings in state -- in which case state must be "free" already. + * + * NB: js_FreeAtomState is called for each "last" context being destroyed in + * a runtime, where there may yet be another context created in the runtime; + * whereas js_FinishAtomState is called from JS_DestroyRuntime, when we know + * that no more contexts will be created. Thus we minimize garbage during + * context-free episodes on a runtime, while preserving atoms created by the + * JS_Intern*String APIs for the life of the runtime. + */ +extern void +js_FinishAtomState(JSAtomState *state); + /* * Atom garbage collection hooks. */ diff --git a/js/src/jsstr.c b/js/src/jsstr.c index cfc5e92f5e6a..e6cfc5e5b477 100644 --- a/js/src/jsstr.c +++ b/js/src/jsstr.c @@ -1998,6 +1998,7 @@ tagify(JSContext *cx, JSObject *obj, jsval *argv, tagbuf[j++] = (jschar)end[i]; tagbuf[j++] = '>'; JS_ASSERT(j == taglen); + tagbuf[j] = 0; str = js_NewString(cx, tagbuf, taglen, 0); if (!str) { @@ -2377,13 +2378,19 @@ js_hash_string_pointer(const void *key) void js_FinalizeString(JSContext *cx, JSString *str) +{ + js_FinalizeStringRT(cx->runtime, str); +} + +void +js_FinalizeStringRT(JSRuntime *rt, JSString *str) { JSHashNumber hash; JSHashEntry *he, **hep; - JS_RUNTIME_UNMETER(cx->runtime, liveStrings); + JS_RUNTIME_UNMETER(rt, liveStrings); if (str->chars) { - JS_free(cx, str->chars); + free(str->chars); str->chars = NULL; if (deflated_string_cache) { hash = js_hash_string_pointer(str); @@ -2391,7 +2398,7 @@ js_FinalizeString(JSContext *cx, JSString *str) hep = JS_HashTableRawLookup(deflated_string_cache, hash, str); he = *hep; if (he) { - JS_free(cx, he->value); + free(he->value); JS_HashTableRawRemove(deflated_string_cache, hep, he); deflated_string_cache_bytes -= str->length; } diff --git a/js/src/jsstr.h b/js/src/jsstr.h index 107870bb2b2b..de6c32e32fd8 100644 --- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -219,6 +219,9 @@ js_NewStringCopyZ(JSContext *cx, const jschar *s, uintN gcflag); extern void js_FinalizeString(JSContext *cx, JSString *str); +extern void +js_FinalizeStringRT(JSRuntime *rt, JSString *str); + /* Wrap a string value in a String object. */ extern JSObject * js_StringToObject(JSContext *cx, JSString *str);