Bug 767750 - rm JSScript::evalHashLink (r=njn)

--HG--
extra : rebase_source : 6cbf87dc2cbc23c60ee278b9fd3b64b0936517bc
This commit is contained in:
Luke Wagner 2012-07-03 10:24:35 -07:00
parent 2c9d40f617
commit 439bdb3b68
5 changed files with 77 additions and 130 deletions

View File

@ -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<JSScript*> 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;

View File

@ -876,6 +876,9 @@ JSRuntime::init(uint32_t maxbytes)
if (!scriptFilenameTable.init())
return false;
if (!evalCache.init())
return false;
debugScopes = this->new_<DebugScopes>(this);
if (!debugScopes || !debugScopes->init()) {
Foreground::delete_(debugScopes);

View File

@ -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<JSScript *, EvalCacheHashPolicy, SystemAllocPolicy> EvalCache;
class NativeIterCache
{
static const size_t SIZE = size_t(1) << 8;

View File

@ -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();

View File

@ -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.