diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 054e0d2bd01..9c16ef3a094 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -147,7 +147,6 @@ CPPSRCS = \ jsopcode.cpp \ jsparse.cpp \ jsprf.cpp \ - jspropcache.cpp \ jsregexp.cpp \ jsscan.cpp \ jsscope.cpp \ diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 04db29144e1..99cf89c50a6 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -91,6 +91,492 @@ /* jsinvoke_cpp___ indicates inclusion from jsinvoke.cpp. */ #if !JS_LONE_INTERPRET ^ defined jsinvoke_cpp___ +JS_REQUIRES_STACK JSPropCacheEntry * +js_FillPropertyCache(JSContext *cx, JSObject *obj, + uintN scopeIndex, uintN protoIndex, JSObject *pobj, + JSScopeProperty *sprop, JSBool adding) +{ + JSPropertyCache *cache; + jsbytecode *pc; + JSScope *scope; + jsuword kshape, vshape, khash; + JSOp op; + const JSCodeSpec *cs; + jsuword vword; + ptrdiff_t pcoff; + JSAtom *atom; + JSPropCacheEntry *entry; + + JS_ASSERT(!cx->runtime->gcRunning); + cache = &JS_PROPERTY_CACHE(cx); + + /* FIXME bug 489098: consider enabling the property cache for eval. */ + if (js_IsPropertyCacheDisabled(cx) || (cx->fp->flags & JSFRAME_EVAL)) { + PCMETER(cache->disfills++); + return JS_NO_PROP_CACHE_FILL; + } + + /* + * Check for fill from js_SetPropertyHelper where the setter removed sprop + * from pobj's scope (via unwatch or delete, e.g.). + */ + scope = OBJ_SCOPE(pobj); + if (!scope->has(sprop)) { + PCMETER(cache->oddfills++); + return JS_NO_PROP_CACHE_FILL; + } + + /* + * Check for overdeep scope and prototype chain. Because resolve, getter, + * and setter hooks can change the prototype chain using JS_SetPrototype + * after js_LookupPropertyWithFlags has returned the nominal protoIndex, + * we have to validate protoIndex if it is non-zero. If it is zero, then + * we know thanks to the scope->has test above, combined with the fact that + * obj == pobj, that protoIndex is invariant. + * + * The scopeIndex can't be wrong. We require JS_SetParent calls to happen + * before any running script might consult a parent-linked scope chain. If + * this requirement is not satisfied, the fill in progress will never hit, + * but vcap vs. scope shape tests ensure nothing malfunctions. + */ + JS_ASSERT_IF(scopeIndex == 0 && protoIndex == 0, obj == pobj); + + if (protoIndex != 0) { + JSObject *tmp = obj; + + for (uintN i = 0; i != scopeIndex; i++) + tmp = OBJ_GET_PARENT(cx, tmp); + JS_ASSERT(tmp != pobj); + + protoIndex = 1; + for (;;) { + tmp = OBJ_GET_PROTO(cx, tmp); + + /* + * We cannot cache properties coming from native objects behind + * non-native ones on the prototype chain. The non-natives can + * mutate in arbitrary way without changing any shapes. + */ + if (!tmp || !OBJ_IS_NATIVE(tmp)) { + PCMETER(cache->noprotos++); + return JS_NO_PROP_CACHE_FILL; + } + if (tmp == pobj) + break; + ++protoIndex; + } + } + + if (scopeIndex > PCVCAP_SCOPEMASK || protoIndex > PCVCAP_PROTOMASK) { + PCMETER(cache->longchains++); + return JS_NO_PROP_CACHE_FILL; + } + + /* + * Optimize the cached vword based on our parameters and the current pc's + * opcode format flags. + */ + pc = cx->fp->regs->pc; + op = js_GetOpcode(cx, cx->fp->script, pc); + cs = &js_CodeSpec[op]; + kshape = 0; + + do { + /* + * Check for a prototype "plain old method" callee computation. What + * is a plain old method? It's a function-valued property with stub + * getter, so get of a function is idempotent. + */ + if (cs->format & JOF_CALLOP) { + jsval v; + + if (sprop->isMethod()) { + /* + * A compiler-created function object, AKA a method, already + * memoized in the property tree. + */ + JS_ASSERT(scope->hasMethodBarrier()); + v = sprop->methodValue(); + JS_ASSERT(VALUE_IS_FUNCTION(cx, v)); + JS_ASSERT(v == LOCKED_OBJ_GET_SLOT(pobj, sprop->slot)); + vword = JSVAL_OBJECT_TO_PCVAL(v); + break; + } + + if (SPROP_HAS_STUB_GETTER(sprop) && + SPROP_HAS_VALID_SLOT(sprop, scope)) { + v = LOCKED_OBJ_GET_SLOT(pobj, sprop->slot); + if (VALUE_IS_FUNCTION(cx, v)) { + /* + * Great, we have a function-valued prototype property + * where the getter is JS_PropertyStub. The type id in + * pobj's scope does not evolve with changes to property + * values, however. + * + * So here, on first cache fill for this method, we brand + * the scope with a new shape and set the JSScope::BRANDED + * flag. Once this flag is set, any property assignment + * that changes the value from or to a different function + * object will result in shape being regenerated. + */ + if (!scope->branded()) { + PCMETER(cache->brandfills++); +#ifdef DEBUG_notme + fprintf(stderr, + "branding %p (%s) for funobj %p (%s), shape %lu\n", + pobj, pobj->getClass()->name, + JSVAL_TO_OBJECT(v), + JS_GetFunctionName(GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(v))), + OBJ_SHAPE(obj)); +#endif + scope->brandingShapeChange(cx, sprop->slot, v); + if (js_IsPropertyCacheDisabled(cx)) /* check for rt->shapeGen overflow */ + return JS_NO_PROP_CACHE_FILL; + scope->setBranded(); + } + vword = JSVAL_OBJECT_TO_PCVAL(v); + break; + } + } + } + + /* If getting a value via a stub getter, we can cache the slot. */ + if (!(cs->format & (JOF_SET | JOF_INCDEC | JOF_FOR)) && + SPROP_HAS_STUB_GETTER(sprop) && + SPROP_HAS_VALID_SLOT(sprop, scope)) { + /* Great, let's cache sprop's slot and use it on cache hit. */ + vword = SLOT_TO_PCVAL(sprop->slot); + } else { + /* Best we can do is to cache sprop (still a nice speedup). */ + vword = SPROP_TO_PCVAL(sprop); + if (adding && + sprop == scope->lastProp && + scope->shape == sprop->shape) { + /* + * Our caller added a new property. We also know that a setter + * that js_NativeSet could have run has not mutated the scope, + * so the added property is still the last one added, and the + * scope is not branded. + * + * We want to cache under scope's shape before the property + * addition to bias for the case when the mutator opcode + * always adds the same property. This allows us to optimize + * periodic execution of object initializers or other explicit + * initialization sequences such as + * + * obj = {}; obj.x = 1; obj.y = 2; + * + * We assume that on average the win from this optimization is + * greater than the cost of an extra mismatch per loop owing to + * the bias for the following case: + * + * obj = {}; ... for (...) { ... obj.x = ... } + * + * On the first iteration of such a for loop, JSOP_SETPROP + * fills the cache with the shape of the newly created object + * obj, not the shape of obj after obj.x has been assigned. + * That mismatches obj's shape on the second iteration. Note + * that on the third and subsequent iterations the cache will + * be hit because the shape is no longer updated. + */ + JS_ASSERT(scope->owned()); + if (sprop->parent) { + kshape = sprop->parent->shape; + } else { + /* + * If obj had its own empty scope before, with a unique + * shape, that is lost. Here we only attempt to find a + * matching empty scope. In unusual cases involving + * __proto__ assignment we may not find one. + */ + JSObject *proto = STOBJ_GET_PROTO(obj); + if (!proto || !OBJ_IS_NATIVE(proto)) + return JS_NO_PROP_CACHE_FILL; + JSScope *protoscope = OBJ_SCOPE(proto); + if (!protoscope->emptyScope || + !js_ObjectIsSimilarToProto(cx, obj, obj->map->ops, OBJ_GET_CLASS(cx, obj), + proto)) { + return JS_NO_PROP_CACHE_FILL; + } + kshape = protoscope->emptyScope->shape; + } + + /* + * When adding we predict no prototype object will later gain a + * readonly property or setter. + */ + vshape = cx->runtime->protoHazardShape; + } + } + } while (0); + + if (kshape == 0) { + kshape = OBJ_SHAPE(obj); + vshape = scope->shape; + } + + khash = PROPERTY_CACHE_HASH_PC(pc, kshape); + if (obj == pobj) { + JS_ASSERT(scopeIndex == 0 && protoIndex == 0); + } else { + if (op == JSOP_LENGTH) { + atom = cx->runtime->atomState.lengthAtom; + } else { + pcoff = (JOF_TYPE(cs->format) == JOF_SLOTATOM) ? SLOTNO_LEN : 0; + GET_ATOM_FROM_BYTECODE(cx->fp->script, pc, pcoff, atom); + } + +#ifdef DEBUG + if (scopeIndex == 0) { + JS_ASSERT(protoIndex != 0); + JS_ASSERT((protoIndex == 1) == (OBJ_GET_PROTO(cx, obj) == pobj)); + } +#endif + + if (scopeIndex != 0 || protoIndex != 1) { + khash = PROPERTY_CACHE_HASH_ATOM(atom, obj); + PCMETER(if (PCVCAP_TAG(cache->table[khash].vcap) <= 1) + cache->pcrecycles++); + pc = (jsbytecode *) atom; + kshape = (jsuword) obj; + + /* + * Make sure that a later shadowing assignment will enter + * PurgeProtoChain and invalidate this entry, bug 479198. + * + * This is thread-safe even though obj is not locked. Only the + * DELEGATE bit of obj->classword can change at runtime, given that + * obj is native; and the bit is only set, never cleared. And on + * platforms where another CPU can fail to see this write, it's OK + * because the property cache and JIT cache are thread-local. + */ + obj->setDelegate(); + } + } + + entry = &cache->table[khash]; + PCMETER(PCVAL_IS_NULL(entry->vword) || cache->recycles++); + entry->kpc = pc; + entry->kshape = kshape; + entry->vcap = PCVCAP_MAKE(vshape, scopeIndex, protoIndex); + entry->vword = vword; + + cache->empty = JS_FALSE; + PCMETER(cache->fills++); + + /* + * The modfills counter is not exact. It increases if a getter or setter + * recurse into the interpreter. + */ + PCMETER(entry == cache->pctestentry || cache->modfills++); + PCMETER(cache->pctestentry = NULL); + return entry; +} + +JS_REQUIRES_STACK JSAtom * +js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, + JSObject **objp, JSObject **pobjp, + JSPropCacheEntry **entryp) +{ + JSOp op; + const JSCodeSpec *cs; + ptrdiff_t pcoff; + JSAtom *atom; + JSObject *obj, *pobj, *tmp; + JSPropCacheEntry *entry; + uint32 vcap; + + JS_ASSERT(uintN((cx->fp->imacpc ? cx->fp->imacpc : pc) - cx->fp->script->code) + < cx->fp->script->length); + + op = js_GetOpcode(cx, cx->fp->script, pc); + cs = &js_CodeSpec[op]; + if (op == JSOP_LENGTH) { + atom = cx->runtime->atomState.lengthAtom; + } else { + pcoff = (JOF_TYPE(cs->format) == JOF_SLOTATOM) ? SLOTNO_LEN : 0; + GET_ATOM_FROM_BYTECODE(cx->fp->script, pc, pcoff, atom); + } + + obj = *objp; + JS_ASSERT(OBJ_IS_NATIVE(obj)); + entry = &JS_PROPERTY_CACHE(cx).table[PROPERTY_CACHE_HASH_ATOM(atom, obj)]; + *entryp = entry; + vcap = entry->vcap; + + if (entry->kpc != (jsbytecode *) atom) { + PCMETER(JS_PROPERTY_CACHE(cx).idmisses++); + +#ifdef DEBUG_notme + entry = &JS_PROPERTY_CACHE(cx).table[PROPERTY_CACHE_HASH_PC(pc, OBJ_SHAPE(obj))]; + fprintf(stderr, + "id miss for %s from %s:%u" + " (pc %u, kpc %u, kshape %u, shape %u)\n", + js_AtomToPrintableString(cx, atom), + cx->fp->script->filename, + js_PCToLineNumber(cx, cx->fp->script, pc), + pc - cx->fp->script->code, + entry->kpc - cx->fp->script->code, + entry->kshape, + OBJ_SHAPE(obj)); + js_Disassemble1(cx, cx->fp->script, pc, + pc - cx->fp->script->code, + JS_FALSE, stderr); +#endif + + return atom; + } + + if (entry->kshape != (jsuword) obj) { + PCMETER(JS_PROPERTY_CACHE(cx).komisses++); + return atom; + } + + pobj = obj; + + if (JOF_MODE(cs->format) == JOF_NAME) { + while (vcap & (PCVCAP_SCOPEMASK << PCVCAP_PROTOBITS)) { + tmp = OBJ_GET_PARENT(cx, pobj); + if (!tmp || !OBJ_IS_NATIVE(tmp)) + break; + pobj = tmp; + vcap -= PCVCAP_PROTOSIZE; + } + + *objp = pobj; + } + + while (vcap & PCVCAP_PROTOMASK) { + tmp = OBJ_GET_PROTO(cx, pobj); + if (!tmp || !OBJ_IS_NATIVE(tmp)) + break; + pobj = tmp; + --vcap; + } + + if (JS_LOCK_OBJ_IF_SHAPE(cx, pobj, PCVCAP_SHAPE(vcap))) { +#ifdef DEBUG + jsid id = ATOM_TO_JSID(atom); + + id = js_CheckForStringIndex(id); + JS_ASSERT(OBJ_SCOPE(pobj)->lookup(id)); + JS_ASSERT_IF(OBJ_SCOPE(pobj)->object, OBJ_SCOPE(pobj)->object == pobj); +#endif + *pobjp = pobj; + return NULL; + } + + PCMETER(JS_PROPERTY_CACHE(cx).vcmisses++); + return atom; +} + +#ifdef DEBUG +#define ASSERT_CACHE_IS_EMPTY(cache) \ + JS_BEGIN_MACRO \ + JSPropertyCache *cache_ = (cache); \ + uintN i_; \ + JS_ASSERT(cache_->empty); \ + for (i_ = 0; i_ < PROPERTY_CACHE_SIZE; i_++) { \ + JS_ASSERT(!cache_->table[i_].kpc); \ + JS_ASSERT(!cache_->table[i_].kshape); \ + JS_ASSERT(!cache_->table[i_].vcap); \ + JS_ASSERT(!cache_->table[i_].vword); \ + } \ + JS_END_MACRO +#else +#define ASSERT_CACHE_IS_EMPTY(cache) ((void)0) +#endif + +JS_STATIC_ASSERT(PCVAL_NULL == 0); + +void +js_PurgePropertyCache(JSContext *cx, JSPropertyCache *cache) +{ + if (cache->empty) { + ASSERT_CACHE_IS_EMPTY(cache); + return; + } + + memset(cache->table, 0, sizeof cache->table); + cache->empty = JS_TRUE; + +#ifdef JS_PROPERTY_CACHE_METERING + { static FILE *fp; + if (!fp) + fp = fopen("/tmp/propcache.stats", "w"); + if (fp) { + fputs("Property cache stats for ", fp); +#ifdef JS_THREADSAFE + fprintf(fp, "thread %lu, ", (unsigned long) cx->thread->id); +#endif + fprintf(fp, "GC %u\n", cx->runtime->gcNumber); + +# define P(mem) fprintf(fp, "%11s %10lu\n", #mem, (unsigned long)cache->mem) + P(fills); + P(nofills); + P(rofills); + P(disfills); + P(oddfills); + P(modfills); + P(brandfills); + P(noprotos); + P(longchains); + P(recycles); + P(pcrecycles); + P(tests); + P(pchits); + P(protopchits); + P(initests); + P(inipchits); + P(inipcmisses); + P(settests); + P(addpchits); + P(setpchits); + P(setpcmisses); + P(slotchanges); + P(setmisses); + P(idmisses); + P(komisses); + P(vcmisses); + P(misses); + P(flushes); + P(pcpurges); +# undef P + + fprintf(fp, "hit rates: pc %g%% (proto %g%%), set %g%%, ini %g%%, full %g%%\n", + (100. * cache->pchits) / cache->tests, + (100. * cache->protopchits) / cache->tests, + (100. * (cache->addpchits + cache->setpchits)) + / cache->settests, + (100. * cache->inipchits) / cache->initests, + (100. * (cache->tests - cache->misses)) / cache->tests); + fflush(fp); + } + } +#endif + + PCMETER(cache->flushes++); +} + +void +js_PurgePropertyCacheForScript(JSContext *cx, JSScript *script) +{ + JSPropertyCache *cache; + JSPropCacheEntry *entry; + + cache = &JS_PROPERTY_CACHE(cx); + for (entry = cache->table; entry < cache->table + PROPERTY_CACHE_SIZE; + entry++) { + if (JS_UPTRDIFF(entry->kpc, script->code) < script->length) { + entry->kpc = NULL; + entry->kshape = 0; +#ifdef DEBUG + entry->vcap = entry->vword = 0; +#endif + } + } +} + /* * Check if the current arena has enough space to fit nslots after sp and, if * so, reserve the necessary space. diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index db488cd1d3a..5beb4ccda7d 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -47,7 +47,6 @@ #include "jspubtd.h" #include "jsfun.h" #include "jsopcode.h" -#include "jspropcache.h" #include "jsscript.h" JS_BEGIN_EXTERN_C @@ -205,6 +204,223 @@ typedef struct JSInlineFrame { #define JSFRAME_SPECIAL (JSFRAME_DEBUGGER | JSFRAME_EVAL) +/* + * Property cache with structurally typed capabilities for invalidation, for + * polymorphic callsite method/get/set speedups. For details, see + * . + */ +#define PROPERTY_CACHE_LOG2 12 +#define PROPERTY_CACHE_SIZE JS_BIT(PROPERTY_CACHE_LOG2) +#define PROPERTY_CACHE_MASK JS_BITMASK(PROPERTY_CACHE_LOG2) + +/* + * Add kshape rather than xor it to avoid collisions between nearby bytecode + * that are evolving an object by setting successive properties, incrementing + * the object's scope->shape on each set. + */ +#define PROPERTY_CACHE_HASH(pc,kshape) \ + (((((jsuword)(pc) >> PROPERTY_CACHE_LOG2) ^ (jsuword)(pc)) + (kshape)) & \ + PROPERTY_CACHE_MASK) + +#define PROPERTY_CACHE_HASH_PC(pc,kshape) \ + PROPERTY_CACHE_HASH(pc, kshape) + +#define PROPERTY_CACHE_HASH_ATOM(atom,obj) \ + PROPERTY_CACHE_HASH((jsuword)(atom) >> 2, OBJ_SHAPE(obj)) + +/* + * Property cache value capability macros. + */ +#define PCVCAP_PROTOBITS 4 +#define PCVCAP_PROTOSIZE JS_BIT(PCVCAP_PROTOBITS) +#define PCVCAP_PROTOMASK JS_BITMASK(PCVCAP_PROTOBITS) + +#define PCVCAP_SCOPEBITS 4 +#define PCVCAP_SCOPESIZE JS_BIT(PCVCAP_SCOPEBITS) +#define PCVCAP_SCOPEMASK JS_BITMASK(PCVCAP_SCOPEBITS) + +#define PCVCAP_TAGBITS (PCVCAP_PROTOBITS + PCVCAP_SCOPEBITS) +#define PCVCAP_TAGMASK JS_BITMASK(PCVCAP_TAGBITS) +#define PCVCAP_TAG(t) ((t) & PCVCAP_TAGMASK) + +#define PCVCAP_MAKE(t,s,p) ((uint32(t) << PCVCAP_TAGBITS) | \ + ((s) << PCVCAP_PROTOBITS) | \ + (p)) +#define PCVCAP_SHAPE(t) ((t) >> PCVCAP_TAGBITS) + +#define SHAPE_OVERFLOW_BIT JS_BIT(32 - PCVCAP_TAGBITS) + +struct JSPropCacheEntry { + jsbytecode *kpc; /* pc if vcap tag is <= 1, else atom */ + jsuword kshape; /* key shape if pc, else obj for atom */ + jsuword vcap; /* value capability, see above */ + jsuword vword; /* value word, see PCVAL_* below */ + + bool adding() const { + return PCVCAP_TAG(vcap) == 0 && kshape != PCVCAP_SHAPE(vcap); + } + + bool directHit() const { + return PCVCAP_TAG(vcap) == 0 && kshape == PCVCAP_SHAPE(vcap); + } +}; + +/* + * Special value for functions returning JSPropCacheEntry * to distinguish + * between failure and no no-cache-fill cases. + */ +#define JS_NO_PROP_CACHE_FILL ((JSPropCacheEntry *) NULL + 1) + +#if defined DEBUG_brendan || defined DEBUG_brendaneich +#define JS_PROPERTY_CACHE_METERING 1 +#endif + +typedef struct JSPropertyCache { + JSPropCacheEntry table[PROPERTY_CACHE_SIZE]; + JSBool empty; +#ifdef JS_PROPERTY_CACHE_METERING + JSPropCacheEntry *pctestentry; /* entry of the last PC-based test */ + uint32 fills; /* number of cache entry fills */ + uint32 nofills; /* couldn't fill (e.g. default get) */ + uint32 rofills; /* set on read-only prop can't fill */ + uint32 disfills; /* fill attempts on disabled cache */ + uint32 oddfills; /* fill attempt after setter deleted */ + uint32 modfills; /* fill that rehashed to a new entry */ + uint32 brandfills; /* scope brandings to type structural + method fills */ + uint32 noprotos; /* resolve-returned non-proto pobj */ + uint32 longchains; /* overlong scope and/or proto chain */ + uint32 recycles; /* cache entries recycled by fills */ + uint32 pcrecycles; /* pc-keyed entries recycled by atom- + keyed fills */ + uint32 tests; /* cache probes */ + uint32 pchits; /* fast-path polymorphic op hits */ + uint32 protopchits; /* pchits hitting immediate prototype */ + uint32 initests; /* cache probes from JSOP_INITPROP */ + uint32 inipchits; /* init'ing next property pchit case */ + uint32 inipcmisses; /* init'ing next property pc misses */ + uint32 settests; /* cache probes from JOF_SET opcodes */ + uint32 addpchits; /* adding next property pchit case */ + uint32 setpchits; /* setting existing property pchit */ + uint32 setpcmisses; /* setting/adding property pc misses */ + uint32 slotchanges; /* clasp->reserveSlots result variance- + induced slot changes */ + uint32 setmisses; /* JSOP_SET{NAME,PROP} total misses */ + uint32 idmisses; /* slow-path key id == atom misses */ + uint32 komisses; /* slow-path key object misses */ + uint32 vcmisses; /* value capability misses */ + uint32 misses; /* cache misses */ + uint32 flushes; /* cache flushes */ + uint32 pcpurges; /* shadowing purges on proto chain */ +# define PCMETER(x) x +#else +# define PCMETER(x) ((void)0) +#endif +} JSPropertyCache; + +/* + * Property cache value tagging/untagging macros. + */ +#define PCVAL_OBJECT 0 +#define PCVAL_SLOT 1 +#define PCVAL_SPROP 2 + +#define PCVAL_TAGBITS 2 +#define PCVAL_TAGMASK JS_BITMASK(PCVAL_TAGBITS) +#define PCVAL_TAG(v) ((v) & PCVAL_TAGMASK) +#define PCVAL_CLRTAG(v) ((v) & ~(jsuword)PCVAL_TAGMASK) +#define PCVAL_SETTAG(v,t) ((jsuword)(v) | (t)) + +#define PCVAL_NULL 0 +#define PCVAL_IS_NULL(v) ((v) == PCVAL_NULL) + +#define PCVAL_IS_OBJECT(v) (PCVAL_TAG(v) == PCVAL_OBJECT) +#define PCVAL_TO_OBJECT(v) ((JSObject *) (v)) +#define OBJECT_TO_PCVAL(obj) ((jsuword) (obj)) + +#define PCVAL_OBJECT_TO_JSVAL(v) OBJECT_TO_JSVAL(PCVAL_TO_OBJECT(v)) +#define JSVAL_OBJECT_TO_PCVAL(v) OBJECT_TO_PCVAL(JSVAL_TO_OBJECT(v)) + +#define PCVAL_IS_SLOT(v) ((v) & PCVAL_SLOT) +#define PCVAL_TO_SLOT(v) ((jsuint)(v) >> 1) +#define SLOT_TO_PCVAL(i) (((jsuword)(i) << 1) | PCVAL_SLOT) + +#define PCVAL_IS_SPROP(v) (PCVAL_TAG(v) == PCVAL_SPROP) +#define PCVAL_TO_SPROP(v) ((JSScopeProperty *) PCVAL_CLRTAG(v)) +#define SPROP_TO_PCVAL(sprop) PCVAL_SETTAG(sprop, PCVAL_SPROP) + +/* + * Fill property cache entry for key cx->fp->pc, optimized value word computed + * from obj and sprop, and entry capability forged from 24-bit OBJ_SHAPE(obj), + * 4-bit scopeIndex, and 4-bit protoIndex. + * + * Return the filled cache entry or JS_NO_PROP_CACHE_FILL if caching was not + * possible. + */ +extern JS_REQUIRES_STACK JSPropCacheEntry * +js_FillPropertyCache(JSContext *cx, JSObject *obj, + uintN scopeIndex, uintN protoIndex, JSObject *pobj, + JSScopeProperty *sprop, JSBool adding); + +/* + * Property cache lookup macros. PROPERTY_CACHE_TEST is designed to inline the + * fast path in js_Interpret, so it makes "just-so" restrictions on parameters, + * e.g. pobj and obj should not be the same variable, since for JOF_PROP-mode + * opcodes, obj must not be changed because of a cache miss. + * + * On return from PROPERTY_CACHE_TEST, if atom is null then obj points to the + * scope chain element in which the property was found, pobj is locked, and + * entry is valid. If atom is non-null then no object is locked but entry is + * still set correctly for use, e.g., by js_FillPropertyCache and atom should + * be used as the id to find. + * + * We must lock pobj on a hit in order to close races with threads that might + * be deleting a property from its scope, or otherwise invalidating property + * caches (on all threads) by re-generating scope->shape. + */ +#define PROPERTY_CACHE_TEST(cx, pc, obj, pobj, entry, atom) \ + do { \ + JSPropertyCache *cache_ = &JS_PROPERTY_CACHE(cx); \ + uint32 kshape_ = (JS_ASSERT(OBJ_IS_NATIVE(obj)), OBJ_SHAPE(obj)); \ + entry = &cache_->table[PROPERTY_CACHE_HASH_PC(pc, kshape_)]; \ + PCMETER(cache_->pctestentry = entry); \ + PCMETER(cache_->tests++); \ + JS_ASSERT(&obj != &pobj); \ + if (entry->kpc == pc && entry->kshape == kshape_) { \ + JSObject *tmp_; \ + pobj = obj; \ + JS_ASSERT(PCVCAP_TAG(entry->vcap) <= 1); \ + if (PCVCAP_TAG(entry->vcap) == 1 && \ + (tmp_ = OBJ_GET_PROTO(cx, pobj)) != NULL) { \ + pobj = tmp_; \ + } \ + \ + if (JS_LOCK_OBJ_IF_SHAPE(cx, pobj, PCVCAP_SHAPE(entry->vcap))) { \ + PCMETER(cache_->pchits++); \ + PCMETER(!PCVCAP_TAG(entry->vcap) || cache_->protopchits++); \ + atom = NULL; \ + break; \ + } \ + } \ + atom = js_FullTestPropertyCache(cx, pc, &obj, &pobj, &entry); \ + if (atom) \ + PCMETER(cache_->misses++); \ + } while (0) + +extern JS_REQUIRES_STACK JSAtom * +js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, + JSObject **objp, JSObject **pobjp, + JSPropCacheEntry **entryp); + +/* The property cache does not need a destructor. */ +#define js_FinishPropertyCache(cache) ((void) 0) + +extern void +js_PurgePropertyCache(JSContext *cx, JSPropertyCache *cache); + +extern void +js_PurgePropertyCacheForScript(JSContext *cx, JSScript *script); + /* * Interpreter stack arena-pool alloc and free functions. */ diff --git a/js/src/jspropcache.cpp b/js/src/jspropcache.cpp deleted file mode 100644 index c6a11c7ff28..00000000000 --- a/js/src/jspropcache.cpp +++ /dev/null @@ -1,532 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sw=4 et tw=79: - * - * ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla Communicator client code, released - * March 31, 1998. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include -#include "jsapi.h" -#include "jscntxt.h" -#include "jsinterp.h" -#include "jsobj.h" -#include "jsscope.h" - -JS_REQUIRES_STACK JSPropCacheEntry * -js_FillPropertyCache(JSContext *cx, JSObject *obj, - uintN scopeIndex, uintN protoIndex, JSObject *pobj, - JSScopeProperty *sprop, JSBool adding) -{ - JSPropertyCache *cache; - jsbytecode *pc; - JSScope *scope; - jsuword kshape, vshape, khash; - JSOp op; - const JSCodeSpec *cs; - jsuword vword; - ptrdiff_t pcoff; - JSAtom *atom; - JSPropCacheEntry *entry; - - JS_ASSERT(!cx->runtime->gcRunning); - cache = &JS_PROPERTY_CACHE(cx); - - /* FIXME bug 489098: consider enabling the property cache for eval. */ - if (js_IsPropertyCacheDisabled(cx) || (cx->fp->flags & JSFRAME_EVAL)) { - PCMETER(cache->disfills++); - return JS_NO_PROP_CACHE_FILL; - } - - /* - * Check for fill from js_SetPropertyHelper where the setter removed sprop - * from pobj's scope (via unwatch or delete, e.g.). - */ - scope = OBJ_SCOPE(pobj); - if (!scope->has(sprop)) { - PCMETER(cache->oddfills++); - return JS_NO_PROP_CACHE_FILL; - } - - /* - * Check for overdeep scope and prototype chain. Because resolve, getter, - * and setter hooks can change the prototype chain using JS_SetPrototype - * after js_LookupPropertyWithFlags has returned the nominal protoIndex, - * we have to validate protoIndex if it is non-zero. If it is zero, then - * we know thanks to the scope->has test above, combined with the fact that - * obj == pobj, that protoIndex is invariant. - * - * The scopeIndex can't be wrong. We require JS_SetParent calls to happen - * before any running script might consult a parent-linked scope chain. If - * this requirement is not satisfied, the fill in progress will never hit, - * but vcap vs. scope shape tests ensure nothing malfunctions. - */ - JS_ASSERT_IF(scopeIndex == 0 && protoIndex == 0, obj == pobj); - - if (protoIndex != 0) { - JSObject *tmp = obj; - - for (uintN i = 0; i != scopeIndex; i++) - tmp = OBJ_GET_PARENT(cx, tmp); - JS_ASSERT(tmp != pobj); - - protoIndex = 1; - for (;;) { - tmp = OBJ_GET_PROTO(cx, tmp); - - /* - * We cannot cache properties coming from native objects behind - * non-native ones on the prototype chain. The non-natives can - * mutate in arbitrary way without changing any shapes. - */ - if (!tmp || !OBJ_IS_NATIVE(tmp)) { - PCMETER(cache->noprotos++); - return JS_NO_PROP_CACHE_FILL; - } - if (tmp == pobj) - break; - ++protoIndex; - } - } - - if (scopeIndex > PCVCAP_SCOPEMASK || protoIndex > PCVCAP_PROTOMASK) { - PCMETER(cache->longchains++); - return JS_NO_PROP_CACHE_FILL; - } - - /* - * Optimize the cached vword based on our parameters and the current pc's - * opcode format flags. - */ - pc = cx->fp->regs->pc; - op = js_GetOpcode(cx, cx->fp->script, pc); - cs = &js_CodeSpec[op]; - kshape = 0; - - do { - /* - * Check for a prototype "plain old method" callee computation. What - * is a plain old method? It's a function-valued property with stub - * getter, so get of a function is idempotent. - */ - if (cs->format & JOF_CALLOP) { - jsval v; - - if (sprop->isMethod()) { - /* - * A compiler-created function object, AKA a method, already - * memoized in the property tree. - */ - JS_ASSERT(scope->hasMethodBarrier()); - v = sprop->methodValue(); - JS_ASSERT(VALUE_IS_FUNCTION(cx, v)); - JS_ASSERT(v == LOCKED_OBJ_GET_SLOT(pobj, sprop->slot)); - vword = JSVAL_OBJECT_TO_PCVAL(v); - break; - } - - if (SPROP_HAS_STUB_GETTER(sprop) && - SPROP_HAS_VALID_SLOT(sprop, scope)) { - v = LOCKED_OBJ_GET_SLOT(pobj, sprop->slot); - if (VALUE_IS_FUNCTION(cx, v)) { - /* - * Great, we have a function-valued prototype property - * where the getter is JS_PropertyStub. The type id in - * pobj's scope does not evolve with changes to property - * values, however. - * - * So here, on first cache fill for this method, we brand - * the scope with a new shape and set the JSScope::BRANDED - * flag. Once this flag is set, any property assignment - * that changes the value from or to a different function - * object will result in shape being regenerated. - */ - if (!scope->branded()) { - PCMETER(cache->brandfills++); -#ifdef DEBUG_notme - fprintf(stderr, - "branding %p (%s) for funobj %p (%s), shape %lu\n", - pobj, pobj->getClass()->name, - JSVAL_TO_OBJECT(v), - JS_GetFunctionName(GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(v))), - OBJ_SHAPE(obj)); -#endif - scope->brandingShapeChange(cx, sprop->slot, v); - if (js_IsPropertyCacheDisabled(cx)) /* check for rt->shapeGen overflow */ - return JS_NO_PROP_CACHE_FILL; - scope->setBranded(); - } - vword = JSVAL_OBJECT_TO_PCVAL(v); - break; - } - } - } - - /* If getting a value via a stub getter, we can cache the slot. */ - if (!(cs->format & (JOF_SET | JOF_INCDEC | JOF_FOR)) && - SPROP_HAS_STUB_GETTER(sprop) && - SPROP_HAS_VALID_SLOT(sprop, scope)) { - /* Great, let's cache sprop's slot and use it on cache hit. */ - vword = SLOT_TO_PCVAL(sprop->slot); - } else { - /* Best we can do is to cache sprop (still a nice speedup). */ - vword = SPROP_TO_PCVAL(sprop); - if (adding && - sprop == scope->lastProp && - scope->shape == sprop->shape) { - /* - * Our caller added a new property. We also know that a setter - * that js_NativeSet could have run has not mutated the scope, - * so the added property is still the last one added, and the - * scope is not branded. - * - * We want to cache under scope's shape before the property - * addition to bias for the case when the mutator opcode - * always adds the same property. This allows us to optimize - * periodic execution of object initializers or other explicit - * initialization sequences such as - * - * obj = {}; obj.x = 1; obj.y = 2; - * - * We assume that on average the win from this optimization is - * greater than the cost of an extra mismatch per loop owing to - * the bias for the following case: - * - * obj = {}; ... for (...) { ... obj.x = ... } - * - * On the first iteration of such a for loop, JSOP_SETPROP - * fills the cache with the shape of the newly created object - * obj, not the shape of obj after obj.x has been assigned. - * That mismatches obj's shape on the second iteration. Note - * that on the third and subsequent iterations the cache will - * be hit because the shape is no longer updated. - */ - JS_ASSERT(scope->owned()); - if (sprop->parent) { - kshape = sprop->parent->shape; - } else { - /* - * If obj had its own empty scope before, with a unique - * shape, that is lost. Here we only attempt to find a - * matching empty scope. In unusual cases involving - * __proto__ assignment we may not find one. - */ - JSObject *proto = STOBJ_GET_PROTO(obj); - if (!proto || !OBJ_IS_NATIVE(proto)) - return JS_NO_PROP_CACHE_FILL; - JSScope *protoscope = OBJ_SCOPE(proto); - if (!protoscope->emptyScope || - !js_ObjectIsSimilarToProto(cx, obj, obj->map->ops, OBJ_GET_CLASS(cx, obj), - proto)) { - return JS_NO_PROP_CACHE_FILL; - } - kshape = protoscope->emptyScope->shape; - } - - /* - * When adding we predict no prototype object will later gain a - * readonly property or setter. - */ - vshape = cx->runtime->protoHazardShape; - } - } - } while (0); - - if (kshape == 0) { - kshape = OBJ_SHAPE(obj); - vshape = scope->shape; - } - - khash = PROPERTY_CACHE_HASH_PC(pc, kshape); - if (obj == pobj) { - JS_ASSERT(scopeIndex == 0 && protoIndex == 0); - } else { - if (op == JSOP_LENGTH) { - atom = cx->runtime->atomState.lengthAtom; - } else { - pcoff = (JOF_TYPE(cs->format) == JOF_SLOTATOM) ? SLOTNO_LEN : 0; - GET_ATOM_FROM_BYTECODE(cx->fp->script, pc, pcoff, atom); - } - -#ifdef DEBUG - if (scopeIndex == 0) { - JS_ASSERT(protoIndex != 0); - JS_ASSERT((protoIndex == 1) == (OBJ_GET_PROTO(cx, obj) == pobj)); - } -#endif - - if (scopeIndex != 0 || protoIndex != 1) { - khash = PROPERTY_CACHE_HASH_ATOM(atom, obj); - PCMETER(if (PCVCAP_TAG(cache->table[khash].vcap) <= 1) - cache->pcrecycles++); - pc = (jsbytecode *) atom; - kshape = (jsuword) obj; - - /* - * Make sure that a later shadowing assignment will enter - * PurgeProtoChain and invalidate this entry, bug 479198. - * - * This is thread-safe even though obj is not locked. Only the - * DELEGATE bit of obj->classword can change at runtime, given that - * obj is native; and the bit is only set, never cleared. And on - * platforms where another CPU can fail to see this write, it's OK - * because the property cache and JIT cache are thread-local. - */ - obj->setDelegate(); - } - } - - entry = &cache->table[khash]; - PCMETER(PCVAL_IS_NULL(entry->vword) || cache->recycles++); - entry->kpc = pc; - entry->kshape = kshape; - entry->vcap = PCVCAP_MAKE(vshape, scopeIndex, protoIndex); - entry->vword = vword; - - cache->empty = JS_FALSE; - PCMETER(cache->fills++); - - /* - * The modfills counter is not exact. It increases if a getter or setter - * recurse into the interpreter. - */ - PCMETER(entry == cache->pctestentry || cache->modfills++); - PCMETER(cache->pctestentry = NULL); - return entry; -} - -JS_REQUIRES_STACK JSAtom * -js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, - JSObject **objp, JSObject **pobjp, - JSPropCacheEntry **entryp) -{ - JSOp op; - const JSCodeSpec *cs; - ptrdiff_t pcoff; - JSAtom *atom; - JSObject *obj, *pobj, *tmp; - JSPropCacheEntry *entry; - uint32 vcap; - - JS_ASSERT(uintN((cx->fp->imacpc ? cx->fp->imacpc : pc) - cx->fp->script->code) - < cx->fp->script->length); - - op = js_GetOpcode(cx, cx->fp->script, pc); - cs = &js_CodeSpec[op]; - if (op == JSOP_LENGTH) { - atom = cx->runtime->atomState.lengthAtom; - } else { - pcoff = (JOF_TYPE(cs->format) == JOF_SLOTATOM) ? SLOTNO_LEN : 0; - GET_ATOM_FROM_BYTECODE(cx->fp->script, pc, pcoff, atom); - } - - obj = *objp; - JS_ASSERT(OBJ_IS_NATIVE(obj)); - entry = &JS_PROPERTY_CACHE(cx).table[PROPERTY_CACHE_HASH_ATOM(atom, obj)]; - *entryp = entry; - vcap = entry->vcap; - - if (entry->kpc != (jsbytecode *) atom) { - PCMETER(JS_PROPERTY_CACHE(cx).idmisses++); - -#ifdef DEBUG_notme - entry = &JS_PROPERTY_CACHE(cx).table[PROPERTY_CACHE_HASH_PC(pc, OBJ_SHAPE(obj))]; - fprintf(stderr, - "id miss for %s from %s:%u" - " (pc %u, kpc %u, kshape %u, shape %u)\n", - js_AtomToPrintableString(cx, atom), - cx->fp->script->filename, - js_PCToLineNumber(cx, cx->fp->script, pc), - pc - cx->fp->script->code, - entry->kpc - cx->fp->script->code, - entry->kshape, - OBJ_SHAPE(obj)); - js_Disassemble1(cx, cx->fp->script, pc, - pc - cx->fp->script->code, - JS_FALSE, stderr); -#endif - - return atom; - } - - if (entry->kshape != (jsuword) obj) { - PCMETER(JS_PROPERTY_CACHE(cx).komisses++); - return atom; - } - - pobj = obj; - - if (JOF_MODE(cs->format) == JOF_NAME) { - while (vcap & (PCVCAP_SCOPEMASK << PCVCAP_PROTOBITS)) { - tmp = OBJ_GET_PARENT(cx, pobj); - if (!tmp || !OBJ_IS_NATIVE(tmp)) - break; - pobj = tmp; - vcap -= PCVCAP_PROTOSIZE; - } - - *objp = pobj; - } - - while (vcap & PCVCAP_PROTOMASK) { - tmp = OBJ_GET_PROTO(cx, pobj); - if (!tmp || !OBJ_IS_NATIVE(tmp)) - break; - pobj = tmp; - --vcap; - } - - if (JS_LOCK_OBJ_IF_SHAPE(cx, pobj, PCVCAP_SHAPE(vcap))) { -#ifdef DEBUG - jsid id = ATOM_TO_JSID(atom); - - id = js_CheckForStringIndex(id); - JS_ASSERT(OBJ_SCOPE(pobj)->lookup(id)); - JS_ASSERT_IF(OBJ_SCOPE(pobj)->object, OBJ_SCOPE(pobj)->object == pobj); -#endif - *pobjp = pobj; - return NULL; - } - - PCMETER(JS_PROPERTY_CACHE(cx).vcmisses++); - return atom; -} - -#ifdef DEBUG -#define ASSERT_CACHE_IS_EMPTY(cache) \ - JS_BEGIN_MACRO \ - JSPropertyCache *cache_ = (cache); \ - uintN i_; \ - JS_ASSERT(cache_->empty); \ - for (i_ = 0; i_ < PROPERTY_CACHE_SIZE; i_++) { \ - JS_ASSERT(!cache_->table[i_].kpc); \ - JS_ASSERT(!cache_->table[i_].kshape); \ - JS_ASSERT(!cache_->table[i_].vcap); \ - JS_ASSERT(!cache_->table[i_].vword); \ - } \ - JS_END_MACRO -#else -#define ASSERT_CACHE_IS_EMPTY(cache) ((void)0) -#endif - -JS_STATIC_ASSERT(PCVAL_NULL == 0); - -void -js_PurgePropertyCache(JSContext *cx, JSPropertyCache *cache) -{ - if (cache->empty) { - ASSERT_CACHE_IS_EMPTY(cache); - return; - } - - memset(cache->table, 0, sizeof cache->table); - cache->empty = JS_TRUE; - -#ifdef JS_PROPERTY_CACHE_METERING - { static FILE *fp; - if (!fp) - fp = fopen("/tmp/propcache.stats", "w"); - if (fp) { - fputs("Property cache stats for ", fp); -#ifdef JS_THREADSAFE - fprintf(fp, "thread %lu, ", (unsigned long) cx->thread->id); -#endif - fprintf(fp, "GC %u\n", cx->runtime->gcNumber); - -# define P(mem) fprintf(fp, "%11s %10lu\n", #mem, (unsigned long)cache->mem) - P(fills); - P(nofills); - P(rofills); - P(disfills); - P(oddfills); - P(modfills); - P(brandfills); - P(noprotos); - P(longchains); - P(recycles); - P(pcrecycles); - P(tests); - P(pchits); - P(protopchits); - P(initests); - P(inipchits); - P(inipcmisses); - P(settests); - P(addpchits); - P(setpchits); - P(setpcmisses); - P(slotchanges); - P(setmisses); - P(idmisses); - P(komisses); - P(vcmisses); - P(misses); - P(flushes); - P(pcpurges); -# undef P - - fprintf(fp, "hit rates: pc %g%% (proto %g%%), set %g%%, ini %g%%, full %g%%\n", - (100. * cache->pchits) / cache->tests, - (100. * cache->protopchits) / cache->tests, - (100. * (cache->addpchits + cache->setpchits)) - / cache->settests, - (100. * cache->inipchits) / cache->initests, - (100. * (cache->tests - cache->misses)) / cache->tests); - fflush(fp); - } - } -#endif - - PCMETER(cache->flushes++); -} - -void -js_PurgePropertyCacheForScript(JSContext *cx, JSScript *script) -{ - JSPropertyCache *cache; - JSPropCacheEntry *entry; - - cache = &JS_PROPERTY_CACHE(cx); - for (entry = cache->table; entry < cache->table + PROPERTY_CACHE_SIZE; - entry++) { - if (JS_UPTRDIFF(entry->kpc, script->code) < script->length) { - entry->kpc = NULL; - entry->kshape = 0; -#ifdef DEBUG - entry->vcap = entry->vword = 0; -#endif - } - } -} diff --git a/js/src/jspropcache.h b/js/src/jspropcache.h deleted file mode 100644 index 9c09a8c662f..00000000000 --- a/js/src/jspropcache.h +++ /dev/null @@ -1,261 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sw=4 et tw=79: - * - * ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla Communicator client code, released - * March 31, 1998. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifndef jspropcache_h___ -#define jspropcache_h___ - -/* - * Property cache with structurally typed capabilities for invalidation, for - * polymorphic callsite method/get/set speedups. For details, see - * . - */ -#define PROPERTY_CACHE_LOG2 12 -#define PROPERTY_CACHE_SIZE JS_BIT(PROPERTY_CACHE_LOG2) -#define PROPERTY_CACHE_MASK JS_BITMASK(PROPERTY_CACHE_LOG2) - -/* - * Add kshape rather than xor it to avoid collisions between nearby bytecode - * that are evolving an object by setting successive properties, incrementing - * the object's scope->shape on each set. - */ -#define PROPERTY_CACHE_HASH(pc,kshape) \ - (((((jsuword)(pc) >> PROPERTY_CACHE_LOG2) ^ (jsuword)(pc)) + (kshape)) & \ - PROPERTY_CACHE_MASK) - -#define PROPERTY_CACHE_HASH_PC(pc,kshape) \ - PROPERTY_CACHE_HASH(pc, kshape) - -#define PROPERTY_CACHE_HASH_ATOM(atom,obj) \ - PROPERTY_CACHE_HASH((jsuword)(atom) >> 2, OBJ_SHAPE(obj)) - -/* - * Property cache value capability macros. - */ -#define PCVCAP_PROTOBITS 4 -#define PCVCAP_PROTOSIZE JS_BIT(PCVCAP_PROTOBITS) -#define PCVCAP_PROTOMASK JS_BITMASK(PCVCAP_PROTOBITS) - -#define PCVCAP_SCOPEBITS 4 -#define PCVCAP_SCOPESIZE JS_BIT(PCVCAP_SCOPEBITS) -#define PCVCAP_SCOPEMASK JS_BITMASK(PCVCAP_SCOPEBITS) - -#define PCVCAP_TAGBITS (PCVCAP_PROTOBITS + PCVCAP_SCOPEBITS) -#define PCVCAP_TAGMASK JS_BITMASK(PCVCAP_TAGBITS) -#define PCVCAP_TAG(t) ((t) & PCVCAP_TAGMASK) - -#define PCVCAP_MAKE(t,s,p) ((uint32(t) << PCVCAP_TAGBITS) | \ - ((s) << PCVCAP_PROTOBITS) | \ - (p)) -#define PCVCAP_SHAPE(t) ((t) >> PCVCAP_TAGBITS) - -#define SHAPE_OVERFLOW_BIT JS_BIT(32 - PCVCAP_TAGBITS) - -struct JSPropCacheEntry { - jsbytecode *kpc; /* pc if vcap tag is <= 1, else atom */ - jsuword kshape; /* key shape if pc, else obj for atom */ - jsuword vcap; /* value capability, see above */ - jsuword vword; /* value word, see PCVAL_* below */ - - bool adding() const { - return PCVCAP_TAG(vcap) == 0 && kshape != PCVCAP_SHAPE(vcap); - } - - bool directHit() const { - return PCVCAP_TAG(vcap) == 0 && kshape == PCVCAP_SHAPE(vcap); - } -}; - -/* - * Special value for functions returning JSPropCacheEntry * to distinguish - * between failure and no no-cache-fill cases. - */ -#define JS_NO_PROP_CACHE_FILL ((JSPropCacheEntry *) NULL + 1) - -#if defined DEBUG_brendan || defined DEBUG_brendaneich -#define JS_PROPERTY_CACHE_METERING 1 -#endif - -typedef struct JSPropertyCache { - JSPropCacheEntry table[PROPERTY_CACHE_SIZE]; - JSBool empty; -#ifdef JS_PROPERTY_CACHE_METERING - JSPropCacheEntry *pctestentry; /* entry of the last PC-based test */ - uint32 fills; /* number of cache entry fills */ - uint32 nofills; /* couldn't fill (e.g. default get) */ - uint32 rofills; /* set on read-only prop can't fill */ - uint32 disfills; /* fill attempts on disabled cache */ - uint32 oddfills; /* fill attempt after setter deleted */ - uint32 modfills; /* fill that rehashed to a new entry */ - uint32 brandfills; /* scope brandings to type structural - method fills */ - uint32 noprotos; /* resolve-returned non-proto pobj */ - uint32 longchains; /* overlong scope and/or proto chain */ - uint32 recycles; /* cache entries recycled by fills */ - uint32 pcrecycles; /* pc-keyed entries recycled by atom- - keyed fills */ - uint32 tests; /* cache probes */ - uint32 pchits; /* fast-path polymorphic op hits */ - uint32 protopchits; /* pchits hitting immediate prototype */ - uint32 initests; /* cache probes from JSOP_INITPROP */ - uint32 inipchits; /* init'ing next property pchit case */ - uint32 inipcmisses; /* init'ing next property pc misses */ - uint32 settests; /* cache probes from JOF_SET opcodes */ - uint32 addpchits; /* adding next property pchit case */ - uint32 setpchits; /* setting existing property pchit */ - uint32 setpcmisses; /* setting/adding property pc misses */ - uint32 slotchanges; /* clasp->reserveSlots result variance- - induced slot changes */ - uint32 setmisses; /* JSOP_SET{NAME,PROP} total misses */ - uint32 idmisses; /* slow-path key id == atom misses */ - uint32 komisses; /* slow-path key object misses */ - uint32 vcmisses; /* value capability misses */ - uint32 misses; /* cache misses */ - uint32 flushes; /* cache flushes */ - uint32 pcpurges; /* shadowing purges on proto chain */ -# define PCMETER(x) x -#else -# define PCMETER(x) ((void)0) -#endif -} JSPropertyCache; - -/* - * Property cache value tagging/untagging macros. - */ -#define PCVAL_OBJECT 0 -#define PCVAL_SLOT 1 -#define PCVAL_SPROP 2 - -#define PCVAL_TAGBITS 2 -#define PCVAL_TAGMASK JS_BITMASK(PCVAL_TAGBITS) -#define PCVAL_TAG(v) ((v) & PCVAL_TAGMASK) -#define PCVAL_CLRTAG(v) ((v) & ~(jsuword)PCVAL_TAGMASK) -#define PCVAL_SETTAG(v,t) ((jsuword)(v) | (t)) - -#define PCVAL_NULL 0 -#define PCVAL_IS_NULL(v) ((v) == PCVAL_NULL) - -#define PCVAL_IS_OBJECT(v) (PCVAL_TAG(v) == PCVAL_OBJECT) -#define PCVAL_TO_OBJECT(v) ((JSObject *) (v)) -#define OBJECT_TO_PCVAL(obj) ((jsuword) (obj)) - -#define PCVAL_OBJECT_TO_JSVAL(v) OBJECT_TO_JSVAL(PCVAL_TO_OBJECT(v)) -#define JSVAL_OBJECT_TO_PCVAL(v) OBJECT_TO_PCVAL(JSVAL_TO_OBJECT(v)) - -#define PCVAL_IS_SLOT(v) ((v) & PCVAL_SLOT) -#define PCVAL_TO_SLOT(v) ((jsuint)(v) >> 1) -#define SLOT_TO_PCVAL(i) (((jsuword)(i) << 1) | PCVAL_SLOT) - -#define PCVAL_IS_SPROP(v) (PCVAL_TAG(v) == PCVAL_SPROP) -#define PCVAL_TO_SPROP(v) ((JSScopeProperty *) PCVAL_CLRTAG(v)) -#define SPROP_TO_PCVAL(sprop) PCVAL_SETTAG(sprop, PCVAL_SPROP) - -/* - * Fill property cache entry for key cx->fp->pc, optimized value word computed - * from obj and sprop, and entry capability forged from 24-bit OBJ_SHAPE(obj), - * 4-bit scopeIndex, and 4-bit protoIndex. - * - * Return the filled cache entry or JS_NO_PROP_CACHE_FILL if caching was not - * possible. - */ -extern JS_REQUIRES_STACK JSPropCacheEntry * -js_FillPropertyCache(JSContext *cx, JSObject *obj, - uintN scopeIndex, uintN protoIndex, JSObject *pobj, - JSScopeProperty *sprop, JSBool adding); - -/* - * Property cache lookup macros. PROPERTY_CACHE_TEST is designed to inline the - * fast path in js_Interpret, so it makes "just-so" restrictions on parameters, - * e.g. pobj and obj should not be the same variable, since for JOF_PROP-mode - * opcodes, obj must not be changed because of a cache miss. - * - * On return from PROPERTY_CACHE_TEST, if atom is null then obj points to the - * scope chain element in which the property was found, pobj is locked, and - * entry is valid. If atom is non-null then no object is locked but entry is - * still set correctly for use, e.g., by js_FillPropertyCache and atom should - * be used as the id to find. - * - * We must lock pobj on a hit in order to close races with threads that might - * be deleting a property from its scope, or otherwise invalidating property - * caches (on all threads) by re-generating scope->shape. - */ -#define PROPERTY_CACHE_TEST(cx, pc, obj, pobj, entry, atom) \ - do { \ - JSPropertyCache *cache_ = &JS_PROPERTY_CACHE(cx); \ - uint32 kshape_ = (JS_ASSERT(OBJ_IS_NATIVE(obj)), OBJ_SHAPE(obj)); \ - entry = &cache_->table[PROPERTY_CACHE_HASH_PC(pc, kshape_)]; \ - PCMETER(cache_->pctestentry = entry); \ - PCMETER(cache_->tests++); \ - JS_ASSERT(&obj != &pobj); \ - if (entry->kpc == pc && entry->kshape == kshape_) { \ - JSObject *tmp_; \ - pobj = obj; \ - JS_ASSERT(PCVCAP_TAG(entry->vcap) <= 1); \ - if (PCVCAP_TAG(entry->vcap) == 1 && \ - (tmp_ = OBJ_GET_PROTO(cx, pobj)) != NULL) { \ - pobj = tmp_; \ - } \ - \ - if (JS_LOCK_OBJ_IF_SHAPE(cx, pobj, PCVCAP_SHAPE(entry->vcap))) { \ - PCMETER(cache_->pchits++); \ - PCMETER(!PCVCAP_TAG(entry->vcap) || cache_->protopchits++); \ - atom = NULL; \ - break; \ - } \ - } \ - atom = js_FullTestPropertyCache(cx, pc, &obj, &pobj, &entry); \ - if (atom) \ - PCMETER(cache_->misses++); \ - } while (0) - -extern JS_REQUIRES_STACK JSAtom * -js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, - JSObject **objp, JSObject **pobjp, - JSPropCacheEntry **entryp); - -/* The property cache does not need a destructor. */ -#define js_FinishPropertyCache(cache) ((void) 0) - -extern void -js_PurgePropertyCache(JSContext *cx, JSPropertyCache *cache); - -extern void -js_PurgePropertyCacheForScript(JSContext *cx, JSScript *script); - -#endif /* jspropcache_h___ */