From 07d98bce68999f4e639a921d05cf46e489d0981b Mon Sep 17 00:00:00 2001 From: Brendan Eich Date: Tue, 27 Jan 2009 13:53:31 -0800 Subject: [PATCH 01/11] Bug 454184 - Implement eval caching (r=mrbkap). --- js/src/jsapi.cpp | 4 ++ js/src/jsapi.h | 6 ++- js/src/jscntxt.cpp | 53 +++++++++++++++++-- js/src/jscntxt.h | 62 +++++++++++++++++----- js/src/jsgc.cpp | 6 ++- js/src/jsobj.cpp | 122 ++++++++++++++++++++++++++++++++++++++------ js/src/jsopcode.cpp | 1 + js/src/jsparse.cpp | 45 ++++++++++++---- js/src/jsparse.h | 3 +- js/src/jsscript.h | 1 + 10 files changed, 257 insertions(+), 46 deletions(-) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 0efa4773007f..ee72277676c8 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -1183,6 +1183,10 @@ JS_GetOptions(JSContext *cx) (cx)->version |= JSVERSION_HAS_XML; \ else \ (cx)->version &= ~JSVERSION_HAS_XML; \ + if ((cx)->options & JSOPTION_ANONFUNFIX) \ + (cx)->version |= JSVERSION_ANONFUNFIX; \ + else \ + (cx)->version &= ~JSVERSION_ANONFUNFIX; \ JS_END_MACRO JS_PUBLIC_API(uint32) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index dddda3db82b3..5552f5ab89bc 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -596,8 +596,10 @@ JS_StringToVersion(const char *string); will be passed to each call to JS_ExecuteScript. */ #define JSOPTION_UNROOTED_GLOBAL JS_BIT(13) /* The GC will not root the - global objects leaving - that up to the embedding. */ + contexts' global objects + (see JS_GetGlobalObject), + leaving that up to the + embedding. */ extern JS_PUBLIC_API(uint32) JS_GetOptions(JSContext *cx); diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index fd33df29233d..2f4e6c4eebe6 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -168,7 +168,7 @@ js_GetCurrentThread(JSRuntime *rt) memset(&thread->traceMonitor, 0, sizeof(thread->traceMonitor)); js_InitJIT(&thread->traceMonitor); #endif - thread->scriptsToGC = NULL; + memset(thread->scriptsToGC, 0, sizeof thread->scriptsToGC); /* * js_SetContextThread initializes the remaining fields as necessary. @@ -197,8 +197,11 @@ js_SetContextThread(JSContext *cx) * current thread. See bug 425828. */ if (JS_CLIST_IS_EMPTY(&thread->contextList)) { - memset(&thread->gsnCache, 0, sizeof(thread->gsnCache)); - memset(&thread->propertyCache, 0, sizeof(thread->propertyCache)); + memset(&thread->gsnCache, 0, sizeof thread->gsnCache); + memset(&thread->propertyCache, 0, sizeof thread->propertyCache); +#ifdef DEBUG + memset(&thread->evalCacheMeter, 0, sizeof thread->evalCacheMeter); +#endif } /* Assert that the previous cx->thread called JS_ClearContextThread(). */ @@ -350,6 +353,49 @@ js_NewContext(JSRuntime *rt, size_t stackChunkSize) return cx; } +#if defined DEBUG && defined XP_UNIX +# include + +static void +DumpEvalCacheMeter(JSContext *cx) +{ + struct { + const char *name; + ptrdiff_t offset; + } table[] = { +#define frob(x) { #x, offsetof(JSEvalCacheMeter, x) } + EVAL_CACHE_METER_LIST(frob) +#undef frob + }; + JSEvalCacheMeter *ecm = &JS_CACHE_LOCUS(cx)->evalCacheMeter; + + static FILE *fp; + if (!fp) { + fp = fopen("/tmp/evalcache.stats", "w"); + if (!fp) + return; + } + + fprintf(fp, "eval cache meter (%p):\n", +#ifdef JS_THREADSAFE + cx->thread +#else + cx->runtime +#endif + ); + for (uintN i = 0; i < JS_ARRAY_LENGTH(table); ++i) { + fprintf(fp, "%-8.8s %llu\n", + table[i].name, *(uint64 *)((uint8 *)ecm + table[i].offset)); + } + fprintf(fp, "hit ratio %g%%\n", ecm->hit * 100. / ecm->probe); + fprintf(fp, "avg steps %g\n", double(ecm->step) / ecm->probe); + fflush(fp); +} +# define DUMP_EVAL_CACHE_METER(cx) DumpEvalCacheMeter(cx) +#else +# define DUMP_EVAL_CACHE_METER(cx) ((void) 0) +#endif + void js_DestroyContext(JSContext *cx, JSDestroyContextMode mode) { @@ -438,6 +484,7 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode) if (last) { js_GC(cx, GC_LAST_CONTEXT); + DUMP_EVAL_CACHE_METER(cx); /* * Free the script filename table if it exists and is empty. Do this diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 22f19194b1fb..f0c7d83af7ab 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -159,6 +159,31 @@ typedef struct JSTraceMonitor { # define JS_EXECUTING_TRACE(cx) JS_FALSE #endif +#ifdef DEBUG +# define JS_EVAL_CACHE_METERING 1 +#endif + +/* Number of potentially reusable scriptsToGC to search for the eval cache. */ +#ifndef JS_EVAL_CACHE_SHIFT +# define JS_EVAL_CACHE_SHIFT 6 +#endif +#define JS_EVAL_CACHE_SIZE JS_BIT(JS_EVAL_CACHE_SHIFT) + +#ifdef JS_EVAL_CACHE_METERING +# define EVAL_CACHE_METER_LIST(_) _(probe), _(hit), _(step), _(noscope) +# define ID(x) x + +/* Have to typedef this for LiveConnect C code, which includes us. */ +typedef struct JSEvalCacheMeter { + uint64 EVAL_CACHE_METER_LIST(ID); +} JSEvalCacheMeter; + +# undef ID +# define DECLARE_EVAL_CACHE_METER JSEvalCacheMeter evalCacheMeter; +#else +# define DECLARE_EVAL_CACHE_METER /* nothing */ +#endif + #ifdef JS_THREADSAFE /* @@ -195,14 +220,13 @@ struct JSThread { JSTraceMonitor traceMonitor; #endif - /* Lock-free list of scripts created by eval to garbage-collect. */ - JSScript *scriptsToGC; + /* Lock-free hashed lists of scripts created by eval to garbage-collect. */ + JSScript *scriptsToGC[JS_EVAL_CACHE_SIZE]; + + DECLARE_EVAL_CACHE_METER }; -#define JS_GSN_CACHE(cx) ((cx)->thread->gsnCache) -#define JS_PROPERTY_CACHE(cx) ((cx)->thread->propertyCache) -#define JS_TRACE_MONITOR(cx) ((cx)->thread->traceMonitor) -#define JS_SCRIPTS_TO_GC(cx) ((cx)->thread->scriptsToGC) +#define JS_CACHE_LOCUS(cx) ((cx)->thread) extern void js_ThreadDestructorCB(void *ptr); @@ -463,13 +487,12 @@ struct JSRuntime { /* Trace-tree JIT recorder/interpreter state. */ JSTraceMonitor traceMonitor; - /* Lock-free list of scripts created by eval to garbage-collect. */ - JSScript *scriptsToGC; + /* Lock-free hashed lists of scripts created by eval to garbage-collect. */ + JSScript *scriptsToGC[JS_EVAL_CACHE_SIZE]; -#define JS_GSN_CACHE(cx) ((cx)->runtime->gsnCache) -#define JS_PROPERTY_CACHE(cx) ((cx)->runtime->propertyCache) -#define JS_TRACE_MONITOR(cx) ((cx)->runtime->traceMonitor) -#define JS_SCRIPTS_TO_GC(cx) ((cx)->runtime->scriptsToGC) + DECLARE_EVAL_CACHE_METER + +#define JS_CACHE_LOCUS(cx) ((cx)->runtime) #endif /* @@ -587,6 +610,19 @@ struct JSRuntime { #endif }; +/* Common macros to access thread-local caches in JSThread or JSRuntime. */ +#define JS_GSN_CACHE(cx) (JS_CACHE_LOCUS(cx)->gsnCache) +#define JS_PROPERTY_CACHE(cx) (JS_CACHE_LOCUS(cx)->propertyCache) +#define JS_TRACE_MONITOR(cx) (JS_CACHE_LOCUS(cx)->traceMonitor) +#define JS_SCRIPTS_TO_GC(cx) (JS_CACHE_LOCUS(cx)->scriptsToGC) + +#ifdef JS_EVAL_CACHE_METERING +# define EVAL_CACHE_METER(x) (JS_CACHE_LOCUS(cx)->evalCacheMeter.x++) +#else +# define EVAL_CACHE_METER(x) ((void) 0) +#endif +#undef DECLARE_EVAL_CACHE_METER + #ifdef DEBUG # define JS_RUNTIME_METER(rt, which) JS_ATOMIC_INCREMENT(&(rt)->which) # define JS_RUNTIME_UNMETER(rt, which) JS_ATOMIC_DECREMENT(&(rt)->which) @@ -1022,6 +1058,8 @@ class JSAutoResolveFlags #define JSVERSION_MASK 0x0FFF /* see JSVersion in jspubtd.h */ #define JSVERSION_HAS_XML 0x1000 /* flag induced by XML option */ +#define JSVERSION_ANONFUNFIX 0x2000 /* see jsapi.h, the comments + for JSOPTION_ANONFUNFIX */ #define JSVERSION_NUMBER(cx) ((JSVersion)((cx)->version & \ JSVERSION_MASK)) diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 31a49bb6b110..51c701d892b8 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3470,7 +3470,8 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) #endif /* Destroy eval'ed scripts. */ - DestroyScriptsToGC(cx, &JS_SCRIPTS_TO_GC(cx)); + for (i = 0; i < JS_ARRAY_LENGTH(JS_SCRIPTS_TO_GC(cx)); i++) + DestroyScriptsToGC(cx, &JS_SCRIPTS_TO_GC(cx)[i]); #ifdef JS_THREADSAFE /* @@ -3492,7 +3493,8 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) #ifdef JS_TRACER js_FlushJITOracle(acx); #endif - DestroyScriptsToGC(cx, &acx->thread->scriptsToGC); + for (i = 0; i < JS_ARRAY_LENGTH(acx->thread->scriptsToGC); i++) + DestroyScriptsToGC(cx, &acx->thread->scriptsToGC[i]); } #else /* The thread-unsafe case just has to clear the runtime's GSN cache. */ diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 0d06aa54ec26..5360dc62d2e1 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -1191,19 +1191,42 @@ js_ComputeFilename(JSContext *cx, JSStackFrame *caller, return caller->script->filename; } +#ifndef EVAL_CACHE_CHAIN_LIMIT +# define EVAL_CACHE_CHAIN_LIMIT 4 +#endif + +static inline JSScript ** +EvalCacheHash(JSContext *cx, JSString *str) +{ + const jschar *s; + size_t n; + uint32 h; + + JSSTRING_CHARS_AND_LENGTH(str, s, n); + if (n > 100) + n = 100; + for (h = 0; n; s++, n--) + h = JS_ROTATE_LEFT32(h, 4) ^ *s; + + h *= JS_GOLDEN_RATIO; + h >>= 32 - JS_EVAL_CACHE_SHIFT; + return &JS_SCRIPTS_TO_GC(cx)[h]; +} + static JSBool obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSStackFrame *fp, *caller; JSBool indirectCall; JSObject *scopeobj; - JSString *str; + uint32 tcflags; + JSPrincipals *principals; const char *file; uintN line; - JSPrincipals *principals; - uint32 tcflags; + JSString *str; JSScript *script; JSBool ok; + JSScript **bucket = NULL; /* avoid GCC warning with early decl&init */ #if JS_HAS_EVAL_THIS_SCOPE JSObject *callerScopeChain = NULL, *callerVarObj = NULL; JSObject *setCallerScopeChain = NULL; @@ -1336,25 +1359,94 @@ obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) goto out; } - str = JSVAL_TO_STRING(argv[0]); + tcflags = TCF_COMPILE_N_GO; if (caller) { + tcflags |= TCF_PUT_STATIC_DEPTH(caller->script->staticDepth + 1); principals = JS_EvalFramePrincipals(cx, fp, caller); file = js_ComputeFilename(cx, caller, principals, &line); } else { + principals = NULL; file = NULL; line = 0; - principals = NULL; } - tcflags = TCF_COMPILE_N_GO; - if (caller) - tcflags |= TCF_PUT_STATIC_DEPTH(caller->script->staticDepth + 1); - script = js_CompileScript(cx, scopeobj, caller, principals, tcflags, - JSSTRING_CHARS(str), JSSTRING_LENGTH(str), - NULL, file, line); + str = JSVAL_TO_STRING(argv[0]); + script = NULL; + + /* Cache local eval scripts indexed by source qualified by scope. */ + bucket = EvalCacheHash(cx, str); + if (caller->fun) { + uintN count = 0; + JSScript **scriptp = bucket; + + EVAL_CACHE_METER(probe); + while ((script = *scriptp) != NULL) { + if ((script->flags & JSSF_SAVED_CALLER_FUN) && + script->version == cx->version && + (script->principals == principals || + (principals->subsume(principals, script->principals) && + script->principals->subsume(script->principals, principals)))) { + /* + * Get the prior (cache-filling) eval's saved caller function. + * See js_CompileScript in jsparse.cpp. + */ + JSFunction *fun; + JS_GET_SCRIPT_FUNCTION(script, 0, fun); + + if (fun == caller->fun) { + /* + * Get the source string passed for safekeeping in the + * atom map by the prior eval to js_CompileScript. + */ + JSString *src = ATOM_TO_STRING(script->atomMap.vector[0]); + + if (src == str || js_EqualStrings(src, str)) { + /* + * Source matches, qualify by comparing scopeobj to the + * COMPILE_N_GO-memoized parent of the first literal + * function or regexp object if any. If none, then this + * script has no compiled-in dependencies on the prior + * eval's scopeobj. + */ + JSObjectArray *objarray = JS_SCRIPT_OBJECTS(script); + int i = 1; + if (objarray->length == 1) { + if (script->regexpsOffset != 0) { + objarray = JS_SCRIPT_REGEXPS(script); + i = 0; + } else { + EVAL_CACHE_METER(noscope); + i = -1; + } + } + if (i < 0 || + STOBJ_GET_PARENT(objarray->vector[i]) == scopeobj) { + EVAL_CACHE_METER(hit); + *scriptp = script->u.nextToGC; + script->u.nextToGC = NULL; + break; + } + } + } + } + + if (++count == EVAL_CACHE_CHAIN_LIMIT) { + script = NULL; + break; + } + EVAL_CACHE_METER(step); + scriptp = &script->u.nextToGC; + } + } + if (!script) { - ok = JS_FALSE; - goto out; + script = js_CompileScript(cx, scopeobj, caller, principals, tcflags, + JSSTRING_CHARS(str), JSSTRING_LENGTH(str), + NULL, file, line, str); + if (!script) { + ok = JS_FALSE; + goto out; + } } if (argc < 2) { @@ -1372,8 +1464,8 @@ obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) if (ok) ok = js_Execute(cx, scopeobj, script, caller, JSFRAME_EVAL, rval); - script->u.nextToGC = JS_SCRIPTS_TO_GC(cx); - JS_SCRIPTS_TO_GC(cx) = script; + script->u.nextToGC = *bucket; + *bucket = script; #ifdef CHECK_SCRIPT_OWNER script->owner = NULL; #endif diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index bcb56a1fbd9c..2d4148aaa6e9 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -2752,6 +2752,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop) case JSOP_CALLUPVAR: case JSOP_GETUPVAR: + JS_ASSERT(jp->script->flags & JSSF_SAVED_CALLER_FUN); if (!jp->fun) JS_GET_SCRIPT_FUNCTION(jp->script, 0, jp->fun); diff --git a/js/src/jsparse.cpp b/js/src/jsparse.cpp index 6dfdd6d96def..5addc8f744ed 100644 --- a/js/src/jsparse.cpp +++ b/js/src/jsparse.cpp @@ -503,7 +503,8 @@ extern JSScript * js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, JSPrincipals *principals, uint32 tcflags, const jschar *chars, size_t length, - FILE *file, const char *filename, uintN lineno) + FILE *file, const char *filename, uintN lineno, + JSString *source) { JSParseContext pc; JSArenaPool codePool, notePool; @@ -543,16 +544,36 @@ js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, cg.treeContext.u.scopeChain = scopeChain; cg.staticDepth = TCF_GET_STATIC_DEPTH(tcflags); - if ((tcflags & TCF_COMPILE_N_GO) && callerFrame && callerFrame->fun) { - /* - * An eval script in a caller frame needs to have its enclosing function - * captured in case it uses an upvar reference, and someone wishes to - * decompile it while running. - */ - JSParsedObjectBox *pob = js_NewParsedObjectBox(cx, &pc, FUN_OBJECT(callerFrame->fun)); - pob->emitLink = cg.objectList.lastPob; - cg.objectList.lastPob = pob; - cg.objectList.length++; + /* + * If funpob is non-null after we create the new script, callerFrame->fun + * was saved in the 0th object table entry. + */ + JSParsedObjectBox *funpob = NULL; + + if (tcflags & TCF_COMPILE_N_GO) { + if (source) { + /* + * Save eval program source in script->atomMap.vector[0] for the + * eval cache (see obj_eval in jsobj.cpp). + */ + JSAtom *atom = js_AtomizeString(cx, source, 0); + if (!atom || !js_IndexAtom(cx, atom, &cg.atomList)) + return NULL; + } + + if (callerFrame && callerFrame->fun) { + /* + * An eval script in a caller frame needs to have its enclosing + * function captured in case it uses an upvar reference, and + * someone wishes to decompile it while it's running. + */ + funpob = js_NewParsedObjectBox(cx, &pc, FUN_OBJECT(callerFrame->fun)); + if (!funpob) + return NULL; + funpob->emitLink = cg.objectList.lastPob; + cg.objectList.lastPob = funpob; + cg.objectList.length++; + } } /* Inline Statements() to emit as we go to save space. */ @@ -647,6 +668,8 @@ js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, JS_DumpArenaStats(stdout); #endif script = js_NewScriptFromCG(cx, &cg); + if (script && funpob) + script->flags |= JSSF_SAVED_CALLER_FUN; #ifdef JS_SCOPE_DEPTH_METER if (script) { diff --git a/js/src/jsparse.h b/js/src/jsparse.h index 90499db0bdda..fbe6350e42c2 100644 --- a/js/src/jsparse.h +++ b/js/src/jsparse.h @@ -456,7 +456,8 @@ extern JSScript * js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, JSPrincipals *principals, uint32 tcflags, const jschar *chars, size_t length, - FILE *file, const char *filename, uintN lineno); + FILE *file, const char *filename, uintN lineno, + JSString *source = NULL); extern JSBool js_CompileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipals *principals, diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 93e4be48e3ef..504570d1dffb 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -130,6 +130,7 @@ struct JSScript { #define JSSF_NO_SCRIPT_RVAL 0x01 /* no need for result value of last expression statement */ +#define JSSF_SAVED_CALLER_FUN 0x02 /* object 0 is caller function */ static JS_INLINE uintN StackDepth(JSScript *script) From ea83fa1bc843db8419bb03164039b2727f8f8864 Mon Sep 17 00:00:00 2001 From: Brendan Eich Date: Tue, 27 Jan 2009 15:52:10 -0800 Subject: [PATCH 02/11] Back out due to Mac Tp orange (454184). --- js/src/jsapi.cpp | 4 -- js/src/jsapi.h | 6 +-- js/src/jscntxt.cpp | 53 ++----------------- js/src/jscntxt.h | 62 +++++----------------- js/src/jsgc.cpp | 6 +-- js/src/jsobj.cpp | 122 ++++++-------------------------------------- js/src/jsopcode.cpp | 1 - js/src/jsparse.cpp | 45 ++++------------ js/src/jsparse.h | 3 +- js/src/jsscript.h | 1 - 10 files changed, 46 insertions(+), 257 deletions(-) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index ee72277676c8..0efa4773007f 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -1183,10 +1183,6 @@ JS_GetOptions(JSContext *cx) (cx)->version |= JSVERSION_HAS_XML; \ else \ (cx)->version &= ~JSVERSION_HAS_XML; \ - if ((cx)->options & JSOPTION_ANONFUNFIX) \ - (cx)->version |= JSVERSION_ANONFUNFIX; \ - else \ - (cx)->version &= ~JSVERSION_ANONFUNFIX; \ JS_END_MACRO JS_PUBLIC_API(uint32) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 5552f5ab89bc..dddda3db82b3 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -596,10 +596,8 @@ JS_StringToVersion(const char *string); will be passed to each call to JS_ExecuteScript. */ #define JSOPTION_UNROOTED_GLOBAL JS_BIT(13) /* The GC will not root the - contexts' global objects - (see JS_GetGlobalObject), - leaving that up to the - embedding. */ + global objects leaving + that up to the embedding. */ extern JS_PUBLIC_API(uint32) JS_GetOptions(JSContext *cx); diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 2f4e6c4eebe6..fd33df29233d 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -168,7 +168,7 @@ js_GetCurrentThread(JSRuntime *rt) memset(&thread->traceMonitor, 0, sizeof(thread->traceMonitor)); js_InitJIT(&thread->traceMonitor); #endif - memset(thread->scriptsToGC, 0, sizeof thread->scriptsToGC); + thread->scriptsToGC = NULL; /* * js_SetContextThread initializes the remaining fields as necessary. @@ -197,11 +197,8 @@ js_SetContextThread(JSContext *cx) * current thread. See bug 425828. */ if (JS_CLIST_IS_EMPTY(&thread->contextList)) { - memset(&thread->gsnCache, 0, sizeof thread->gsnCache); - memset(&thread->propertyCache, 0, sizeof thread->propertyCache); -#ifdef DEBUG - memset(&thread->evalCacheMeter, 0, sizeof thread->evalCacheMeter); -#endif + memset(&thread->gsnCache, 0, sizeof(thread->gsnCache)); + memset(&thread->propertyCache, 0, sizeof(thread->propertyCache)); } /* Assert that the previous cx->thread called JS_ClearContextThread(). */ @@ -353,49 +350,6 @@ js_NewContext(JSRuntime *rt, size_t stackChunkSize) return cx; } -#if defined DEBUG && defined XP_UNIX -# include - -static void -DumpEvalCacheMeter(JSContext *cx) -{ - struct { - const char *name; - ptrdiff_t offset; - } table[] = { -#define frob(x) { #x, offsetof(JSEvalCacheMeter, x) } - EVAL_CACHE_METER_LIST(frob) -#undef frob - }; - JSEvalCacheMeter *ecm = &JS_CACHE_LOCUS(cx)->evalCacheMeter; - - static FILE *fp; - if (!fp) { - fp = fopen("/tmp/evalcache.stats", "w"); - if (!fp) - return; - } - - fprintf(fp, "eval cache meter (%p):\n", -#ifdef JS_THREADSAFE - cx->thread -#else - cx->runtime -#endif - ); - for (uintN i = 0; i < JS_ARRAY_LENGTH(table); ++i) { - fprintf(fp, "%-8.8s %llu\n", - table[i].name, *(uint64 *)((uint8 *)ecm + table[i].offset)); - } - fprintf(fp, "hit ratio %g%%\n", ecm->hit * 100. / ecm->probe); - fprintf(fp, "avg steps %g\n", double(ecm->step) / ecm->probe); - fflush(fp); -} -# define DUMP_EVAL_CACHE_METER(cx) DumpEvalCacheMeter(cx) -#else -# define DUMP_EVAL_CACHE_METER(cx) ((void) 0) -#endif - void js_DestroyContext(JSContext *cx, JSDestroyContextMode mode) { @@ -484,7 +438,6 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode) if (last) { js_GC(cx, GC_LAST_CONTEXT); - DUMP_EVAL_CACHE_METER(cx); /* * Free the script filename table if it exists and is empty. Do this diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index f0c7d83af7ab..22f19194b1fb 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -159,31 +159,6 @@ typedef struct JSTraceMonitor { # define JS_EXECUTING_TRACE(cx) JS_FALSE #endif -#ifdef DEBUG -# define JS_EVAL_CACHE_METERING 1 -#endif - -/* Number of potentially reusable scriptsToGC to search for the eval cache. */ -#ifndef JS_EVAL_CACHE_SHIFT -# define JS_EVAL_CACHE_SHIFT 6 -#endif -#define JS_EVAL_CACHE_SIZE JS_BIT(JS_EVAL_CACHE_SHIFT) - -#ifdef JS_EVAL_CACHE_METERING -# define EVAL_CACHE_METER_LIST(_) _(probe), _(hit), _(step), _(noscope) -# define ID(x) x - -/* Have to typedef this for LiveConnect C code, which includes us. */ -typedef struct JSEvalCacheMeter { - uint64 EVAL_CACHE_METER_LIST(ID); -} JSEvalCacheMeter; - -# undef ID -# define DECLARE_EVAL_CACHE_METER JSEvalCacheMeter evalCacheMeter; -#else -# define DECLARE_EVAL_CACHE_METER /* nothing */ -#endif - #ifdef JS_THREADSAFE /* @@ -220,13 +195,14 @@ struct JSThread { JSTraceMonitor traceMonitor; #endif - /* Lock-free hashed lists of scripts created by eval to garbage-collect. */ - JSScript *scriptsToGC[JS_EVAL_CACHE_SIZE]; - - DECLARE_EVAL_CACHE_METER + /* Lock-free list of scripts created by eval to garbage-collect. */ + JSScript *scriptsToGC; }; -#define JS_CACHE_LOCUS(cx) ((cx)->thread) +#define JS_GSN_CACHE(cx) ((cx)->thread->gsnCache) +#define JS_PROPERTY_CACHE(cx) ((cx)->thread->propertyCache) +#define JS_TRACE_MONITOR(cx) ((cx)->thread->traceMonitor) +#define JS_SCRIPTS_TO_GC(cx) ((cx)->thread->scriptsToGC) extern void js_ThreadDestructorCB(void *ptr); @@ -487,12 +463,13 @@ struct JSRuntime { /* Trace-tree JIT recorder/interpreter state. */ JSTraceMonitor traceMonitor; - /* Lock-free hashed lists of scripts created by eval to garbage-collect. */ - JSScript *scriptsToGC[JS_EVAL_CACHE_SIZE]; + /* Lock-free list of scripts created by eval to garbage-collect. */ + JSScript *scriptsToGC; - DECLARE_EVAL_CACHE_METER - -#define JS_CACHE_LOCUS(cx) ((cx)->runtime) +#define JS_GSN_CACHE(cx) ((cx)->runtime->gsnCache) +#define JS_PROPERTY_CACHE(cx) ((cx)->runtime->propertyCache) +#define JS_TRACE_MONITOR(cx) ((cx)->runtime->traceMonitor) +#define JS_SCRIPTS_TO_GC(cx) ((cx)->runtime->scriptsToGC) #endif /* @@ -610,19 +587,6 @@ struct JSRuntime { #endif }; -/* Common macros to access thread-local caches in JSThread or JSRuntime. */ -#define JS_GSN_CACHE(cx) (JS_CACHE_LOCUS(cx)->gsnCache) -#define JS_PROPERTY_CACHE(cx) (JS_CACHE_LOCUS(cx)->propertyCache) -#define JS_TRACE_MONITOR(cx) (JS_CACHE_LOCUS(cx)->traceMonitor) -#define JS_SCRIPTS_TO_GC(cx) (JS_CACHE_LOCUS(cx)->scriptsToGC) - -#ifdef JS_EVAL_CACHE_METERING -# define EVAL_CACHE_METER(x) (JS_CACHE_LOCUS(cx)->evalCacheMeter.x++) -#else -# define EVAL_CACHE_METER(x) ((void) 0) -#endif -#undef DECLARE_EVAL_CACHE_METER - #ifdef DEBUG # define JS_RUNTIME_METER(rt, which) JS_ATOMIC_INCREMENT(&(rt)->which) # define JS_RUNTIME_UNMETER(rt, which) JS_ATOMIC_DECREMENT(&(rt)->which) @@ -1058,8 +1022,6 @@ class JSAutoResolveFlags #define JSVERSION_MASK 0x0FFF /* see JSVersion in jspubtd.h */ #define JSVERSION_HAS_XML 0x1000 /* flag induced by XML option */ -#define JSVERSION_ANONFUNFIX 0x2000 /* see jsapi.h, the comments - for JSOPTION_ANONFUNFIX */ #define JSVERSION_NUMBER(cx) ((JSVersion)((cx)->version & \ JSVERSION_MASK)) diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 51c701d892b8..31a49bb6b110 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3470,8 +3470,7 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) #endif /* Destroy eval'ed scripts. */ - for (i = 0; i < JS_ARRAY_LENGTH(JS_SCRIPTS_TO_GC(cx)); i++) - DestroyScriptsToGC(cx, &JS_SCRIPTS_TO_GC(cx)[i]); + DestroyScriptsToGC(cx, &JS_SCRIPTS_TO_GC(cx)); #ifdef JS_THREADSAFE /* @@ -3493,8 +3492,7 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) #ifdef JS_TRACER js_FlushJITOracle(acx); #endif - for (i = 0; i < JS_ARRAY_LENGTH(acx->thread->scriptsToGC); i++) - DestroyScriptsToGC(cx, &acx->thread->scriptsToGC[i]); + DestroyScriptsToGC(cx, &acx->thread->scriptsToGC); } #else /* The thread-unsafe case just has to clear the runtime's GSN cache. */ diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 5360dc62d2e1..0d06aa54ec26 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -1191,42 +1191,19 @@ js_ComputeFilename(JSContext *cx, JSStackFrame *caller, return caller->script->filename; } -#ifndef EVAL_CACHE_CHAIN_LIMIT -# define EVAL_CACHE_CHAIN_LIMIT 4 -#endif - -static inline JSScript ** -EvalCacheHash(JSContext *cx, JSString *str) -{ - const jschar *s; - size_t n; - uint32 h; - - JSSTRING_CHARS_AND_LENGTH(str, s, n); - if (n > 100) - n = 100; - for (h = 0; n; s++, n--) - h = JS_ROTATE_LEFT32(h, 4) ^ *s; - - h *= JS_GOLDEN_RATIO; - h >>= 32 - JS_EVAL_CACHE_SHIFT; - return &JS_SCRIPTS_TO_GC(cx)[h]; -} - static JSBool obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSStackFrame *fp, *caller; JSBool indirectCall; JSObject *scopeobj; - uint32 tcflags; - JSPrincipals *principals; + JSString *str; const char *file; uintN line; - JSString *str; + JSPrincipals *principals; + uint32 tcflags; JSScript *script; JSBool ok; - JSScript **bucket = NULL; /* avoid GCC warning with early decl&init */ #if JS_HAS_EVAL_THIS_SCOPE JSObject *callerScopeChain = NULL, *callerVarObj = NULL; JSObject *setCallerScopeChain = NULL; @@ -1359,94 +1336,25 @@ obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) goto out; } - tcflags = TCF_COMPILE_N_GO; + str = JSVAL_TO_STRING(argv[0]); if (caller) { - tcflags |= TCF_PUT_STATIC_DEPTH(caller->script->staticDepth + 1); principals = JS_EvalFramePrincipals(cx, fp, caller); file = js_ComputeFilename(cx, caller, principals, &line); } else { - principals = NULL; file = NULL; line = 0; + principals = NULL; } - str = JSVAL_TO_STRING(argv[0]); - script = NULL; - - /* Cache local eval scripts indexed by source qualified by scope. */ - bucket = EvalCacheHash(cx, str); - if (caller->fun) { - uintN count = 0; - JSScript **scriptp = bucket; - - EVAL_CACHE_METER(probe); - while ((script = *scriptp) != NULL) { - if ((script->flags & JSSF_SAVED_CALLER_FUN) && - script->version == cx->version && - (script->principals == principals || - (principals->subsume(principals, script->principals) && - script->principals->subsume(script->principals, principals)))) { - /* - * Get the prior (cache-filling) eval's saved caller function. - * See js_CompileScript in jsparse.cpp. - */ - JSFunction *fun; - JS_GET_SCRIPT_FUNCTION(script, 0, fun); - - if (fun == caller->fun) { - /* - * Get the source string passed for safekeeping in the - * atom map by the prior eval to js_CompileScript. - */ - JSString *src = ATOM_TO_STRING(script->atomMap.vector[0]); - - if (src == str || js_EqualStrings(src, str)) { - /* - * Source matches, qualify by comparing scopeobj to the - * COMPILE_N_GO-memoized parent of the first literal - * function or regexp object if any. If none, then this - * script has no compiled-in dependencies on the prior - * eval's scopeobj. - */ - JSObjectArray *objarray = JS_SCRIPT_OBJECTS(script); - int i = 1; - if (objarray->length == 1) { - if (script->regexpsOffset != 0) { - objarray = JS_SCRIPT_REGEXPS(script); - i = 0; - } else { - EVAL_CACHE_METER(noscope); - i = -1; - } - } - if (i < 0 || - STOBJ_GET_PARENT(objarray->vector[i]) == scopeobj) { - EVAL_CACHE_METER(hit); - *scriptp = script->u.nextToGC; - script->u.nextToGC = NULL; - break; - } - } - } - } - - if (++count == EVAL_CACHE_CHAIN_LIMIT) { - script = NULL; - break; - } - EVAL_CACHE_METER(step); - scriptp = &script->u.nextToGC; - } - } - + tcflags = TCF_COMPILE_N_GO; + if (caller) + tcflags |= TCF_PUT_STATIC_DEPTH(caller->script->staticDepth + 1); + script = js_CompileScript(cx, scopeobj, caller, principals, tcflags, + JSSTRING_CHARS(str), JSSTRING_LENGTH(str), + NULL, file, line); if (!script) { - script = js_CompileScript(cx, scopeobj, caller, principals, tcflags, - JSSTRING_CHARS(str), JSSTRING_LENGTH(str), - NULL, file, line, str); - if (!script) { - ok = JS_FALSE; - goto out; - } + ok = JS_FALSE; + goto out; } if (argc < 2) { @@ -1464,8 +1372,8 @@ obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) if (ok) ok = js_Execute(cx, scopeobj, script, caller, JSFRAME_EVAL, rval); - script->u.nextToGC = *bucket; - *bucket = script; + script->u.nextToGC = JS_SCRIPTS_TO_GC(cx); + JS_SCRIPTS_TO_GC(cx) = script; #ifdef CHECK_SCRIPT_OWNER script->owner = NULL; #endif diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 2d4148aaa6e9..bcb56a1fbd9c 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -2752,7 +2752,6 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop) case JSOP_CALLUPVAR: case JSOP_GETUPVAR: - JS_ASSERT(jp->script->flags & JSSF_SAVED_CALLER_FUN); if (!jp->fun) JS_GET_SCRIPT_FUNCTION(jp->script, 0, jp->fun); diff --git a/js/src/jsparse.cpp b/js/src/jsparse.cpp index 5addc8f744ed..6dfdd6d96def 100644 --- a/js/src/jsparse.cpp +++ b/js/src/jsparse.cpp @@ -503,8 +503,7 @@ extern JSScript * js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, JSPrincipals *principals, uint32 tcflags, const jschar *chars, size_t length, - FILE *file, const char *filename, uintN lineno, - JSString *source) + FILE *file, const char *filename, uintN lineno) { JSParseContext pc; JSArenaPool codePool, notePool; @@ -544,36 +543,16 @@ js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, cg.treeContext.u.scopeChain = scopeChain; cg.staticDepth = TCF_GET_STATIC_DEPTH(tcflags); - /* - * If funpob is non-null after we create the new script, callerFrame->fun - * was saved in the 0th object table entry. - */ - JSParsedObjectBox *funpob = NULL; - - if (tcflags & TCF_COMPILE_N_GO) { - if (source) { - /* - * Save eval program source in script->atomMap.vector[0] for the - * eval cache (see obj_eval in jsobj.cpp). - */ - JSAtom *atom = js_AtomizeString(cx, source, 0); - if (!atom || !js_IndexAtom(cx, atom, &cg.atomList)) - return NULL; - } - - if (callerFrame && callerFrame->fun) { - /* - * An eval script in a caller frame needs to have its enclosing - * function captured in case it uses an upvar reference, and - * someone wishes to decompile it while it's running. - */ - funpob = js_NewParsedObjectBox(cx, &pc, FUN_OBJECT(callerFrame->fun)); - if (!funpob) - return NULL; - funpob->emitLink = cg.objectList.lastPob; - cg.objectList.lastPob = funpob; - cg.objectList.length++; - } + if ((tcflags & TCF_COMPILE_N_GO) && callerFrame && callerFrame->fun) { + /* + * An eval script in a caller frame needs to have its enclosing function + * captured in case it uses an upvar reference, and someone wishes to + * decompile it while running. + */ + JSParsedObjectBox *pob = js_NewParsedObjectBox(cx, &pc, FUN_OBJECT(callerFrame->fun)); + pob->emitLink = cg.objectList.lastPob; + cg.objectList.lastPob = pob; + cg.objectList.length++; } /* Inline Statements() to emit as we go to save space. */ @@ -668,8 +647,6 @@ js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, JS_DumpArenaStats(stdout); #endif script = js_NewScriptFromCG(cx, &cg); - if (script && funpob) - script->flags |= JSSF_SAVED_CALLER_FUN; #ifdef JS_SCOPE_DEPTH_METER if (script) { diff --git a/js/src/jsparse.h b/js/src/jsparse.h index fbe6350e42c2..90499db0bdda 100644 --- a/js/src/jsparse.h +++ b/js/src/jsparse.h @@ -456,8 +456,7 @@ extern JSScript * js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, JSPrincipals *principals, uint32 tcflags, const jschar *chars, size_t length, - FILE *file, const char *filename, uintN lineno, - JSString *source = NULL); + FILE *file, const char *filename, uintN lineno); extern JSBool js_CompileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipals *principals, diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 504570d1dffb..93e4be48e3ef 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -130,7 +130,6 @@ struct JSScript { #define JSSF_NO_SCRIPT_RVAL 0x01 /* no need for result value of last expression statement */ -#define JSSF_SAVED_CALLER_FUN 0x02 /* object 0 is caller function */ static JS_INLINE uintN StackDepth(JSScript *script) From c5808076bde853e5d0cda4a4c826f389e11b1f05 Mon Sep 17 00:00:00 2001 From: Brendan Eich Date: Tue, 27 Jan 2009 16:40:40 -0800 Subject: [PATCH 03/11] Bug 454184 - Implement eval caching (r=mrbkap). --- js/src/jsapi.cpp | 4 ++ js/src/jsapi.h | 6 ++- js/src/jscntxt.cpp | 53 +++++++++++++++++-- js/src/jscntxt.h | 62 +++++++++++++++++----- js/src/jsgc.cpp | 6 ++- js/src/jsobj.cpp | 122 ++++++++++++++++++++++++++++++++++++++------ js/src/jsopcode.cpp | 1 + js/src/jsparse.cpp | 45 ++++++++++++---- js/src/jsparse.h | 3 +- js/src/jsscript.h | 1 + 10 files changed, 257 insertions(+), 46 deletions(-) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 0efa4773007f..ee72277676c8 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -1183,6 +1183,10 @@ JS_GetOptions(JSContext *cx) (cx)->version |= JSVERSION_HAS_XML; \ else \ (cx)->version &= ~JSVERSION_HAS_XML; \ + if ((cx)->options & JSOPTION_ANONFUNFIX) \ + (cx)->version |= JSVERSION_ANONFUNFIX; \ + else \ + (cx)->version &= ~JSVERSION_ANONFUNFIX; \ JS_END_MACRO JS_PUBLIC_API(uint32) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index dddda3db82b3..5552f5ab89bc 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -596,8 +596,10 @@ JS_StringToVersion(const char *string); will be passed to each call to JS_ExecuteScript. */ #define JSOPTION_UNROOTED_GLOBAL JS_BIT(13) /* The GC will not root the - global objects leaving - that up to the embedding. */ + contexts' global objects + (see JS_GetGlobalObject), + leaving that up to the + embedding. */ extern JS_PUBLIC_API(uint32) JS_GetOptions(JSContext *cx); diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index fd33df29233d..2f4e6c4eebe6 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -168,7 +168,7 @@ js_GetCurrentThread(JSRuntime *rt) memset(&thread->traceMonitor, 0, sizeof(thread->traceMonitor)); js_InitJIT(&thread->traceMonitor); #endif - thread->scriptsToGC = NULL; + memset(thread->scriptsToGC, 0, sizeof thread->scriptsToGC); /* * js_SetContextThread initializes the remaining fields as necessary. @@ -197,8 +197,11 @@ js_SetContextThread(JSContext *cx) * current thread. See bug 425828. */ if (JS_CLIST_IS_EMPTY(&thread->contextList)) { - memset(&thread->gsnCache, 0, sizeof(thread->gsnCache)); - memset(&thread->propertyCache, 0, sizeof(thread->propertyCache)); + memset(&thread->gsnCache, 0, sizeof thread->gsnCache); + memset(&thread->propertyCache, 0, sizeof thread->propertyCache); +#ifdef DEBUG + memset(&thread->evalCacheMeter, 0, sizeof thread->evalCacheMeter); +#endif } /* Assert that the previous cx->thread called JS_ClearContextThread(). */ @@ -350,6 +353,49 @@ js_NewContext(JSRuntime *rt, size_t stackChunkSize) return cx; } +#if defined DEBUG && defined XP_UNIX +# include + +static void +DumpEvalCacheMeter(JSContext *cx) +{ + struct { + const char *name; + ptrdiff_t offset; + } table[] = { +#define frob(x) { #x, offsetof(JSEvalCacheMeter, x) } + EVAL_CACHE_METER_LIST(frob) +#undef frob + }; + JSEvalCacheMeter *ecm = &JS_CACHE_LOCUS(cx)->evalCacheMeter; + + static FILE *fp; + if (!fp) { + fp = fopen("/tmp/evalcache.stats", "w"); + if (!fp) + return; + } + + fprintf(fp, "eval cache meter (%p):\n", +#ifdef JS_THREADSAFE + cx->thread +#else + cx->runtime +#endif + ); + for (uintN i = 0; i < JS_ARRAY_LENGTH(table); ++i) { + fprintf(fp, "%-8.8s %llu\n", + table[i].name, *(uint64 *)((uint8 *)ecm + table[i].offset)); + } + fprintf(fp, "hit ratio %g%%\n", ecm->hit * 100. / ecm->probe); + fprintf(fp, "avg steps %g\n", double(ecm->step) / ecm->probe); + fflush(fp); +} +# define DUMP_EVAL_CACHE_METER(cx) DumpEvalCacheMeter(cx) +#else +# define DUMP_EVAL_CACHE_METER(cx) ((void) 0) +#endif + void js_DestroyContext(JSContext *cx, JSDestroyContextMode mode) { @@ -438,6 +484,7 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode) if (last) { js_GC(cx, GC_LAST_CONTEXT); + DUMP_EVAL_CACHE_METER(cx); /* * Free the script filename table if it exists and is empty. Do this diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 22f19194b1fb..f0c7d83af7ab 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -159,6 +159,31 @@ typedef struct JSTraceMonitor { # define JS_EXECUTING_TRACE(cx) JS_FALSE #endif +#ifdef DEBUG +# define JS_EVAL_CACHE_METERING 1 +#endif + +/* Number of potentially reusable scriptsToGC to search for the eval cache. */ +#ifndef JS_EVAL_CACHE_SHIFT +# define JS_EVAL_CACHE_SHIFT 6 +#endif +#define JS_EVAL_CACHE_SIZE JS_BIT(JS_EVAL_CACHE_SHIFT) + +#ifdef JS_EVAL_CACHE_METERING +# define EVAL_CACHE_METER_LIST(_) _(probe), _(hit), _(step), _(noscope) +# define ID(x) x + +/* Have to typedef this for LiveConnect C code, which includes us. */ +typedef struct JSEvalCacheMeter { + uint64 EVAL_CACHE_METER_LIST(ID); +} JSEvalCacheMeter; + +# undef ID +# define DECLARE_EVAL_CACHE_METER JSEvalCacheMeter evalCacheMeter; +#else +# define DECLARE_EVAL_CACHE_METER /* nothing */ +#endif + #ifdef JS_THREADSAFE /* @@ -195,14 +220,13 @@ struct JSThread { JSTraceMonitor traceMonitor; #endif - /* Lock-free list of scripts created by eval to garbage-collect. */ - JSScript *scriptsToGC; + /* Lock-free hashed lists of scripts created by eval to garbage-collect. */ + JSScript *scriptsToGC[JS_EVAL_CACHE_SIZE]; + + DECLARE_EVAL_CACHE_METER }; -#define JS_GSN_CACHE(cx) ((cx)->thread->gsnCache) -#define JS_PROPERTY_CACHE(cx) ((cx)->thread->propertyCache) -#define JS_TRACE_MONITOR(cx) ((cx)->thread->traceMonitor) -#define JS_SCRIPTS_TO_GC(cx) ((cx)->thread->scriptsToGC) +#define JS_CACHE_LOCUS(cx) ((cx)->thread) extern void js_ThreadDestructorCB(void *ptr); @@ -463,13 +487,12 @@ struct JSRuntime { /* Trace-tree JIT recorder/interpreter state. */ JSTraceMonitor traceMonitor; - /* Lock-free list of scripts created by eval to garbage-collect. */ - JSScript *scriptsToGC; + /* Lock-free hashed lists of scripts created by eval to garbage-collect. */ + JSScript *scriptsToGC[JS_EVAL_CACHE_SIZE]; -#define JS_GSN_CACHE(cx) ((cx)->runtime->gsnCache) -#define JS_PROPERTY_CACHE(cx) ((cx)->runtime->propertyCache) -#define JS_TRACE_MONITOR(cx) ((cx)->runtime->traceMonitor) -#define JS_SCRIPTS_TO_GC(cx) ((cx)->runtime->scriptsToGC) + DECLARE_EVAL_CACHE_METER + +#define JS_CACHE_LOCUS(cx) ((cx)->runtime) #endif /* @@ -587,6 +610,19 @@ struct JSRuntime { #endif }; +/* Common macros to access thread-local caches in JSThread or JSRuntime. */ +#define JS_GSN_CACHE(cx) (JS_CACHE_LOCUS(cx)->gsnCache) +#define JS_PROPERTY_CACHE(cx) (JS_CACHE_LOCUS(cx)->propertyCache) +#define JS_TRACE_MONITOR(cx) (JS_CACHE_LOCUS(cx)->traceMonitor) +#define JS_SCRIPTS_TO_GC(cx) (JS_CACHE_LOCUS(cx)->scriptsToGC) + +#ifdef JS_EVAL_CACHE_METERING +# define EVAL_CACHE_METER(x) (JS_CACHE_LOCUS(cx)->evalCacheMeter.x++) +#else +# define EVAL_CACHE_METER(x) ((void) 0) +#endif +#undef DECLARE_EVAL_CACHE_METER + #ifdef DEBUG # define JS_RUNTIME_METER(rt, which) JS_ATOMIC_INCREMENT(&(rt)->which) # define JS_RUNTIME_UNMETER(rt, which) JS_ATOMIC_DECREMENT(&(rt)->which) @@ -1022,6 +1058,8 @@ class JSAutoResolveFlags #define JSVERSION_MASK 0x0FFF /* see JSVersion in jspubtd.h */ #define JSVERSION_HAS_XML 0x1000 /* flag induced by XML option */ +#define JSVERSION_ANONFUNFIX 0x2000 /* see jsapi.h, the comments + for JSOPTION_ANONFUNFIX */ #define JSVERSION_NUMBER(cx) ((JSVersion)((cx)->version & \ JSVERSION_MASK)) diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 31a49bb6b110..51c701d892b8 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3470,7 +3470,8 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) #endif /* Destroy eval'ed scripts. */ - DestroyScriptsToGC(cx, &JS_SCRIPTS_TO_GC(cx)); + for (i = 0; i < JS_ARRAY_LENGTH(JS_SCRIPTS_TO_GC(cx)); i++) + DestroyScriptsToGC(cx, &JS_SCRIPTS_TO_GC(cx)[i]); #ifdef JS_THREADSAFE /* @@ -3492,7 +3493,8 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind) #ifdef JS_TRACER js_FlushJITOracle(acx); #endif - DestroyScriptsToGC(cx, &acx->thread->scriptsToGC); + for (i = 0; i < JS_ARRAY_LENGTH(acx->thread->scriptsToGC); i++) + DestroyScriptsToGC(cx, &acx->thread->scriptsToGC[i]); } #else /* The thread-unsafe case just has to clear the runtime's GSN cache. */ diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 0d06aa54ec26..5360dc62d2e1 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -1191,19 +1191,42 @@ js_ComputeFilename(JSContext *cx, JSStackFrame *caller, return caller->script->filename; } +#ifndef EVAL_CACHE_CHAIN_LIMIT +# define EVAL_CACHE_CHAIN_LIMIT 4 +#endif + +static inline JSScript ** +EvalCacheHash(JSContext *cx, JSString *str) +{ + const jschar *s; + size_t n; + uint32 h; + + JSSTRING_CHARS_AND_LENGTH(str, s, n); + if (n > 100) + n = 100; + for (h = 0; n; s++, n--) + h = JS_ROTATE_LEFT32(h, 4) ^ *s; + + h *= JS_GOLDEN_RATIO; + h >>= 32 - JS_EVAL_CACHE_SHIFT; + return &JS_SCRIPTS_TO_GC(cx)[h]; +} + static JSBool obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSStackFrame *fp, *caller; JSBool indirectCall; JSObject *scopeobj; - JSString *str; + uint32 tcflags; + JSPrincipals *principals; const char *file; uintN line; - JSPrincipals *principals; - uint32 tcflags; + JSString *str; JSScript *script; JSBool ok; + JSScript **bucket = NULL; /* avoid GCC warning with early decl&init */ #if JS_HAS_EVAL_THIS_SCOPE JSObject *callerScopeChain = NULL, *callerVarObj = NULL; JSObject *setCallerScopeChain = NULL; @@ -1336,25 +1359,94 @@ obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) goto out; } - str = JSVAL_TO_STRING(argv[0]); + tcflags = TCF_COMPILE_N_GO; if (caller) { + tcflags |= TCF_PUT_STATIC_DEPTH(caller->script->staticDepth + 1); principals = JS_EvalFramePrincipals(cx, fp, caller); file = js_ComputeFilename(cx, caller, principals, &line); } else { + principals = NULL; file = NULL; line = 0; - principals = NULL; } - tcflags = TCF_COMPILE_N_GO; - if (caller) - tcflags |= TCF_PUT_STATIC_DEPTH(caller->script->staticDepth + 1); - script = js_CompileScript(cx, scopeobj, caller, principals, tcflags, - JSSTRING_CHARS(str), JSSTRING_LENGTH(str), - NULL, file, line); + str = JSVAL_TO_STRING(argv[0]); + script = NULL; + + /* Cache local eval scripts indexed by source qualified by scope. */ + bucket = EvalCacheHash(cx, str); + if (caller->fun) { + uintN count = 0; + JSScript **scriptp = bucket; + + EVAL_CACHE_METER(probe); + while ((script = *scriptp) != NULL) { + if ((script->flags & JSSF_SAVED_CALLER_FUN) && + script->version == cx->version && + (script->principals == principals || + (principals->subsume(principals, script->principals) && + script->principals->subsume(script->principals, principals)))) { + /* + * Get the prior (cache-filling) eval's saved caller function. + * See js_CompileScript in jsparse.cpp. + */ + JSFunction *fun; + JS_GET_SCRIPT_FUNCTION(script, 0, fun); + + if (fun == caller->fun) { + /* + * Get the source string passed for safekeeping in the + * atom map by the prior eval to js_CompileScript. + */ + JSString *src = ATOM_TO_STRING(script->atomMap.vector[0]); + + if (src == str || js_EqualStrings(src, str)) { + /* + * Source matches, qualify by comparing scopeobj to the + * COMPILE_N_GO-memoized parent of the first literal + * function or regexp object if any. If none, then this + * script has no compiled-in dependencies on the prior + * eval's scopeobj. + */ + JSObjectArray *objarray = JS_SCRIPT_OBJECTS(script); + int i = 1; + if (objarray->length == 1) { + if (script->regexpsOffset != 0) { + objarray = JS_SCRIPT_REGEXPS(script); + i = 0; + } else { + EVAL_CACHE_METER(noscope); + i = -1; + } + } + if (i < 0 || + STOBJ_GET_PARENT(objarray->vector[i]) == scopeobj) { + EVAL_CACHE_METER(hit); + *scriptp = script->u.nextToGC; + script->u.nextToGC = NULL; + break; + } + } + } + } + + if (++count == EVAL_CACHE_CHAIN_LIMIT) { + script = NULL; + break; + } + EVAL_CACHE_METER(step); + scriptp = &script->u.nextToGC; + } + } + if (!script) { - ok = JS_FALSE; - goto out; + script = js_CompileScript(cx, scopeobj, caller, principals, tcflags, + JSSTRING_CHARS(str), JSSTRING_LENGTH(str), + NULL, file, line, str); + if (!script) { + ok = JS_FALSE; + goto out; + } } if (argc < 2) { @@ -1372,8 +1464,8 @@ obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) if (ok) ok = js_Execute(cx, scopeobj, script, caller, JSFRAME_EVAL, rval); - script->u.nextToGC = JS_SCRIPTS_TO_GC(cx); - JS_SCRIPTS_TO_GC(cx) = script; + script->u.nextToGC = *bucket; + *bucket = script; #ifdef CHECK_SCRIPT_OWNER script->owner = NULL; #endif diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index bcb56a1fbd9c..2d4148aaa6e9 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -2752,6 +2752,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop) case JSOP_CALLUPVAR: case JSOP_GETUPVAR: + JS_ASSERT(jp->script->flags & JSSF_SAVED_CALLER_FUN); if (!jp->fun) JS_GET_SCRIPT_FUNCTION(jp->script, 0, jp->fun); diff --git a/js/src/jsparse.cpp b/js/src/jsparse.cpp index 6dfdd6d96def..5addc8f744ed 100644 --- a/js/src/jsparse.cpp +++ b/js/src/jsparse.cpp @@ -503,7 +503,8 @@ extern JSScript * js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, JSPrincipals *principals, uint32 tcflags, const jschar *chars, size_t length, - FILE *file, const char *filename, uintN lineno) + FILE *file, const char *filename, uintN lineno, + JSString *source) { JSParseContext pc; JSArenaPool codePool, notePool; @@ -543,16 +544,36 @@ js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, cg.treeContext.u.scopeChain = scopeChain; cg.staticDepth = TCF_GET_STATIC_DEPTH(tcflags); - if ((tcflags & TCF_COMPILE_N_GO) && callerFrame && callerFrame->fun) { - /* - * An eval script in a caller frame needs to have its enclosing function - * captured in case it uses an upvar reference, and someone wishes to - * decompile it while running. - */ - JSParsedObjectBox *pob = js_NewParsedObjectBox(cx, &pc, FUN_OBJECT(callerFrame->fun)); - pob->emitLink = cg.objectList.lastPob; - cg.objectList.lastPob = pob; - cg.objectList.length++; + /* + * If funpob is non-null after we create the new script, callerFrame->fun + * was saved in the 0th object table entry. + */ + JSParsedObjectBox *funpob = NULL; + + if (tcflags & TCF_COMPILE_N_GO) { + if (source) { + /* + * Save eval program source in script->atomMap.vector[0] for the + * eval cache (see obj_eval in jsobj.cpp). + */ + JSAtom *atom = js_AtomizeString(cx, source, 0); + if (!atom || !js_IndexAtom(cx, atom, &cg.atomList)) + return NULL; + } + + if (callerFrame && callerFrame->fun) { + /* + * An eval script in a caller frame needs to have its enclosing + * function captured in case it uses an upvar reference, and + * someone wishes to decompile it while it's running. + */ + funpob = js_NewParsedObjectBox(cx, &pc, FUN_OBJECT(callerFrame->fun)); + if (!funpob) + return NULL; + funpob->emitLink = cg.objectList.lastPob; + cg.objectList.lastPob = funpob; + cg.objectList.length++; + } } /* Inline Statements() to emit as we go to save space. */ @@ -647,6 +668,8 @@ js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, JS_DumpArenaStats(stdout); #endif script = js_NewScriptFromCG(cx, &cg); + if (script && funpob) + script->flags |= JSSF_SAVED_CALLER_FUN; #ifdef JS_SCOPE_DEPTH_METER if (script) { diff --git a/js/src/jsparse.h b/js/src/jsparse.h index 90499db0bdda..fbe6350e42c2 100644 --- a/js/src/jsparse.h +++ b/js/src/jsparse.h @@ -456,7 +456,8 @@ extern JSScript * js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, JSPrincipals *principals, uint32 tcflags, const jschar *chars, size_t length, - FILE *file, const char *filename, uintN lineno); + FILE *file, const char *filename, uintN lineno, + JSString *source = NULL); extern JSBool js_CompileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipals *principals, diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 93e4be48e3ef..504570d1dffb 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -130,6 +130,7 @@ struct JSScript { #define JSSF_NO_SCRIPT_RVAL 0x01 /* no need for result value of last expression statement */ +#define JSSF_SAVED_CALLER_FUN 0x02 /* object 0 is caller function */ static JS_INLINE uintN StackDepth(JSScript *script) From 49ad8d597cae257284957588fabb8f86c66137c5 Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Tue, 27 Jan 2009 16:41:43 -0800 Subject: [PATCH 04/11] Backed out changeset d50d3681b94e (attempted re-landing of 474771). --- js/src/jscntxt.h | 2 -- js/src/jsinterp.cpp | 25 +++++++++---------------- js/src/jsobj.cpp | 2 +- js/src/jsstaticcheck.h | 4 ++-- js/src/jstracer.cpp | 33 +++++++++------------------------ js/src/trace-test.js | 30 ++++++++++++++++++++++++++++++ 6 files changed, 51 insertions(+), 45 deletions(-) diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index f48d0ff472ee..4dec415c58c8 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -153,10 +153,8 @@ typedef struct JSTraceMonitor { #ifdef JS_TRACER # define JS_ON_TRACE(cx) (JS_TRACE_MONITOR(cx).onTrace) -# define JS_EXECUTING_TRACE(cx) (JS_ON_TRACE(cx) && !JS_TRACE_MONITOR(cx).recorder) #else # define JS_ON_TRACE(cx) JS_FALSE -# define JS_EXECUTING_TRACE(cx) JS_FALSE #endif #ifdef JS_THREADSAFE diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 28dc7cc319cf..2bb92398ecbb 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -2574,21 +2574,15 @@ js_Interpret(JSContext *cx) #ifdef JS_TRACER /* We had better not be entering the interpreter from JIT-compiled code. */ - TraceRecorder *tr = NULL; - if (JS_ON_TRACE(cx)) { - tr = TRACE_RECORDER(cx); - SET_TRACE_RECORDER(cx, NULL); - JS_TRACE_MONITOR(cx).onTrace = JS_FALSE; - /* - * ON_TRACE means either recording or coming from traced code. - * If there's no recorder (the latter case), don't care. - */ - if (tr) { - if (tr->wasDeepAborted()) - tr->removeFragmentoReferences(); - else - tr->pushAbortStack(); - } + TraceRecorder *tr = TRACE_RECORDER(cx); + SET_TRACE_RECORDER(cx, NULL); + /* If a recorder is pending and we try to re-enter the interpreter, flag + the recorder to be destroyed when we return. */ + if (tr) { + if (tr->wasDeepAborted()) + tr->removeFragmentoReferences(); + else + tr->pushAbortStack(); } #endif @@ -7089,7 +7083,6 @@ js_Interpret(JSContext *cx) #ifdef JS_TRACER if (tr) { - JS_TRACE_MONITOR(cx).onTrace = JS_TRUE; SET_TRACE_RECORDER(cx, tr); if (!tr->wasDeepAborted()) { tr->popAbortStack(); diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 0d06aa54ec26..9f2c143d5111 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -3614,7 +3614,7 @@ js_FindPropertyHelper(JSContext *cx, jsid id, JSObject **objp, JSProperty *prop; JSScopeProperty *sprop; - JS_ASSERT_IF(entryp, !JS_EXECUTING_TRACE(cx)); + JS_ASSERT_IF(entryp, !JS_ON_TRACE(cx)); obj = js_GetTopStackFrame(cx)->scopeChain; shape = OBJ_SHAPE(obj); for (scopeIndex = 0; ; scopeIndex++) { diff --git a/js/src/jsstaticcheck.h b/js/src/jsstaticcheck.h index 001c94ca7531..54080013d896 100644 --- a/js/src/jsstaticcheck.h +++ b/js/src/jsstaticcheck.h @@ -55,14 +55,14 @@ inline JS_FORCES_STACK void VOUCH_DOES_NOT_REQUIRE_STACK() {} inline JS_FORCES_STACK void JS_ASSERT_NOT_EXECUTING_TRACE(JSContext *cx) { - JS_ASSERT(!JS_EXECUTING_TRACE(cx)); + JS_ASSERT(!JS_ON_TRACE(cx)); } #else #define MUST_FLOW_THROUGH(label) ((void) 0) #define MUST_FLOW_LABEL(label) #define VOUCH_DOES_NOT_REQUIRE_STACK() ((void) 0) -#define JS_ASSERT_NOT_EXECUTING_TRACE(cx) JS_ASSERT(!JS_EXECUTING_TRACE(cx)) +#define JS_ASSERT_NOT_EXECUTING_TRACE(cx) JS_ASSERT(!JS_ON_TRACE(cx)) #endif #endif /* jsstaticcheck_h___ */ diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index 7c099fcd4193..04baa622a65e 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -2935,9 +2935,6 @@ js_DeleteRecorder(JSContext* cx) JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); /* Aborting and completing a trace end up here. */ - JS_ASSERT(tm->onTrace); - tm->onTrace = false; - delete tm->recorder; tm->recorder = NULL; } @@ -2965,15 +2962,6 @@ js_StartRecorder(JSContext* cx, VMSideExit* anchor, Fragment* f, TreeInfo* ti, { JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); - /* - * Emulate on-trace semantics and avoid rooting headaches while recording, - * by suppressing last-ditch GC attempts while recording a trace. This does - * means that trace recording must not nest or the following assertion will - * botch. - */ - JS_ASSERT(!tm->onTrace); - tm->onTrace = true; - /* start recording if no exception during construction */ tm->recorder = new (&gc) TraceRecorder(cx, anchor, f, ti, stackSlots, ngslots, typeMap, @@ -3867,15 +3855,12 @@ js_ExecuteTree(JSContext* cx, Fragment* f, uintN& inlineCallCount, #endif #endif - /* - * We may be called from js_MonitorLoopEdge while not recording, or while - * recording. Rather than over-generalize by using a counter instead of a - * flag, we simply sample and update tm->onTrace if necessary. - */ - bool onTrace = tm->onTrace; - if (!onTrace) - tm->onTrace = true; - VMSideExit* lr; + /* Set a flag that indicates to the runtime system that we are running in native code + now and we don't want automatic GC to happen. Instead we will get a silent failure, + which will cause a trace exit at which point the interpreter re-tries the operation + and eventually triggers the GC. */ + JS_ASSERT(!tm->onTrace); + tm->onTrace = true; debug_only(fflush(NULL);) GuardRecord* rec; @@ -3884,13 +3869,13 @@ js_ExecuteTree(JSContext* cx, Fragment* f, uintN& inlineCallCount, #else rec = u.func(&state, NULL); #endif - lr = (VMSideExit*)rec->exit; + VMSideExit* lr = (VMSideExit*)rec->exit; AUDIT(traceTriggered); JS_ASSERT(lr->exitType != LOOP_EXIT || !lr->calldepth); - tm->onTrace = onTrace; + tm->onTrace = false; /* Except if we find that this is a nested bailout, the guard the call returned is the one we have to use to adjust pc and sp. */ @@ -4460,7 +4445,7 @@ js_FlushJITCache(JSContext* cx) JS_FORCES_STACK JSStackFrame * js_GetTopStackFrame(JSContext *cx) { - if (JS_EXECUTING_TRACE(cx)) { + if (JS_ON_TRACE(cx)) { /* * TODO: If executing a tree, synthesize stack frames and bail off * trace. See bug 462027. diff --git a/js/src/trace-test.js b/js/src/trace-test.js index 179716559a74..3b2207618895 100644 --- a/js/src/trace-test.js +++ b/js/src/trace-test.js @@ -4041,6 +4041,36 @@ function testReverseArgTypes() { testReverseArgTypes.expected = 1; test(testReverseArgTypes); +function testInterpreterReentry() { + this.__defineSetter__('x', function(){}) + for (var j = 0; j < 5; ++j) { x = 3; } + return 1; +} +testInterpreterReentry.expected = 1; +test(testInterpreterReentry); + +function testInterpreterReentry2() { + var a = false; + var b = {}; + var c = false; + var d = {}; + this.__defineGetter__('e', function(){}); + for (let f in this) print(f); + [1 for each (g in this) for each (h in [])] + return 1; +} +testInterpreterReentry2.expected = 1; +test(testInterpreterReentry2); + +function testInterpreterReentry3() { + for (let i=0;i<5;++i) this["y" + i] = function(){}; + this.__defineGetter__('e', function (x2) { yield; }); + [1 for each (a in this) for (b in {})]; + return 1; +} +testInterpreterReentry3.expected = 1; +test(testInterpreterReentry3); + /***************************************************************************** * * * _____ _ _ _____ ______ _____ _______ * From bfb8f4fcede7574593e77bd869073b7902319f86 Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Tue, 27 Jan 2009 17:06:38 -0800 Subject: [PATCH 05/11] Properly initialize demote flag (475479, r=danderson). --- js/src/jstracer.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index dab1b1e4764f..f9fc84fc8079 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -2298,8 +2298,6 @@ TraceRecorder::deduceTypeStability(Fragment* root_peer, Fragment** stable_peer, if (stable_peer) *stable_peer = NULL; - demote = false; - /* * Rather than calculate all of this stuff twice, it gets cached locally. The "stage" buffers * are for calls to set() that will change the exit types. @@ -3465,7 +3463,7 @@ js_CloseLoop(JSContext* cx) return false; } - bool demote; + bool demote = false; Fragment* f = r->getFragment(); r->closeLoop(tm, demote); js_DeleteRecorder(cx); From 56836565dee0e0e3b02092590fddbeadef400b32 Mon Sep 17 00:00:00 2001 From: Blake Kaplan Date: Tue, 27 Jan 2009 17:10:44 -0800 Subject: [PATCH 06/11] Bug 475469 - Don't assume cx->fp is a scripted frame. r=dmandelin --- js/src/jsregexp.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/js/src/jsregexp.cpp b/js/src/jsregexp.cpp index b28ba93848f9..5e9221e0848b 100644 --- a/js/src/jsregexp.cpp +++ b/js/src/jsregexp.cpp @@ -3831,11 +3831,16 @@ MatchRegExp(REGlobalData *gData, REMatchState *x) (native = GetNativeRegExp(gData->cx, gData->regexp))) { gData->skipped = (ptrdiff_t) x->cp; - debug_only_v(printf("entering REGEXP trace at %s:%u@%u, code: %p\n", - gData->cx->fp->script->filename, - js_FramePCToLineNumber(gData->cx, gData->cx->fp), - FramePCOffset(gData->cx->fp), - native);); +#ifdef JS_JIT_SPEW + { + JSStackFrame *caller = js_GetScriptedCaller(gData->cx, NULL); + debug_only_v(printf("entering REGEXP trace at %s:%u@%u, code: %p\n", + caller ? caller->script->filename : "", + caller ? js_FramePCToLineNumber(gData->cx, caller) : 0, + caller ? FramePCOffset(caller) : 0, + (void *) native);); + } +#endif #if defined(JS_NO_FASTCALL) && defined(NANOJIT_IA32) SIMULATE_FASTCALL(result, x, gData, native); From fe3b4d73fcadb7402fd3b54ed4ab1b7ebf02ec75 Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Wed, 28 Jan 2009 00:12:37 -0800 Subject: [PATCH 07/11] Update the tracker if the global object's dslots are reallocated at recording time (475645, r=brendan). --- js/src/jstracer.cpp | 52 ++++++++++++++++++++++++++++++++++----------- js/src/jstracer.h | 6 ++++-- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index f9fc84fc8079..b1ba066bdb9d 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -1722,6 +1722,7 @@ TraceRecorder::import(LIns* base, ptrdiff_t offset, jsval* p, uint8& t, ins = lir->insLoad(LIR_ldp, base, offset); } } + checkForGlobalObjectReallocation(); tracker.set(p, ins); #ifdef DEBUG char name[64]; @@ -1809,7 +1810,7 @@ TraceRecorder::lazilyImportGlobalSlot(unsigned slot) if (slot != uint16(slot)) /* we use a table of 16-bit ints, bail out if that's not enough */ return false; jsval* vp = &STOBJ_GET_SLOT(globalObj, slot); - if (tracker.has(vp)) + if (known(vp)) return true; /* we already have it */ unsigned index = traceMonitor->globalSlots->length(); /* If this the first global we are adding, remember the shape of the global object. */ @@ -1841,7 +1842,8 @@ TraceRecorder::writeBack(LIns* i, LIns* base, ptrdiff_t offset) JS_REQUIRES_STACK void TraceRecorder::set(jsval* p, LIns* i, bool initializing) { - JS_ASSERT(initializing || tracker.has(p)); + JS_ASSERT(initializing || known(p)); + checkForGlobalObjectReallocation(); tracker.set(p, i); /* If we are writing to this location for the first time, calculate the offset into the native frame manually, otherwise just look up the last load or store associated with @@ -1873,11 +1875,43 @@ TraceRecorder::set(jsval* p, LIns* i, bool initializing) } JS_REQUIRES_STACK LIns* -TraceRecorder::get(jsval* p) const +TraceRecorder::get(jsval* p) { + checkForGlobalObjectReallocation(); return tracker.get(p); } +JS_REQUIRES_STACK bool +TraceRecorder::known(jsval* p) +{ + checkForGlobalObjectReallocation(); + return tracker.has(p); +} + +/* + * The dslots of the global object are sometimes reallocated by the interpreter. + * This function check for that condition and re-maps the entries of the tracker + * accordingly. + */ +JS_REQUIRES_STACK void +TraceRecorder::checkForGlobalObjectReallocation() +{ + if (global_dslots != globalObj->dslots) { + debug_only_v(printf("globalObj->dslots relocated, updating tracker\n");) + jsval* src = global_dslots; + jsval* dst = globalObj->dslots; + jsuint length = globalObj->dslots[-1] - JS_INITIAL_NSLOTS; + LIns** map = (LIns**)alloca(sizeof(LIns*) * length); + for (jsuint n = 0; n < length; ++n) { + map[n] = tracker.get(src); + tracker.set(src++, NULL); + } + for (jsuint n = 0; n < length; ++n) + tracker.set(dst++, map[n]); + global_dslots = globalObj->dslots; + } +} + /* Determine whether the current branch instruction terminates the loop. */ static bool js_IsLoopExit(jsbytecode* pc, jsbytecode* header) @@ -1997,7 +2031,7 @@ TraceRecorder::adjustCallerTypes(Fragment* f) } JS_REQUIRES_STACK uint8 -TraceRecorder::determineSlotType(jsval* vp) const +TraceRecorder::determineSlotType(jsval* vp) { uint8 m; LIns* i = get(vp); @@ -4142,12 +4176,6 @@ TraceRecorder::monitorRecording(JSContext* cx, TraceRecorder* tr, JSOp op) // opcode-case-guts record hook (record_FastNativeCallComplete). tr->pendingTraceableNative = NULL; - // In the future, handle dslots realloc by computing an offset from dslots instead. - if (tr->global_dslots != tr->globalObj->dslots) { - js_AbortRecording(cx, "globalObj->dslots reallocated"); - return JSMRS_STOP; - } - jsbytecode* pc = cx->fp->regs->pc; /* If we hit a break, end the loop and generate an always taken loop exit guard. For other @@ -5972,9 +6000,9 @@ TraceRecorder::record_JSOP_PICK() jsval* sp = cx->fp->regs->sp; jsint n = cx->fp->regs->pc[1]; JS_ASSERT(sp - (n+1) >= StackBase(cx->fp)); - LIns* top = tracker.get(sp - (n+1)); + LIns* top = get(sp - (n+1)); for (jsint i = 0; i < n; ++i) - set(sp - (n+1) + i, tracker.get(sp - n + i)); + set(sp - (n+1) + i, get(sp - n + i)); set(&sp[-1], top); return true; } diff --git a/js/src/jstracer.h b/js/src/jstracer.h index 6ecfe9a87950..f910ef4b5ade 100644 --- a/js/src/jstracer.h +++ b/js/src/jstracer.h @@ -373,9 +373,11 @@ class TraceRecorder : public avmplus::GCObject { nanojit::LIns* addName(nanojit::LIns* ins, const char* name); - JS_REQUIRES_STACK nanojit::LIns* get(jsval* p) const; nanojit::LIns* writeBack(nanojit::LIns* i, nanojit::LIns* base, ptrdiff_t offset); JS_REQUIRES_STACK void set(jsval* p, nanojit::LIns* l, bool initializing = false); + JS_REQUIRES_STACK nanojit::LIns* get(jsval* p); + JS_REQUIRES_STACK bool known(jsval* p); + JS_REQUIRES_STACK void checkForGlobalObjectReallocation(); JS_REQUIRES_STACK bool checkType(jsval& v, uint8 t, jsval*& stage_val, nanojit::LIns*& stage_ins, unsigned& stage_count); @@ -492,7 +494,7 @@ public: static JS_REQUIRES_STACK JSMonitorRecordingStatus monitorRecording(JSContext* cx, TraceRecorder* tr, JSOp op); - JS_REQUIRES_STACK uint8 determineSlotType(jsval* vp) const; + JS_REQUIRES_STACK uint8 determineSlotType(jsval* vp); JS_REQUIRES_STACK nanojit::LIns* snapshot(ExitType exitType); nanojit::Fragment* getFragment() const { return fragment; } JS_REQUIRES_STACK bool isLoopHeader(JSContext* cx) const; From c83d6719d62f606fde2086418186563f1370b527 Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Wed, 28 Jan 2009 00:35:20 -0800 Subject: [PATCH 08/11] Abort if we hit SETGVAR with a NULL slot (465567, r=brendan). --- js/src/jsinterp.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 2bb92398ecbb..d721991b5d16 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -5610,6 +5610,10 @@ js_Interpret(JSContext *cx) * JSOP_SETGVAR has arity 1: [rval], not arity 2: [obj, rval] * as JSOP_SETNAME does, where [obj] is due to JSOP_BINDNAME. */ +#ifdef JS_TRACER + if (TRACE_RECORDER(cx)) + js_AbortRecording(cx, "SETGVAR with NULL slot"); +#endif LOAD_ATOM(0); id = ATOM_TO_JSID(atom); if (!OBJ_SET_PROPERTY(cx, obj, id, &rval)) From caef58e9a880156369a688439342682b8f0b7eee Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Wed, 28 Jan 2009 04:24:13 -0800 Subject: [PATCH 09/11] Trigger a new build. --- js/src/jsfile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/jsfile.cpp b/js/src/jsfile.cpp index 15d323d9e33c..0211e3b298f4 100644 --- a/js/src/jsfile.cpp +++ b/js/src/jsfile.cpp @@ -304,7 +304,7 @@ js_fileBaseName(JSContext *cx, const char *pathname) index = strlen(pathname)-1; - /* Chop off trailing seperators. */ + /* Chop off trailing separators. */ while (index > 0 && (pathname[index]==FILESEPARATOR || pathname[index]==FILESEPARATOR2)) { --index; From f66fcb2aad869266d16d3524e315eb7b078594c7 Mon Sep 17 00:00:00 2001 From: Jason Orendorff Date: Wed, 28 Jan 2009 09:24:35 -0600 Subject: [PATCH 10/11] Bug 468782 - TM: js_FastValueToIterator and js_FastCallIteratorNext can reenter (relanding with a bug fix). r=brendan. Note that this changeset alone does not fix the bug; an upcoming patch in bug 462027 completes the fix. --- js/src/builtins.tbl | 2 - js/src/imacro_asm.js.in | 33 ++++++- js/src/imacros.c.out | 64 +++++++++++- js/src/imacros.jsasm | 60 ++++++++++++ js/src/jsbuiltins.cpp | 17 ---- js/src/jscntxt.h | 14 +++ js/src/jsgc.cpp | 5 + js/src/jsinterp.cpp | 21 +++- js/src/jsopcode.tbl | 2 +- js/src/jstracer.cpp | 211 +++++++++++++++++++++------------------- js/src/jstracer.h | 4 +- js/src/trace-test.js | 36 +++++++ 12 files changed, 339 insertions(+), 130 deletions(-) diff --git a/js/src/builtins.tbl b/js/src/builtins.tbl index 295bc8d636d2..5fcd4801e20d 100644 --- a/js/src/builtins.tbl +++ b/js/src/builtins.tbl @@ -81,8 +81,6 @@ BUILTIN3(extern, JSVAL, js_Any_getprop, CONTEXT, OBJECT, STRING, BUILTIN4(extern, BOOL, js_Any_setprop, CONTEXT, OBJECT, STRING, JSVAL, 0, 0) BUILTIN3(extern, JSVAL, js_Any_getelem, CONTEXT, OBJECT, INT32, 0, 0) BUILTIN4(extern, BOOL, js_Any_setelem, CONTEXT, OBJECT, INT32, JSVAL, 0, 0) -BUILTIN3(extern, OBJECT, js_FastValueToIterator, CONTEXT, UINT32, JSVAL, 0, 0) -BUILTIN2(extern, JSVAL, js_FastCallIteratorNext, CONTEXT, OBJECT, 0, 0) BUILTIN2(FRIEND, BOOL, js_CloseIterator, CONTEXT, JSVAL, 0, 0) BUILTIN2(extern, SIDEEXIT, js_CallTree, INTERPSTATE, FRAGMENT, 0, 0) BUILTIN2(extern, OBJECT, js_FastNewObject, CONTEXT, OBJECT, 0, 0) diff --git a/js/src/imacro_asm.js.in b/js/src/imacro_asm.js.in index 1dd465d7617c..28b2d0aa2da6 100644 --- a/js/src/imacro_asm.js.in +++ b/js/src/imacro_asm.js.in @@ -98,22 +98,42 @@ function formatoffset(n, w) { function immediate(op) { let info = op.info; + let imm1Expr = /^\(/.test(op.imm1); if (info.flags.indexOf("JOF_ATOM") >= 0) { if (/^(?:void|object|function|string|number|boolean)$/.test(op.imm1)) return "0, COMMON_TYPE_ATOM_INDEX(JSTYPE_" + op.imm1.toUpperCase() + ")"; return "0, COMMON_ATOM_INDEX(" + op.imm1 + ")"; } - if (info.flags.indexOf("JOF_JUMP") >= 0) + if (info.flags.indexOf("JOF_JUMP") >= 0) { + ASSERT(!imm1Expr); return ((op.target >> 8) & 0xff) + ", " + (op.target & 0xff); + } if (info.flags.indexOf("JOF_UINT8") >= 0 || info.flags.indexOf("JOF_INT8") >= 0) { + if (imm1Expr) + return op.imm1; + if (isNaN(Number(op.imm1)) || Number(op.imm1) != parseInt(op.imm1)) + throw new Error("invalid 8-bit operand: " + op.imm1); return (op.imm1 & 0xff); } - if (info.flags.indexOf("JOF_UINT16") >= 0) + if (info.flags.indexOf("JOF_UINT16") >= 0) { + if (imm1Expr) + return '(_ & 0xff00) >> 8, (_ & 0xff)'.replace(/_/g, op.imm1); return ((op.imm1 & 0xff00) >> 8) + ", " + (op.imm1 & 0xff); + } throw new Error(info.jsop + " format not yet implemented"); } +const line_regexp_parts = [ + "^(?:(\\w+):)?", + "\\s*(\\.?\\w+)", + "(?:\\s+(\\w+|\\([^)]*\\)))?", + "(?:\\s+([\\w-]+|\\([^)]*\\)))?", + "(?:\\s*(?:#.*))?$" +]; + +const line_regexp = new RegExp(line_regexp_parts.join("")); + /* * Syntax (spaces are significant only to delimit tokens): * @@ -121,10 +141,13 @@ function immediate(op) { * Directive ::= (name ':')? Operation * Operation ::= opname Operands? * Operands ::= Operand (',' Operand)* - * Operand ::= name | number + * Operand ::= name | number | '(' Expr ')' + * Expr ::= a constant-expression in the C++ language + * containing no parentheses * * We simplify given line structure and the maximum of one immediate operand, - * by parsing using split and regexps. + * by parsing using split and regexps. For ease of parsing, parentheses are + * banned in an Expr for now, even in quotes or a C++ comment. * * Pseudo-ops start with . and include .igroup and .imacro, terminated by .end. * .imacro must nest in .igroup, neither nests in itself. See imacros.jsasm for @@ -141,7 +164,7 @@ function assemble(filename) { for (let i = 0; i < a.length; i++) { if (/^\s*(?:#.*)?$/.test(a[i])) continue; - let m = /(?:(\w+):)?\s*(\.?\w+)(?:\s+(\w+))?(?:\s+([\w-]+))?(?:\s*(?:#.*))?$/.exec(a[i]); + let m = line_regexp.exec(a[i]); if (!m) throw new Error(a[i]); diff --git a/js/src/imacros.c.out b/js/src/imacros.c.out index d9ecc59adc6e..6f9b21c9d89e 100644 --- a/js/src/imacros.c.out +++ b/js/src/imacros.c.out @@ -536,6 +536,64 @@ static struct { /* 6*/ JSOP_STOP, }, }; +static struct { + jsbytecode for_in[10]; + jsbytecode for_each[10]; + jsbytecode for_in_native[10]; + jsbytecode for_each_native[10]; +} iter_imacros = { + { +/* 0*/ JSOP_CALLPROP, 0, COMMON_ATOM_INDEX(iterator), +/* 3*/ JSOP_INT8, (JSITER_ENUMERATE), +/* 5*/ JSOP_CALL, 0, 1, +/* 8*/ JSOP_PUSH, +/* 9*/ JSOP_STOP, + }, + { +/* 0*/ JSOP_CALLPROP, 0, COMMON_ATOM_INDEX(iterator), +/* 3*/ JSOP_INT8, (JSITER_ENUMERATE|JSITER_FOREACH), +/* 5*/ JSOP_CALL, 0, 1, +/* 8*/ JSOP_PUSH, +/* 9*/ JSOP_STOP, + }, + { +/* 0*/ JSOP_CALLBUILTIN, ((JSBUILTIN_ObjectToIterator) & 0xff00) >> 8, ((JSBUILTIN_ObjectToIterator) & 0xff), +/* 3*/ JSOP_INT8, (JSITER_ENUMERATE), +/* 5*/ JSOP_CALL, 0, 1, +/* 8*/ JSOP_PUSH, +/* 9*/ JSOP_STOP, + }, + { +/* 0*/ JSOP_CALLBUILTIN, ((JSBUILTIN_ObjectToIterator) & 0xff00) >> 8, ((JSBUILTIN_ObjectToIterator) & 0xff), +/* 3*/ JSOP_INT8, (JSITER_ENUMERATE|JSITER_FOREACH), +/* 5*/ JSOP_CALL, 0, 1, +/* 8*/ JSOP_PUSH, +/* 9*/ JSOP_STOP, + }, +}; +static struct { + jsbytecode custom_iter_next[10]; + jsbytecode native_iter_next[12]; +} nextiter_imacros = { + { +/* 0*/ JSOP_POP, +/* 1*/ JSOP_DUP, +/* 2*/ JSOP_CALLPROP, 0, COMMON_ATOM_INDEX(next), +/* 5*/ JSOP_CALL, 0, 0, +/* 8*/ JSOP_TRUE, +/* 9*/ JSOP_STOP, + }, + { +/* 0*/ JSOP_POP, +/* 1*/ JSOP_DUP, +/* 2*/ JSOP_CALLBUILTIN, ((JSBUILTIN_CallIteratorNext) & 0xff00) >> 8, ((JSBUILTIN_CallIteratorNext) & 0xff), +/* 5*/ JSOP_CALL, 0, 0, +/* 8*/ JSOP_DUP, +/* 9*/ JSOP_HOLE, +/*10*/ JSOP_STRICTNE, +/*11*/ JSOP_STOP, + }, +}; uint8 js_opcode2extra[JSOP_LIMIT] = { 0, /* JSOP_NOP */ 0, /* JSOP_PUSH */ @@ -612,8 +670,8 @@ uint8 js_opcode2extra[JSOP_LIMIT] = { 0, /* JSOP_STRICTEQ */ 0, /* JSOP_STRICTNE */ 0, /* JSOP_NULLTHIS */ - 0, /* JSOP_ITER */ - 0, /* JSOP_NEXTITER */ + 3, /* JSOP_ITER */ + 2, /* JSOP_NEXTITER */ 0, /* JSOP_ENDITER */ 7, /* JSOP_APPLY */ 0, /* JSOP_SWAP */ @@ -763,7 +821,7 @@ uint8 js_opcode2extra[JSOP_LIMIT] = { 0, /* JSOP_CALLGVAR */ 0, /* JSOP_CALLLOCAL */ 0, /* JSOP_CALLARG */ - 0, /* JSOP_UNUSED226 */ + 0, /* JSOP_CALLBUILTIN */ 0, /* JSOP_INT8 */ 0, /* JSOP_INT32 */ 0, /* JSOP_LENGTH */ diff --git a/js/src/imacros.jsasm b/js/src/imacros.jsasm index 6ed1e0599482..e9fce508e059 100644 --- a/js/src/imacros.jsasm +++ b/js/src/imacros.jsasm @@ -575,3 +575,63 @@ .end # .end + +.igroup iter JSOP_ITER + + .imacro for_in # obj + callprop iterator # fun obj + int8 (JSITER_ENUMERATE) # fun obj flags + call 1 # iterobj + push # iterobj undef + stop + .end + + .imacro for_each # obj + callprop iterator # fun obj + int8 (JSITER_ENUMERATE|JSITER_FOREACH) # fun obj flags + call 1 # iterobj + push # iterobj undef + stop + .end + + .imacro for_in_native # obj + callbuiltin (JSBUILTIN_ObjectToIterator) # fun obj + int8 (JSITER_ENUMERATE) # fun obj flags + call 1 # iterobj + push # iterobj undef + stop + .end + + .imacro for_each_native # obj + callbuiltin (JSBUILTIN_ObjectToIterator) # fun obj + int8 (JSITER_ENUMERATE|JSITER_FOREACH) # fun obj flags + call 1 # iterobj + push # iterobj undef + stop + .end + +.end + +.igroup nextiter JSOP_NEXTITER + + .imacro custom_iter_next # iterobj prevval + pop # iterobj + dup # iterobj iterobj + callprop next # iterobj fun iterobj + call 0 # iterobj nextval + true # iterobj nextval true + stop + .end + + .imacro native_iter_next # iterobj prevval + pop # iterobj + dup # iterobj iterobj + callbuiltin (JSBUILTIN_CallIteratorNext) # iterobj fun iterobj + call 0 # iterobj nextval? + dup # iterobj nextval? nextval? + hole # iterobj nextval? nextval? hole + strictne # iterobj nextval? boolean + stop + .end + +.end diff --git a/js/src/jsbuiltins.cpp b/js/src/jsbuiltins.cpp index e3023986f282..158fc71d6523 100644 --- a/js/src/jsbuiltins.cpp +++ b/js/src/jsbuiltins.cpp @@ -243,23 +243,6 @@ js_Any_setelem(JSContext* cx, JSObject* obj, int32 index, jsval v) return OBJ_SET_PROPERTY(cx, obj, id, &v); } -JSObject* FASTCALL -js_FastValueToIterator(JSContext* cx, jsuint flags, jsval v) -{ - if (!js_ValueToIterator(cx, flags, &v)) - return NULL; - return JSVAL_TO_OBJECT(v); -} - -jsval FASTCALL -js_FastCallIteratorNext(JSContext* cx, JSObject* iterobj) -{ - jsval v; - if (!js_CallIteratorNext(cx, iterobj, &v)) - return JSVAL_ERROR_COOKIE; - return v; -} - SideExit* FASTCALL js_CallTree(InterpState* state, Fragment* f) { diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 8c7b4ec9b47f..31b584c71beb 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -254,6 +254,12 @@ typedef enum JSRuntimeState { JSRTS_LANDING } JSRuntimeState; +typedef enum JSBuiltinFunctionId { + JSBUILTIN_ObjectToIterator, + JSBUILTIN_CallIteratorNext, + JSBUILTIN_LIMIT +} JSBuiltinFunctionId; + typedef struct JSPropertyTreeEntry { JSDHashEntryHdr hdr; JSScopeProperty *child; @@ -363,6 +369,14 @@ struct JSRuntime { JSString *emptyString; JSString **unitStrings; + /* + * Builtin functions, lazily created and held for use by the trace recorder. + * + * This field would be #ifdef JS_TRACER, but XPConnect is compiled without + * -DJS_TRACER and includes this header. + */ + JSObject *builtinFunctions[JSBUILTIN_LIMIT]; + /* List of active contexts sharing this runtime; protected by gcLock. */ JSCList contextList; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 51c701d892b8..b0e8b7b47fde 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3131,6 +3131,11 @@ js_TraceRuntime(JSTracer *trc, JSBool allAtoms) rt->gcExtraRootsTraceOp(trc, rt->gcExtraRootsData); #ifdef JS_TRACER + for (int i = 0; i < JSBUILTIN_LIMIT; i++) { + if (rt->builtinFunctions[i]) + JS_CALL_OBJECT_TRACER(trc, rt->builtinFunctions[i], "builtin function"); + } + #ifdef JS_THREADSAFE /* Trace the loop table(s) which can contain pointers to code objects. */ while ((acx = js_ContextIterator(rt, JS_FALSE, &iter)) != NULL) { diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index d721991b5d16..08db793d025b 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -3215,7 +3215,6 @@ js_Interpret(JSContext *cx) CHECK_INTERRUPT_HANDLER(); rval = BOOLEAN_TO_JSVAL(regs.sp[-1] != JSVAL_HOLE); PUSH(rval); - TRACE_0(IteratorNextComplete); END_CASE(JSOP_NEXTITER) BEGIN_CASE(JSOP_ENDITER) @@ -6724,6 +6723,19 @@ js_Interpret(JSContext *cx) } END_CASE(JSOP_LEAVEBLOCK) + BEGIN_CASE(JSOP_CALLBUILTIN) +#ifdef JS_TRACER + obj = js_GetBuiltinFunction(cx, GET_INDEX(regs.pc)); + if (!obj) + goto error; + rval = FETCH_OPND(-1); + PUSH_OPND(rval); + STORE_OPND(-2, OBJECT_TO_JSVAL(obj)); +#else + goto bad_opcode; /* This is an imacro-only opcode. */ +#endif + END_CASE(JSOP_CALLBUILTIN) + #if JS_HAS_GENERATORS BEGIN_CASE(JSOP_GENERATOR) ASSERT_NOT_THROWING(cx); @@ -6833,10 +6845,12 @@ js_Interpret(JSContext *cx) L_JSOP_UNUSED208: L_JSOP_UNUSED209: L_JSOP_UNUSED219: - L_JSOP_UNUSED226: #else /* !JS_THREADED_INTERP */ default: +#endif +#ifndef JS_TRACER + bad_opcode: #endif { char numBuf[12]; @@ -6855,7 +6869,8 @@ js_Interpret(JSContext *cx) if (fp->imacpc && cx->throwing) { // To keep things simple, we hard-code imacro exception handlers here. if (*fp->imacpc == JSOP_NEXTITER) { - JS_ASSERT(*regs.pc == JSOP_CALL); + // pc may point to JSOP_DUP here due to bug 474854. + JS_ASSERT(*regs.pc == JSOP_CALL || *regs.pc == JSOP_DUP); if (js_ValueIsStopIteration(cx->exception)) { cx->throwing = JS_FALSE; cx->exception = JSVAL_VOID; diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl index bf200c70b3b5..54f97225b337 100644 --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -545,7 +545,7 @@ OPDEF(JSOP_INDEXBASE3, 222,"atombase3", NULL, 1, 0, 0, 0, JOF_BYTE | OPDEF(JSOP_CALLGVAR, 223, "callgvar", NULL, 3, 0, 2, 19, JOF_ATOM|JOF_NAME|JOF_CALLOP) OPDEF(JSOP_CALLLOCAL, 224, "calllocal", NULL, 3, 0, 2, 19, JOF_LOCAL|JOF_NAME|JOF_CALLOP) OPDEF(JSOP_CALLARG, 225, "callarg", NULL, 3, 0, 2, 19, JOF_QARG |JOF_NAME|JOF_CALLOP) -OPDEF(JSOP_UNUSED226, 226, "unused226", NULL, 1, 0, 1, 1, JOF_BYTE) +OPDEF(JSOP_CALLBUILTIN, 226, "callbuiltin", NULL, 3, 0, 2, 0, JOF_UINT16) /* * Opcodes to hold 8-bit and 32-bit immediate integer operands. diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index b1ba066bdb9d..ad6edd0bc56d 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -2106,7 +2106,7 @@ TraceRecorder::snapshot(ExitType exitType) bool resumeAfter = (pendingTraceableNative && JSTN_ERRTYPE(pendingTraceableNative) == FAIL_JSVAL); if (resumeAfter) { - JS_ASSERT(*pc == JSOP_CALL || *pc == JSOP_APPLY || *pc == JSOP_NEXTITER); + JS_ASSERT(*pc == JSOP_CALL || *pc == JSOP_APPLY); pc += cs.length; regs->pc = pc; MUST_FLOW_THROUGH("restore_pc"); @@ -2133,11 +2133,10 @@ TraceRecorder::snapshot(ExitType exitType) ); JS_ASSERT(unsigned(m - typemap) == ngslots + stackSlots); - /* If we are capturing the stack state on a specific instruction, the value on or near - the top of the stack is a boxed value. Either pc[-cs.length] is JSOP_NEXTITER and we - want one below top of stack, or else it's JSOP_CALL and we want top of stack. */ + /* If we are capturing the stack state on a specific instruction, the value on + the top of the stack is a boxed value. */ if (resumeAfter) { - typemap[stackSlots + ((pc[-cs.length] == JSOP_NEXTITER) ? -2 : -1)] = JSVAL_BOXED; + typemap[stackSlots - 1] = JSVAL_BOXED; /* Now restore the the original pc (after which early returns are ok). */ MUST_FLOW_LABEL(restore_pc); @@ -7685,114 +7684,40 @@ TraceRecorder::record_JSOP_IMACOP() return true; } -static struct { - jsbytecode for_in[10]; - jsbytecode for_each[10]; -} iter_imacros = { - { - JSOP_CALLPROP, 0, COMMON_ATOM_INDEX(iterator), - JSOP_INT8, JSITER_ENUMERATE, - JSOP_CALL, 0, 1, - JSOP_PUSH, - JSOP_STOP - }, - - { - JSOP_CALLPROP, 0, COMMON_ATOM_INDEX(iterator), - JSOP_INT8, JSITER_ENUMERATE | JSITER_FOREACH, - JSOP_CALL, 0, 1, - JSOP_PUSH, - JSOP_STOP - } -}; - -JS_STATIC_ASSERT(sizeof(iter_imacros) < IMACRO_PC_ADJ_LIMIT); - JS_REQUIRES_STACK bool TraceRecorder::record_JSOP_ITER() { jsval& v = stackval(-1); - if (!JSVAL_IS_PRIMITIVE(v)) { - jsuint flags = cx->fp->regs->pc[1]; + if (JSVAL_IS_PRIMITIVE(v)) + ABORT_TRACE("for-in on a primitive value"); - if (!hasIteratorMethod(JSVAL_TO_OBJECT(v))) { - LIns* args[] = { get(&v), INS_CONST(flags), cx_ins }; - LIns* v_ins = lir->insCall(&js_FastValueToIterator_ci, args); - guard(false, lir->ins_eq0(v_ins), MISMATCH_EXIT); - set(&v, v_ins); - - LIns* void_ins = INS_CONST(JSVAL_TO_BOOLEAN(JSVAL_VOID)); - stack(0, void_ins); - return true; - } + jsuint flags = cx->fp->regs->pc[1]; + if (hasIteratorMethod(JSVAL_TO_OBJECT(v))) { if (flags == JSITER_ENUMERATE) return call_imacro(iter_imacros.for_in); if (flags == (JSITER_ENUMERATE | JSITER_FOREACH)) return call_imacro(iter_imacros.for_each); - ABORT_TRACE("unimplemented JSITER_* flags"); + } else { + if (flags == JSITER_ENUMERATE) + return call_imacro(iter_imacros.for_in_native); + if (flags == (JSITER_ENUMERATE | JSITER_FOREACH)) + return call_imacro(iter_imacros.for_each_native); } - - ABORT_TRACE("for-in on a primitive value"); + ABORT_TRACE("unimplemented JSITER_* flags"); } -static JSTraceableNative js_FastCallIteratorNext_tn = { - NULL, // JSFastNative native; - &js_FastCallIteratorNext_ci, // const nanojit::CallInfo *builtin; - "C", // const char *prefix; - "o", // const char *argtypes; - FAIL_JSVAL // uintN flags; -}; - -static jsbytecode nextiter_imacro[] = { - JSOP_POP, - JSOP_DUP, - JSOP_CALLPROP, 0, COMMON_ATOM_INDEX(next), - JSOP_CALL, 0, 0, - JSOP_TRUE, - JSOP_STOP -}; - -JS_STATIC_ASSERT(sizeof(nextiter_imacro) < IMACRO_PC_ADJ_LIMIT); - JS_REQUIRES_STACK bool TraceRecorder::record_JSOP_NEXTITER() { jsval& iterobj_val = stackval(-2); - if (!JSVAL_IS_PRIMITIVE(iterobj_val)) { - LIns* iterobj_ins = get(&iterobj_val); + if (JSVAL_IS_PRIMITIVE(iterobj_val)) + ABORT_TRACE("for-in on a primitive value"); - if (guardClass(JSVAL_TO_OBJECT(iterobj_val), iterobj_ins, &js_IteratorClass, BRANCH_EXIT)) { - LIns* args[] = { iterobj_ins, cx_ins }; - LIns* v_ins = lir->insCall(&js_FastCallIteratorNext_ci, args); - guard(false, lir->ins2(LIR_eq, v_ins, INS_CONST(JSVAL_ERROR_COOKIE)), OOM_EXIT); - - LIns* flag_ins = lir->ins_eq0(lir->ins2(LIR_eq, v_ins, INS_CONST(JSVAL_HOLE))); - stack(-1, v_ins); - stack(0, flag_ins); - - pendingTraceableNative = &js_FastCallIteratorNext_tn; - return true; - } - - // Custom iterator, possibly a generator. - return call_imacro(nextiter_imacro); - } - - ABORT_TRACE("for-in on a primitive value"); -} - -JS_REQUIRES_STACK bool -TraceRecorder::record_IteratorNextComplete() -{ - JS_ASSERT(*cx->fp->regs->pc == JSOP_NEXTITER); - JS_ASSERT(pendingTraceableNative == &js_FastCallIteratorNext_tn); - - jsval& v = stackval(-2); - LIns* v_ins = get(&v); - unbox_jsval(v, v_ins); - set(&v, v_ins); - return true; + LIns* iterobj_ins = get(&iterobj_val); + if (guardClass(JSVAL_TO_OBJECT(iterobj_val), iterobj_ins, &js_IteratorClass, BRANCH_EXIT)) + return call_imacro(nextiter_imacros.native_iter_next); + return call_imacro(nextiter_imacros.custom_iter_next); } JS_REQUIRES_STACK bool @@ -8821,6 +8746,97 @@ TraceRecorder::record_JSOP_CALLARG() return true; } +/* Functions for use with JSOP_CALLBUILTIN. */ + +static JSBool +ObjectToIterator(JSContext *cx, uintN argc, jsval *vp) +{ + jsval *argv = JS_ARGV(cx, vp); + JS_ASSERT(JSVAL_IS_INT(argv[0])); + JS_SET_RVAL(cx, vp, JS_THIS(cx, vp)); + return js_ValueToIterator(cx, JSVAL_TO_INT(argv[0]), &JS_RVAL(cx, vp)); +} + +static JSObject* FASTCALL +ObjectToIterator_tn(JSContext* cx, JSObject *obj, int32 flags) +{ + jsval v = OBJECT_TO_JSVAL(obj); + if (!js_ValueToIterator(cx, flags, &v)) + return NULL; + return JSVAL_TO_OBJECT(v); +} + +static JSBool +CallIteratorNext(JSContext *cx, uintN argc, jsval *vp) +{ + return js_CallIteratorNext(cx, JS_THIS_OBJECT(cx, vp), &JS_RVAL(cx, vp)); +} + +static jsval FASTCALL +CallIteratorNext_tn(JSContext* cx, JSObject* iterobj) +{ + jsval v; + if (!js_CallIteratorNext(cx, iterobj, &v)) + return JSVAL_ERROR_COOKIE; + return v; +} + +JS_DEFINE_TRCINFO_1(ObjectToIterator, + (3, (static, OBJECT_FAIL_NULL, ObjectToIterator_tn, CONTEXT, THIS, INT32, 0, 0))) +JS_DEFINE_TRCINFO_1(CallIteratorNext, + (2, (static, JSVAL_FAIL, CallIteratorNext_tn, CONTEXT, THIS, 0, 0))) + +static const struct BuiltinFunctionInfo { + JSTraceableNative *tn; + int nargs; +} builtinFunctionInfo[JSBUILTIN_LIMIT] = { + {ObjectToIterator_trcinfo, 1}, + {CallIteratorNext_trcinfo, 0} +}; + +JSObject * +js_GetBuiltinFunction(JSContext *cx, uintN index) +{ + JSRuntime *rt = cx->runtime; + JSObject *funobj = rt->builtinFunctions[index]; + + if (!funobj) { + /* Use NULL parent and atom. Builtin functions never escape to scripts. */ + JSFunction *fun = js_NewFunction(cx, + NULL, + (JSNative) builtinFunctionInfo[index].tn, + builtinFunctionInfo[index].nargs, + JSFUN_FAST_NATIVE | JSFUN_TRACEABLE, + NULL, + NULL); + if (fun) { + funobj = FUN_OBJECT(fun); + STOBJ_CLEAR_PROTO(funobj); + STOBJ_CLEAR_PARENT(funobj); + + JS_LOCK_GC(rt); + if (!rt->builtinFunctions[index]) /* retest now that the lock is held */ + rt->builtinFunctions[index] = funobj; + else + funobj = rt->builtinFunctions[index]; + JS_UNLOCK_GC(rt); + } + } + return funobj; +} + +JS_REQUIRES_STACK bool +TraceRecorder::record_JSOP_CALLBUILTIN() +{ + JSObject *obj = js_GetBuiltinFunction(cx, GET_INDEX(cx->fp->regs->pc)); + if (!obj) + return false; + + stack(0, get(&stackval(-1))); + stack(-1, INS_CONSTPTR(obj)); + return true; +} + JS_REQUIRES_STACK bool TraceRecorder::record_JSOP_NULLTHIS() { @@ -8979,7 +8995,7 @@ static void InitIMacroCode() { if (imacro_code[JSOP_NEXTITER]) { - JS_ASSERT(imacro_code[JSOP_NEXTITER] == nextiter_imacro - 1); + JS_ASSERT(imacro_code[JSOP_NEXTITER] == (jsbytecode*)&nextiter_imacros - 1); return; } @@ -8990,7 +9006,7 @@ InitIMacroCode() imacro_code[JSOP_ADD] = (jsbytecode*)&add_imacros - 1; imacro_code[JSOP_ITER] = (jsbytecode*)&iter_imacros - 1; - imacro_code[JSOP_NEXTITER] = nextiter_imacro - 1; + imacro_code[JSOP_NEXTITER] = (jsbytecode*)&nextiter_imacros - 1; imacro_code[JSOP_APPLY] = (jsbytecode*)&apply_imacros - 1; imacro_code[JSOP_NEG] = (jsbytecode*)&unary_imacros - 1; @@ -9016,4 +9032,3 @@ UNUSED(207) UNUSED(208) UNUSED(209) UNUSED(219) -UNUSED(226) diff --git a/js/src/jstracer.h b/js/src/jstracer.h index f910ef4b5ade..c82e59032dd8 100644 --- a/js/src/jstracer.h +++ b/js/src/jstracer.h @@ -520,7 +520,6 @@ public: JS_REQUIRES_STACK bool record_SetPropMiss(JSPropCacheEntry* entry); JS_REQUIRES_STACK bool record_DefLocalFunSetSlot(uint32 slot, JSObject* obj); JS_REQUIRES_STACK bool record_FastNativeCallComplete(); - JS_REQUIRES_STACK bool record_IteratorNextComplete(); nanojit::Fragment* getOuterToBlacklist() { return outerToBlacklist; } void deepAbort() { deepAborted = true; } @@ -577,6 +576,9 @@ js_FlushJITCache(JSContext* cx); extern void js_FlushJITOracle(JSContext* cx); +extern JSObject * +js_GetBuiltinFunction(JSContext *cx, uintN index); + #else /* !JS_TRACER */ #define TRACE_0(x) ((void)0) diff --git a/js/src/trace-test.js b/js/src/trace-test.js index 3b2207618895..a06fe1ae9dec 100644 --- a/js/src/trace-test.js +++ b/js/src/trace-test.js @@ -2545,6 +2545,42 @@ function testApply() { testApply.expected = "5,5,5,5,5,5,5,5,5,5"; test(testApply); +function testNestedForIn() { + var a = {x: 1, y: 2, z: 3}; + var s = ''; + for (var p1 in a) + for (var p2 in a) + s += p1 + p2 + ' '; + return s; +} +testNestedForIn.expected = 'xx xy xz yx yy yz zx zy zz '; +test(testNestedForIn); + +function testForEach() { + var r; + var a = ["zero", "one", "two", "three"]; + for (var i = 0; i < RUNLOOP; i++) { + r = ""; + for each (var s in a) + r += s + " "; + } + return r; +} +testForEach.expected = "zero one two three "; +test(testForEach); + +function testThinForEach() { + var a = ["red"]; + var n = 0; + for (var i = 0; i < 10; i++) + for each (var v in a) + if (v) + n++; + return n; +} +testThinForEach.expected = 10; +test(testThinForEach); + function testComparisons() { // All the special values from each of the types in From 6e5aed2c64845727f8d3b661312226656a7c3a84 Mon Sep 17 00:00:00 2001 From: Andrei Saprykin Date: Wed, 28 Jan 2009 18:23:42 +0100 Subject: [PATCH 11/11] bug 475680 - Using gcNumber in the cycle collector heuristics. r=igor --- dom/src/base/nsJSEnvironment.cpp | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/dom/src/base/nsJSEnvironment.cpp b/dom/src/base/nsJSEnvironment.cpp index f8e6c8250010..55ce32d95957 100644 --- a/dom/src/base/nsJSEnvironment.cpp +++ b/dom/src/base/nsJSEnvironment.cpp @@ -177,7 +177,7 @@ static PRUint32 sCCollectCount; static PRBool sUserIsActive; static PRTime sPreviousCCTime; static PRUint32 sCollectedObjectsCounts; -static PRUint32 sGCCount; +static PRUint32 sSavedGCCount; static PRUint32 sCCSuspectChanges; static PRUint32 sCCSuspectedCount; static nsITimer *sGCTimer; @@ -868,7 +868,6 @@ MaybeGC(JSContext *cx) || cx->runtime->gcZeal > 0 #endif ) { - ++sGCCount; JS_GC(cx); } } @@ -3409,12 +3408,12 @@ nsJSContext::CC() #endif sPreviousCCTime = PR_Now(); sDelayedCCollectCount = 0; - sGCCount = 0; sCCSuspectChanges = 0; // nsCycleCollector_collect() will run a ::JS_GC() indirectly, so // we do not explicitly call ::JS_GC() here. sCollectedObjectsCounts = nsCycleCollector_collect(); sCCSuspectedCount = nsCycleCollector_suspectedCount(); + sSavedGCCount = JS_GetGCParameter(nsJSRuntime::sRuntime, JSGC_NUMBER); #ifdef DEBUG_smaug printf("Collected %u objects, %u suspected objects, took %lldms\n", sCollectedObjectsCounts, sCCSuspectedCount, @@ -3422,6 +3421,21 @@ nsJSContext::CC() #endif } +static inline uint32 +GetGCRunsSinceLastCC() +{ + // To avoid crash if nsJSRuntime is not properly initialized. + // See the bug 474586 + if (!nsJSRuntime::sRuntime) + return 0; + + // Since JS_GetGCParameter() and sSavedGCCount are unsigned, the following + // gives the correct result even when the GC counter wraps around + // UINT32_MAX since the last call to JS_GetGCParameter(). + return JS_GetGCParameter(nsJSRuntime::sRuntime, JSGC_NUMBER) - + sSavedGCCount; +} + //static PRBool nsJSContext::MaybeCC(PRBool aHigherProbability) @@ -3430,7 +3444,7 @@ nsJSContext::MaybeCC(PRBool aHigherProbability) // Don't check suspected count if CC will be called anyway. if (sCCSuspectChanges <= NS_MIN_SUSPECT_CHANGES || - sGCCount <= NS_MAX_GC_COUNT) { + GetGCRunsSinceLastCC() <= NS_MAX_GC_COUNT) { #ifdef DEBUG_smaug PRTime now = PR_Now(); #endif @@ -3447,8 +3461,8 @@ nsJSContext::MaybeCC(PRBool aHigherProbability) } } #ifdef DEBUG_smaug - printf("sCCSuspectChanges %u, sGCCount %u\n", - sCCSuspectChanges, sGCCount); + printf("sCCSuspectChanges %u, GC runs %u\n", + sCCSuspectChanges, GetGCRunsSinceLastCC()); #endif // Increase the probability also if the previous call to cycle collector @@ -3461,7 +3475,7 @@ nsJSContext::MaybeCC(PRBool aHigherProbability) if (!sGCTimer && (sDelayedCCollectCount > NS_MAX_DELAYED_CCOLLECT) && ((sCCSuspectChanges > NS_MIN_SUSPECT_CHANGES && - sGCCount > NS_MAX_GC_COUNT) || + GetGCRunsSinceLastCC() > NS_MAX_GC_COUNT) || (sCCSuspectChanges > NS_MAX_SUSPECT_CHANGES))) { if ((PR_Now() - sPreviousCCTime) >= PRTime(NS_MIN_CC_INTERVAL * PR_USEC_PER_MSEC)) { @@ -3691,7 +3705,7 @@ nsJSRuntime::Startup() sUserIsActive = PR_FALSE; sPreviousCCTime = 0; sCollectedObjectsCounts = 0; - sGCCount = 0; + sSavedGCCount = 0; sCCSuspectChanges = 0; sCCSuspectedCount = 0; sGCTimer = nsnull; @@ -3798,6 +3812,8 @@ nsJSRuntime::Init() NS_ASSERTION(!gOldJSGCCallback, "nsJSRuntime initialized more than once"); + sSavedGCCount = JS_GetGCParameter(nsJSRuntime::sRuntime, JSGC_NUMBER); + // Save the old GC callback to chain to it, for GC-observing generality. gOldJSGCCallback = ::JS_SetGCCallbackRT(sRuntime, DOMGCCallback);