From 579d4f6d243048b264e6f0677700cbd78cc3143d Mon Sep 17 00:00:00 2001 From: Brendan Eich Date: Thu, 25 Jun 2009 12:05:09 -0700 Subject: [PATCH] Seed new empty scope shape from prototype to handle foreshadowing, enable deep propcache hits keyed by shapes, simplify code (497789, r=igor). --- js/src/jsinterp.cpp | 80 +++++++++++++++++++-------------------------- js/src/jsinterp.h | 14 +++----- js/src/jsscope.cpp | 26 +++++++++------ js/src/jstracer.cpp | 38 +++++++-------------- 4 files changed, 64 insertions(+), 94 deletions(-) diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index e1cbf84ebb6..81162bfced6 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -115,12 +115,10 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, JSPropertyCache *cache; jsbytecode *pc; JSScope *scope; - jsuword kshape, vshape, khash; + jsuword kshape, vshape; JSOp op; const JSCodeSpec *cs; jsuword vword; - ptrdiff_t pcoff; - JSAtom *atom; JSPropCacheEntry *entry; JS_ASSERT(!cx->runtime->gcRunning); @@ -313,19 +311,11 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, vshape = scope->shape; } - khash = PROPERTY_CACHE_HASH_PC(pc, kshape); if (obj == pobj) { JS_ASSERT(scopeIndex == 0 && protoIndex == 0); JS_ASSERT(OBJ_SCOPE(obj)->object == obj); JS_ASSERT(kshape != 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); @@ -334,12 +324,6 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, #endif if (scopeIndex != 0 || protoIndex != 1) { - khash = PROPERTY_CACHE_HASH_ATOM(atom, obj, pobj); - 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. @@ -354,7 +338,7 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, } } - entry = &cache->table[khash]; + entry = &cache->table[PROPERTY_CACHE_HASH_PC(pc, kshape)]; PCMETER(PCVAL_IS_NULL(entry->vword) || cache->recycles++); entry->kpc = pc; entry->kshape = kshape; @@ -373,42 +357,41 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, return entry; } +static inline JSAtom * +GetAtomFromBytecode(JSContext *cx, jsbytecode *pc, JSOp op, const JSCodeSpec *cs) +{ + if (op == JSOP_LENGTH) + return cx->runtime->atomState.lengthAtom; + + ptrdiff_t pcoff = (JOF_TYPE(cs->format) == JOF_SLOTATOM) ? SLOTNO_LEN : 0; + JSAtom *atom; + GET_ATOM_FROM_BYTECODE(cx->fp->script, pc, pcoff, atom); + return atom; +} + JS_REQUIRES_STACK JSAtom * js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, JSObject **objp, JSObject **pobjp, - JSPropCacheEntry **entryp) + JSPropCacheEntry *entry) { - 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); - } + JSOp op = js_GetOpcode(cx, cx->fp->script, pc); + const JSCodeSpec *cs = &js_CodeSpec[op]; obj = *objp; JS_ASSERT(OBJ_IS_NATIVE(obj)); - entry = &JS_PROPERTY_CACHE(cx).table[PROPERTY_CACHE_HASH_ATOM(atom, obj, NULL)]; - *entryp = entry; vcap = entry->vcap; - if (entry->kpc != (jsbytecode *) atom) { - PCMETER(JS_PROPERTY_CACHE(cx).idmisses++); + if (entry->kpc != pc) { + PCMETER(JS_PROPERTY_CACHE(cx).kpcmisses++); + JSAtom *atom = GetAtomFromBytecode(cx, pc, op, cs); #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", @@ -427,11 +410,15 @@ js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, return atom; } - if (entry->kshape != (jsuword) obj) { - PCMETER(JS_PROPERTY_CACHE(cx).komisses++); - return atom; + if (entry->kshape != OBJ_SHAPE(obj)) { + PCMETER(JS_PROPERTY_CACHE(cx).kshmisses++); + return GetAtomFromBytecode(cx, pc, op, cs); } + /* + * PROPERTY_CACHE_TEST handles only the direct and immediate-prototype hit + * cases, all others go here. + */ pobj = obj; if (JOF_MODE(cs->format) == JOF_NAME) { @@ -456,6 +443,7 @@ js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, if (JS_LOCK_OBJ_IF_SHAPE(cx, pobj, PCVCAP_SHAPE(vcap))) { #ifdef DEBUG + JSAtom *atom = GetAtomFromBytecode(cx, pc, op, cs); jsid id = ATOM_TO_JSID(atom); CHECK_FOR_STRING_INDEX(id); @@ -467,7 +455,7 @@ js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, } PCMETER(JS_PROPERTY_CACHE(cx).vcmisses++); - return atom; + return GetAtomFromBytecode(cx, pc, op, cs); } #ifdef DEBUG @@ -522,7 +510,6 @@ js_PurgePropertyCache(JSContext *cx, JSPropertyCache *cache) P(noprotos); P(longchains); P(recycles); - P(pcrecycles); P(tests); P(pchits); P(protopchits); @@ -535,8 +522,8 @@ js_PurgePropertyCache(JSContext *cx, JSPropertyCache *cache) P(setpcmisses); P(slotchanges); P(setmisses); - P(idmisses); - P(komisses); + P(kpcmisses); + P(kshmisses); P(vcmisses); P(misses); P(flushes); @@ -569,9 +556,8 @@ js_PurgePropertyCacheForScript(JSContext *cx, JSScript *script) entry++) { if (JS_UPTRDIFF(entry->kpc, script->code) < script->length) { entry->kpc = NULL; - entry->kshape = 0; #ifdef DEBUG - entry->vcap = entry->vword = 0; + entry->kshape = entry->vcap = entry->vword = 0; #endif } } @@ -4810,7 +4796,7 @@ js_Interpret(JSContext *cx) } atom = js_FullTestPropertyCache(cx, regs.pc, &obj, &obj2, - &entry); + entry); if (atom) { PCMETER(cache->misses++); PCMETER(cache->setmisses++); diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index 1f4be263a61..d89fa209211 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -217,9 +217,6 @@ typedef struct JSInlineFrame { #define PROPERTY_CACHE_HASH_PC(pc,kshape) \ PROPERTY_CACHE_HASH(pc, kshape) -#define PROPERTY_CACHE_HASH_ATOM(atom,obj,pobj) \ - PROPERTY_CACHE_HASH((jsuword)(atom) >> 2, OBJ_SHAPE(obj)) - /* * Property cache value capability macros. */ @@ -282,8 +279,6 @@ typedef struct JSPropertyCache { 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 */ @@ -297,8 +292,8 @@ typedef struct JSPropertyCache { 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 kpcmisses; /* slow-path key pc misses */ + uint32 kshmisses; /* slow-path key shape misses */ uint32 vcmisses; /* value capability misses */ uint32 misses; /* cache misses */ uint32 flushes; /* cache flushes */ @@ -380,7 +375,6 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, 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 && \ OBJ_IS_NATIVE(tmp_)) { \ @@ -395,7 +389,7 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, break; \ } \ } \ - atom = js_FullTestPropertyCache(cx, pc, &obj, &pobj, &entry); \ + atom = js_FullTestPropertyCache(cx, pc, &obj, &pobj, entry); \ if (atom) \ PCMETER(cache_->misses++); \ } while (0) @@ -403,7 +397,7 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, extern JS_REQUIRES_STACK JSAtom * js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, JSObject **objp, JSObject **pobjp, - JSPropCacheEntry **entryp); + JSPropCacheEntry *entry); /* The property cache does not need a destructor. */ #define js_FinishPropertyCache(cache) ((void) 0) diff --git a/js/src/jsscope.cpp b/js/src/jsscope.cpp index 0ade163f9e5..806349acf4e 100644 --- a/js/src/jsscope.cpp +++ b/js/src/jsscope.cpp @@ -110,8 +110,12 @@ js_GetMutableScope(JSContext *cx, JSObject *obj) static void InitMinimalScope(JSContext *cx, JSScope *scope) { - js_LeaveTraceIfGlobalObject(cx, scope->object); - scope->shape = 0; + JSObject *obj = scope->object; + js_LeaveTraceIfGlobalObject(cx, obj); + + JSObject *proto = OBJ_GET_PROTO(cx, obj); + scope->shape = (proto && OBJ_IS_NATIVE(proto)) ? OBJ_SHAPE(proto) : 0; + scope->hashShift = JS_DHASH_BITS - MIN_SCOPE_SIZE_LOG2; scope->entryCount = scope->removedCount = 0; scope->table = NULL; @@ -1018,7 +1022,7 @@ js_AddScopeProperty(JSContext *cx, JSScope *scope, jsid id, uintN attrs, uintN flags, intN shortid) { JSScopeProperty **spp, *sprop, *overwriting, **spvec, **spp2, child; - uint32 size, splen, i; + uintN size, splen, i; int change; JSTempValueRooter tvr; @@ -1198,14 +1202,16 @@ js_AddScopeProperty(JSContext *cx, JSScope *scope, jsid id, * sprop, while the former simply tests whether sprop->id * is bound in scope. */ - if (!SCOPE_GET_PROPERTY(scope, sprop->id)) - continue; + if (SCOPE_GET_PROPERTY(scope, sprop->id)) { + JS_ASSERT(sprop != overwriting); + spvec[--i] = sprop; + } + sprop = sprop->parent; + } while (i != 0); - JS_ASSERT(sprop != overwriting); - JS_ASSERT(i != 0); - spvec[--i] = sprop; - } while ((sprop = sprop->parent) != NULL); - JS_ASSERT(i == 0); + JSObject *proto = OBJ_GET_PROTO(cx, scope->object); + if (proto && OBJ_IS_NATIVE(proto)) + sprop = OBJ_SCOPE(proto)->lastProp; /* * Now loop forward through spvec, forking the property tree diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index 17a2bc30b5f..3d97c11c001 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -7409,33 +7409,17 @@ TraceRecorder::test_property_cache(JSObject* obj, LIns* obj_ins, JSObject*& obj2 JS_ASSERT(cx->requestDepth); #endif - // Emit guard(s), common code for both hit and miss cases. - // Check for first-level cache hit and guard on kshape if possible. - // Otherwise guard on key object exact match. - if (PCVCAP_TAG(entry->vcap) <= 1) { - if (aobj != globalObj) { - LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)), - "shape"); - guard(true, addName(lir->ins2i(LIR_eq, shape_ins, entry->kshape), "guard(kshape)(test_property_cache)"), - BRANCH_EXIT); - } - } else { -#ifdef DEBUG - JSOp op = js_GetOpcode(cx, cx->fp->script, pc); - JSAtom *pcatom; - if (op == JSOP_LENGTH) { - pcatom = cx->runtime->atomState.lengthAtom; - } else { - ptrdiff_t pcoff = (JOF_TYPE(js_CodeSpec[op].format) == JOF_SLOTATOM) ? SLOTNO_LEN : 0; - GET_ATOM_FROM_BYTECODE(cx->fp->script, pc, pcoff, pcatom); - } - JS_ASSERT(entry->kpc == (jsbytecode *) pcatom); - JS_ASSERT(entry->kshape == jsuword(aobj)); -#endif - if (aobj != globalObj && !obj_ins->isconstp()) { - guard(true, addName(lir->ins2i(LIR_eq, obj_ins, entry->kshape), "guard(kobj)"), - BRANCH_EXIT); - } + /* + * Guard on the shape of the directly accessed native object, unless it's + * the global object whose shape can't change on trace. + */ + if (aobj != globalObj) { + LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)), + "shape"); + guard(true, + addName(lir->ins2i(LIR_eq, shape_ins, entry->kshape), + "guard(kshape)(test_property_cache)"), + BRANCH_EXIT); } // For any hit that goes up the scope and/or proto chains, we will need to