From b040e5c72476fdc3e9606d2274ab2ee39c6bec51 Mon Sep 17 00:00:00 2001 From: Jason Orendorff Date: Thu, 17 Sep 2009 18:24:38 -0500 Subject: [PATCH] Bug 500431 - Encapsulate the property cache using C++ best practices - Part 2, refactoring. r=Waldo. --- js/src/Makefile.in | 2 + js/src/jsbuiltins.cpp | 1 + js/src/jscntxt.cpp | 3 +- js/src/jscntxt.h | 6 - js/src/jsinterp.cpp | 1 + js/src/jsobj.cpp | 18 +-- js/src/jsops.cpp | 308 ++++++++++++++++-------------------- js/src/jspropcache.cpp | 210 +++++++++++------------- js/src/jspropcache.h | 220 +++++++++++--------------- js/src/jspropcacheinlines.h | 223 ++++++++++++++++++++++++++ js/src/jsscript.cpp | 4 +- js/src/jstracer.cpp | 5 +- 12 files changed, 562 insertions(+), 439 deletions(-) create mode 100644 js/src/jspropcacheinlines.h diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 054e0d2bd01..b2eb3829353 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -201,6 +201,8 @@ INSTALLED_HEADERS = \ jsotypes.h \ jsparse.h \ jsprf.h \ + jspropcache.h \ + jspropcacheinlines.h \ jsproto.tbl \ jsprvtd.h \ jspubtd.h \ diff --git a/js/src/jsbuiltins.cpp b/js/src/jsbuiltins.cpp index c065608c747..02714169ea1 100644 --- a/js/src/jsbuiltins.cpp +++ b/js/src/jsbuiltins.cpp @@ -61,6 +61,7 @@ #include "jsvector.h" #include "jsatominlines.h" +#include "jspropcacheinlines.h" using namespace avmplus; using namespace nanojit; diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index c4f4f5efd3b..b0af20f8de9 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -98,7 +98,6 @@ FinishThreadData(JSThreadData *data) #endif js_FinishGSNCache(&data->gsnCache); - js_FinishPropertyCache(&data->propertyCache); #if defined JS_TRACER js_FinishJIT(&data->traceMonitor); #endif @@ -110,7 +109,7 @@ PurgeThreadData(JSContext *cx, JSThreadData *data) js_PurgeGSNCache(&data->gsnCache); /* FIXME: bug 506341. */ - js_PurgePropertyCache(cx, &data->propertyCache); + data->propertyCache.purge(cx); # ifdef JS_TRACER JSTraceMonitor *tm = &data->traceMonitor; diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 612019ba904..5d9970adfee 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -1702,12 +1702,6 @@ js_GetTopStackFrame(JSContext *cx) return cx->fp; } -static JS_INLINE JSBool -js_IsPropertyCacheDisabled(JSContext *cx) -{ - return cx->runtime->shapeGen >= SHAPE_OVERFLOW_BIT; -} - static JS_INLINE uint32 js_RegenerateShapeForGC(JSContext *cx) { diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 04db29144e1..e3785f99b90 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -84,6 +84,7 @@ #endif #include "jsatominlines.h" +#include "jspropcacheinlines.h" #include "jsscriptinlines.h" #include "jsautooplen.h" diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index d6be9d3d493..d6e0b4dd4ab 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -3838,7 +3838,7 @@ js_DefineNativeProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, if (defineHow & JSDNP_CACHE_RESULT) { JS_ASSERT_NOT_ON_TRACE(cx); JSPropCacheEntry *entry; - entry = js_FillPropertyCache(cx, obj, 0, 0, obj, sprop, added); + entry = JS_PROPERTY_CACHE(cx).fill(cx, obj, 0, 0, obj, sprop, added); TRACE_2(SetPropHit, entry, sprop); } if (propp) @@ -4090,9 +4090,8 @@ js_FindPropertyHelper(JSContext *cx, jsid id, JSBool cacheResult, } #endif if (cacheResult) { - entry = js_FillPropertyCache(cx, scopeChain, - scopeIndex, protoIndex, pobj, - (JSScopeProperty *) prop, false); + entry = JS_PROPERTY_CACHE(cx).fill(cx, scopeChain, scopeIndex, protoIndex, pobj, + (JSScopeProperty *) prop, false); } SCOPE_DEPTH_ACCUM(&rt->scopeSearchDepthStats, scopeIndex); goto out; @@ -4173,9 +4172,8 @@ js_FindIdentifierBase(JSContext *cx, JSObject *scopeChain, jsid id) #ifdef DEBUG JSPropCacheEntry *entry = #endif - js_FillPropertyCache(cx, scopeChain, - scopeIndex, protoIndex, pobj, - (JSScopeProperty *) prop, false); + JS_PROPERTY_CACHE(cx).fill(cx, scopeChain, scopeIndex, protoIndex, pobj, + (JSScopeProperty *) prop, false); JS_ASSERT(entry); JS_UNLOCK_OBJ(cx, pobj); return obj; @@ -4421,7 +4419,7 @@ js_GetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN getHow, if (getHow & JSGET_CACHE_RESULT) { JS_ASSERT_NOT_ON_TRACE(cx); - js_FillPropertyCache(cx, aobj, 0, protoIndex, obj2, sprop, false); + JS_PROPERTY_CACHE(cx).fill(cx, aobj, 0, protoIndex, obj2, sprop, false); } if (!js_NativeGet(cx, obj, obj2, sprop, getHow, vp)) @@ -4599,7 +4597,7 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow, if (attrs & JSPROP_SHARED) { if (defineHow & JSDNP_CACHE_RESULT) { JSPropCacheEntry *entry; - entry = js_FillPropertyCache(cx, obj, 0, protoIndex, pobj, sprop, false); + entry = JS_PROPERTY_CACHE(cx).fill(cx, obj, 0, protoIndex, pobj, sprop, false); TRACE_2(SetPropHit, entry, sprop); } @@ -4701,7 +4699,7 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow, if (defineHow & JSDNP_CACHE_RESULT) { JSPropCacheEntry *entry; - entry = js_FillPropertyCache(cx, obj, 0, 0, obj, sprop, added); + entry = JS_PROPERTY_CACHE(cx).fill(cx, obj, 0, 0, obj, sprop, added); TRACE_2(SetPropHit, entry, sprop); } diff --git a/js/src/jsops.cpp b/js/src/jsops.cpp index 2557b9ecac3..cb730513edd 100644 --- a/js/src/jsops.cpp +++ b/js/src/jsops.cpp @@ -1682,11 +1682,8 @@ entry = NULL; atom = NULL; if (JS_LIKELY(obj->map->ops->setProperty == js_SetProperty)) { - JSPropertyCache *cache = &JS_PROPERTY_CACHE(cx); - uint32 kshape = OBJ_SHAPE(obj); - /* - * Open-code PROPERTY_CACHE_TEST, specializing for two + * Open-code JSPropertyCache::test, specializing for two * important set-property cases. First: * * function f(a, b, c) { @@ -1704,161 +1701,149 @@ * will (possibly after the first iteration) always exist * in native object o. */ - entry = &cache->table[PROPERTY_CACHE_HASH_PC(regs.pc, kshape)]; - PCMETER(cache->pctestentry = entry); - PCMETER(cache->tests++); - PCMETER(cache->settests++); - if (entry->kpc == regs.pc && entry->kshape == kshape) { - JS_ASSERT(PCVCAP_TAG(entry->vcap) <= 1); - if (JS_LOCK_OBJ_IF_SHAPE(cx, obj, kshape)) { - JS_ASSERT(PCVAL_IS_SPROP(entry->vword)); - sprop = PCVAL_TO_SPROP(entry->vword); - JS_ASSERT(!(sprop->attrs & JSPROP_READONLY)); - JS_ASSERT_IF(!(sprop->attrs & JSPROP_SHARED), - PCVCAP_TAG(entry->vcap) == 0); + if (JS_PROPERTY_CACHE(cx).testForSet(cx, regs.pc, &obj, + &obj2, &entry, &atom)) { + JS_ASSERT(PCVAL_IS_SPROP(entry->vword)); + sprop = PCVAL_TO_SPROP(entry->vword); + JS_ASSERT(!(sprop->attrs & JSPROP_READONLY)); + JS_ASSERT_IF(!(sprop->attrs & JSPROP_SHARED), + PCVCAP_TAG(entry->vcap) == 0); - JSScope *scope = OBJ_SCOPE(obj); - JS_ASSERT(!scope->sealed()); + JSScope *scope = OBJ_SCOPE(obj); + JS_ASSERT(!scope->sealed()); - /* - * Fastest path: check whether the cached sprop is - * already in scope and call NATIVE_SET and break - * to get out of the do-while(0). But we can call - * NATIVE_SET only if obj owns scope or sprop is - * shared. - */ - bool checkForAdd; - if (sprop->attrs & JSPROP_SHARED) { - if (PCVCAP_TAG(entry->vcap) == 0 || - ((obj2 = OBJ_GET_PROTO(cx, obj)) && - OBJ_IS_NATIVE(obj2) && - OBJ_SHAPE(obj2) == PCVCAP_SHAPE(entry->vcap))) { - goto fast_set_propcache_hit; - } - - /* The cache entry doesn't apply. vshape mismatch. */ - checkForAdd = false; - } else if (scope->owned()) { - if (sprop == scope->lastProp || scope->has(sprop)) { - fast_set_propcache_hit: - PCMETER(cache->pchits++); - PCMETER(cache->setpchits++); - NATIVE_SET(cx, obj, sprop, entry, &rval); - JS_UNLOCK_SCOPE(cx, scope); - break; - } - checkForAdd = - !(sprop->attrs & JSPROP_SHARED) && - sprop->parent == scope->lastProp && - !scope->hadMiddleDelete(); - } else { - scope = js_GetMutableScope(cx, obj); - if (!scope) { - JS_UNLOCK_OBJ(cx, obj); - goto error; - } - checkForAdd = !sprop->parent; + /* + * Fastest path: check whether the cached sprop is + * already in scope and call NATIVE_SET and break to + * get out of the do-while(0). But we can call + * NATIVE_SET only if obj owns scope or sprop is + * shared. + */ + bool checkForAdd; + if (sprop->attrs & JSPROP_SHARED) { + if (PCVCAP_TAG(entry->vcap) == 0 || + ((obj2 = OBJ_GET_PROTO(cx, obj)) && + OBJ_IS_NATIVE(obj2) && + OBJ_SHAPE(obj2) == PCVCAP_SHAPE(entry->vcap))) { + goto fast_set_propcache_hit; } - if (checkForAdd && - SPROP_HAS_STUB_SETTER(sprop) && - (slot = sprop->slot) == scope->freeslot) { - /* - * Fast path: adding a plain old property that - * was once at the frontier of the property - * tree, whose slot is next to claim among the - * allocated slots in obj, where scope->table - * has not been created yet. - * - * We may want to remove hazard conditions - * above and inline compensation code here, - * depending on real-world workloads. - */ - JS_ASSERT(!(obj->getClass()->flags & - JSCLASS_SHARE_ALL_PROPERTIES)); - + /* The cache entry doesn't apply. vshape mismatch. */ + checkForAdd = false; + } else if (scope->owned()) { + if (sprop == scope->lastProp || scope->has(sprop)) { + fast_set_propcache_hit: PCMETER(cache->pchits++); - PCMETER(cache->addpchits++); - - /* - * Beware classes such as Function that use - * the reserveSlots hook to allocate a number - * of reserved slots that may vary with obj. - */ - if (slot < STOBJ_NSLOTS(obj) && - !OBJ_GET_CLASS(cx, obj)->reserveSlots) { - ++scope->freeslot; - } else { - if (!js_AllocSlot(cx, obj, &slot)) { - JS_UNLOCK_SCOPE(cx, scope); - goto error; - } - } - - /* - * If this obj's number of reserved slots - * differed, or if something created a hash - * table for scope, we must pay the price of - * JSScope::add. - * - * If slot does not match the cached sprop's - * slot, update the cache entry in the hope - * that obj and other instances with the same - * number of reserved slots are now "hot". - */ - if (slot != sprop->slot || scope->table) { - JSScopeProperty *sprop2 = - scope->add(cx, sprop->id, - sprop->getter, sprop->setter, - slot, sprop->attrs, - sprop->flags, sprop->shortid); - if (!sprop2) { - js_FreeSlot(cx, obj, slot); - JS_UNLOCK_SCOPE(cx, scope); - goto error; - } - if (sprop2 != sprop) { - PCMETER(cache->slotchanges++); - JS_ASSERT(slot != sprop->slot && - slot == sprop2->slot && - sprop2->id == sprop->id); - entry->vword = SPROP_TO_PCVAL(sprop2); - } - sprop = sprop2; - } else { - scope->extend(cx, sprop); - } - - /* - * No method change check here because here we - * are adding a new property, not updating an - * existing slot's value that might contain a - * method of a branded scope. - */ - TRACE_2(SetPropHit, entry, sprop); - LOCKED_OBJ_SET_SLOT(obj, slot, rval); + PCMETER(cache->setpchits++); + NATIVE_SET(cx, obj, sprop, entry, &rval); JS_UNLOCK_SCOPE(cx, scope); - - /* - * Purge the property cache of the id we may - * have just shadowed in obj's scope and proto - * chains. We do this after unlocking obj's - * scope to avoid lock nesting. - */ - js_PurgeScopeChain(cx, obj, sprop->id); break; } - JS_UNLOCK_SCOPE(cx, scope); - PCMETER(cache->setpcmisses++); + checkForAdd = + !(sprop->attrs & JSPROP_SHARED) && + sprop->parent == scope->lastProp && + !scope->hadMiddleDelete(); + } else { + scope = js_GetMutableScope(cx, obj); + if (!scope) { + JS_UNLOCK_OBJ(cx, obj); + goto error; + } + checkForAdd = !sprop->parent; } + + if (checkForAdd && + SPROP_HAS_STUB_SETTER(sprop) && + (slot = sprop->slot) == scope->freeslot) { + /* + * Fast path: adding a plain old property that was + * once at the frontier of the property tree, whose + * slot is next to claim among the allocated slots + * in obj, where scope->table has not been created + * yet. + * + * We may want to remove hazard conditions above + * and inline compensation code here, depending on + * real-world workloads. + */ + JS_ASSERT(!(obj->getClass()->flags & + JSCLASS_SHARE_ALL_PROPERTIES)); + + PCMETER(cache->pchits++); + PCMETER(cache->addpchits++); + + /* + * Beware classes such as Function that use the + * reserveSlots hook to allocate a number of + * reserved slots that may vary with obj. + */ + if (slot < STOBJ_NSLOTS(obj) && + !OBJ_GET_CLASS(cx, obj)->reserveSlots) { + ++scope->freeslot; + } else { + if (!js_AllocSlot(cx, obj, &slot)) { + JS_UNLOCK_SCOPE(cx, scope); + goto error; + } + } + + /* + * If this obj's number of reserved slots differed, + * or if something created a hash table for scope, + * we must pay the price of JSScope::add. + * + * If slot does not match the cached sprop's slot, + * update the cache entry in the hope that obj and + * other instances with the same number of reserved + * slots are now "hot". + */ + if (slot != sprop->slot || scope->table) { + JSScopeProperty *sprop2 = + scope->add(cx, sprop->id, + sprop->getter, sprop->setter, + slot, sprop->attrs, + sprop->flags, sprop->shortid); + if (!sprop2) { + js_FreeSlot(cx, obj, slot); + JS_UNLOCK_SCOPE(cx, scope); + goto error; + } + if (sprop2 != sprop) { + PCMETER(cache->slotchanges++); + JS_ASSERT(slot != sprop->slot && + slot == sprop2->slot && + sprop2->id == sprop->id); + entry->vword = SPROP_TO_PCVAL(sprop2); + } + sprop = sprop2; + } else { + scope->extend(cx, sprop); + } + + /* + * No method change check here because here we are + * adding a new property, not updating an existing + * slot's value that might contain a method of a + * branded scope. + */ + TRACE_2(SetPropHit, entry, sprop); + LOCKED_OBJ_SET_SLOT(obj, slot, rval); + JS_UNLOCK_SCOPE(cx, scope); + + /* + * Purge the property cache of the id we may have + * just shadowed in obj's scope and proto + * chains. We do this after unlocking obj's scope + * to avoid lock nesting. + */ + js_PurgeScopeChain(cx, obj, sprop->id); + break; + } + JS_UNLOCK_SCOPE(cx, scope); + PCMETER(cache->setpcmisses++); } - atom = js_FullTestPropertyCache(cx, regs.pc, &obj, &obj2, - &entry); - if (atom) { - PCMETER(cache->misses++); - PCMETER(cache->setmisses++); - } else { + if (!atom) { ASSERT_VALID_PROPERTY_CACHE_HIT(0, obj, obj2, entry); sprop = NULL; if (obj == obj2) { @@ -3463,37 +3448,16 @@ JS_ASSERT(!(obj->getClass()->flags & JSCLASS_SHARE_ALL_PROPERTIES)); do { - JSScope *scope; - uint32 kshape; - JSPropertyCache *cache; - JSPropCacheEntry *entry; - JS_LOCK_OBJ(cx, obj); - scope = OBJ_SCOPE(obj); + JSScope *scope = OBJ_SCOPE(obj); // FIXME: bug 513291 -- uncomment this assertion and remove the // (!scope->owned()) => js_GetMutableScope code further // below. // JS_ASSERT(scope->object == obj); JS_ASSERT(!scope->sealed()); - kshape = scope->shape; - cache = &JS_PROPERTY_CACHE(cx); - entry = &cache->table[PROPERTY_CACHE_HASH_PC(regs.pc, kshape)]; - PCMETER(cache->pctestentry = entry); - PCMETER(cache->tests++); - PCMETER(cache->initests++); - - if (entry->kpc == regs.pc && - entry->kshape == kshape && - PCVCAP_SHAPE(entry->vcap) == rt->protoHazardShape) { - JS_ASSERT(PCVCAP_TAG(entry->vcap) == 0); - - PCMETER(cache->pchits++); - PCMETER(cache->inipchits++); - - JS_ASSERT(PCVAL_IS_SPROP(entry->vword)); - sprop = PCVAL_TO_SPROP(entry->vword); - JS_ASSERT(!(sprop->attrs & JSPROP_READONLY)); + JSPropCacheEntry *entry; + if (JS_PROPERTY_CACHE(cx).testForInit(cx, regs.pc, obj, &entry, &sprop)) { /* * If this property has a non-stub setter, it must be * __proto__, __parent__, or another "shared prototype" diff --git a/js/src/jspropcache.cpp b/js/src/jspropcache.cpp index c6a11c7ff28..3b96963da58 100644 --- a/js/src/jspropcache.cpp +++ b/js/src/jspropcache.cpp @@ -1,5 +1,5 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sw=4 et tw=79: + * vim: set ts=8 sw=4 et tw=99: * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 @@ -42,31 +42,28 @@ #include "jsapi.h" #include "jscntxt.h" #include "jsinterp.h" -#include "jsobj.h" #include "jsscope.h" +#include "jspropcacheinlines.h" JS_REQUIRES_STACK JSPropCacheEntry * -js_FillPropertyCache(JSContext *cx, JSObject *obj, - uintN scopeIndex, uintN protoIndex, JSObject *pobj, - JSScopeProperty *sprop, JSBool adding) +JSPropertyCache::fill(JSContext *cx, JSObject *obj, uintN scopeIndex, uintN protoIndex, + JSObject *pobj, JSScopeProperty *sprop, JSBool adding) { - 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); - cache = &JS_PROPERTY_CACHE(cx); + JS_ASSERT(this == &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++); + PCMETER(disfills++); return JS_NO_PROP_CACHE_FILL; } @@ -76,7 +73,7 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, */ scope = OBJ_SCOPE(pobj); if (!scope->has(sprop)) { - PCMETER(cache->oddfills++); + PCMETER(oddfills++); return JS_NO_PROP_CACHE_FILL; } @@ -112,7 +109,7 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, * mutate in arbitrary way without changing any shapes. */ if (!tmp || !OBJ_IS_NATIVE(tmp)) { - PCMETER(cache->noprotos++); + PCMETER(noprotos++); return JS_NO_PROP_CACHE_FILL; } if (tmp == pobj) @@ -122,7 +119,7 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, } if (scopeIndex > PCVCAP_SCOPEMASK || protoIndex > PCVCAP_PROTOMASK) { - PCMETER(cache->longchains++); + PCMETER(longchains++); return JS_NO_PROP_CACHE_FILL; } @@ -174,7 +171,7 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, * object will result in shape being regenerated. */ if (!scope->branded()) { - PCMETER(cache->brandfills++); + PCMETER(brandfills++); #ifdef DEBUG_notme fprintf(stderr, "branding %p (%s) for funobj %p (%s), shape %lu\n", @@ -269,7 +266,6 @@ 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); } else { @@ -288,12 +284,6 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, #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. @@ -305,31 +295,16 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, * because the property cache and JIT cache are thread-local. */ obj->setDelegate(); + + return fillByAtom(atom, obj, vshape, scopeIndex, protoIndex, vword); } } - 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; + return fillByPC(pc, kshape, vshape, scopeIndex, protoIndex, vword); } JS_REQUIRES_STACK JSAtom * -js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, - JSObject **objp, JSObject **pobjp, +JSPropertyCache::fullTest(JSContext *cx, jsbytecode *pc, JSObject **objp, JSObject **pobjp, JSPropCacheEntry **entryp) { JSOp op; @@ -354,15 +329,15 @@ js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, obj = *objp; JS_ASSERT(OBJ_IS_NATIVE(obj)); - entry = &JS_PROPERTY_CACHE(cx).table[PROPERTY_CACHE_HASH_ATOM(atom, obj)]; + entry = &table[PROPERTY_CACHE_HASH_ATOM(atom, obj)]; *entryp = entry; vcap = entry->vcap; if (entry->kpc != (jsbytecode *) atom) { - PCMETER(JS_PROPERTY_CACHE(cx).idmisses++); + PCMETER(idmisses++); #ifdef DEBUG_notme - entry = &JS_PROPERTY_CACHE(cx).table[PROPERTY_CACHE_HASH_PC(pc, OBJ_SHAPE(obj))]; + entry = &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", @@ -382,7 +357,7 @@ js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, } if (entry->kshape != (jsuword) obj) { - PCMETER(JS_PROPERTY_CACHE(cx).komisses++); + PCMETER(komisses++); return atom; } @@ -420,107 +395,104 @@ js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, return NULL; } - PCMETER(JS_PROPERTY_CACHE(cx).vcmisses++); + PCMETER(vcmisses++); return atom; } +inline void +JSPropertyCache::assertEmpty() +{ #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) + JS_ASSERT(empty); + for (uintN i = 0; i < PROPERTY_CACHE_SIZE; i++) { + JS_ASSERT(!table[i].kpc); + JS_ASSERT(!table[i].kshape); + JS_ASSERT(!table[i].vcap); + JS_ASSERT(!table[i].vword); + } #endif +} + JS_STATIC_ASSERT(PCVAL_NULL == 0); void -js_PurgePropertyCache(JSContext *cx, JSPropertyCache *cache) +JSPropertyCache::purge(JSContext *cx) { - if (cache->empty) { - ASSERT_CACHE_IS_EMPTY(cache); + JS_ASSERT(this == &JS_PROPERTY_CACHE(cx)); + + if (empty) { + assertEmpty(); return; } - memset(cache->table, 0, sizeof cache->table); - cache->empty = JS_TRUE; + memset(table, 0, sizeof table); + 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); + { + 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); +# define P(mem) fprintf(fp, "%11s %10lu\n", #mem, (unsigned long)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); + fprintf(fp, "hit rates: pc %g%% (proto %g%%), set %g%%, ini %g%%, full %g%%\n", + (100. * pchits) / tests, + (100. * protopchits) / tests, + (100. * (addpchits + setpchits)) / settests, + (100. * inipchits) / initests, + (100. * (tests - misses)) / tests); + fflush(fp); + } } - } #endif - PCMETER(cache->flushes++); + PCMETER(flushes++); } void -js_PurgePropertyCacheForScript(JSContext *cx, JSScript *script) +JSPropertyCache::purgeForScript(JSContext *cx, JSScript *script) { - JSPropertyCache *cache; - JSPropCacheEntry *entry; + JS_ASSERT(this == &JS_PROPERTY_CACHE(cx)); - cache = &JS_PROPERTY_CACHE(cx); - for (entry = cache->table; entry < cache->table + PROPERTY_CACHE_SIZE; - entry++) { + for (JSPropCacheEntry *entry = table; entry < table + PROPERTY_CACHE_SIZE; entry++) { if (JS_UPTRDIFF(entry->kpc, script->code) < script->length) { entry->kpc = NULL; entry->kshape = 0; diff --git a/js/src/jspropcache.h b/js/src/jspropcache.h index 9c09a8c662f..bfb45974298 100644 --- a/js/src/jspropcache.h +++ b/js/src/jspropcache.h @@ -46,46 +46,33 @@ * 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)) +const int PROPERTY_CACHE_LOG2 = 12; +const jsuword PROPERTY_CACHE_SIZE = JS_BIT(PROPERTY_CACHE_LOG2); +const jsuword PROPERTY_CACHE_MASK = JS_BITMASK(PROPERTY_CACHE_LOG2); /* * Property cache value capability macros. */ -#define PCVCAP_PROTOBITS 4 -#define PCVCAP_PROTOSIZE JS_BIT(PCVCAP_PROTOBITS) -#define PCVCAP_PROTOMASK JS_BITMASK(PCVCAP_PROTOBITS) +const int PCVCAP_PROTOBITS = 4; +const jsuword PCVCAP_PROTOSIZE = JS_BIT(PCVCAP_PROTOBITS); +const jsuword PCVCAP_PROTOMASK = JS_BITMASK(PCVCAP_PROTOBITS); -#define PCVCAP_SCOPEBITS 4 -#define PCVCAP_SCOPESIZE JS_BIT(PCVCAP_SCOPEBITS) -#define PCVCAP_SCOPEMASK JS_BITMASK(PCVCAP_SCOPEBITS) +const int PCVCAP_SCOPEBITS = 4; +const jsuword PCVCAP_SCOPESIZE = JS_BIT(PCVCAP_SCOPEBITS); +const jsuword 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) +const jsuword PCVCAP_TAGBITS = PCVCAP_PROTOBITS + PCVCAP_SCOPEBITS; +const jsuword PCVCAP_TAGMASK = JS_BITMASK(PCVCAP_TAGBITS); +inline jsuword PCVCAP_TAG(jsuword t) { return t & PCVCAP_TAGMASK; } -#define PCVCAP_MAKE(t,s,p) ((uint32(t) << PCVCAP_TAGBITS) | \ - ((s) << PCVCAP_PROTOBITS) | \ - (p)) -#define PCVCAP_SHAPE(t) ((t) >> PCVCAP_TAGBITS) +inline jsuword PCVCAP_MAKE(jsuword t, jsuword s, jsuword p) +{ + return (uint32(t) << PCVCAP_TAGBITS) | (s << PCVCAP_PROTOBITS) | p; +} -#define SHAPE_OVERFLOW_BIT JS_BIT(32 - PCVCAP_TAGBITS) +inline jsuword PCVCAP_SHAPE(jsuword t) { return t >> PCVCAP_TAGBITS; } + +const jsuword SHAPE_OVERFLOW_BIT = JS_BIT(32 - PCVCAP_TAGBITS); struct JSPropCacheEntry { jsbytecode *kpc; /* pc if vcap tag is <= 1, else atom */ @@ -106,15 +93,62 @@ struct JSPropCacheEntry { * 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) +JSPropCacheEntry * const 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 { +class JSPropertyCache { +public: + JS_ALWAYS_INLINE JS_REQUIRES_STACK void + test(JSContext *cx, jsbytecode *pc, JSObject **objp, + JSObject **pobjp, JSPropCacheEntry **entryp, JSAtom **atomp); + + JS_ALWAYS_INLINE JS_REQUIRES_STACK bool + testForSet(JSContext *cx, jsbytecode *pc, JSObject **objp, + JSObject **pobjp, JSPropCacheEntry **entryp, JSAtom **atomp); + + JS_ALWAYS_INLINE bool + testForInit(JSContext *cx, jsbytecode *pc, JSObject *obj, + JSPropCacheEntry **entryp, JSScopeProperty **spropp); + + /* + * 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. + */ + JS_REQUIRES_STACK JSPropCacheEntry * + fill(JSContext *cx, JSObject *obj, uintN scopeIndex, uintN protoIndex, JSObject *pobj, + JSScopeProperty *sprop, JSBool adding); + + void purge(JSContext *cx); + void purgeForScript(JSContext *cx, JSScript *script); + +private: + JS_REQUIRES_STACK JSAtom * + fullTest(JSContext *cx, jsbytecode *pc, JSObject **objp, JSObject **pobjp, + JSPropCacheEntry **entryp); + + inline void assertEmpty(); + + inline JSPropCacheEntry *fillEntry(jsuword khash, jsbytecode *pc, jsuword kshape, + jsuword vcap, jsuword vword); + + inline JSPropCacheEntry *fillByPC(jsbytecode *pc, jsuword kshape, + jsuword vshape, jsuword scopeIndex, jsuword protoIndex, + jsuword vword); + + inline JSPropCacheEntry *fillByAtom(JSAtom *atom, JSObject *obj, + jsuword vshape, jsuword scopeIndex, jsuword protoIndex, + jsuword vword); + 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 */ @@ -153,109 +187,43 @@ typedef struct JSPropertyCache { #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 +const jsuword PCVAL_OBJECT = 0; +const jsuword PCVAL_SLOT = 1; +const jsuword 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)) +const int PCVAL_TAGBITS = 2; +const jsuword PCVAL_TAGMASK = JS_BITMASK(PCVAL_TAGBITS); -#define PCVAL_NULL 0 -#define PCVAL_IS_NULL(v) ((v) == PCVAL_NULL) +inline jsuword PCVAL_TAG(jsuword v) { return v & PCVAL_TAGMASK; } +inline jsuword PCVAL_CLRTAG(jsuword v) { return v & ~PCVAL_TAGMASK; } +inline jsuword PCVAL_SETTAG(jsuword v, jsuword t) { return v | t; } -#define PCVAL_IS_OBJECT(v) (PCVAL_TAG(v) == PCVAL_OBJECT) -#define PCVAL_TO_OBJECT(v) ((JSObject *) (v)) -#define OBJECT_TO_PCVAL(obj) ((jsuword) (obj)) +const jsuword PCVAL_NULL = 0; +inline bool PCVAL_IS_NULL(jsuword v) { return v == PCVAL_NULL; } -#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)) +inline bool PCVAL_IS_OBJECT(jsuword v) { return PCVAL_TAG(v) == PCVAL_OBJECT; } +inline JSObject *PCVAL_TO_OBJECT(jsuword v) { return (JSObject *) v; } +inline jsuword OBJECT_TO_PCVAL(JSObject *obj) { return (jsuword) obj; } -#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) +inline jsval PCVAL_OBJECT_TO_JSVAL(jsuword v) { return OBJECT_TO_JSVAL(PCVAL_TO_OBJECT(v)); } +inline jsuword JSVAL_OBJECT_TO_PCVAL(jsval v) { return OBJECT_TO_PCVAL(JSVAL_TO_OBJECT(v)); } -#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) +inline bool PCVAL_IS_SLOT(jsuword v) { return (v & PCVAL_SLOT) != 0; } +inline jsuint PCVAL_TO_SLOT(jsuword v) { return (jsuint) v >> 1; } +inline jsuword SLOT_TO_PCVAL(jsuint i) { return ((jsuword) i << 1) | PCVAL_SLOT; } -/* - * 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); +inline bool PCVAL_IS_SPROP(jsuword v) { return PCVAL_TAG(v) == PCVAL_SPROP; } +inline JSScopeProperty *PCVAL_TO_SPROP(jsuword v) { return (JSScopeProperty *) PCVAL_CLRTAG(v); } -/* - * 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); +inline jsuword +SPROP_TO_PCVAL(JSScopeProperty *sprop) +{ + return PCVAL_SETTAG((jsuword) sprop, PCVAL_SPROP); +} #endif /* jspropcache_h___ */ diff --git a/js/src/jspropcacheinlines.h b/js/src/jspropcacheinlines.h new file mode 100644 index 00000000000..2788491ae87 --- /dev/null +++ b/js/src/jspropcacheinlines.h @@ -0,0 +1,223 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** 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 jspropcacheinlines_h___ +#define jspropcacheinlines_h___ + +inline JSBool +js_IsPropertyCacheDisabled(JSContext *cx) +{ + return cx->runtime->shapeGen >= SHAPE_OVERFLOW_BIT; +} + +/* + * 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. + */ +inline jsuword +PROPERTY_CACHE_HASH(jsuword pc, jsuword kshape) +{ + return (((pc >> PROPERTY_CACHE_LOG2) ^ pc) + (kshape)) & PROPERTY_CACHE_MASK; +} + +inline jsuword +PROPERTY_CACHE_HASH_PC(jsbytecode *pc, jsuword kshape) +{ + return PROPERTY_CACHE_HASH((jsuword) pc, kshape); +} + +inline jsuword +PROPERTY_CACHE_HASH_ATOM(JSAtom *atom, JSObject *obj) +{ + return PROPERTY_CACHE_HASH((jsuword) atom >> 2, OBJ_SHAPE(obj)); +} + +/* + * Property cache lookup. + * + * 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 JSPropertyCache::fill 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) \ + JS_PROPERTY_CACHE(cx).test(cx, pc, &obj, &pobj, &entry, &atom) + +JS_ALWAYS_INLINE void +JSPropertyCache::test(JSContext *cx, jsbytecode *pc, JSObject **objp, + JSObject **pobjp, JSPropCacheEntry **entryp, JSAtom **atomp) +{ + JS_ASSERT(this == &JS_PROPERTY_CACHE(cx)); + JS_ASSERT(OBJ_IS_NATIVE(*objp)); + + uint32 kshape = OBJ_SHAPE(*objp); + JSPropCacheEntry *entry = &table[PROPERTY_CACHE_HASH_PC(pc, kshape)]; + PCMETER(pctestentry = entry); + PCMETER(tests++); + JS_ASSERT(objp != pobjp); + if (entry->kpc == pc && entry->kshape == kshape) { + JSObject *tmp; + JSObject *pobj = *objp; + + JS_ASSERT(PCVCAP_TAG(entry->vcap) <= 1); + if (PCVCAP_TAG(entry->vcap) == 1 && (tmp = pobj->getProto()) != NULL) + pobj = tmp; + + if (JS_LOCK_OBJ_IF_SHAPE(cx, pobj, PCVCAP_SHAPE(entry->vcap))) { + PCMETER(pchits++); + PCMETER(!PCVCAP_TAG(entry->vcap) || protopchits++); + *pobjp = pobj; + *entryp = entry; + *atomp = NULL; + return; + } + } + *atomp = fullTest(cx, pc, objp, pobjp, entryp); + if (*atomp) + PCMETER(misses++); +} + +JS_ALWAYS_INLINE bool +JSPropertyCache::testForSet(JSContext *cx, jsbytecode *pc, JSObject **objp, + JSObject **pobjp, JSPropCacheEntry **entryp, JSAtom **atomp) +{ + uint32 kshape = OBJ_SHAPE(*objp); + JSPropCacheEntry *entry = &table[PROPERTY_CACHE_HASH_PC(pc, kshape)]; + PCMETER(pctestentry = entry); + PCMETER(tests++); + PCMETER(settests++); + + *entryp = entry; + if (entry->kpc == pc && entry->kshape == kshape) { + JS_ASSERT(PCVCAP_TAG(entry->vcap) <= 1); + if (JS_LOCK_OBJ_IF_SHAPE(cx, *objp, kshape)) + return true; + } + + *atomp = JS_PROPERTY_CACHE(cx).fullTest(cx, pc, objp, pobjp, entryp); + if (*atomp) { + PCMETER(cache->misses++); + PCMETER(cache->setmisses++); + } + + return false; +} + +JS_ALWAYS_INLINE bool +JSPropertyCache::testForInit(JSContext *cx, jsbytecode *pc, JSObject *obj, + JSPropCacheEntry **entryp, JSScopeProperty **spropp) +{ + uint32 shape = OBJ_SCOPE(obj)->shape; + JSPropCacheEntry *entry = &table[PROPERTY_CACHE_HASH_PC(pc, shape)]; + PCMETER(pctestentry = entry); + PCMETER(tests++); + PCMETER(initests++); + + if (entry->kpc == pc && + entry->kshape == shape && + PCVCAP_SHAPE(entry->vcap) == cx->runtime->protoHazardShape) + { + JS_ASSERT(PCVCAP_TAG(entry->vcap) == 0); + PCMETER(cache->pchits++); + PCMETER(cache->inipchits++); + JS_ASSERT(PCVAL_IS_SPROP(entry->vword)); + JSScopeProperty *sprop = PCVAL_TO_SPROP(entry->vword); + JS_ASSERT(!(sprop->attrs & JSPROP_READONLY)); + *spropp = sprop; + *entryp = entry; + return true; + } + return false; +} + +inline JSPropCacheEntry * +JSPropertyCache::fillEntry(jsuword khash, jsbytecode *pc, jsuword kshape, + jsuword vcap, jsuword vword) +{ + JSPropCacheEntry *entry = &table[khash]; + PCMETER(PCVAL_IS_NULL(entry->vword) || recycles++); + entry->kpc = pc; + entry->kshape = kshape; + entry->vcap = vcap; + entry->vword = vword; + + empty = JS_FALSE; + PCMETER(fills++); + + /* + * The modfills counter is not exact. It increases if a getter or setter + * recurse into the interpreter. + */ + PCMETER(entry == pctestentry || modfills++); + PCMETER(pctestentry = NULL); + return entry; +} + +inline JSPropCacheEntry * +JSPropertyCache::fillByPC(jsbytecode *pc, jsuword kshape, + jsuword vshape, jsuword scopeIndex, jsuword protoIndex, + jsuword vword) +{ + return fillEntry(PROPERTY_CACHE_HASH_PC(pc, kshape), pc, kshape, + PCVCAP_MAKE(vshape, scopeIndex, protoIndex), vword); +} + +inline JSPropCacheEntry * +JSPropertyCache::fillByAtom(JSAtom *atom, JSObject *obj, + jsuword vshape, jsuword scopeIndex, jsuword protoIndex, + jsuword vword) +{ + JS_ASSERT(obj->isDelegate()); + jsuword khash = PROPERTY_CACHE_HASH_ATOM(atom, obj); + PCMETER(if (PCVCAP_TAG(table[khash].vcap) <= 1) + pcrecycles++); + return fillEntry(khash, + reinterpret_cast(atom), + reinterpret_cast(obj), + PCVCAP_MAKE(vshape, scopeIndex, protoIndex), + vword); +} + +#endif /* jspropcacheinlines_h___ */ diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 0c2d8eb1ffb..9994a53bb24 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1619,7 +1619,7 @@ js_DestroyScript(JSContext *cx, JSScript *script) * regenerating shapes, so we don't have to purge fragments if the GC is * currently running. * - * JS_THREADSAFE note: js_PurgePropertyCacheForScript purges only the + * JS_THREADSAFE note: JSPropertyCache::purgeForScript purges only the * current thread's property cache, so a script not owned by a function * or object, which hands off lifetime management for that script to the * GC, must be used by only one thread over its lifetime. @@ -1638,7 +1638,7 @@ js_DestroyScript(JSContext *cx, JSScript *script) JSStackFrame *fp = js_GetTopStackFrame(cx); if (!(fp && (fp->flags & JSFRAME_EVAL))) { - js_PurgePropertyCacheForScript(cx, script); + JS_PROPERTY_CACHE(cx).purgeForScript(cx, script); #ifdef CHECK_SCRIPT_OWNER JS_ASSERT(script->owner == cx->thread); diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index 4d120704143..8b27b06e05f 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -77,6 +77,7 @@ #include "jsxml.h" #include "jsatominlines.h" +#include "jspropcacheinlines.h" #include "jsscriptinlines.h" #include "jsautooplen.h" // generated headers last @@ -8521,8 +8522,8 @@ TraceRecorder::test_property_cache(JSObject* obj, LIns* obj_ins, JSObject*& obj2 obj2->dropProperty(cx, prop); ABORT_TRACE("property found on non-native object"); } - entry = js_FillPropertyCache(cx, aobj, 0, protoIndex, obj2, - (JSScopeProperty*) prop, false); + entry = JS_PROPERTY_CACHE(cx).fill(cx, aobj, 0, protoIndex, obj2, + (JSScopeProperty*) prop, false); JS_ASSERT(entry); if (entry == JS_NO_PROP_CACHE_FILL) entry = NULL;