mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-18 15:55:36 +00:00
Add scoped local root support, plus a few cleanups from the huge e4x patch (40757, r=shaver).
This commit is contained in:
parent
da25ffb6d6
commit
5a95353303
@ -97,7 +97,7 @@ MSG_DEF(JSMSG_TOO_MANY_LITERALS, 14, 0, JSEXN_INTERNALERR, "too many liter
|
||||
MSG_DEF(JSMSG_CANT_WATCH, 15, 1, JSEXN_NONE, "can't watch non-native objects of class {0}")
|
||||
MSG_DEF(JSMSG_STACK_UNDERFLOW, 16, 2, JSEXN_INTERNALERR, "internal error compiling {0}: stack underflow at pc {1}")
|
||||
MSG_DEF(JSMSG_NEED_DIET, 17, 1, JSEXN_INTERNALERR, "{0} too large")
|
||||
MSG_DEF(JSMSG_UNUSED18, 18, 0, JSEXN_NONE, "<Error #18 is currently unused>")
|
||||
MSG_DEF(JSMSG_TOO_MANY_LOCAL_ROOTS, 18, 0, JSEXN_ERR, "out of local root space")
|
||||
MSG_DEF(JSMSG_READ_ONLY, 19, 1, JSEXN_ERR, "{0} is read-only")
|
||||
MSG_DEF(JSMSG_BAD_FORMAL, 20, 0, JSEXN_SYNTAXERR, "malformed formal parameter")
|
||||
MSG_DEF(JSMSG_SAME_FORMAL, 21, 1, JSEXN_NONE, "duplicate formal argument {0}")
|
||||
@ -225,7 +225,7 @@ MSG_DEF(JSMSG_BAD_REGEXP_FLAG, 142, 0, JSEXN_SYNTAXERR, "invalid flag aft
|
||||
MSG_DEF(JSMSG_SHARPVAR_TOO_BIG, 143, 0, JSEXN_SYNTAXERR, "overlarge sharp variable number")
|
||||
MSG_DEF(JSMSG_ILLEGAL_CHARACTER, 144, 0, JSEXN_SYNTAXERR, "illegal character")
|
||||
MSG_DEF(JSMSG_BAD_OCTAL, 145, 1, JSEXN_NONE, "{0} is not a legal ECMA-262 octal constant")
|
||||
MSG_DEF(JSMSG_BAD_INDIRECT_CALL, 146, 1, JSEXN_EVALERR, "function {0} must be called directly, and not by way of a function of another name.")
|
||||
MSG_DEF(JSMSG_BAD_INDIRECT_CALL, 146, 1, JSEXN_EVALERR, "function {0} must be called directly, and not by way of a function of another name")
|
||||
MSG_DEF(JSMSG_UNCAUGHT_EXCEPTION, 147, 1, JSEXN_NONE, "uncaught exception: {0}")
|
||||
MSG_DEF(JSMSG_INVALID_BACKREF, 148, 0, JSEXN_SYNTAXERR, "non-octal digit in an escape sequence that doesn't match a back-reference")
|
||||
MSG_DEF(JSMSG_BAD_BACKREF, 149, 0, JSEXN_SYNTAXERR, "back-reference exceeds number of capturing parentheses")
|
||||
|
@ -1561,6 +1561,27 @@ JS_ClearNewbornRoots(JSContext *cx)
|
||||
cx->lastAtom = NULL;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_EnterLocalRootScope(JSContext *cx)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
return js_EnterLocalRootScope(cx);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS_LeaveLocalRootScope(JSContext *cx)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
js_LeaveLocalRootScope(cx);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS_ForgetLocalRoot(JSContext *cx, void *thing)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
js_ForgetLocalRoot(cx, (jsval) thing);
|
||||
}
|
||||
|
||||
#include "jshash.h" /* Added by JSIFY */
|
||||
|
||||
#ifdef DEBUG
|
||||
@ -3288,26 +3309,7 @@ JS_NewScriptObject(JSContext *cx, JSScript *script)
|
||||
{
|
||||
JSObject *obj;
|
||||
|
||||
/*
|
||||
* We use a dummy stack frame to protect the script from a GC caused
|
||||
* by debugger-hook execution.
|
||||
*
|
||||
* XXX We really need a way to manage local roots and such more
|
||||
* XXX automatically, at which point we can remove this one-off hack
|
||||
* XXX and others within the engine. See bug 40757 for discussion.
|
||||
*/
|
||||
JSStackFrame dummy;
|
||||
|
||||
CHECK_REQUEST(cx);
|
||||
|
||||
memset(&dummy, 0, sizeof dummy);
|
||||
dummy.down = cx->fp;
|
||||
dummy.script = script;
|
||||
cx->fp = &dummy;
|
||||
|
||||
obj = js_NewObject(cx, &js_ScriptClass, NULL, NULL);
|
||||
|
||||
cx->fp = dummy.down;
|
||||
if (!obj)
|
||||
return NULL;
|
||||
|
||||
|
@ -567,13 +567,67 @@ JS_RemoveRootRT(JSRuntime *rt, void *rp);
|
||||
* JS_GC entry point clears them for the context on which GC is being forced.
|
||||
* Embeddings may need to do likewise for all contexts.
|
||||
*
|
||||
* XXXbe See bug 40757 (http://bugzilla.mozilla.org/show_bug.cgi?id=40757),
|
||||
* which proposes switching (with an #ifdef, alas, if we want to maintain API
|
||||
* compatibility) to a JNI-like extensible local root frame stack model.
|
||||
* See the scoped local root API immediately below for a better way to manage
|
||||
* newborns in cases where native hooks (functions, getters, setters, etc.)
|
||||
* create many GC-things, potentially without connecting them to predefined
|
||||
* local roots such as *rval or argv[i] in an active native function. Using
|
||||
* JS_EnterLocalRootScope disables updating of the context's per-gc-thing-type
|
||||
* newborn roots, until control flow unwinds and leaves the outermost nesting
|
||||
* local root scope.
|
||||
*/
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_ClearNewbornRoots(JSContext *cx);
|
||||
|
||||
/*
|
||||
* Scoped local root management allows native functions, getter/setters, etc.
|
||||
* to avoid worrying about the newborn root pigeon-holes, overloading local
|
||||
* roots allocated in argv and *rval, or ending up having to call JS_Add*Root
|
||||
* and JS_RemoveRoot to manage global roots temporarily.
|
||||
*
|
||||
* Instead, calling JS_EnterLocalRootScope and JS_LeaveLocalRootScope around
|
||||
* the body of the native hook causes the engine to allocate a local root for
|
||||
* each newborn created in between the two API calls, using a local root stack
|
||||
* associated with cx. For example:
|
||||
*
|
||||
* JSBool
|
||||
* my_GetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
||||
* {
|
||||
* JSBool ok;
|
||||
*
|
||||
* if (!JS_EnterLocalRootScope(cx))
|
||||
* return JS_FALSE;
|
||||
* ok = my_GetPropertyBody(cx, obj, id, vp);
|
||||
* JS_LeaveLocalRootScope(cx);
|
||||
* return ok;
|
||||
* }
|
||||
*
|
||||
* NB: JS_LeaveLocalRootScope must be called once for every prior successful
|
||||
* call to JS_EnterLocalRootScope. If JS_EnterLocalRootScope fails, you must
|
||||
* not make the matching JS_LeaveLocalRootScope call.
|
||||
*
|
||||
* In case a native hook allocates many objects or other GC-things, but the
|
||||
* native protects some of those GC-things by storing them as property values
|
||||
* in an object that is itself protected, the hook can call JS_ForgetLocalRoot
|
||||
* to free the local root automatically pushed for the now-protected GC-thing.
|
||||
*
|
||||
* JS_ForgetLocalRoot works on any GC-thing allocated in the current local
|
||||
* root scope, but it's more time-efficient when called on references to more
|
||||
* recently created GC-things. Calling it successively on other than the most
|
||||
* recently allocated GC-thing will tend to average the time inefficiency, and
|
||||
* may risk O(n^2) growth rate, but in any event, you shouldn't allocate too
|
||||
* many local roots if you can root as you go (build a tree of objects from
|
||||
* the top down, forgetting each latest-allocated GC-thing immediately upon
|
||||
* linking it to its parent).
|
||||
*/
|
||||
extern JS_PUBLIC_API(JSBool)
|
||||
JS_EnterLocalRootScope(JSContext *cx);
|
||||
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_LeaveLocalRootScope(JSContext *cx);
|
||||
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_ForgetLocalRoot(JSContext *cx, void *thing);
|
||||
|
||||
#ifdef DEBUG
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_DumpNamedRoots(JSRuntime *rt,
|
||||
|
324
js/src/jscntxt.c
324
js/src/jscntxt.c
@ -162,6 +162,8 @@ js_DestroyContext(JSContext *cx, JSGCMode gcmode)
|
||||
JSRuntime *rt;
|
||||
JSBool last;
|
||||
JSArgumentFormatMap *map;
|
||||
JSLocalRootStack *lrs;
|
||||
JSLocalRootChunk *lrc;
|
||||
|
||||
rt = cx->runtime;
|
||||
|
||||
@ -275,6 +277,15 @@ js_DestroyContext(JSContext *cx, JSGCMode gcmode)
|
||||
cx->resolvingTable = NULL;
|
||||
}
|
||||
|
||||
lrs = cx->localRootStack;
|
||||
if (lrs) {
|
||||
while ((lrc = lrs->topChunk) != &lrs->firstChunk) {
|
||||
lrs->topChunk = lrc->down;
|
||||
JS_free(cx, lrc);
|
||||
}
|
||||
JS_free(cx, lrs);
|
||||
}
|
||||
|
||||
/* Finally, free cx itself. */
|
||||
free(cx);
|
||||
}
|
||||
@ -310,6 +321,319 @@ js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp)
|
||||
return cx;
|
||||
}
|
||||
|
||||
JS_STATIC_DLL_CALLBACK(const void *)
|
||||
resolving_GetKey(JSDHashTable *table, JSDHashEntryHdr *hdr)
|
||||
{
|
||||
JSResolvingEntry *entry = (JSResolvingEntry *)hdr;
|
||||
|
||||
return &entry->key;
|
||||
}
|
||||
|
||||
JS_STATIC_DLL_CALLBACK(JSDHashNumber)
|
||||
resolving_HashKey(JSDHashTable *table, const void *ptr)
|
||||
{
|
||||
const JSResolvingKey *key = (const JSResolvingKey *)ptr;
|
||||
|
||||
return ((JSDHashNumber)key->obj >> JSVAL_TAGBITS) ^ key->id;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
resolving_MatchEntry(JSDHashTable *table,
|
||||
const JSDHashEntryHdr *hdr,
|
||||
const void *ptr)
|
||||
{
|
||||
const JSResolvingEntry *entry = (const JSResolvingEntry *)hdr;
|
||||
const JSResolvingKey *key = (const JSResolvingKey *)ptr;
|
||||
|
||||
return entry->key.obj == key->obj && entry->key.id == key->id;
|
||||
}
|
||||
|
||||
static const JSDHashTableOps resolving_dhash_ops = {
|
||||
JS_DHashAllocTable,
|
||||
JS_DHashFreeTable,
|
||||
resolving_GetKey,
|
||||
resolving_HashKey,
|
||||
resolving_MatchEntry,
|
||||
JS_DHashMoveEntryStub,
|
||||
JS_DHashClearEntryStub,
|
||||
JS_DHashFinalizeStub,
|
||||
NULL
|
||||
};
|
||||
|
||||
JSBool
|
||||
js_StartResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
|
||||
JSResolvingEntry **entryp)
|
||||
{
|
||||
JSDHashTable *table;
|
||||
JSResolvingEntry *entry;
|
||||
|
||||
table = cx->resolvingTable;
|
||||
if (!table) {
|
||||
table = JS_NewDHashTable(&resolving_dhash_ops, NULL,
|
||||
sizeof(JSResolvingEntry),
|
||||
JS_DHASH_MIN_SIZE);
|
||||
if (!table)
|
||||
goto outofmem;
|
||||
cx->resolvingTable = table;
|
||||
}
|
||||
|
||||
entry = (JSResolvingEntry *)
|
||||
JS_DHashTableOperate(table, key, JS_DHASH_ADD);
|
||||
if (!entry)
|
||||
goto outofmem;
|
||||
|
||||
if (entry->flags & flag) {
|
||||
/* An entry for (key, flag) exists already -- dampen recursion. */
|
||||
entry = NULL;
|
||||
} else {
|
||||
/* Fill in key if we were the first to add entry, then set flag. */
|
||||
if (!entry->key.obj)
|
||||
entry->key = *key;
|
||||
entry->flags |= flag;
|
||||
}
|
||||
*entryp = entry;
|
||||
return JS_TRUE;
|
||||
|
||||
outofmem:
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
js_StopResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
|
||||
JSResolvingEntry *entry, uint32 generation)
|
||||
{
|
||||
JSDHashTable *table;
|
||||
|
||||
/*
|
||||
* Clear flag from entry->flags and return early if other flags remain.
|
||||
* We must take care to re-lookup entry if the table has changed since
|
||||
* it was found by js_StartResolving.
|
||||
*/
|
||||
table = cx->resolvingTable;
|
||||
if (!entry || table->generation != generation) {
|
||||
entry = (JSResolvingEntry *)
|
||||
JS_DHashTableOperate(table, key, JS_DHASH_LOOKUP);
|
||||
}
|
||||
JS_ASSERT(JS_DHASH_ENTRY_IS_BUSY(&entry->hdr));
|
||||
entry->flags &= ~flag;
|
||||
if (entry->flags)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Do a raw remove only if fewer entries were removed than would cause
|
||||
* alpha to be less than .5 (alpha is at most .75). Otherwise, we just
|
||||
* call JS_DHashTableOperate to re-lookup the key and remove its entry,
|
||||
* compressing or shrinking the table as needed.
|
||||
*/
|
||||
if (table->removedCount < JS_DHASH_TABLE_SIZE(table) >> 2)
|
||||
JS_DHashTableRawRemove(table, &entry->hdr);
|
||||
else
|
||||
JS_DHashTableOperate(table, key, JS_DHASH_REMOVE);
|
||||
}
|
||||
|
||||
JSBool
|
||||
js_EnterLocalRootScope(JSContext *cx)
|
||||
{
|
||||
JSLocalRootStack *lrs;
|
||||
int mark;
|
||||
|
||||
lrs = cx->localRootStack;
|
||||
if (!lrs) {
|
||||
lrs = (JSLocalRootStack *) JS_malloc(cx, sizeof *lrs);
|
||||
if (!lrs)
|
||||
return JS_FALSE;
|
||||
lrs->scopeMark = JSLRS_NULL_MARK;
|
||||
lrs->rootCount = 0;
|
||||
lrs->topChunk = &lrs->firstChunk;
|
||||
lrs->firstChunk.down = NULL;
|
||||
cx->localRootStack = lrs;
|
||||
}
|
||||
|
||||
/* Push lrs->scopeMark to save it for restore when leaving. */
|
||||
mark = js_PushLocalRoot(cx, lrs, INT_TO_JSVAL(lrs->scopeMark));
|
||||
if (mark < 0)
|
||||
return JS_FALSE;
|
||||
lrs->scopeMark = (uint16) mark;
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
js_LeaveLocalRootScope(JSContext *cx)
|
||||
{
|
||||
JSLocalRootStack *lrs;
|
||||
unsigned mark, m, n;
|
||||
JSLocalRootChunk *lrc;
|
||||
|
||||
/* Defend against buggy native callers. */
|
||||
lrs = cx->localRootStack;
|
||||
JS_ASSERT(lrs && lrs->rootCount != 0);
|
||||
if (!lrs || lrs->rootCount == 0)
|
||||
return;
|
||||
|
||||
mark = lrs->scopeMark;
|
||||
JS_ASSERT(mark != JSLRS_NULL_MARK);
|
||||
if (mark == JSLRS_NULL_MARK)
|
||||
return;
|
||||
|
||||
/* Free any chunks being popped by this leave operation. */
|
||||
m = mark >> JSLRS_CHUNK_SHIFT;
|
||||
n = (lrs->rootCount - 1) >> JSLRS_CHUNK_SHIFT;
|
||||
while (n > m) {
|
||||
lrc = lrs->topChunk;
|
||||
JS_ASSERT(lrc != &lrs->firstChunk);
|
||||
lrs->topChunk = lrc->down;
|
||||
JS_free(cx, lrc);
|
||||
--n;
|
||||
}
|
||||
|
||||
/* Pop the scope, restoring lrs->scopeMark. */
|
||||
lrc = lrs->topChunk;
|
||||
m = mark & JSLRS_CHUNK_MASK;
|
||||
lrs->scopeMark = JSVAL_TO_INT(lrc->roots[m]);
|
||||
lrc->roots[m] = JSVAL_NULL;
|
||||
lrs->rootCount = (uint16) mark;
|
||||
|
||||
/*
|
||||
* Free the stack eagerly, risking malloc churn. The alternative would
|
||||
* require an lrs->entryCount member, maintained by Enter and Leave, and
|
||||
* tested by the GC in addition to the cx->localRootStack non-null test.
|
||||
*
|
||||
* That approach would risk hoarding 264 bytes (net) per context. Right
|
||||
* now it seems better to give fresh (dirty in CPU write-back cache, and
|
||||
* the data is no longer needed) memory back to the malloc heap.
|
||||
*/
|
||||
if (mark == 0) {
|
||||
cx->localRootStack = NULL;
|
||||
JS_free(cx, lrs);
|
||||
} else if (m == 0) {
|
||||
lrs->topChunk = lrc->down;
|
||||
JS_free(cx, lrc);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
js_ForgetLocalRoot(JSContext *cx, jsval v)
|
||||
{
|
||||
JSLocalRootStack *lrs;
|
||||
unsigned i, j, m, n, mark;
|
||||
JSLocalRootChunk *lrc, *lrc2;
|
||||
jsval top;
|
||||
|
||||
lrs = cx->localRootStack;
|
||||
JS_ASSERT(lrs && lrs->rootCount);
|
||||
if (!lrs || lrs->rootCount == 0)
|
||||
return;
|
||||
|
||||
/* Prepare to pop the top-most value from the stack. */
|
||||
n = lrs->rootCount - 1;
|
||||
m = n & JSLRS_CHUNK_MASK;
|
||||
lrc = lrs->topChunk;
|
||||
top = lrc->roots[m];
|
||||
|
||||
/* Be paranoid about calls on an empty scope. */
|
||||
mark = lrs->scopeMark;
|
||||
JS_ASSERT(mark < n);
|
||||
if (mark >= n)
|
||||
return;
|
||||
|
||||
/* If v was not the last root pushed in the top scope, find it. */
|
||||
if (top != v) {
|
||||
/* Search downward in case v was recently pushed. */
|
||||
i = n;
|
||||
j = m;
|
||||
lrc2 = lrc;
|
||||
while (--i > mark) {
|
||||
if (j == 0)
|
||||
lrc2 = lrc2->down;
|
||||
j = i & JSLRS_CHUNK_MASK;
|
||||
if (lrc2->roots[j] == v)
|
||||
break;
|
||||
}
|
||||
|
||||
/* If we didn't find v in this scope, assert and bail out. */
|
||||
JS_ASSERT(i != mark);
|
||||
if (i == mark)
|
||||
return;
|
||||
|
||||
/* Swap top and v so common tail code can pop v. */
|
||||
lrc2->roots[j] = top;
|
||||
}
|
||||
|
||||
/* Pop the last value from the stack. */
|
||||
lrc->roots[m] = JSVAL_NULL;
|
||||
lrs->rootCount = n;
|
||||
if (m == 0) {
|
||||
JS_ASSERT(n != 0);
|
||||
JS_ASSERT(lrc != &lrs->firstChunk);
|
||||
lrs->topChunk = lrc->down;
|
||||
JS_free(cx, lrc);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
js_PushLocalRoot(JSContext *cx, JSLocalRootStack *lrs, jsval v)
|
||||
{
|
||||
unsigned n, m;
|
||||
JSLocalRootChunk *lrc;
|
||||
|
||||
n = lrs->rootCount;
|
||||
m = n & JSLRS_CHUNK_MASK;
|
||||
if (n == 0 || m != 0) {
|
||||
/*
|
||||
* At start of first chunk, or not at start of a non-first top chunk.
|
||||
* Check for lrs->rootCount overflow.
|
||||
*/
|
||||
if ((uint16)(n + 1) == 0) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
||||
JSMSG_TOO_MANY_LOCAL_ROOTS);
|
||||
return -1;
|
||||
}
|
||||
lrc = lrs->topChunk;
|
||||
JS_ASSERT(n != 0 || lrc == &lrs->firstChunk);
|
||||
} else {
|
||||
/*
|
||||
* After lrs->firstChunk, trying to index at a power-of-two chunk
|
||||
* boundary: need a new chunk.
|
||||
*/
|
||||
lrc = (JSLocalRootChunk *) JS_malloc(cx, sizeof *lrc);
|
||||
if (!lrc)
|
||||
return -1;
|
||||
lrc->down = lrs->topChunk;
|
||||
lrs->topChunk = lrc;
|
||||
}
|
||||
lrs->rootCount = n + 1;
|
||||
lrc->roots[m] = v;
|
||||
return (int) m;
|
||||
}
|
||||
|
||||
void
|
||||
js_MarkLocalRoots(JSContext *cx, JSLocalRootStack *lrs)
|
||||
{
|
||||
unsigned n, m, mark;
|
||||
JSLocalRootChunk *lrc;
|
||||
|
||||
n = lrs->rootCount;
|
||||
if (n == 0)
|
||||
return;
|
||||
|
||||
mark = lrs->scopeMark;
|
||||
lrc = lrs->topChunk;
|
||||
while (--n > mark) {
|
||||
#ifdef GC_MARK_DEBUG
|
||||
char name[22];
|
||||
JS_snprintf(name, sizeof name, "<local root %u>", n);
|
||||
#else
|
||||
const char *name = NULL;
|
||||
#endif
|
||||
m = n & JSLRS_CHUNK_MASK;
|
||||
JS_ASSERT(JSVAL_IS_GCTHING(lrc->roots[m]));
|
||||
JS_MarkGCThing(cx, JSVAL_TO_GCTHING(lrc->roots[m]), name, NULL);
|
||||
if (m == 0)
|
||||
lrc = lrc->down;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ReportError(JSContext *cx, const char *message, JSErrorReport *reportp)
|
||||
{
|
||||
|
@ -308,6 +308,26 @@ typedef struct JSResolvingEntry {
|
||||
#define JSRESFLAG_LOOKUP 0x1 /* resolving id from lookup */
|
||||
#define JSRESFLAG_WATCH 0x2 /* resolving id from watch */
|
||||
|
||||
typedef struct JSLocalRootChunk JSLocalRootChunk;
|
||||
|
||||
#define JSLRS_CHUNK_SHIFT 6
|
||||
#define JSLRS_CHUNK_SIZE JS_BIT(JSLRS_CHUNK_SHIFT)
|
||||
#define JSLRS_CHUNK_MASK JS_BITMASK(JSLRS_CHUNK_SHIFT)
|
||||
|
||||
struct JSLocalRootChunk {
|
||||
jsval roots[JSLRS_CHUNK_SIZE];
|
||||
JSLocalRootChunk *down;
|
||||
};
|
||||
|
||||
typedef struct JSLocalRootStack {
|
||||
uint16 scopeMark;
|
||||
uint16 rootCount;
|
||||
JSLocalRootChunk *topChunk;
|
||||
JSLocalRootChunk firstChunk;
|
||||
} JSLocalRootStack;
|
||||
|
||||
#define JSLRS_NULL_MARK ((uint16) -1)
|
||||
|
||||
struct JSContext {
|
||||
JSCList links;
|
||||
|
||||
@ -417,6 +437,9 @@ struct JSContext {
|
||||
|
||||
/* Optional hook to find principals for an object being accessed on cx. */
|
||||
JSObjectPrincipalsFinder findObjectPrincipals;
|
||||
|
||||
/* Optional stack of scoped local GC roots. */
|
||||
JSLocalRootStack *localRootStack;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -448,6 +471,35 @@ js_ValidContextPointer(JSRuntime *rt, JSContext *cx);
|
||||
extern JSContext *
|
||||
js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp);
|
||||
|
||||
/*
|
||||
* JSClass.resolve and watchpoint recursion damping machinery.
|
||||
*/
|
||||
extern JSBool
|
||||
js_StartResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
|
||||
JSResolvingEntry **entryp);
|
||||
|
||||
extern void
|
||||
js_StopResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
|
||||
JSResolvingEntry *entry, uint32 generation);
|
||||
|
||||
/*
|
||||
* Local root set management.
|
||||
*/
|
||||
extern JSBool
|
||||
js_EnterLocalRootScope(JSContext *cx);
|
||||
|
||||
extern void
|
||||
js_LeaveLocalRootScope(JSContext *cx);
|
||||
|
||||
extern void
|
||||
js_ForgetLocalRoot(JSContext *cx, jsval v);
|
||||
|
||||
extern int
|
||||
js_PushLocalRoot(JSContext *cx, JSLocalRootStack *lrs, jsval v);
|
||||
|
||||
extern void
|
||||
js_MarkLocalRoots(JSContext *cx, JSLocalRootStack *lrs);
|
||||
|
||||
/*
|
||||
* Report an exception, which is currently realized as a printf-style format
|
||||
* string and its arguments.
|
||||
|
@ -458,6 +458,7 @@ js_AllocGCThing(JSContext *cx, uintN flags)
|
||||
JSRuntime *rt;
|
||||
JSGCThing *thing;
|
||||
uint8 *flagp;
|
||||
JSLocalRootStack *lrs;
|
||||
|
||||
#ifdef TOO_MUCH_GC
|
||||
js_GC(cx, GC_KEEP_ATOMS);
|
||||
@ -531,18 +532,35 @@ retry:
|
||||
METER(rt->gcStats.retry++);
|
||||
goto retry;
|
||||
}
|
||||
METER(rt->gcStats.fail++);
|
||||
JS_UNLOCK_GC(rt);
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Find the flags pointer given thing's address. */
|
||||
flagp = js_GetGCThingFlags(thing);
|
||||
}
|
||||
|
||||
lrs = cx->localRootStack;
|
||||
if (lrs) {
|
||||
/*
|
||||
* If we're in a local root scope, don't set cx->newborn[type] at all,
|
||||
* to avoid entraining garbage from it for an unbounded amount of time
|
||||
* on this context. A caller will leave the local root scope and pop
|
||||
* this reference, allowing thing to be GC'd if it has no other refs.
|
||||
* See JS_EnterLocalRootScope and related APIs.
|
||||
*/
|
||||
if (js_PushLocalRoot(cx, lrs, (jsval) thing) < 0)
|
||||
goto fail;
|
||||
} else {
|
||||
/*
|
||||
* No local root scope, so we're stuck with the old, fragile model of
|
||||
* depending on a pigeon-hole newborn per type per context.
|
||||
*/
|
||||
cx->newborn[flags & GCF_TYPEMASK] = thing;
|
||||
}
|
||||
|
||||
/* We can't fail now, so update flags and rt->gcBytes. */
|
||||
*flagp = (uint8)flags;
|
||||
rt->gcBytes += sizeof(JSGCThing) + sizeof(uint8);
|
||||
cx->newborn[flags & GCF_TYPEMASK] = thing;
|
||||
|
||||
/*
|
||||
* Clear thing before unlocking in case a GC run is about to scan it,
|
||||
@ -552,6 +570,12 @@ retry:
|
||||
thing->flagp = NULL;
|
||||
JS_UNLOCK_GC(rt);
|
||||
return thing;
|
||||
|
||||
fail:
|
||||
METER(rt->gcStats.fail++);
|
||||
JS_UNLOCK_GC(rt);
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JSBool
|
||||
@ -584,7 +608,7 @@ js_LockGCThingRT(JSRuntime *rt, void *thing)
|
||||
/* Objects may require "deep locking", i.e., rooting by value. */
|
||||
if (lockbits == 0) {
|
||||
if (!rt->gcLocksHash) {
|
||||
rt->gcLocksHash =
|
||||
rt->gcLocksHash =
|
||||
JS_NewDHashTable(JS_DHashGetStubOps(), NULL,
|
||||
sizeof(JSGCLockHashEntry),
|
||||
GC_ROOTS_SIZE);
|
||||
@ -592,7 +616,7 @@ js_LockGCThingRT(JSRuntime *rt, void *thing)
|
||||
goto error;
|
||||
} else {
|
||||
#ifdef DEBUG
|
||||
JSDHashEntryHdr *hdr =
|
||||
JSDHashEntryHdr *hdr =
|
||||
JS_DHashTableOperate(rt->gcLocksHash, thing,
|
||||
JS_DHASH_LOOKUP);
|
||||
JS_ASSERT(JS_DHASH_ENTRY_IS_FREE(hdr));
|
||||
@ -1243,18 +1267,6 @@ restart:
|
||||
GC_MARK(cx, fp->scopeChain, "scope chain", NULL);
|
||||
if (fp->sharpArray)
|
||||
GC_MARK(cx, fp->sharpArray, "sharp array", NULL);
|
||||
|
||||
if (fp->objAtomMap) {
|
||||
JSAtom **vector, *atom;
|
||||
|
||||
nslots = fp->objAtomMap->length;
|
||||
vector = fp->objAtomMap->vector;
|
||||
for (i = 0; i < nslots; i++) {
|
||||
atom = vector[i];
|
||||
if (atom)
|
||||
GC_MARK_ATOM(cx, atom, NULL);
|
||||
}
|
||||
}
|
||||
} while ((fp = fp->down) != NULL);
|
||||
}
|
||||
|
||||
@ -1287,6 +1299,9 @@ restart:
|
||||
METER(rt->gcStats.segslots += sh->nslots);
|
||||
GC_MARK_JSVALS(cx, sh->nslots, JS_STACK_SEGMENT(sh), "stack");
|
||||
}
|
||||
|
||||
if (acx->localRootStack)
|
||||
js_MarkLocalRoots(cx, acx->localRootStack);
|
||||
}
|
||||
#ifdef DUMP_CALL_TABLE
|
||||
js_DumpCallTable(cx);
|
||||
|
@ -1180,7 +1180,6 @@ have_fun:
|
||||
frame.sharpDepth = 0;
|
||||
frame.sharpArray = NULL;
|
||||
frame.dormantNext = NULL;
|
||||
frame.objAtomMap = NULL;
|
||||
|
||||
/* Compute the 'this' parameter and store it in frame as frame.thisp. */
|
||||
ok = ComputeThis(cx, thisp, &frame);
|
||||
@ -1479,7 +1478,6 @@ js_Execute(JSContext *cx, JSObject *chain, JSScript *script,
|
||||
frame.sharpDepth = 0;
|
||||
frame.flags = flags;
|
||||
frame.dormantNext = NULL;
|
||||
frame.objAtomMap = NULL;
|
||||
|
||||
/*
|
||||
* Here we wrap the call to js_Interpret with code to (conditionally)
|
||||
|
@ -72,8 +72,6 @@ struct JSStackFrame {
|
||||
JSObject *sharpArray; /* scope for #n= initializer vars */
|
||||
uint32 flags; /* frame flags -- see below */
|
||||
JSStackFrame *dormantNext; /* next dormant frame chain */
|
||||
JSAtomMap *objAtomMap; /* object atom map, non-null only if we
|
||||
hit a regexp object literal */
|
||||
};
|
||||
|
||||
typedef struct JSInlineFrame {
|
||||
|
123
js/src/jsobj.c
123
js/src/jsobj.c
@ -1125,116 +1125,6 @@ out:
|
||||
return ok;
|
||||
}
|
||||
|
||||
JS_STATIC_DLL_CALLBACK(const void *)
|
||||
resolving_GetKey(JSDHashTable *table, JSDHashEntryHdr *hdr)
|
||||
{
|
||||
JSResolvingEntry *entry = (JSResolvingEntry *)hdr;
|
||||
|
||||
return &entry->key;
|
||||
}
|
||||
|
||||
JS_STATIC_DLL_CALLBACK(JSDHashNumber)
|
||||
resolving_HashKey(JSDHashTable *table, const void *ptr)
|
||||
{
|
||||
const JSResolvingKey *key = (const JSResolvingKey *)ptr;
|
||||
|
||||
return ((JSDHashNumber)key->obj >> JSVAL_TAGBITS) ^ key->id;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
resolving_MatchEntry(JSDHashTable *table,
|
||||
const JSDHashEntryHdr *hdr,
|
||||
const void *ptr)
|
||||
{
|
||||
const JSResolvingEntry *entry = (const JSResolvingEntry *)hdr;
|
||||
const JSResolvingKey *key = (const JSResolvingKey *)ptr;
|
||||
|
||||
return entry->key.obj == key->obj && entry->key.id == key->id;
|
||||
}
|
||||
|
||||
static const JSDHashTableOps resolving_dhash_ops = {
|
||||
JS_DHashAllocTable,
|
||||
JS_DHashFreeTable,
|
||||
resolving_GetKey,
|
||||
resolving_HashKey,
|
||||
resolving_MatchEntry,
|
||||
JS_DHashMoveEntryStub,
|
||||
JS_DHashClearEntryStub,
|
||||
JS_DHashFinalizeStub,
|
||||
NULL
|
||||
};
|
||||
|
||||
static JSBool
|
||||
StartResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
|
||||
JSResolvingEntry **entryp)
|
||||
{
|
||||
JSDHashTable *table;
|
||||
JSResolvingEntry *entry;
|
||||
|
||||
table = cx->resolvingTable;
|
||||
if (!table) {
|
||||
table = JS_NewDHashTable(&resolving_dhash_ops, NULL,
|
||||
sizeof(JSResolvingEntry),
|
||||
JS_DHASH_MIN_SIZE);
|
||||
if (!table)
|
||||
goto outofmem;
|
||||
cx->resolvingTable = table;
|
||||
}
|
||||
|
||||
entry = (JSResolvingEntry *)
|
||||
JS_DHashTableOperate(table, key, JS_DHASH_ADD);
|
||||
if (!entry)
|
||||
goto outofmem;
|
||||
|
||||
if (entry->flags & flag) {
|
||||
/* An entry for (key, flag) exists already -- dampen recursion. */
|
||||
entry = NULL;
|
||||
} else {
|
||||
/* Fill in key if we were the first to add entry, then set flag. */
|
||||
if (!entry->key.obj)
|
||||
entry->key = *key;
|
||||
entry->flags |= flag;
|
||||
}
|
||||
*entryp = entry;
|
||||
return JS_TRUE;
|
||||
|
||||
outofmem:
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
StopResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
|
||||
JSResolvingEntry *entry, uint32 generation)
|
||||
{
|
||||
JSDHashTable *table;
|
||||
|
||||
/*
|
||||
* Clear flag from entry->flags and return early if other flags remain.
|
||||
* We must take care to re-lookup entry if the table has changed since
|
||||
* it was found by StartResolving.
|
||||
*/
|
||||
table = cx->resolvingTable;
|
||||
if (table->generation != generation) {
|
||||
entry = (JSResolvingEntry *)
|
||||
JS_DHashTableOperate(table, key, JS_DHASH_LOOKUP);
|
||||
}
|
||||
entry->flags &= ~flag;
|
||||
if (entry->flags)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Do a raw remove only if fewer entries were removed than would cause
|
||||
* alpha to be less than .5 (alpha is at most .75). Otherwise, we just
|
||||
* call JS_DHashTableOperate to re-lookup the key and remove its entry,
|
||||
* compressing or shrinking the table as needed.
|
||||
*/
|
||||
if (table->removedCount < JS_DHASH_TABLE_SIZE(table) >> 2)
|
||||
JS_DHashTableRawRemove(table, &entry->hdr);
|
||||
else
|
||||
JS_DHashTableOperate(table, key, JS_DHASH_REMOVE);
|
||||
}
|
||||
|
||||
#if JS_HAS_OBJ_WATCHPOINT
|
||||
|
||||
static JSBool
|
||||
@ -1251,7 +1141,7 @@ obj_watch_handler(JSContext *cx, JSObject *obj, jsval id, jsval old, jsval *nvp,
|
||||
/* Avoid recursion on (obj, id) already being watched on cx. */
|
||||
key.obj = obj;
|
||||
key.id = id;
|
||||
if (!StartResolving(cx, &key, JSRESFLAG_WATCH, &entry))
|
||||
if (!js_StartResolving(cx, &key, JSRESFLAG_WATCH, &entry))
|
||||
return JS_FALSE;
|
||||
if (!entry)
|
||||
return JS_TRUE;
|
||||
@ -1262,7 +1152,7 @@ obj_watch_handler(JSContext *cx, JSObject *obj, jsval id, jsval old, jsval *nvp,
|
||||
argv[1] = old;
|
||||
argv[2] = *nvp;
|
||||
ok = js_InternalCall(cx, obj, OBJECT_TO_JSVAL(funobj), 3, argv, nvp);
|
||||
StopResolving(cx, &key, JSRESFLAG_WATCH, entry, generation);
|
||||
js_StopResolving(cx, &key, JSRESFLAG_WATCH, entry, generation);
|
||||
return ok;
|
||||
}
|
||||
|
||||
@ -1963,8 +1853,11 @@ js_NewObject(JSContext *cx, JSClass *clasp, JSObject *proto, JSObject *parent)
|
||||
/* Store newslots after initializing all of 'em, just in case. */
|
||||
obj->slots = newslots;
|
||||
|
||||
if (cx->runtime->objectHook)
|
||||
if (cx->runtime->objectHook) {
|
||||
JS_KEEP_ATOMS(cx->runtime);
|
||||
cx->runtime->objectHook(cx, obj, JS_TRUE, cx->runtime->objectHookData);
|
||||
JS_UNKEEP_ATOMS(cx->runtime);
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
||||
@ -2495,7 +2388,7 @@ js_LookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
|
||||
* returning. But note that JS_DHASH_ADD may find an existing
|
||||
* entry, in which case we bail to suppress runaway recursion.
|
||||
*/
|
||||
if (!StartResolving(cx, &key, JSRESFLAG_LOOKUP, &entry)) {
|
||||
if (!js_StartResolving(cx, &key, JSRESFLAG_LOOKUP, &entry)) {
|
||||
JS_UNLOCK_OBJ(cx, obj);
|
||||
return JS_FALSE;
|
||||
}
|
||||
@ -2590,7 +2483,7 @@ js_LookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
|
||||
}
|
||||
|
||||
cleanup:
|
||||
StopResolving(cx, &key, JSRESFLAG_LOOKUP, entry, generation);
|
||||
js_StopResolving(cx, &key, JSRESFLAG_LOOKUP, entry, generation);
|
||||
if (!ok || *propp)
|
||||
return ok;
|
||||
}
|
||||
|
@ -945,9 +945,11 @@ FunctionDef(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
|
||||
op = JSOP_NOP;
|
||||
|
||||
/*
|
||||
* Pending a better automatic GC root management scheme (see Mozilla bug
|
||||
* 40757, http://bugzilla.mozilla.org/show_bug.cgi?id=40757), we need to
|
||||
* atomize here to protect against a GC activation.
|
||||
* Absent use of the new scoped local GC roots API around compiler calls,
|
||||
* we need to atomize here to protect against a GC activation. Atoms are
|
||||
* protected from GC during compilation by the JS_FRIEND_API entry points
|
||||
* in this file. There doesn't seem to be any gain in switching from the
|
||||
* atom-keeping method to the bulkier, slower scoped local roots method.
|
||||
*/
|
||||
pn->pn_funAtom = js_AtomizeObject(cx, fun->object, 0);
|
||||
if (!pn->pn_funAtom)
|
||||
|
@ -1135,25 +1135,10 @@ js_CallNewScriptHook(JSContext *cx, JSScript *script, JSFunction *fun)
|
||||
rt = cx->runtime;
|
||||
hook = rt->newScriptHook;
|
||||
if (hook) {
|
||||
/*
|
||||
* We use a dummy stack frame to protect the script from a GC caused
|
||||
* by debugger-hook execution.
|
||||
*
|
||||
* XXX We really need a way to manage local roots and such more
|
||||
* XXX automatically, at which point we can remove this one-off hack
|
||||
* XXX and others within the engine. See bug 40757 for discussion.
|
||||
*/
|
||||
JSStackFrame dummy;
|
||||
|
||||
memset(&dummy, 0, sizeof dummy);
|
||||
dummy.down = cx->fp;
|
||||
dummy.script = script;
|
||||
cx->fp = &dummy;
|
||||
|
||||
JS_KEEP_ATOMS(rt);
|
||||
hook(cx, script->filename, script->lineno, script, fun,
|
||||
rt->newScriptHookData);
|
||||
|
||||
cx->fp = dummy.down;
|
||||
JS_UNKEEP_ATOMS(rt);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user