diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp index 641228694c47..f09a4200909d 100644 --- a/js/src/builtin/Eval.cpp +++ b/js/src/builtin/Eval.cpp @@ -10,6 +10,7 @@ #include "builtin/Eval.h" #include "frontend/BytecodeCompiler.h" +#include "mozilla/HashFunctions.h" #include "vm/GlobalObject.h" #include "jsinterpinlines.h" @@ -30,108 +31,42 @@ AssertInnerizedScopeChain(JSContext *cx, JSObject &scopeobj) #endif } -void -EvalCache::purge() +static bool +IsEvalCacheCandidate(JSScript *script) { - // Purge all scripts from the eval cache. In addition to removing them from - // table_, null out the evalHashLink field of any script removed. Since - // evalHashLink is in a union with globalObject, this allows the GC to - // indiscriminately use the union as a nullable globalObject pointer. - for (size_t i = 0; i < ArrayLength(table_); ++i) { - for (JSScript **listHeadp = &table_[i]; *listHeadp; ) { - JSScript *script = *listHeadp; - JS_ASSERT(GetGCThingTraceKind(script) == JSTRACE_SCRIPT); - *listHeadp = script->evalHashLink; - script->evalHashLink = NULL; - } - } + // Make sure there are no inner objects which might use the wrong parent + // and/or call scope by reusing the previous eval's script. Skip the + // script's first object, which entrains the eval's scope. + return script->savedCallerFun && + !script->hasSingletons && + script->objects()->length == 1 && + !script->hasRegexps(); } -JSScript ** -EvalCache::bucket(JSLinearString *str) +/* static */ HashNumber +EvalCacheHashPolicy::hash(const EvalCacheLookup &l) { - const jschar *s = str->chars(); - size_t n = str->length(); - - if (n > 100) - n = 100; - uint32_t h; - for (h = 0; n; s++, n--) - h = JS_ROTATE_LEFT32(h, 4) ^ *s; - - h *= JS_GOLDEN_RATIO; - h >>= 32 - SHIFT; - JS_ASSERT(h < ArrayLength(table_)); - return &table_[h]; + return AddToHash(HashString(l.str->chars(), l.str->length()), + l.caller, + l.staticLevel, + l.version, + l.compartment); } -static JSScript * -EvalCacheLookup(JSContext *cx, JSLinearString *str, StackFrame *caller, unsigned staticLevel, - JSPrincipals *principals, JSObject &scopeobj, JSScript **bucket) +/* static */ bool +EvalCacheHashPolicy::match(JSScript *script, const EvalCacheLookup &l) { - // Cache local eval scripts indexed by source qualified by scope. - // - // An eval cache entry should never be considered a hit unless its - // strictness matches that of the new eval code. The existing code takes - // care of this, because hits are qualified by the function from which - // eval was called, whose strictness doesn't change. (We don't cache evals - // in eval code, so the calling function corresponds to the calling script, - // and its strictness never varies.) Scripts produced by calls to eval from - // global code aren't cached. - // - // FIXME bug 620141: Qualify hits by calling script rather than function. - // Then we wouldn't need the unintuitive !isEvalFrame() hack in EvalKernel - // to avoid caching nested evals in functions (thus potentially mismatching - // on strict mode), and we could cache evals in global code if desired. - unsigned count = 0; - JSScript **scriptp = bucket; + JS_ASSERT(IsEvalCacheCandidate(script)); - JSVersion version = cx->findVersion(); - JSScript *script; - JSSubsumePrincipalsOp subsume = cx->runtime->securityCallbacks->subsumePrincipals; - while ((script = *scriptp) != NULL) { - if (script->savedCallerFun && - script->staticLevel == staticLevel && - script->getVersion() == version && - !script->hasSingletons && - (!subsume || script->principals == principals || - (subsume(principals, script->principals) && - subsume(script->principals, principals)))) - { - // Get the prior (cache-filling) eval's saved caller function. - // See frontend::CompileScript. - JSFunction *fun = script->getCallerFunction(); + // Get the source string passed for safekeeping in the atom map + // by the prior eval to frontend::CompileScript. + JSAtom *keyStr = script->atoms[0]; - if (fun == caller->fun()) { - /* - * Get the source string passed for safekeeping in the atom map - * by the prior eval to frontend::CompileScript. - */ - JSAtom *src = script->atoms[0]; - - if (src == str || EqualStrings(src, str)) { - // Source matches. Make sure there are no inner objects - // which might use the wrong parent and/or call scope by - // reusing the previous eval's script. Skip the script's - // first object, which entrains the eval's scope. - JS_ASSERT(script->objects()->length >= 1); - if (script->objects()->length == 1 && - !script->hasRegexps()) { - JS_ASSERT(staticLevel == script->staticLevel); - *scriptp = script->evalHashLink; - script->evalHashLink = NULL; - return script; - } - } - } - } - - static const unsigned EVAL_CACHE_CHAIN_LIMIT = 4; - if (++count == EVAL_CACHE_CHAIN_LIMIT) - return NULL; - scriptp = script->evalHashLink.unsafeGet(); - } - return NULL; + return EqualStrings(keyStr, l.str) && + script->getCallerFunction() == l.caller && + script->staticLevel == l.staticLevel && + script->getVersion() == l.version && + script->compartment() == l.compartment; } // There are two things we want to do with each script executed in EvalKernel: @@ -141,21 +76,21 @@ EvalCacheLookup(JSContext *cx, JSLinearString *str, StackFrame *caller, unsigned // NB: Although the eval cache keeps a script alive wrt to the JS engine, from // a jsdbgapi user's perspective, we want each eval() to create and destroy a // script. This hides implementation details and means we don't have to deal -// with calls to JS_GetScriptObject for scripts in the eval cache (currently, -// script->object aliases script->evalHashLink()). +// with calls to JS_GetScriptObject for scripts in the eval cache. class EvalScriptGuard { JSContext *cx_; - JSLinearString *str_; - JSScript **bucket_; Rooted script_; + /* These fields are only valid if lookup_.str is non-NULL. */ + EvalCacheLookup lookup_; + EvalCache::AddPtr p_; + public: - EvalScriptGuard(JSContext *cx, JSLinearString *str) - : cx_(cx), - str_(str), - script_(cx) { - bucket_ = cx->runtime->evalCache.bucket(str); + EvalScriptGuard(JSContext *cx) + : cx_(cx), script_(cx) + { + lookup_.str = NULL; } ~EvalScriptGuard() { @@ -163,17 +98,23 @@ class EvalScriptGuard CallDestroyScriptHook(cx_->runtime->defaultFreeOp(), script_); script_->isActiveEval = false; script_->isCachedEval = true; - script_->evalHashLink = *bucket_; - *bucket_ = script_; + if (lookup_.str && IsEvalCacheCandidate(script_)) + cx_->runtime->evalCache.relookupOrAdd(p_, lookup_, script_); } } - void lookupInEvalCache(StackFrame *caller, unsigned staticLevel, - JSPrincipals *principals, JSObject &scopeobj) { - if (JSScript *found = EvalCacheLookup(cx_, str_, caller, staticLevel, - principals, scopeobj, bucket_)) { - js_CallNewScriptHook(cx_, found, NULL); - script_ = found; + void lookupInEvalCache(JSLinearString *str, JSFunction *caller, unsigned staticLevel) + { + lookup_.str = str; + lookup_.caller = caller; + lookup_.staticLevel = staticLevel; + lookup_.version = cx_->findVersion(); + lookup_.compartment = cx_->compartment; + p_ = cx_->runtime->evalCache.lookupForAdd(lookup_); + if (p_) { + script_ = *p_; + cx_->runtime->evalCache.remove(p_); + js_CallNewScriptHook(cx_, script_, NULL); script_->isCachedEval = false; script_->isActiveEval = true; } @@ -247,11 +188,6 @@ EvalKernel(JSContext *cx, const CallArgs &args, EvalType evalType, StackFrame *c if (!ComputeThis(cx, caller)) return false; thisv = caller->thisValue(); - -#ifdef DEBUG - jsbytecode *callerPC = caller->pcQuadratic(cx); - JS_ASSERT(callerPC && JSOp(*callerPC) == JSOP_EVAL); -#endif } else { JS_ASSERT(args.callee().global() == *scopeobj); staticLevel = 0; @@ -310,12 +246,12 @@ EvalKernel(JSContext *cx, const CallArgs &args, EvalType evalType, StackFrame *c } } - EvalScriptGuard esg(cx, linearStr); + EvalScriptGuard esg(cx); JSPrincipals *principals = PrincipalsForCompiledCode(args, cx); if (evalType == DIRECT_EVAL && caller->isNonEvalFunctionFrame()) - esg.lookupInEvalCache(caller, staticLevel, principals, *scopeobj); + esg.lookupInEvalCache(linearStr, caller->fun(), staticLevel); if (!esg.foundScript()) { unsigned lineno; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 4348b6a1b7b2..e5867fb749a9 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -876,6 +876,9 @@ JSRuntime::init(uint32_t maxbytes) if (!scriptFilenameTable.init()) return false; + if (!evalCache.init()) + return false; + debugScopes = this->new_(this); if (!debugScopes || !debugScopes->init()) { Foreground::delete_(debugScopes); diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index f69397c8f7d3..ae5f1c3701ec 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -173,18 +173,25 @@ class ToSourceCache void purge(); }; -class EvalCache +struct EvalCacheLookup { - static const unsigned SHIFT = 6; - static const unsigned LENGTH = 1 << SHIFT; - JSScript *table_[LENGTH]; - - public: - EvalCache() { PodArrayZero(table_); } - JSScript **bucket(JSLinearString *str); - void purge(); + JSLinearString *str; + JSFunction *caller; + unsigned staticLevel; + JSVersion version; + JSCompartment *compartment; }; +struct EvalCacheHashPolicy +{ + typedef EvalCacheLookup Lookup; + + static HashNumber hash(const Lookup &l); + static bool match(JSScript *script, const EvalCacheLookup &l); +}; + +typedef HashSet EvalCache; + class NativeIterCache { static const size_t SIZE = size_t(1) << 8; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 025a5ff01dd0..838cd19d1260 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3056,7 +3056,7 @@ PurgeRuntime(JSTracer *trc) rt->newObjectCache.purge(); rt->nativeIterCache.purge(); rt->toSourceCache.purge(); - rt->evalCache.purge(); + rt->evalCache.clear(); for (ContextIter acx(rt); !acx.done(); acx.next()) acx->purge(); diff --git a/js/src/jsscript.h b/js/src/jsscript.h index e292b88cb4c4..165dcb2cc6ce 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -407,9 +407,6 @@ struct JSScript : public js::gc::Cell JSPrincipals *principals;/* principals for this script */ JSPrincipals *originPrincipals; /* see jsapi.h 'originPrincipals' comment */ - /* The next link in the eval cache */ - js::HeapPtrScript evalHashLink; - /* Persistent type information retained across GCs. */ js::types::TypeScript *types; @@ -437,6 +434,10 @@ struct JSScript : public js::gc::Cell * or has had backedges taken. Reset if the * script's JIT code is forcibly discarded. */ +#if JS_BITS_PER_WORD == 32 + uint32_t pad32; +#endif + #ifdef DEBUG // Unique identifier within the compartment for this script, used for // printing analysis information.