diff --git a/mozilla/js/src/jscntxt.c b/mozilla/js/src/jscntxt.c index 71b83d10c84..68996c8ab8c 100644 --- a/mozilla/js/src/jscntxt.c +++ b/mozilla/js/src/jscntxt.c @@ -439,6 +439,8 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode) JS_free(cx, lrs); } + JS_CLEAR_GSN_CACHE(cx); + #ifdef JS_THREADSAFE js_ClearContextThread(cx); #endif diff --git a/mozilla/js/src/jscntxt.h b/mozilla/js/src/jscntxt.h index b866c90d5b9..db0c175efb9 100644 --- a/mozilla/js/src/jscntxt.h +++ b/mozilla/js/src/jscntxt.h @@ -1,4 +1,5 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=78: * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 @@ -694,9 +695,37 @@ struct JSContext { /* Top of the GC mark stack. */ void *gcCurrentMarkNode; #endif + + /* + * js_GetSrcNote cache to avoid O(n^2) growth in finding a source note for + * a given pc in a script. + */ + struct JSGSNCache { + JSScript *script; + JSDHashTable table; +#ifdef JS_GSNMETER + uint32 hits; + uint32 misses; + uint32 fills; + uint32 clears; +# define GSN_CACHE_METER(cx,cnt) (++(cx)->gsnCache.cnt) +#else +# define GSN_CACHE_METER(cx,cnt) /* nothing */ +#endif + } gsnCache; }; -#define JS_THREAD_ID(cx) ((cx)->thread ? (cx)->thread->id : 0) +#define JS_CLEAR_GSN_CACHE(cx) \ + JS_BEGIN_MACRO \ + (cx)->gsnCache.script = NULL; \ + if ((cx)->gsnCache.table.ops) { \ + JS_DHashTableFinish(&(cx)->gsnCache.table); \ + (cx)->gsnCache.table.ops = NULL; \ + } \ + GSN_CACHE_METER(cx, clears); \ + JS_END_MACRO + +#define JS_THREAD_ID(cx) ((cx)->thread ? (cx)->thread->id : 0) #ifdef __cplusplus /* FIXME(bug 332648): Move this into a public header. */ diff --git a/mozilla/js/src/jsgc.c b/mozilla/js/src/jsgc.c index 1d5a0393dfa..9f99fba718c 100644 --- a/mozilla/js/src/jsgc.c +++ b/mozilla/js/src/jsgc.c @@ -2723,6 +2723,9 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) * Set all thread local freelists to NULL. We may visit a thread's * freelist more than once. To avoid redundant clearing we unroll the * current thread's step. + * + * Also, in case a JSScript wrapped in an object was finalized, we clear + * the acx->gsnCache.script pointer and finish the cache's hashtable. */ memset(cx->thread->gcFreeLists, 0, sizeof cx->thread->gcFreeLists); iter = NULL; @@ -2730,6 +2733,7 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) if (!acx->thread || acx->thread == cx->thread) continue; memset(acx->thread->gcFreeLists, 0, sizeof acx->thread->gcFreeLists); + JS_CLEAR_GSN_CACHE(acx); } #endif diff --git a/mozilla/js/src/jsscript.c b/mozilla/js/src/jsscript.c index 0691e0d3197..feac94c8d6e 100644 --- a/mozilla/js/src/jsscript.c +++ b/mozilla/js/src/jsscript.c @@ -1393,6 +1393,8 @@ js_DestroyScript(JSContext *cx, JSScript *script) js_FreeAtomMap(cx, &script->atomMap); if (script->principals) JSPRINCIPALS_DROP(cx, script->principals); + if (cx->gsnCache.script == script) + JS_CLEAR_GSN_CACHE(cx); JS_free(cx, script); } @@ -1413,22 +1415,77 @@ js_MarkScript(JSContext *cx, JSScript *script) js_MarkScriptFilename(script->filename); } +typedef struct GSNCacheEntry { + JSDHashEntryHdr hdr; + jsbytecode *pc; + jssrcnote *sn; +} GSNCacheEntry; + +#define GSN_CACHE_THRESHOLD 100 + jssrcnote * -js_GetSrcNote(JSScript *script, jsbytecode *pc) +js_GetSrcNoteCached(JSContext *cx, JSScript *script, jsbytecode *pc) { - jssrcnote *sn; - ptrdiff_t offset, target; + ptrdiff_t target, offset; + GSNCacheEntry *entry; + jssrcnote *sn, *result; + uintN nsrcnotes; + target = PTRDIFF(pc, script->code, jsbytecode); if ((uint32)target >= script->length) return NULL; - offset = 0; - for (sn = SCRIPT_NOTES(script); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { - offset += SN_DELTA(sn); - if (offset == target && SN_IS_GETTABLE(sn)) - return sn; + + if (cx->gsnCache.script == script) { + GSN_CACHE_METER(cx, hits); + entry = (GSNCacheEntry *) + JS_DHashTableOperate(&cx->gsnCache.table, pc, JS_DHASH_LOOKUP); + return entry->sn; } - return NULL; + + GSN_CACHE_METER(cx, misses); + offset = 0; + for (sn = SCRIPT_NOTES(script); ; sn = SN_NEXT(sn)) { + if (SN_IS_TERMINATOR(sn)) { + result = NULL; + break; + } + offset += SN_DELTA(sn); + if (offset == target && SN_IS_GETTABLE(sn)) { + result = sn; + break; + } + } + + if (cx->gsnCache.script != script && + script->length >= GSN_CACHE_THRESHOLD) { + JS_CLEAR_GSN_CACHE(cx); + nsrcnotes = 0; + for (sn = SCRIPT_NOTES(script); !SN_IS_TERMINATOR(sn); + sn = SN_NEXT(sn)) { + if (SN_IS_GETTABLE(sn)) + ++nsrcnotes; + } + if (JS_DHashTableInit(&cx->gsnCache.table, JS_DHashGetStubOps(), NULL, + sizeof(GSNCacheEntry), nsrcnotes)) { + pc = script->code; + for (sn = SCRIPT_NOTES(script); !SN_IS_TERMINATOR(sn); + sn = SN_NEXT(sn)) { + pc += SN_DELTA(sn); + if (SN_IS_GETTABLE(sn)) { + entry = (GSNCacheEntry *) + JS_DHashTableOperate(&cx->gsnCache.table, pc, + JS_DHASH_ADD); + entry->pc = pc; + entry->sn = sn; + } + } + cx->gsnCache.script = script; + GSN_CACHE_METER(cx, fills); + } + } + + return result; } uintN diff --git a/mozilla/js/src/jsscript.h b/mozilla/js/src/jsscript.h index 10cd330fc9c..18ad373d007 100644 --- a/mozilla/js/src/jsscript.h +++ b/mozilla/js/src/jsscript.h @@ -1,4 +1,5 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=78: * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 @@ -187,8 +188,15 @@ js_DestroyScript(JSContext *cx, JSScript *script); extern void js_MarkScript(JSContext *cx, JSScript *script); +/* + * To perturb as little code as possible, we introduce a js_GetSrcNote lookup + * cache without adding an explicit cx parameter. Thus js_GetSrcNote becomes + * a macro that uses cx from its calls' lexical environments. + */ +#define js_GetSrcNote(script,pc) js_GetSrcNoteCached(cx, script, pc) + extern jssrcnote * -js_GetSrcNote(JSScript *script, jsbytecode *pc); +js_GetSrcNoteCached(JSContext *cx, JSScript *script, jsbytecode *pc); /* XXX need cx to lock function objects declared by prolog bytecodes. */ extern uintN