mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 20:05:49 +00:00
Bug 767750 - rm JSScript::evalHashLink (r=njn)
--HG-- extra : rebase_source : 64101b0dbe1a8246e043637dca7469016362f62d
This commit is contained in:
parent
9e0785bc9d
commit
9c907e3113
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user