diff --git a/js/src/imacros.c.out b/js/src/imacros.c.out index 660ef6cde590..25b713cb0b41 100644 --- a/js/src/imacros.c.out +++ b/js/src/imacros.c.out @@ -914,6 +914,8 @@ uint8 js_opcode2extra[JSOP_LIMIT] = { 0, /* JSOP_DEFLOCALFUN_DBGFC */ 0, /* JSOP_LAMBDA_DBGFC */ 3, /* JSOP_CONCATN */ + 0, /* JSOP_SETMETHOD */ + 0, /* JSOP_INITMETHOD */ }; #define JSOP_IS_IMACOP(x) (0 \ || x == JSOP_BITOR \ diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 71a336078082..d8223654ecb9 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -3534,7 +3534,7 @@ JS_GetMethodById(JSContext *cx, JSObject *obj, jsid id, JSObject **objp, jsval *vp) { CHECK_REQUEST(cx); - if (!js_GetMethod(cx, obj, id, false, vp)) + if (!js_GetMethod(cx, obj, id, JSGET_METHOD_BARRIER, vp)) return JS_FALSE; if (objp) *objp = obj; @@ -5088,8 +5088,8 @@ JS_CallFunctionName(JSContext *cx, JSObject *obj, const char *name, uintN argc, JSAutoTempValueRooter tvr(cx); JSAtom *atom = js_Atomize(cx, name, strlen(name), 0); JSBool ok = atom && - JS_GetMethodById(cx, obj, ATOM_TO_JSID(atom), NULL, - tvr.addr()) && + js_GetMethod(cx, obj, ATOM_TO_JSID(atom), + JSGET_NO_METHOD_BARRIER, tvr.addr()) && js_InternalCall(cx, obj, tvr.value(), argc, argv, rval); LAST_FRAME_CHECKS(cx, ok); return ok; diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index b7310018f0fc..88f86b60ac1d 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -787,7 +787,7 @@ array_getProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp) if (prop) { if (OBJ_IS_NATIVE(obj2)) { sprop = (JSScopeProperty *) prop; - if (!js_NativeGet(cx, obj, obj2, sprop, vp)) + if (!js_NativeGet(cx, obj, obj2, sprop, JSGET_METHOD_BARRIER, vp)) return JS_FALSE; } obj2->dropProperty(cx, prop); diff --git a/js/src/jsemit.cpp b/js/src/jsemit.cpp index 2cb87e335224..fd06f8e43d3c 100644 --- a/js/src/jsemit.cpp +++ b/js/src/jsemit.cpp @@ -6522,7 +6522,17 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) ale = cg->atomList.add(cg->compiler, pn3->pn_atom); if (!ale) return JS_FALSE; - EMIT_INDEX_OP(JSOP_INITPROP, ALE_INDEX(ale)); + + JSOp initOp = (PN_OP(pn2->pn_right) == JSOP_LAMBDA && + !(pn2->pn_right->pn_funbox->tcflags + & (TCF_FUN_USES_ARGUMENTS | TCF_FUN_USES_OWN_NAME)) +#if JS_HAS_GETTER_SETTER + && op != JSOP_GETTER && op != JSOP_SETTER +#endif + ) + ? JSOP_INITMETHOD + : JSOP_INITPROP; + EMIT_INDEX_OP(initOp, ALE_INDEX(ale)); } } diff --git a/js/src/jsemit.h b/js/src/jsemit.h index 4a568d5c450d..302dbed37d6f 100644 --- a/js/src/jsemit.h +++ b/js/src/jsemit.h @@ -248,8 +248,8 @@ struct JSTreeContext { /* tree context for semantic checks */ parameter name */ #define TCF_FUN_HEAVYWEIGHT 0x100 /* function needs Call object per call */ #define TCF_FUN_IS_GENERATOR 0x200 /* parsed yield statement in function */ -#define TCF_FUN_IS_FUNARG 0x400 /* function escapes as an argument, return - value, or via the heap */ +#define TCF_FUN_USES_OWN_NAME 0x400 /* named function expression that uses its + own name */ #define TCF_HAS_FUNCTION_STMT 0x800 /* block contains a function statement */ #define TCF_GENEXP_LAMBDA 0x1000 /* flag lambda from generator expression */ #define TCF_COMPILE_N_GO 0x2000 /* compiler-and-go mode of script, can @@ -267,7 +267,7 @@ struct JSTreeContext { /* tree context for semantic checks */ TCF_FUN_PARAM_ARGUMENTS | \ TCF_FUN_HEAVYWEIGHT | \ TCF_FUN_IS_GENERATOR | \ - TCF_FUN_IS_FUNARG | \ + TCF_FUN_USES_OWN_NAME | \ TCF_HAS_SHARPS) /* diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 9dd5aaffaeb6..cc511e3adcf2 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -1,5 +1,5 @@ /* -*- Mode: C++; tab-width: 4; 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 @@ -186,41 +186,57 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, * 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) && - SPROP_HAS_STUB_GETTER(sprop) && - SPROP_HAS_VALID_SLOT(sprop, scope)) { + if (cs->format & JOF_CALLOP) { jsval v; - v = LOCKED_OBJ_GET_SLOT(pobj, sprop->slot); - if (VALUE_IS_FUNCTION(cx, v)) { + if (sprop->isMethod()) { /* - * 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 SCOPE_BRANDED flag. Once - * this scope flag is set, any write to a function-valued plain - * old property in pobj will result in shape being regenerated. + * A compiler-created function object, AKA a method, already + * memoized in the property tree. */ - 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(); - } + 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. */ @@ -237,30 +253,30 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj, 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 + * 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. It allows to optimize - * periodic execution of object initializers or explicit - * initialization sequences like + * 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 - * bigger that the cost of an extra mismatch per loop due to + * 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 JSOP_SETPROP fills the cache with - * the shape of newly created object, not the shape after - * obj.x is assigned. That mismatches obj's shape on the - * second iteration. Note that on third and the following - * iterations the cache will be hit since the shape no longer - * mutates. + * 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) { @@ -991,7 +1007,7 @@ js_OnUnknownMethod(JSContext *cx, jsval *vp) MUST_FLOW_THROUGH("out"); id = ATOM_TO_JSID(cx->runtime->atomState.noSuchMethodAtom); - ok = js_GetMethod(cx, obj, id, false, &tvr.u.value); + ok = js_GetMethod(cx, obj, id, JSGET_NO_METHOD_BARRIER, &tvr.u.value); if (!ok) goto out; if (JSVAL_IS_PRIMITIVE(tvr.u.value)) { @@ -2085,9 +2101,9 @@ js_TraceOpcode(JSContext *cx) fp->script, cx->tracePrevPc); /* - * If there aren't that many elements on the stack, then - * we have probably entered a new frame, and printing output - * would just be misleading. + * If there aren't that many elements on the stack, then we have + * probably entered a new frame, and printing output would just be + * misleading. */ if (ndefs != 0 && ndefs < regs->sp - fp->slots) { @@ -2533,8 +2549,6 @@ AssertValidPropertyCacheHit(JSContext *cx, JSScript *script, JSFrameRegs& regs, } if (!ok) return false; - if (!prop) - return true; if (cx->runtime->gcNumber != sample || PCVCAP_SHAPE(entry->vcap) != OBJ_SHAPE(pobj)) { pobj->dropProperty(cx, prop); @@ -2546,18 +2560,26 @@ AssertValidPropertyCacheHit(JSContext *cx, JSScript *script, JSFrameRegs& regs, JSScopeProperty *sprop = (JSScopeProperty *) prop; if (PCVAL_IS_SLOT(entry->vword)) { JS_ASSERT(PCVAL_TO_SLOT(entry->vword) == sprop->slot); + JS_ASSERT(!sprop->isMethod()); } else if (PCVAL_IS_SPROP(entry->vword)) { JS_ASSERT(PCVAL_TO_SPROP(entry->vword) == sprop); + JS_ASSERT_IF(sprop->isMethod(), + sprop->methodValue() == LOCKED_OBJ_GET_SLOT(pobj, sprop->slot)); } else { jsval v; JS_ASSERT(PCVAL_IS_OBJECT(entry->vword)); JS_ASSERT(entry->vword != PCVAL_NULL); JS_ASSERT(OBJ_SCOPE(pobj)->branded()); - JS_ASSERT(SPROP_HAS_STUB_GETTER(sprop)); + JS_ASSERT(SPROP_HAS_STUB_GETTER_OR_IS_METHOD(sprop)); JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(pobj))); v = LOCKED_OBJ_GET_SLOT(pobj, sprop->slot); JS_ASSERT(VALUE_IS_FUNCTION(cx, v)); JS_ASSERT(PCVAL_TO_OBJECT(entry->vword) == JSVAL_TO_OBJECT(v)); + + if (sprop->isMethod()) { + JS_ASSERT(js_CodeSpec[*regs.pc].format & JOF_CALLOP); + JS_ASSERT(sprop->methodValue() == v); + } } pobj->dropProperty(cx, prop); @@ -2590,9 +2612,11 @@ JS_STATIC_ASSERT(JSOP_DEFFUN_FC_LENGTH == JSOP_DEFFUN_DBGFC_LENGTH); /* * Same for JSOP_SETNAME and JSOP_SETPROP, which differ only slightly but - * remain distinct for the decompiler. + * remain distinct for the decompiler. Likewise for JSOP_INIT{PROP,METHOD}. */ JS_STATIC_ASSERT(JSOP_SETNAME_LENGTH == JSOP_SETPROP_LENGTH); +JS_STATIC_ASSERT(JSOP_SETNAME_LENGTH == JSOP_SETMETHOD_LENGTH); +JS_STATIC_ASSERT(JSOP_INITPROP_LENGTH == JSOP_INITMETHOD_LENGTH); /* See TRY_BRANCH_AFTER_COND. */ JS_STATIC_ASSERT(JSOP_IFNE_LENGTH == JSOP_IFEQ_LENGTH); @@ -2653,14 +2677,6 @@ js_Interpret(JSContext *cx) #endif JSAutoResolveFlags rf(cx, JSRESOLVE_INFER); -#ifdef __GNUC__ -# define JS_EXTENSION __extension__ -# define JS_EXTENSION_(s) __extension__ ({ s; }) -#else -# define JS_EXTENSION -# define JS_EXTENSION_(s) s -#endif - # ifdef DEBUG /* * We call this macro from BEGIN_CASE in threaded interpreters, diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index 46a9d9bcbc2d..6d343af37404 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -260,6 +260,10 @@ struct JSPropCacheEntry { bool adding() const { return PCVCAP_TAG(vcap) == 0 && kshape != PCVCAP_SHAPE(vcap); } + + bool directHit() const { + return PCVCAP_TAG(vcap) == 0 && kshape == PCVCAP_SHAPE(vcap); + } }; /* diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index 6f974a435ea5..b4905200e04e 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -374,7 +374,7 @@ js_ValueToIterator(JSContext *cx, uintN flags, jsval *vp) *vp = OBJECT_TO_JSVAL(iterobj); } else { atom = cx->runtime->atomState.iteratorAtom; - if (!js_GetMethod(cx, obj, ATOM_TO_JSID(atom), false, vp)) + if (!js_GetMethod(cx, obj, ATOM_TO_JSID(atom), JSGET_NO_METHOD_BARRIER, vp)) goto bad; if (JSVAL_IS_VOID(*vp)) { default_iter: diff --git a/js/src/jslock.cpp b/js/src/jslock.cpp index 81d1150d831a..94f441ad2fea 100644 --- a/js/src/jslock.cpp +++ b/js/src/jslock.cpp @@ -52,7 +52,6 @@ #include "jscntxt.h" #include "jsdtoa.h" #include "jsgc.h" -#include "jsfun.h" /* for VALUE_IS_FUNCTION from LOCKED_OBJ_WRITE_SLOT */ #include "jslock.h" #include "jsscope.h" #include "jsstr.h" @@ -831,7 +830,7 @@ js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v) if (CX_THREAD_IS_RUNNING_GC(cx) || scope->sealed() || (title->ownercx && ClaimTitle(title, cx))) { - LOCKED_OBJ_WRITE_SLOT(cx, obj, slot, v); + LOCKED_OBJ_SET_SLOT(obj, slot, v); return; } @@ -841,7 +840,7 @@ js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v) JS_ASSERT(CURRENT_THREAD_IS_ME(me)); if (NativeCompareAndSwap(&tl->owner, 0, me)) { if (scope == OBJ_SCOPE(obj)) { - LOCKED_OBJ_WRITE_SLOT(cx, obj, slot, v); + LOCKED_OBJ_SET_SLOT(obj, slot, v); if (!NativeCompareAndSwap(&tl->owner, me, 0)) { /* Assert that scope locks never revert to flyweight. */ JS_ASSERT(title->ownercx != cx); @@ -853,15 +852,14 @@ js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v) } if (!NativeCompareAndSwap(&tl->owner, me, 0)) js_Dequeue(tl); - } - else if (Thin_RemoveWait(ReadWord(tl->owner)) == me) { - LOCKED_OBJ_WRITE_SLOT(cx, obj, slot, v); + } else if (Thin_RemoveWait(ReadWord(tl->owner)) == me) { + LOCKED_OBJ_SET_SLOT(obj, slot, v); return; } #endif js_LockObj(cx, obj); - LOCKED_OBJ_WRITE_SLOT(cx, obj, slot, v); + LOCKED_OBJ_SET_SLOT(obj, slot, v); /* * Same drill as above, in js_GetSlotThreadSafe. diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 804fddaa0519..856b00074b2c 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -414,7 +414,7 @@ MarkSharpObjects(JSContext *cx, JSObject *obj, JSIdArray **idap) JSScopeProperty *sprop = (JSScopeProperty *) prop; val = JSVAL_NULL; if (attrs & JSPROP_GETTER) - val = js_CastAsObjectJSVal(sprop->getter); + val = sprop->getterValue(); if (attrs & JSPROP_SETTER) { if (val != JSVAL_NULL) { /* Mark the getter, then set val to setter. */ @@ -422,7 +422,7 @@ MarkSharpObjects(JSContext *cx, JSObject *obj, JSIdArray **idap) NULL) != NULL); } - val = js_CastAsObjectJSVal(sprop->setter); + val = sprop->setterValue(); } } else { ok = obj->getProperty(cx, id, &val); @@ -778,7 +778,7 @@ obj_toSource(JSContext *cx, uintN argc, jsval *vp) (attrs & (JSPROP_GETTER | JSPROP_SETTER))) { JSScopeProperty *sprop = (JSScopeProperty *) prop; if (attrs & JSPROP_GETTER) { - val[valcnt] = js_CastAsObjectJSVal(sprop->getter); + val[valcnt] = sprop->getterValue(); gsopold[valcnt] = ATOM_TO_STRING(cx->runtime->atomState.getterAtom); gsop[valcnt] = @@ -787,7 +787,7 @@ obj_toSource(JSContext *cx, uintN argc, jsval *vp) valcnt++; } if (attrs & JSPROP_SETTER) { - val[valcnt] = js_CastAsObjectJSVal(sprop->setter); + val[valcnt] = sprop->setterValue(); gsopold[valcnt] = ATOM_TO_STRING(cx->runtime->atomState.setterAtom); gsop[valcnt] = @@ -1897,7 +1897,7 @@ obj_lookupGetter(JSContext *cx, uintN argc, jsval *vp) if (OBJ_IS_NATIVE(pobj)) { sprop = (JSScopeProperty *) prop; if (sprop->attrs & JSPROP_GETTER) - *vp = js_CastAsObjectJSVal(sprop->getter); + *vp = sprop->getterValue(); } pobj->dropProperty(cx, prop); } @@ -1922,7 +1922,7 @@ obj_lookupSetter(JSContext *cx, uintN argc, jsval *vp) if (OBJ_IS_NATIVE(pobj)) { sprop = (JSScopeProperty *) prop; if (sprop->attrs & JSPROP_SETTER) - *vp = js_CastAsObjectJSVal(sprop->setter); + *vp = sprop->setterValue(); } pobj->dropProperty(cx, prop); } @@ -3547,6 +3547,8 @@ js_AddNativeProperty(JSContext *cx, JSObject *obj, jsid id, JSScope *scope; JSScopeProperty *sprop; + JS_ASSERT(!(flags & SPROP_IS_METHOD)); + /* * Purge the property cache of now-shadowed id in obj's scope chain. Do * this optimistically (assuming no failure below) before locking obj, so @@ -3598,23 +3600,23 @@ js_DefineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, * nominal initial value of a slot-full property, while GC safety wants that * value to be stored before the call-out through the hook. Optimize to do * both while saving cycles for classes that stub their addProperty hook. - * - * As in js_SetProtoOrParent (see above), we maintain the "any Array prototype - * has indexed properties hazard" flag by conservatively setting it. */ -#define ADD_PROPERTY_HELPER(cx,clasp,obj,scope,sprop,vp,cleanup) \ - JS_BEGIN_MACRO \ - if ((clasp)->addProperty != JS_PropertyStub) { \ - jsval nominal_ = *(vp); \ - if (!(clasp)->addProperty(cx, obj, SPROP_USERID(sprop), vp)) { \ - cleanup; \ - } \ - if (*(vp) != nominal_) { \ - if (SPROP_HAS_VALID_SLOT(sprop, scope)) \ - LOCKED_OBJ_WRITE_SLOT(cx, obj, (sprop)->slot, *(vp)); \ - } \ - } \ - JS_END_MACRO +static inline bool +AddPropertyHelper(JSContext *cx, JSClass *clasp, JSObject *obj, JSScope *scope, + JSScopeProperty *sprop, jsval *vp) +{ + if (clasp->addProperty != JS_PropertyStub) { + jsval nominal = *vp; + + if (!clasp->addProperty(cx, obj, SPROP_USERID(sprop), vp)) + return false; + if (*vp != nominal) { + if (SPROP_HAS_VALID_SLOT(sprop, scope)) + LOCKED_OBJ_SET_SLOT(obj, sprop->slot, *vp); + } + } + return true; +} JSBool js_DefineNativeProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, @@ -3627,7 +3629,7 @@ js_DefineNativeProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, JSScopeProperty *sprop; JSBool added; - JS_ASSERT((defineHow & ~(JSDNP_CACHE_RESULT | JSDNP_DONT_PURGE)) == 0); + JS_ASSERT((defineHow & ~(JSDNP_CACHE_RESULT | JSDNP_DONT_PURGE | JSDNP_SET_METHOD)) == 0); js_LeaveTraceIfGlobalObject(cx, obj); /* Convert string indices to integers if appropriate. */ @@ -3699,10 +3701,12 @@ js_DefineNativeProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, /* Use the object's class getter and setter by default. */ clasp = obj->getClass(); - if (!getter) - getter = clasp->getProperty; - if (!setter) - setter = clasp->setProperty; + if (!(defineHow & JSDNP_SET_METHOD)) { + if (!getter) + getter = clasp->getProperty; + if (!setter) + setter = clasp->setProperty; + } /* Get obj's own scope if it has one, or create a new one for obj. */ scope = js_GetMutableScope(cx, obj); @@ -3714,6 +3718,20 @@ js_DefineNativeProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, /* Add a new property, or replace an existing one of the same id. */ if (clasp->flags & JSCLASS_SHARE_ALL_PROPERTIES) attrs |= JSPROP_SHARED; + + if (defineHow & JSDNP_SET_METHOD) { + JS_ASSERT(clasp == &js_ObjectClass); + JS_ASSERT(VALUE_IS_FUNCTION(cx, value)); + JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); + JS_ASSERT(!getter && !setter); + + JSObject *funobj = JSVAL_TO_OBJECT(value); + if (FUN_OBJECT(GET_FUNCTION_PRIVATE(cx, funobj)) == funobj) { + flags |= SPROP_IS_METHOD; + getter = js_CastAsPropertyOp(funobj); + } + } + sprop = scope->add(cx, id, getter, setter, SPROP_INVALID_SLOT, attrs, flags, shortid); if (!sprop) @@ -3723,12 +3741,13 @@ js_DefineNativeProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, /* Store value before calling addProperty, in case the latter GC's. */ if (SPROP_HAS_VALID_SLOT(sprop, scope)) - LOCKED_OBJ_WRITE_SLOT(cx, obj, sprop->slot, value); + LOCKED_OBJ_SET_SLOT(obj, sprop->slot, value); /* XXXbe called with lock held */ - ADD_PROPERTY_HELPER(cx, clasp, obj, scope, sprop, &value, - scope->remove(cx, id); - goto error); + if (!AddPropertyHelper(cx, clasp, obj, scope, sprop, &value)) { + scope->remove(cx, id); + goto error; + } if (defineHow & JSDNP_CACHE_RESULT) { JS_ASSERT_NOT_ON_TRACE(cx); @@ -4108,7 +4127,7 @@ js_FindIdentifierBase(JSContext *cx, JSObject *scopeChain, jsid id) JSBool js_NativeGet(JSContext *cx, JSObject *obj, JSObject *pobj, - JSScopeProperty *sprop, jsval *vp) + JSScopeProperty *sprop, uintN getHow, jsval *vp) { js_LeaveTraceIfGlobalObject(cx, pobj); @@ -4127,30 +4146,41 @@ js_NativeGet(JSContext *cx, JSObject *obj, JSObject *pobj, ? LOCKED_OBJ_GET_SLOT(pobj, slot) : JSVAL_VOID; if (SPROP_HAS_STUB_GETTER(sprop)) - return JS_TRUE; + return true; + + if (JS_UNLIKELY(sprop->isMethod()) && (getHow & JSGET_NO_METHOD_BARRIER)) { + JS_ASSERT(sprop->methodValue() == *vp); + return true; + } sample = cx->runtime->propertyRemovals; JS_UNLOCK_SCOPE(cx, scope); JS_PUSH_TEMP_ROOT_SPROP(cx, sprop, &tvr); JS_PUSH_TEMP_ROOT_OBJECT(cx, pobj, &tvr2); - ok = js_GetSprop(cx, sprop, obj, vp); + ok = sprop->get(cx, obj, pobj, vp); JS_POP_TEMP_ROOT(cx, &tvr2); JS_POP_TEMP_ROOT(cx, &tvr); if (!ok) - return JS_FALSE; + return false; JS_LOCK_SCOPE(cx, scope); if (SLOT_IN_SCOPE(slot, scope) && (JS_LIKELY(cx->runtime->propertyRemovals == sample) || scope->has(sprop))) { - LOCKED_OBJ_SET_SLOT(pobj, slot, *vp); + jsval v = *vp; + if (!scope->methodWriteBarrier(cx, sprop, v)) { + JS_UNLOCK_SCOPE(cx, scope); + return false; + } + LOCKED_OBJ_SET_SLOT(pobj, slot, v); } - return JS_TRUE; + return true; } JSBool -js_NativeSet(JSContext *cx, JSObject *obj, JSScopeProperty *sprop, jsval *vp) +js_NativeSet(JSContext *cx, JSObject *obj, JSScopeProperty *sprop, bool added, + jsval *vp) { js_LeaveTraceIfGlobalObject(cx, obj); @@ -4169,8 +4199,14 @@ js_NativeSet(JSContext *cx, JSObject *obj, JSScopeProperty *sprop, jsval *vp) OBJ_CHECK_SLOT(obj, slot); /* If sprop has a stub setter, keep scope locked and just store *vp. */ - if (SPROP_HAS_STUB_SETTER(sprop)) - goto set_slot; + if (SPROP_HAS_STUB_SETTER(sprop)) { + if (!added && !scope->methodWriteBarrier(cx, sprop, *vp)) { + JS_UNLOCK_SCOPE(cx, scope); + return false; + } + LOCKED_OBJ_SET_SLOT(obj, slot, *vp); + return true; + } } else { /* * Allow API consumers to create shared properties with stub setters. @@ -4183,31 +4219,35 @@ js_NativeSet(JSContext *cx, JSObject *obj, JSScopeProperty *sprop, jsval *vp) */ if (!(sprop->attrs & JSPROP_GETTER) && SPROP_HAS_STUB_SETTER(sprop)) { JS_ASSERT(!(sprop->attrs & JSPROP_SETTER)); - return JS_TRUE; + return true; } } sample = cx->runtime->propertyRemovals; JS_UNLOCK_SCOPE(cx, scope); JS_PUSH_TEMP_ROOT_SPROP(cx, sprop, &tvr); - ok = js_SetSprop(cx, sprop, obj, vp); + ok = sprop->set(cx, obj, vp); JS_POP_TEMP_ROOT(cx, &tvr); if (!ok) - return JS_FALSE; + return false; JS_LOCK_SCOPE(cx, scope); if (SLOT_IN_SCOPE(slot, scope) && (JS_LIKELY(cx->runtime->propertyRemovals == sample) || scope->has(sprop))) { - set_slot: - LOCKED_OBJ_WRITE_SLOT(cx, obj, slot, *vp); + jsval v = *vp; + if (!added && !scope->methodWriteBarrier(cx, sprop, v)) { + JS_UNLOCK_SCOPE(cx, scope); + return false; + } + LOCKED_OBJ_SET_SLOT(obj, slot, v); } - return JS_TRUE; + return true; } JSBool -js_GetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, +js_GetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN getHow, jsval *vp) { JSObject *aobj, *obj2; @@ -4215,7 +4255,8 @@ js_GetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, JSProperty *prop; JSScopeProperty *sprop; - JS_ASSERT_IF(cacheResult, !JS_ON_TRACE(cx)); + JS_ASSERT_IF(getHow & JSGET_CACHE_RESULT, !JS_ON_TRACE(cx)); + /* Convert string indices to integers if appropriate. */ id = js_CheckForStringIndex(id); @@ -4230,7 +4271,7 @@ js_GetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, if (!OBJ_GET_CLASS(cx, obj)->getProperty(cx, obj, ID_TO_VALUE(id), vp)) return JS_FALSE; - PCMETER(cacheResult && JS_PROPERTY_CACHE(cx).nofills++); + PCMETER(getHow & JSGET_CACHE_RESULT && JS_PROPERTY_CACHE(cx).nofills++); /* * Give a strict warning if foo.bar is evaluated by a script for an @@ -4292,12 +4333,12 @@ js_GetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, sprop = (JSScopeProperty *) prop; - if (cacheResult) { + if (getHow & JSGET_CACHE_RESULT) { JS_ASSERT_NOT_ON_TRACE(cx); js_FillPropertyCache(cx, aobj, 0, protoIndex, obj2, sprop, false); } - if (!js_NativeGet(cx, obj, obj2, sprop, vp)) + if (!js_NativeGet(cx, obj, obj2, sprop, getHow, vp)) return JS_FALSE; JS_UNLOCK_OBJ(cx, obj2); @@ -4307,20 +4348,19 @@ js_GetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, JSBool js_GetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp) { - return js_GetPropertyHelper(cx, obj, id, false, vp); + return js_GetPropertyHelper(cx, obj, id, JSGET_METHOD_BARRIER, vp); } JSBool -js_GetMethod(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, - jsval *vp) +js_GetMethod(JSContext *cx, JSObject *obj, jsid id, uintN getHow, jsval *vp) { JSAutoResolveFlags rf(cx, JSRESOLVE_QUALIFIED); if (obj->map->ops == &js_ObjectOps || obj->map->ops->getProperty == js_GetProperty) { - return js_GetPropertyHelper(cx, obj, id, cacheResult, vp); + return js_GetPropertyHelper(cx, obj, id, getHow, vp); } - JS_ASSERT_IF(cacheResult, OBJ_IS_DENSE_ARRAY(cx, obj)); + JS_ASSERT_IF(getHow & JSGET_CACHE_RESULT, OBJ_IS_DENSE_ARRAY(cx, obj)); #if JS_HAS_XML_SUPPORT if (OBJECT_IS_XML(cx, obj)) return js_GetXMLMethod(cx, obj, id, vp); @@ -4351,10 +4391,11 @@ js_CheckUndeclaredVarAssignment(JSContext *cx) /* * Note: all non-error exits in this function must notify the tracer using - * SetPropHit when called from the interpreter loop (cacheResult is true). + * SetPropHit when called from the interpreter, which is detected by testing + * (defineHow & JSDNP_CACHE_RESULT). */ JSBool -js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, +js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow, jsval *vp) { int protoIndex; @@ -4368,7 +4409,8 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, JSPropertyOp getter, setter; bool added; - if (cacheResult) + JS_ASSERT((defineHow & ~(JSDNP_CACHE_RESULT | JSDNP_SET_METHOD)) == 0); + if (defineHow & JSDNP_CACHE_RESULT) JS_ASSERT_NOT_ON_TRACE(cx); /* Convert string indices to integers if appropriate. */ @@ -4441,8 +4483,8 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, if (attrs & JSPROP_READONLY) { if (!JS_HAS_STRICT_OPTION(cx)) { /* Just return true per ECMA if not in strict mode. */ - PCMETER(cacheResult && JS_PROPERTY_CACHE(cx).rofills++); - if (cacheResult) + PCMETER((defineHow & JSDNP_CACHE_RESULT) && JS_PROPERTY_CACHE(cx).rofills++); + if (defineHow & JSDNP_CACHE_RESULT) TRACE_2(SetPropHit, JS_NO_PROP_CACHE_FILL, sprop); return JS_TRUE; #ifdef JS_TRACER @@ -4469,7 +4511,7 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, /* Don't clone a shared prototype property. */ if (attrs & JSPROP_SHARED) { - if (cacheResult) { + if (defineHow & JSDNP_CACHE_RESULT) { JSPropCacheEntry *entry; entry = js_FillPropertyCache(cx, obj, 0, protoIndex, pobj, sprop, false); TRACE_2(SetPropHit, entry, sprop); @@ -4480,7 +4522,7 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, return JS_TRUE; } - return js_SetSprop(cx, sprop, obj, vp); + return sprop->set(cx, obj, vp); } /* Restore attrs to the ECMA default for new properties. */ @@ -4527,8 +4569,26 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, JS_UNLOCK_OBJ(cx, obj); return JS_FALSE; } + if (clasp->flags & JSCLASS_SHARE_ALL_PROPERTIES) attrs |= JSPROP_SHARED; + + /* + * Check for Object class here to avoid defining a method on a class + * with magic resolve, addProperty, getProperty, etc. hooks. + */ + if ((defineHow & JSDNP_SET_METHOD) && + obj->getClass() == &js_ObjectClass) { + JS_ASSERT(VALUE_IS_FUNCTION(cx, *vp)); + JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); + + JSObject *funobj = JSVAL_TO_OBJECT(*vp); + if (FUN_OBJECT(GET_FUNCTION_PRIVATE(cx, funobj)) == funobj) { + flags |= SPROP_IS_METHOD; + getter = js_CastAsPropertyOp(funobj); + } + } + sprop = scope->add(cx, id, getter, setter, SPROP_INVALID_SLOT, attrs, flags, shortid); if (!sprop) { @@ -4545,20 +4605,21 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, LOCKED_OBJ_SET_SLOT(obj, sprop->slot, JSVAL_VOID); /* XXXbe called with obj locked */ - ADD_PROPERTY_HELPER(cx, clasp, obj, scope, sprop, vp, - scope->remove(cx, id); - JS_UNLOCK_SCOPE(cx, scope); - return JS_FALSE); + if (!AddPropertyHelper(cx, clasp, obj, scope, sprop, vp)) { + scope->remove(cx, id); + JS_UNLOCK_SCOPE(cx, scope); + return JS_FALSE; + } added = true; } - if (cacheResult) { + if (defineHow & JSDNP_CACHE_RESULT) { JSPropCacheEntry *entry; entry = js_FillPropertyCache(cx, obj, 0, 0, obj, sprop, added); TRACE_2(SetPropHit, entry, sprop); } - if (!js_NativeSet(cx, obj, sprop, vp)) + if (!js_NativeSet(cx, obj, sprop, added, vp)) return NULL; JS_UNLOCK_SCOPE(cx, scope); @@ -5493,7 +5554,7 @@ js_TryMethod(JSContext *cx, JSObject *obj, JSAtom *atom, older = JS_SetErrorReporter(cx, NULL); id = ATOM_TO_JSID(atom); fval = JSVAL_VOID; - ok = js_GetMethod(cx, obj, id, false, &fval); + ok = js_GetMethod(cx, obj, id, JSGET_NO_METHOD_BARRIER, &fval); if (!ok) JS_ClearPendingException(cx); JS_SetErrorReporter(cx, older); diff --git a/js/src/jsobj.h b/js/src/jsobj.h index d245ebe8627e..61d8494af75c 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -350,41 +350,6 @@ STOBJ_GET_CLASS(const JSObject* obj) #define LOCKED_OBJ_SET_SLOT(obj,slot,value) \ (OBJ_CHECK_SLOT(obj, slot), STOBJ_SET_SLOT(obj, slot, value)) -/* - * NB: Don't call LOCKED_OBJ_SET_SLOT or STOBJ_SET_SLOT for a write to a slot - * that may contain a function reference already, or where the new value is a - * function ref, and the object's scope may be branded with a property cache - * structural type capability that distinguishes versions of the object with - * and without the function property. Instead use LOCKED_OBJ_WRITE_SLOT or a - * fast inline equivalent (JSOP_SETNAME/JSOP_SETPROP cases in jsinterp.cpp). - */ -#define LOCKED_OBJ_WRITE_SLOT(cx,obj,slot,newval) \ - JS_BEGIN_MACRO \ - LOCKED_OBJ_WRITE_BARRIER(cx, obj, slot, newval); \ - LOCKED_OBJ_SET_SLOT(obj, slot, newval); \ - JS_END_MACRO - -/* - * Write barrier macro monitoring property update for slot in obj from its old - * value to newval. - * - * NB: obj must be locked, and remains locked after the calls to this macro. - */ -#define LOCKED_OBJ_WRITE_BARRIER(cx,obj,slot,newval) \ - JS_BEGIN_MACRO \ - JSScope *scope_ = OBJ_SCOPE(obj); \ - JS_ASSERT(scope_->object == obj); \ - if (scope_->branded()) { \ - jsval oldval_ = LOCKED_OBJ_GET_SLOT(obj, slot); \ - if (oldval_ != (newval) && \ - (VALUE_IS_FUNCTION(cx, oldval_) || \ - VALUE_IS_FUNCTION(cx, newval))) { \ - scope_->methodShapeChange(cx, slot, newval); \ - } \ - } \ - GC_POKE(cx, oldval); \ - JS_END_MACRO - #ifdef JS_THREADSAFE /* Thread-safe functions and wrapper macros for accessing slots in obj. */ @@ -398,7 +363,7 @@ STOBJ_GET_CLASS(const JSObject* obj) JS_BEGIN_MACRO \ OBJ_CHECK_SLOT(obj, slot); \ if (OBJ_SCOPE(obj)->title.ownercx == cx) \ - LOCKED_OBJ_WRITE_SLOT(cx, obj, slot, value); \ + LOCKED_OBJ_SET_SLOT(obj, slot, value); \ else \ js_SetSlotThreadSafe(cx, obj, slot, value); \ JS_END_MACRO @@ -424,7 +389,7 @@ STOBJ_GET_CLASS(const JSObject* obj) #else /* !JS_THREADSAFE */ #define OBJ_GET_SLOT(cx,obj,slot) LOCKED_OBJ_GET_SLOT(obj,slot) -#define OBJ_SET_SLOT(cx,obj,slot,value) LOCKED_OBJ_WRITE_SLOT(cx,obj,slot,value) +#define OBJ_SET_SLOT(cx,obj,slot,value) LOCKED_OBJ_SET_SLOT(obj,slot,value) #endif /* !JS_THREADSAFE */ @@ -724,6 +689,9 @@ js_DefineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, */ const uintN JSDNP_CACHE_RESULT = 1; /* an interpreter call from JSOP_INITPROP */ const uintN JSDNP_DONT_PURGE = 2; /* suppress js_PurgeScopeChain */ +const uintN JSDNP_SET_METHOD = 4; /* js_{DefineNativeProperty,SetPropertyHelper} + must pass the SPROP_IS_METHOD flag on to + js_AddScopeProperty */ /* * On error, return false. On success, if propp is non-null, return true with @@ -801,6 +769,23 @@ js_FindIdentifierBase(JSContext *cx, JSObject *scopeChain, jsid id); extern JSObject * js_FindVariableScope(JSContext *cx, JSFunction **funp); +/* + * JSGET_CACHE_RESULT is the analogue of JSDNP_CACHE_RESULT for js_GetMethod. + * + * JSGET_METHOD_BARRIER (the default, hence 0 but provided for documentation) + * enables a read barrier that preserves standard function object semantics (by + * default we assume our caller won't leak a joined callee to script, where it + * would create hazardous mutable object sharing as well as observable identity + * according to == and ===. + * + * JSGET_NO_METHOD_BARRIER avoids the performance overhead of the method read + * barrier, which is not needed when invoking a lambda that otherwise does not + * leak its callee reference (via arguments.callee or its name). + */ +const uintN JSGET_CACHE_RESULT = 1; // from a caching interpreter opcode +const uintN JSGET_METHOD_BARRIER = 0; // get can leak joined function object +const uintN JSGET_NO_METHOD_BARRIER = 2; // call to joined function can't leak + /* * NB: js_NativeGet and js_NativeSet are called with the scope containing sprop * (pobj's scope for Get, obj's for Set) locked, and on successful return, that @@ -809,21 +794,21 @@ js_FindVariableScope(JSContext *cx, JSFunction **funp); */ extern JSBool js_NativeGet(JSContext *cx, JSObject *obj, JSObject *pobj, - JSScopeProperty *sprop, jsval *vp); + JSScopeProperty *sprop, uintN getHow, jsval *vp); extern JSBool -js_NativeSet(JSContext *cx, JSObject *obj, JSScopeProperty *sprop, jsval *vp); +js_NativeSet(JSContext *cx, JSObject *obj, JSScopeProperty *sprop, bool added, + jsval *vp); extern JSBool -js_GetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, +js_GetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN getHow, jsval *vp); extern JSBool js_GetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp); extern JSBool -js_GetMethod(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, - jsval *vp); +js_GetMethod(JSContext *cx, JSObject *obj, jsid id, uintN getHow, jsval *vp); /* * Check whether it is OK to assign an undeclared property of the global @@ -833,7 +818,7 @@ extern JS_FRIEND_API(JSBool) js_CheckUndeclaredVarAssignment(JSContext *cx); extern JSBool -js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, JSBool cacheResult, +js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow, jsval *vp); extern JSBool diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 9b59b926beb4..c538dbb8e190 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -3856,6 +3856,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop) goto do_getprop; case JSOP_SETPROP: + case JSOP_SETMETHOD: LOAD_ATOM(0); GET_QUOTE_AND_FMT("%s[%s] %s= %s", "%s.%s %s= %s", xval); rval = POP_STR(); @@ -4517,6 +4518,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop) break; case JSOP_INITPROP: + case JSOP_INITMETHOD: LOAD_ATOM(0); xval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), (jschar) diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl index 283fdd9dd5d9..035913d8e766 100644 --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -246,8 +246,8 @@ OPDEF(JSOP_UINT16, 88, "uint16", NULL, 3, 0, 1, 16, JOF_UINT16 /* Object and array literal support. */ OPDEF(JSOP_NEWINIT, 89, "newinit", NULL, 2, 0, 1, 19, JOF_INT8) OPDEF(JSOP_ENDINIT, 90, "endinit", NULL, 1, 0, 0, 19, JOF_BYTE) -OPDEF(JSOP_INITPROP, 91, "initprop", NULL, 3, 1, 0, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) -OPDEF(JSOP_INITELEM, 92, "initelem", NULL, 1, 2, 0, 3, JOF_BYTE |JOF_ELEM|JOF_SET|JOF_DETECTING) +OPDEF(JSOP_INITPROP, 91, "initprop", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) +OPDEF(JSOP_INITELEM, 92, "initelem", NULL, 1, 3, 1, 3, JOF_BYTE |JOF_ELEM|JOF_SET|JOF_DETECTING) OPDEF(JSOP_DEFSHARP, 93, "defsharp", NULL, 3, 0, 0, 0, JOF_UINT16) OPDEF(JSOP_USESHARP, 94, "usesharp", NULL, 3, 0, 1, 0, JOF_UINT16) @@ -582,7 +582,7 @@ OPDEF(JSOP_OBJTOP, 227,"objtop", NULL, 3, 0, 0, 0, JOF_UINT16 OPDEF(JSOP_LOOP, 228, "loop", NULL, 1, 0, 0, 0, JOF_BYTE) /* - * Debugger versions of JSOP_{GET,CALL}UPVAR. + * Debugger versions of JSOP_{GET,CALL}UPVAR and the flat closure (_FC) ops. */ OPDEF(JSOP_GETUPVAR_DBG, 229,"getupvar_dbg", NULL, 3, 0, 1, 19, JOF_UINT16|JOF_NAME) OPDEF(JSOP_CALLUPVAR_DBG, 230,"callupvar_dbg", NULL, 3, 0, 2, 19, JOF_UINT16|JOF_NAME|JOF_CALLOP) @@ -595,3 +595,9 @@ OPDEF(JSOP_LAMBDA_DBGFC, 233,"lambda_dbgfc", NULL, 3, 0, 1, 19, JOF_ * immediate. See record_JSOP_CONCATN for recording behavior. */ OPDEF(JSOP_CONCATN, 234,"concatn", NULL, 3, -1, 1, 0, JOF_UINT16|JOF_TMPSLOT2) + +/* + * Joined function object as method optimization support. + */ +OPDEF(JSOP_SETMETHOD, 235,"setmethod", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) +OPDEF(JSOP_INITMETHOD, 236,"initmethod", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) diff --git a/js/src/jsops.cpp b/js/src/jsops.cpp index da0fa4c371da..8e1c0f756670 100644 --- a/js/src/jsops.cpp +++ b/js/src/jsops.cpp @@ -590,7 +590,7 @@ goto error; \ JS_END_MACRO -#define NATIVE_GET(cx,obj,pobj,sprop,vp) \ +#define NATIVE_GET(cx,obj,pobj,sprop,getHow,vp) \ JS_BEGIN_MACRO \ if (SPROP_HAS_STUB_GETTER(sprop)) { \ /* Fast path for Object instance properties. */ \ @@ -600,7 +600,7 @@ ? LOCKED_OBJ_GET_SLOT(pobj, (sprop)->slot) \ : JSVAL_VOID; \ } else { \ - if (!js_NativeGet(cx, obj, pobj, sprop, vp)) \ + if (!js_NativeGet(cx, obj, pobj, sprop, getHow, vp)) \ goto error; \ } \ JS_END_MACRO @@ -609,11 +609,12 @@ JS_BEGIN_MACRO \ TRACE_2(SetPropHit, entry, sprop); \ if (SPROP_HAS_STUB_SETTER(sprop) && \ - (sprop)->slot != SPROP_INVALID_SLOT) { \ - /* Fast path for, e.g., Object instance properties. */ \ - LOCKED_OBJ_WRITE_SLOT(cx, obj, (sprop)->slot, *vp); \ + (sprop)->slot != SPROP_INVALID_SLOT && \ + !OBJ_SCOPE(obj)->branded()) { \ + /* Fast path for, e.g., plain Object instance properties. */ \ + LOCKED_OBJ_SET_SLOT(obj, (sprop)->slot, *vp); \ } else { \ - if (!js_NativeSet(cx, obj, sprop, vp)) \ + if (!js_NativeSet(cx, obj, sprop, false, vp)) \ goto error; \ } \ JS_END_MACRO @@ -1487,6 +1488,11 @@ JSObject *aobj; JSPropCacheEntry *entry; + /* + * We do not impose the method read barrier if in an imacro, + * assuming any property gets it does (e.g., for 'toString' + * from JSOP_NEW) will not be leaked to the calling script. + */ aobj = js_GetProtoIfDenseArray(cx, obj); if (JS_LIKELY(aobj->map->ops->getProperty == js_GetProperty)) { PROPERTY_CACHE_TEST(cx, regs.pc, aobj, obj2, entry, atom); @@ -1501,7 +1507,9 @@ } else { JS_ASSERT(PCVAL_IS_SPROP(entry->vword)); sprop = PCVAL_TO_SPROP(entry->vword); - NATIVE_GET(cx, obj, obj2, sprop, &rval); + NATIVE_GET(cx, obj, obj2, sprop, + fp->imacpc ? JSGET_NO_METHOD_BARRIER : JSGET_METHOD_BARRIER, + &rval); } JS_UNLOCK_OBJ(cx, obj2); break; @@ -1515,7 +1523,11 @@ } id = ATOM_TO_JSID(atom); if (entry - ? !js_GetPropertyHelper(cx, obj, id, true, &rval) + ? !js_GetPropertyHelper(cx, obj, id, + fp->imacpc + ? JSGET_CACHE_RESULT | JSGET_NO_METHOD_BARRIER + : JSGET_CACHE_RESULT | JSGET_METHOD_BARRIER, + &rval) : !obj->getProperty(cx, id, &rval)) { goto error; } @@ -1592,7 +1604,7 @@ } else { JS_ASSERT(PCVAL_IS_SPROP(entry->vword)); sprop = PCVAL_TO_SPROP(entry->vword); - NATIVE_GET(cx, obj, obj2, sprop, &rval); + NATIVE_GET(cx, obj, obj2, sprop, JSGET_NO_METHOD_BARRIER, &rval); } JS_UNLOCK_OBJ(cx, obj2); STORE_OPND(-1, rval); @@ -1611,14 +1623,22 @@ id = ATOM_TO_JSID(atom); PUSH(JSVAL_NULL); if (!JSVAL_IS_PRIMITIVE(lval)) { - if (!js_GetMethod(cx, obj, id, !!entry, &rval)) + if (!js_GetMethod(cx, obj, id, + entry + ? JSGET_CACHE_RESULT | JSGET_NO_METHOD_BARRIER + : JSGET_NO_METHOD_BARRIER, + &rval)) { goto error; + } STORE_OPND(-1, OBJECT_TO_JSVAL(obj)); STORE_OPND(-2, rval); } else { JS_ASSERT(obj->map->ops->getProperty == js_GetProperty); - if (!js_GetPropertyHelper(cx, obj, id, true, &rval)) + if (!js_GetPropertyHelper(cx, obj, id, + JSGET_CACHE_RESULT | JSGET_NO_METHOD_BARRIER, + &rval)) { goto error; + } STORE_OPND(-1, lval); STORE_OPND(-2, rval); } @@ -1648,9 +1668,11 @@ BEGIN_CASE(JSOP_SETNAME) BEGIN_CASE(JSOP_SETPROP) + BEGIN_CASE(JSOP_SETMETHOD) rval = FETCH_OPND(-1); + JS_ASSERT_IF(op == JSOP_SETMETHOD, VALUE_IS_FUNCTION(cx, rval)); lval = FETCH_OPND(-2); - JS_ASSERT(!JSVAL_IS_PRIMITIVE(lval) || op == JSOP_SETPROP); + JS_ASSERT_IF(op == JSOP_SETNAME, !JSVAL_IS_PRIMITIVE(lval)); VALUE_TO_OBJECT(cx, -2, lval, obj); do { @@ -1806,7 +1828,12 @@ scope->extend(cx, sprop); } - LOCKED_OBJ_WRITE_BARRIER(cx, obj, slot, rval); + /* + * 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); @@ -1850,7 +1877,10 @@ LOAD_ATOM(0); id = ATOM_TO_JSID(atom); if (entry) { - if (!js_SetPropertyHelper(cx, obj, id, true, &rval)) + uintN defineHow = (op == JSOP_SETMETHOD) + ? JSDNP_CACHE_RESULT | JSDNP_SET_METHOD + : JSDNP_CACHE_RESULT; + if (!js_SetPropertyHelper(cx, obj, id, defineHow, &rval)) goto error; } else { if (!obj->setProperty(cx, id, &rval)) @@ -1907,7 +1937,7 @@ END_CASE(JSOP_GETELEM) BEGIN_CASE(JSOP_CALLELEM) - ELEMENT_OP(-1, js_GetMethod(cx, obj, id, false, &rval)); + ELEMENT_OP(-1, js_GetMethod(cx, obj, id, JSGET_NO_METHOD_BARRIER, &rval)); #if JS_HAS_NO_SUCH_METHOD if (JS_UNLIKELY(JSVAL_IS_VOID(rval))) { regs.sp[-2] = regs.sp[-1]; @@ -2310,7 +2340,7 @@ } else { sprop = (JSScopeProperty *)prop; do_native_get: - NATIVE_GET(cx, obj, obj2, sprop, &rval); + NATIVE_GET(cx, obj, obj2, sprop, JSGET_METHOD_BARRIER, &rval); obj2->dropProperty(cx, (JSProperty *) sprop); } @@ -2840,8 +2870,13 @@ } else { slot = JSVAL_TO_INT(lval); JS_LOCK_OBJ(cx, obj); - LOCKED_OBJ_WRITE_SLOT(cx, obj, slot, rval); - JS_UNLOCK_OBJ(cx, obj); + JSScope *scope = OBJ_SCOPE(obj); + if (!scope->methodWriteBarrier(cx, slot, rval)) { + JS_UNLOCK_SCOPE(cx, scope); + goto error; + } + LOCKED_OBJ_SET_SLOT(obj, slot, rval); + JS_UNLOCK_SCOPE(cx, scope); } END_SET_CASE(JSOP_SETGVAR) @@ -2892,7 +2927,7 @@ sprop = (JSScopeProperty *) prop; if ((sprop->attrs & JSPROP_PERMANENT) && SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(obj)) && - SPROP_HAS_STUB_GETTER(sprop) && + SPROP_HAS_STUB_GETTER_OR_IS_METHOD(sprop) && SPROP_HAS_STUB_SETTER(sprop)) { /* * Fast globals use frame variables to map the global @@ -2967,8 +3002,7 @@ /* * Protect obj from any GC hiding below JSObject::setProperty or * JSObject::defineProperty. All paths from here must flow through - * the "Restore fp->scopeChain" code below the - * parent->defineProperty call. + * the fp->scopeChain code below the parent->defineProperty call. */ MUST_FLOW_THROUGH("restore_scope"); fp->scopeChain = obj; @@ -2988,7 +3022,7 @@ * and setters do not need a slot, their value is stored elsewhere * in the property itself, not in obj slots. */ - setter = getter = JS_PropertyStub; + getter = setter = JS_PropertyStub; flags = JSFUN_GSFLAG2ATTR(fun->flags); if (flags) { /* Function cannot be both getter a setter. */ @@ -3102,10 +3136,10 @@ ok = parent->defineProperty(cx, id, rval, (flags & JSPROP_GETTER) - ? JS_EXTENSION (JSPropertyOp) obj + ? js_CastAsPropertyOp(obj) : JS_PropertyStub, (flags & JSPROP_SETTER) - ? JS_EXTENSION (JSPropertyOp) obj + ? js_CastAsPropertyOp(obj) : JS_PropertyStub, attrs); } @@ -3185,24 +3219,55 @@ LOAD_FUNCTION(0); obj = FUN_OBJECT(fun); - if (FUN_NULL_CLOSURE(fun)) { - obj = js_CloneFunctionObject(cx, fun, fp->scopeChain); - if (!obj) - goto error; - } else { - parent = js_GetScopeChain(cx, fp); - if (!parent) - goto error; + /* do-while(0) so we can break instead of using a goto. */ + do { + if (FUN_NULL_CLOSURE(fun)) { + parent = fp->scopeChain; + + if (OBJ_GET_PARENT(cx, obj) == parent) { + op = JSOp(regs.pc[JSOP_LAMBDA_LENGTH]); + + /* + * Optimize ({method: function () { ... }, ...}) and + * this.method = function () { ... }; bytecode sequences. + * + * Note that we jump to the entry points for JSOP_SETPROP + * and JSOP_INITPROP without calling the trace recorder, + * because the record hooks for those ops are essentially + * no-ops (this can't change given the predictive shape + * guarding the recorder must do). + */ + if (op == JSOP_SETMETHOD) { +#ifdef DEBUG + op2 = JSOp(regs.pc[JSOP_LAMBDA_LENGTH + JSOP_SETMETHOD_LENGTH]); + JS_ASSERT(op2 == JSOP_POP || op2 == JSOP_POPV); +#endif + + lval = FETCH_OPND(-1); + if (JSVAL_IS_OBJECT(lval) && + (obj2 = JSVAL_TO_OBJECT(lval)) && + OBJ_GET_CLASS(cx, obj2) == &js_ObjectClass) { + break; + } + } else if (op == JSOP_INITMETHOD) { + lval = FETCH_OPND(-1); + JS_ASSERT(!JSVAL_IS_PRIMITIVE(lval)); + obj2 = JSVAL_TO_OBJECT(lval); + JS_ASSERT(OBJ_GET_CLASS(cx, obj2) == &js_ObjectClass); + JS_ASSERT(OBJ_SCOPE(obj2)->object == obj2); + break; + } + } + } else { + parent = js_GetScopeChain(cx, fp); + if (!parent) + goto error; + } - /* - * FIXME: bug 471214, Cloning here even when the compiler saw - * the right parent is wasteful but we don't fully support - * joined function objects, yet. - */ obj = js_CloneFunctionObject(cx, fun, parent); if (!obj) goto error; - } + } while (0); PUSH_OPND(OBJECT_TO_JSVAL(obj)); END_CASE(JSOP_LAMBDA) @@ -3306,12 +3371,12 @@ goto error; if (op == JSOP_GETTER) { - getter = JS_EXTENSION (JSPropertyOp) JSVAL_TO_OBJECT(rval); + getter = js_CastAsPropertyOp(JSVAL_TO_OBJECT(rval)); setter = JS_PropertyStub; attrs = JSPROP_GETTER; } else { getter = JS_PropertyStub; - setter = JS_EXTENSION (JSPropertyOp) JSVAL_TO_OBJECT(rval); + setter = js_CastAsPropertyOp(JSVAL_TO_OBJECT(rval)); attrs = JSPROP_SETTER; } attrs |= JSPROP_ENUMERATE | JSPROP_SHARED; @@ -3324,8 +3389,10 @@ goto error; regs.sp += i; - if (js_CodeSpec[op2].ndefs) + if (js_CodeSpec[op2].ndefs > js_CodeSpec[op2].nuses) { + JS_ASSERT(js_CodeSpec[op2].ndefs == js_CodeSpec[op2].nuses + 1); STORE_OPND(-1, rval); + } len = js_CodeSpec[op2].length; DO_NEXT_OP(len); #endif /* JS_HAS_GETTER_SETTER */ @@ -3347,11 +3414,26 @@ BEGIN_CASE(JSOP_NEWINIT) i = GET_INT8(regs.pc); JS_ASSERT(i == JSProto_Array || i == JSProto_Object); - obj = (i == JSProto_Array) - ? js_NewArrayObject(cx, 0, NULL) - : js_NewObject(cx, &js_ObjectClass, NULL, NULL); - if (!obj) - goto error; + if (i == JSProto_Array) { + obj = js_NewArrayObject(cx, 0, NULL); + if (!obj) + goto error; + } else { + obj = js_NewObject(cx, &js_ObjectClass, NULL, NULL); + if (!obj) + goto error; + + if (regs.pc[JSOP_NEWINIT_LENGTH] != JSOP_ENDINIT) { + JS_LOCK_OBJ(cx, obj); + JSScope *scope = js_GetMutableScope(cx, obj); + if (!scope) { + JS_UNLOCK_OBJ(cx, obj); + goto error; + } + JS_UNLOCK_SCOPE(cx, scope); + } + } + PUSH_OPND(OBJECT_TO_JSVAL(obj)); fp->sharpDepth++; CHECK_INTERRUPT_HANDLER(); @@ -3369,6 +3451,7 @@ END_CASE(JSOP_ENDINIT) BEGIN_CASE(JSOP_INITPROP) + BEGIN_CASE(JSOP_INITMETHOD) /* Load the property's initial value into rval. */ JS_ASSERT(regs.sp - StackBase(fp) >= 2); rval = FETCH_OPND(-1); @@ -3388,6 +3471,10 @@ JS_LOCK_OBJ(cx, obj); 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); @@ -3469,13 +3556,26 @@ JS_ASSERT(sprop2 == sprop); } else { JS_ASSERT(scope->owned()); + + /* Inline-specialized version of JSScope::extend. */ js_LeaveTraceIfGlobalObject(cx, obj); scope->shape = sprop->shape; ++scope->entryCount; scope->lastProp = sprop; + + jsuint index; + if (js_IdIsIndex(sprop->id, &index)) + scope->setIndexedProperties(); + + if (sprop->isMethod()) + scope->setMethodBarrier(); } - LOCKED_OBJ_WRITE_BARRIER(cx, obj, slot, rval); + /* + * 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); @@ -3496,12 +3596,16 @@ goto error; } + uintN defineHow = (op == JSOP_INITMETHOD) + ? JSDNP_CACHE_RESULT | JSDNP_SET_METHOD + : JSDNP_CACHE_RESULT; if (!(JS_UNLIKELY(atom == cx->runtime->atomState.protoAtom) - ? js_SetPropertyHelper(cx, obj, id, true, &rval) + ? js_SetPropertyHelper(cx, obj, id, defineHow, &rval) : js_DefineNativeProperty(cx, obj, id, rval, NULL, NULL, JSPROP_ENUMERATE, 0, 0, NULL, - JSDNP_CACHE_RESULT))) + defineHow))) { goto error; + } } while (0); /* Common tail for property cache hit and miss cases. */ diff --git a/js/src/jsparse.cpp b/js/src/jsparse.cpp index 62d344a806d5..3c7da7996a16 100644 --- a/js/src/jsparse.cpp +++ b/js/src/jsparse.cpp @@ -2332,20 +2332,10 @@ LeaveFunction(JSParseNode *fn, JSTreeContext *funtc, JSTreeContext *tc, /* * If this named function expression uses its own name other - * than to call itself, flag this function as using arguments, - * as if it had used arguments.callee instead of its own name. - * - * This abuses the plain sense of TCF_FUN_USES_ARGUMENTS, but - * we are out of tcflags bits at the moment. If it deoptimizes - * code unfairly (see JSCompiler::setFunctionKinds, where this - * flag is interpreted in its broader sense, not only to mean - * "this function might leak arguments.callee"), we can perhaps - * try to work harder to add a TCF_FUN_LEAKS_ITSELF flag and - * use that more precisely, both here and for unnamed function - * expressions. + * than to call itself, flag this function specially. */ if (dn->isFunArg()) - fn->pn_funbox->tcflags |= TCF_FUN_USES_ARGUMENTS; + fn->pn_funbox->tcflags |= TCF_FUN_USES_OWN_NAME; foundCallee = 1; continue; } @@ -5479,6 +5469,20 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) pn->pn_type = TOK_SEMI; pn->pn_pos = pn2->pn_pos; pn->pn_kid = pn2; + + /* + * Specialize JSOP_SETPROP into JSOP_SETMETHOD to defer or avoid null + * closure cloning. Do this here rather than in AssignExpr as only now + * do we know that the uncloned (unjoined in ES3 terms) function object + * result of the assignment expression can't escape. + */ + if (PN_TYPE(pn2) == TOK_ASSIGN && PN_OP(pn2) == JSOP_NOP && + PN_OP(pn2->pn_left) == JSOP_SETPROP && + PN_OP(pn2->pn_right) == JSOP_LAMBDA && + !(pn2->pn_right->pn_funbox->tcflags + & (TCF_FUN_USES_ARGUMENTS | TCF_FUN_USES_OWN_NAME))) { + pn2->pn_left->pn_op = JSOP_SETMETHOD; + } break; } @@ -6745,7 +6749,7 @@ CheckForImmediatelyAppliedLambda(JSParseNode *pn) JSFunctionBox *funbox = pn->pn_funbox; JS_ASSERT(((JSFunction *) funbox->object)->flags & JSFUN_LAMBDA); - if (!(funbox->tcflags & TCF_FUN_USES_ARGUMENTS)) + if (!(funbox->tcflags & (TCF_FUN_USES_ARGUMENTS | TCF_FUN_USES_OWN_NAME))) pn->pn_dflags &= ~PND_FUNARG; } return pn; diff --git a/js/src/jsscope.cpp b/js/src/jsscope.cpp index c49643372340..072e25163418 100644 --- a/js/src/jsscope.cpp +++ b/js/src/jsscope.cpp @@ -262,26 +262,6 @@ JSScope::destroy(JSContext *cx, JSScope *scope) } #ifdef JS_DUMP_PROPTREE_STATS -typedef struct JSScopeStats { - jsrefcount searches; - jsrefcount hits; - jsrefcount misses; - jsrefcount hashes; - jsrefcount steps; - jsrefcount stepHits; - jsrefcount stepMisses; - jsrefcount adds; - jsrefcount redundantAdds; - jsrefcount addFailures; - jsrefcount changeFailures; - jsrefcount compresses; - jsrefcount grows; - jsrefcount removes; - jsrefcount removeFrees; - jsrefcount uselessRemoves; - jsrefcount shrinks; -} JSScopeStats; - JS_FRIEND_DATA(JSScopeStats) js_scope_stats = {0}; # define METER(x) JS_ATOMIC_INCREMENT(&js_scope_stats.x) @@ -444,12 +424,14 @@ js_HashScopeProperty(JSDHashTable *table, const void *key) /* Accumulate from least to most random so the low bits are most random. */ hash = 0; + JS_ASSERT_IF(sprop->isMethod(), + !sprop->setter || sprop->setter == js_watch_set); gsop = sprop->getter; if (gsop) - hash = JS_ROTATE_LEFT32(hash, 4) ^ (jsword)gsop; + hash = JS_ROTATE_LEFT32(hash, 4) ^ jsword(gsop); gsop = sprop->setter; if (gsop) - hash = JS_ROTATE_LEFT32(hash, 4) ^ (jsword)gsop; + hash = JS_ROTATE_LEFT32(hash, 4) ^ jsword(gsop); hash = JS_ROTATE_LEFT32(hash, 4) ^ (sprop->flags & ~SPROP_FLAGS_NOT_MATCHED); @@ -1054,6 +1036,9 @@ JSScope::add(JSContext *cx, jsid id, JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, this)); CHECK_ANCESTOR_LINE(this, true); + JS_ASSERT_IF(attrs & JSPROP_GETTER, getter); + JS_ASSERT_IF(attrs & JSPROP_SETTER, setter); + /* * You can't add properties to a sealed scope. But note well that you can * change property attributes in a sealed scope, even though that replaces @@ -1069,10 +1054,17 @@ JSScope::add(JSContext *cx, jsid id, * Normalize stub getter and setter values for faster is-stub testing in * the SPROP_CALL_[GS]ETTER macros. */ - if (getter == JS_PropertyStub) - getter = NULL; if (setter == JS_PropertyStub) setter = NULL; + if (flags & SPROP_IS_METHOD) { + /* Here, getter is the method, a function object reference. */ + JS_ASSERT(getter); + JS_ASSERT(!setter || setter == js_watch_set); + JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); + } else { + if (getter == JS_PropertyStub) + getter = NULL; + } /* * Search for id in order to claim its entry, allocating a property tree @@ -1350,10 +1342,6 @@ JSScope::add(JSContext *cx, jsid id, (void) createTable(cx, false); } - jsuint index; - if (js_IdIsIndex(sprop->id, &index)) - setIndexedProperties(); - METER(adds); return sprop; @@ -1432,8 +1420,7 @@ JSScope::change(JSContext *cx, JSScopeProperty *sprop, * Optimize the case where the last property added to this scope is * changed to have a different attrs, getter, or setter. In the last * property case, we need not fork the property tree. But since we do - * not call JSScope::addProperty, we may need to allocate a new slot - * directly. + * not call JSScope::add, we may need to allocate a new slot directly. */ if ((sprop->attrs & JSPROP_SHARED) && !(attrs & JSPROP_SHARED)) { JS_ASSERT(child.slot == SPROP_INVALID_SLOT); @@ -1453,8 +1440,8 @@ JSScope::change(JSContext *cx, JSScopeProperty *sprop, } } else { /* - * Let JSScope::addProperty handle this |overwriting| case, including - * the conservation of sprop->slot (if it's valid). We must not call + * Let JSScope::add handle this |overwriting| case, including the + * conservation of sprop->slot (if it's valid). We must not call * JSScope::remove here, because it will free a valid sprop->slot and * JSScope::add won't re-allocate it. */ @@ -1589,10 +1576,47 @@ JSScope::deletingShapeChange(JSContext *cx, JSScopeProperty *sprop) generateOwnShape(cx); } -void +bool +JSScope::methodShapeChange(JSContext *cx, JSScopeProperty *sprop, jsval toval) +{ + if (sprop->isMethod()) { +#ifdef DEBUG + jsval prev = LOCKED_OBJ_GET_SLOT(object, sprop->slot); + JS_ASSERT(sprop->methodValue() == prev); + JS_ASSERT(hasMethodBarrier()); + JS_ASSERT(object->getClass() == &js_ObjectClass); + JS_ASSERT(!sprop->setter || sprop->setter == js_watch_set); +#endif + + /* + * Pass null to make a stub getter, but pass along sprop->setter to + * preserve watchpoints. Clear SPROP_IS_METHOD from flags as we are + * despecializing from a method memoized in the property tree to a + * plain old function-valued property. + */ + sprop = add(cx, sprop->id, NULL, sprop->setter, sprop->slot, + sprop->attrs, sprop->flags & ~SPROP_IS_METHOD, + sprop->shortid); + if (!sprop) + return false; + } + + generateOwnShape(cx); + return true; +} + +bool JSScope::methodShapeChange(JSContext *cx, uint32 slot, jsval toval) { - generateOwnShape(cx); + if (!hasMethodBarrier()) { + generateOwnShape(cx); + } else { + for (JSScopeProperty *sprop = lastProp; sprop; sprop = sprop->parent) { + if (sprop->slot == slot && (!hadMiddleDelete() || has(sprop))) + return methodShapeChange(cx, sprop, toval); + } + } + return true; } void @@ -1656,6 +1680,23 @@ PrintPropertyGetterOrSetter(JSTracer *trc, char *buf, size_t bufsize) JS_snprintf(buf, bufsize, " %s", name); } } + +static void +PrintPropertyMethod(JSTracer *trc, char *buf, size_t bufsize) +{ + JSScopeProperty *sprop; + jsid id; + size_t n; + + JS_ASSERT(trc->debugPrinter == PrintPropertyMethod); + sprop = (JSScopeProperty *)trc->debugPrintArg; + id = sprop->id; + + JS_ASSERT(JSID_IS_ATOM(id)); + n = js_PutEscapedString(buf, bufsize - 1, ATOM_TO_STRING(JSID_TO_ATOM(id)), 0); + if (n < bufsize - 1) + JS_snprintf(buf + n, bufsize - n, " method"); +} #endif void @@ -1669,14 +1710,19 @@ JSScopeProperty::trace(JSTracer *trc) if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) { if (attrs & JSPROP_GETTER) { JS_SET_TRACING_DETAILS(trc, PrintPropertyGetterOrSetter, this, 0); - JS_CallTracer(trc, js_CastAsObject(getter), JSTRACE_OBJECT); + JS_CallTracer(trc, getterObject(), JSTRACE_OBJECT); } if (attrs & JSPROP_SETTER) { JS_SET_TRACING_DETAILS(trc, PrintPropertyGetterOrSetter, this, 1); - JS_CallTracer(trc, js_CastAsObject(setter), JSTRACE_OBJECT); + JS_CallTracer(trc, setterObject(), JSTRACE_OBJECT); } } #endif /* JS_HAS_GETTER_SETTER */ + + if (isMethod()) { + JS_SET_TRACING_DETAILS(trc, PrintPropertyMethod, this, 0); + JS_CallTracer(trc, methodObject(), JSTRACE_OBJECT); + } } #ifdef JS_DUMP_PROPTREE_STATS diff --git a/js/src/jsscope.h b/js/src/jsscope.h index 6fc56a3ed4f5..1f44a13d64f2 100644 --- a/js/src/jsscope.h +++ b/js/src/jsscope.h @@ -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=78: + * vim: set ts=8 sw=4 et tw=99: * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 @@ -45,6 +45,7 @@ */ #include "jstypes.h" #include "jslock.h" +#include "jsfun.h" #include "jsobj.h" #include "jsprvtd.h" #include "jspubtd.h" @@ -279,15 +280,30 @@ struct JSScope { void extend(JSContext *cx, JSScopeProperty *sprop); + /* + * Read barrier to clone a joined function object stored as a method. + * Defined inline further below. + */ + inline bool methodReadBarrier(JSContext *cx, JSScopeProperty *sprop, jsval *vp); + + /* + * Write barrier to check for a method value change. Defined inline below + * after methodReadBarrier. Two flavors to handle JSOP_*GVAR, which deals + * in slots not sprops, while not deoptimizing to map slot to sprop unless + * flags show this is necessary. The methodShapeChange overload (directly + * below) parallels this. + */ + inline bool methodWriteBarrier(JSContext *cx, JSScopeProperty *sprop, jsval v); + inline bool methodWriteBarrier(JSContext *cx, uint32 slot, jsval v); + void trace(JSTracer *trc); void brandingShapeChange(JSContext *cx, uint32 slot, jsval v); void deletingShapeChange(JSContext *cx, JSScopeProperty *sprop); - void methodShapeChange(JSContext *cx, uint32 slot, jsval toval); + bool methodShapeChange(JSContext *cx, JSScopeProperty *sprop, jsval toval); + bool methodShapeChange(JSContext *cx, uint32 slot, jsval toval); void protoShapeChange(JSContext *cx); - void replacingShapeChange(JSContext *cx, - JSScopeProperty *sprop, - JSScopeProperty *newsprop); + void replacingShapeChange(JSContext *cx, JSScopeProperty *sprop, JSScopeProperty *newsprop); void sealingShapeChange(JSContext *cx); void shadowingShapeChange(JSContext *cx, JSScopeProperty *sprop); @@ -300,12 +316,13 @@ struct JSScope { BRANDED = 0x0004, INDEXED_PROPERTIES = 0x0008, OWN_SHAPE = 0x0010, + METHOD_BARRIER = 0x0020, /* * This flag toggles with each shape-regenerating GC cycle. * See JSRuntime::gcRegenShapesScopeFlag. */ - SHAPE_REGEN = 0x0020 + SHAPE_REGEN = 0x0040 }; bool hadMiddleDelete() { return flags & MIDDLE_DELETE; } @@ -336,6 +353,42 @@ struct JSScope { bool hasRegenFlag(uint8 regenFlag) { return (flags & SHAPE_REGEN) == regenFlag; } + /* + * A scope has a method barrier when some compiler-created "null closure" + * function objects (functions that do not use lexical bindings above their + * scope, only free variable names) that have a correct JSSLOT_PARENT value + * thanks to the COMPILE_N_GO optimization are stored as newly added direct + * property values. + * + * The de-facto standard JS language requires each evaluation of such a + * closure to result in a unique (according to === and observable effects) + * function object. ES3 tried to allow implementations to "join" such + * objects to a single compiler-created object, but this makes an overt + * mutation hazard, also an "identity hazard" against interoperation among + * implementations that join and do not join. + * + * To stay compatible with the de-facto standard, we store the compiler- + * created function object as the method value, set the METHOD_BARRIER + * flag, and brand the scope with a predictable shape that reflects its + * method values, which are cached and traced without being loaded, based + * on shape-qualified cache hit logic and equivalent trace guards. See + * BRANDED above. + * + * This means scope->hasMethodBarrier() => scope->branded(), but of course + * not the other way around. + * + * Then when reading from a scope for which scope->hasMethodBarrier() is + * true, we count on the scope's qualified/guarded shape being unique and + * add a read barrier that clones the compiler-created function object on + * demand, reshaping the scope. + * + * This read barrier is bypassed when evaluating the callee sub-expression + * of a call expression (see the JOF_CALLOP opcodes in jsopcode.tbl), since + * such ops do not present an identity or mutation hazard. + */ + bool hasMethodBarrier() { return flags & METHOD_BARRIER; } + void setMethodBarrier() { flags |= METHOD_BARRIER | BRANDED; } + bool owned() { return object != NULL; } }; @@ -377,7 +430,8 @@ js_CastAsPropertyOp(JSObject *object) struct JSScopeProperty { jsid id; /* int-tagged jsval/untagged JSAtom* */ JSPropertyOp getter; /* getter and setter hooks or objects */ - JSPropertyOp setter; + JSPropertyOp setter; /* getter is JSObject* and setter is 0 + if sprop->isMethod() */ uint32 slot; /* abstract index in object slots */ uint8 attrs; /* attributes, see jsapi.h JSPROP_* */ uint8 flags; /* flags, see below for defines */ @@ -387,6 +441,52 @@ struct JSScopeProperty { to many-kids data structure */ uint32 shape; /* property cache shape identifier */ +/* Bits stored in sprop->flags. */ +#define SPROP_MARK 0x01 +#define SPROP_IS_ALIAS 0x02 +#define SPROP_HAS_SHORTID 0x04 +#define SPROP_FLAG_SHAPE_REGEN 0x08 +#define SPROP_IS_METHOD 0x10 + + bool isMethod() const { + return flags & SPROP_IS_METHOD; + } + JSObject *methodObject() const { + JS_ASSERT(isMethod()); + return js_CastAsObject(getter); + } + jsval methodValue() const { + JS_ASSERT(isMethod()); + return js_CastAsObjectJSVal(getter); + } + + bool hasGetterObject() const { + return attrs & JSPROP_GETTER; + } + JSObject *getterObject() const { + JS_ASSERT(hasGetterObject()); + return js_CastAsObject(getter); + } + jsval getterValue() const { + JS_ASSERT(hasGetterObject()); + return js_CastAsObjectJSVal(getter); + } + + bool hasSetterObject() const { + return attrs & JSPROP_SETTER; + } + JSObject *setterObject() const { + JS_ASSERT(hasSetterObject()); + return js_CastAsObject(setter); + } + jsval setterValue() const { + JS_ASSERT(hasSetterObject()); + return js_CastAsObjectJSVal(setter); + } + + bool get(JSContext* cx, JSObject* obj, JSObject *pobj, jsval* vp); + bool set(JSContext* cx, JSObject* obj, jsval* vp); + void trace(JSTracer *trc); }; @@ -410,24 +510,18 @@ struct JSScopeProperty { (*(spp) = (JSScopeProperty *) ((jsuword)(sprop) \ | SPROP_HAD_COLLISION(*(spp)))) -JS_ALWAYS_INLINE JSScopeProperty * +inline JSScopeProperty * JSScope::lookup(jsid id) { return SPROP_FETCH(search(id, false)); } -JS_ALWAYS_INLINE bool +inline bool JSScope::has(JSScopeProperty *sprop) { return lookup(sprop->id) == sprop; } -/* Bits stored in sprop->flags. */ -#define SPROP_MARK 0x01 -#define SPROP_IS_ALIAS 0x02 -#define SPROP_HAS_SHORTID 0x04 -#define SPROP_FLAG_SHAPE_REGEN 0x08 - /* * If SPROP_HAS_SHORTID is set in sprop->flags, we use sprop->shortid rather * than id when calling sprop's getter or setter. @@ -444,6 +538,9 @@ JSScope::has(JSScopeProperty *sprop) #define SPROP_HAS_STUB_GETTER(sprop) (!(sprop)->getter) #define SPROP_HAS_STUB_SETTER(sprop) (!(sprop)->setter) +#define SPROP_HAS_STUB_GETTER_OR_IS_METHOD(sprop) \ + (SPROP_HAS_STUB_GETTER(sprop) || (sprop)->isMethod()) + #ifndef JS_THREADSAFE # define js_GenerateShape(cx, gcLocked) js_GenerateShape (cx) #endif @@ -452,6 +549,28 @@ extern uint32 js_GenerateShape(JSContext *cx, bool gcLocked); #ifdef JS_DUMP_PROPTREE_STATS +struct JSScopeStats { + jsrefcount searches; + jsrefcount hits; + jsrefcount misses; + jsrefcount hashes; + jsrefcount steps; + jsrefcount stepHits; + jsrefcount stepMisses; + jsrefcount adds; + jsrefcount redundantAdds; + jsrefcount addFailures; + jsrefcount changeFailures; + jsrefcount compresses; + jsrefcount grows; + jsrefcount removes; + jsrefcount removeFrees; + jsrefcount uselessRemoves; + jsrefcount shrinks; +}; + +extern JS_FRIEND_DATA(JSScopeStats) js_scope_stats; + # define METER(x) JS_ATOMIC_INCREMENT(&js_scope_stats.x) #else # define METER(x) /* nothing */ @@ -512,9 +631,69 @@ JSScope::extend(JSContext *cx, JSScopeProperty *sprop) js_LeaveTraceIfGlobalObject(cx, object); shape = (!lastProp || shape == lastProp->shape) ? sprop->shape - : js_GenerateShape(cx, JS_FALSE); + : js_GenerateShape(cx, false); ++entryCount; lastProp = sprop; + + jsuint index; + if (js_IdIsIndex(sprop->id, &index)) + setIndexedProperties(); + + if (sprop->isMethod()) + setMethodBarrier(); +} + +/* + * Property read barrier for deferred cloning of compiler-created function + * objects optimized as typically non-escaping, ad-hoc methods in obj. + * + * Called only from JSScopeProperty::get when sprop->isMethod(), or JIT- + * equivalent code. sprop->isMethod() implies that scope->hasMethodBarrier() + * for the scope containing that sprop. + */ +inline bool +JSScope::methodReadBarrier(JSContext *cx, JSScopeProperty *sprop, jsval *vp) +{ + JS_ASSERT(hasMethodBarrier()); + JS_ASSERT(has(sprop)); + JS_ASSERT(sprop->isMethod()); + JS_ASSERT(sprop->methodValue() == *vp); + + JS_ASSERT(object->getClass() == &js_ObjectClass); + + JSObject *funobj = JSVAL_TO_OBJECT(*vp); + JSFunction *fun = GET_FUNCTION_PRIVATE(cx, funobj); + JS_ASSERT(FUN_OBJECT(fun) == funobj && FUN_NULL_CLOSURE(fun)); + + funobj = js_CloneFunctionObject(cx, fun, OBJ_GET_PARENT(cx, funobj)); + if (!funobj) + return false; + *vp = OBJECT_TO_JSVAL(funobj); + return js_SetPropertyHelper(cx, object, sprop->id, 0, vp); +} + +inline bool +JSScope::methodWriteBarrier(JSContext *cx, JSScopeProperty *sprop, jsval v) +{ + if (branded()) { + jsval prev = LOCKED_OBJ_GET_SLOT(object, sprop->slot); + + if (prev != v && VALUE_IS_FUNCTION(cx, prev)) + return methodShapeChange(cx, sprop, v); + } + return true; +} + +inline bool +JSScope::methodWriteBarrier(JSContext *cx, uint32 slot, jsval v) +{ + if (branded()) { + jsval prev = LOCKED_OBJ_GET_SLOT(object, slot); + + if (prev != v && VALUE_IS_FUNCTION(cx, prev)) + return methodShapeChange(cx, slot, v); + } + return true; } inline void @@ -564,16 +743,23 @@ JSScope::trace(JSTracer *trc) } } - -static JS_INLINE bool -js_GetSprop(JSContext* cx, JSScopeProperty* sprop, JSObject* obj, jsval* vp) +inline bool +JSScopeProperty::get(JSContext* cx, JSObject* obj, JSObject *pobj, jsval* vp) { - JS_ASSERT(!SPROP_HAS_STUB_GETTER(sprop)); + JS_ASSERT(!SPROP_HAS_STUB_GETTER(this)); - if (sprop->attrs & JSPROP_GETTER) { - jsval fval = js_CastAsObjectJSVal(sprop->getter); - return js_InternalGetOrSet(cx, obj, sprop->id, fval, JSACC_READ, - 0, 0, vp); + if (attrs & JSPROP_GETTER) { + JS_ASSERT(!isMethod()); + jsval fval = getterValue(); + return js_InternalGetOrSet(cx, obj, id, fval, JSACC_READ, 0, 0, vp); + } + + if (isMethod()) { + *vp = methodValue(); + + JSScope *scope = OBJ_SCOPE(pobj); + JS_ASSERT(scope->object == pobj); + return scope->methodReadBarrier(cx, this, vp); } /* @@ -584,30 +770,28 @@ js_GetSprop(JSContext* cx, JSScopeProperty* sprop, JSObject* obj, jsval* vp) */ if (STOBJ_GET_CLASS(obj) == &js_WithClass) obj = obj->map->ops->thisObject(cx, obj); - return sprop->getter(cx, obj, SPROP_USERID(sprop), vp); + return getter(cx, obj, SPROP_USERID(this), vp); } -static JS_INLINE bool -js_SetSprop(JSContext* cx, JSScopeProperty* sprop, JSObject* obj, jsval* vp) +inline bool +JSScopeProperty::set(JSContext* cx, JSObject* obj, jsval* vp) { - JS_ASSERT(!(SPROP_HAS_STUB_SETTER(sprop) && - !(sprop->attrs & JSPROP_GETTER))); + JS_ASSERT_IF(SPROP_HAS_STUB_SETTER(this), attrs & JSPROP_GETTER); - if (sprop->attrs & JSPROP_SETTER) { - jsval fval = js_CastAsObjectJSVal(sprop->setter); - return js_InternalGetOrSet(cx, obj, (sprop)->id, fval, JSACC_WRITE, - 1, vp, vp); + if (attrs & JSPROP_SETTER) { + jsval fval = setterValue(); + return js_InternalGetOrSet(cx, obj, id, fval, JSACC_WRITE, 1, vp, vp); } - if (sprop->attrs & JSPROP_GETTER) { + if (attrs & JSPROP_GETTER) { js_ReportGetterOnlyAssignment(cx); - return JS_FALSE; + return false; } - /* See the comment in js_GetSprop as to why we can check for 'with'. */ + /* See the comment in JSScopeProperty::get as to why we can check for With. */ if (STOBJ_GET_CLASS(obj) == &js_WithClass) obj = obj->map->ops->thisObject(cx, obj); - return sprop->setter(cx, obj, SPROP_USERID(sprop), vp); + return setter(cx, obj, SPROP_USERID(this), vp); } /* Macro for common expression to test for shared permanent attributes. */ diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index 1c2725678bfb..c552ab9c0d51 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -2577,7 +2577,7 @@ GetFromClosure(JSContext* cx, JSObject* call, const ClosureVarInfo* cv, double* #ifdef DEBUG JSBool rv = #endif - js_GetPropertyHelper(cx, call, cv->id, JS_FALSE, &v); + js_GetPropertyHelper(cx, call, cv->id, JSGET_METHOD_BARRIER, &v); JS_ASSERT(rv); } JSTraceType type = getCoercedType(v); @@ -2932,8 +2932,10 @@ TraceRecorder::isValidSlot(JSScope* scope, JSScopeProperty* sprop) } /* This check applies even when setflags == 0. */ - if (setflags != JOF_SET && !SPROP_HAS_STUB_GETTER(sprop)) + if (setflags != JOF_SET && !SPROP_HAS_STUB_GETTER(sprop)) { + JS_ASSERT(!sprop->isMethod()); ABORT_TRACE_RV("non-stub getter", false); + } if (!SPROP_HAS_VALID_SLOT(sprop, scope)) ABORT_TRACE_RV("slotless obj property", false); @@ -3289,7 +3291,7 @@ TraceRecorder::snapshot(ExitType exitType) JSTN_ERRTYPE(pendingTraceableNative) == FAIL_STATUS); if (resumeAfter) { JS_ASSERT(*pc == JSOP_CALL || *pc == JSOP_APPLY || *pc == JSOP_NEW || - *pc == JSOP_SETPROP || *pc == JSOP_SETNAME); + *pc == JSOP_SETPROP || *pc == JSOP_SETNAME || *pc == JSOP_SETMETHOD); pc += cs.length; regs->pc = pc; MUST_FLOW_THROUGH("restore_pc"); @@ -4411,7 +4413,7 @@ TraceRecorder::hasMethod(JSObject* obj, jsid id) JSScope* scope = OBJ_SCOPE(pobj); JSScopeProperty* sprop = (JSScopeProperty*) prop; - if (SPROP_HAS_STUB_GETTER(sprop) && + if (SPROP_HAS_STUB_GETTER_OR_IS_METHOD(sprop) && SPROP_HAS_VALID_SLOT(sprop, scope)) { jsval v = LOCKED_OBJ_GET_SLOT(pobj, sprop->slot); if (VALUE_IS_FUNCTION(cx, v)) { @@ -5727,7 +5729,7 @@ LeaveTree(InterpState& state, VMSideExit* lr) op == JSOP_GETPROP || op == JSOP_GETTHISPROP || op == JSOP_GETARGPROP || op == JSOP_GETLOCALPROP || op == JSOP_LENGTH || op == JSOP_GETELEM || op == JSOP_CALLELEM || - op == JSOP_SETPROP || op == JSOP_SETNAME || + op == JSOP_SETPROP || op == JSOP_SETNAME || op == JSOP_SETMETHOD || op == JSOP_SETELEM || op == JSOP_INITELEM || op == JSOP_INSTANCEOF); const JSCodeSpec& cs = js_CodeSpec[op]; @@ -6987,7 +6989,7 @@ TraceRecorder::scopeChainProp(JSObject* obj, jsval*& vp, LIns*& ins, NameResult& ABORT_TRACE("deep abort from property lookup"); if (obj == obj2 && OBJ_GET_CLASS(cx, obj) == &js_CallClass) - return callProp(obj, obj2, prop, ATOM_TO_JSID(atom), vp, ins, nr); + return callProp(obj, prop, ATOM_TO_JSID(atom), vp, ins, nr); obj2->dropProperty(cx, prop); ABORT_TRACE("fp->scopeChain is not global or active call object"); @@ -6997,12 +6999,13 @@ TraceRecorder::scopeChainProp(JSObject* obj, jsval*& vp, LIns*& ins, NameResult& * Generate LIR to access a property of a Call object. */ JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::callProp(JSObject* obj, JSObject* obj2, JSProperty* prop, jsid id, jsval*& vp, +TraceRecorder::callProp(JSObject* obj, JSProperty* prop, jsid id, jsval*& vp, LIns*& ins, NameResult& nr) { JSScopeProperty *sprop = (JSScopeProperty*) prop; - uint32 setflags = (js_CodeSpec[*cx->fp->regs->pc].format & (JOF_SET | JOF_INCDEC | JOF_FOR)); + JSOp op = JSOp(*cx->fp->regs->pc); + uint32 setflags = (js_CodeSpec[op].format & (JOF_SET | JOF_INCDEC | JOF_FOR)); if (setflags && (sprop->attrs & JSPROP_READONLY)) ABORT_TRACE("writing to a read-only property"); @@ -7025,7 +7028,7 @@ TraceRecorder::callProp(JSObject* obj, JSObject* obj2, JSProperty* prop, jsid id } else { ABORT_TRACE("dynamic property of Call object"); } - obj2->dropProperty(cx, prop); + obj->dropProperty(cx, prop); if (frameIfInRange(obj)) { // At this point we are guaranteed to be looking at an active call oject @@ -7035,12 +7038,19 @@ TraceRecorder::callProp(JSObject* obj, JSObject* obj2, JSProperty* prop, jsid id return JSRS_CONTINUE; } } else { + // Call objects do not yet have sprop->isMethod() properties, but they + // should. See bug 514046, for which this code is future-proof. Remove + // this comment when that bug is fixed (so, FIXME: 514046). #ifdef DEBUG JSBool rv = #endif - js_GetPropertyHelper(cx, obj, sprop->id, JS_FALSE, &nr.v); + js_GetPropertyHelper(cx, obj, sprop->id, + (op == JSOP_CALLNAME) + ? JSGET_NO_METHOD_BARRIER + : JSGET_METHOD_BARRIER, + &nr.v); JS_ASSERT(rv); - obj2->dropProperty(cx, prop); + obj->dropProperty(cx, prop); } LIns* obj_ins; @@ -8081,7 +8091,8 @@ JS_REQUIRES_STACK JSRecordingStatus TraceRecorder::test_property_cache(JSObject* obj, LIns* obj_ins, JSObject*& obj2, jsuword& pcval) { jsbytecode* pc = cx->fp->regs->pc; - JS_ASSERT(*pc != JSOP_INITPROP && *pc != JSOP_SETNAME && *pc != JSOP_SETPROP); + JS_ASSERT(*pc != JSOP_INITPROP && *pc != JSOP_INITMETHOD && + *pc != JSOP_SETNAME && *pc != JSOP_SETPROP && *pc != JSOP_SETMETHOD); // Mimic the interpreter's special case for dense arrays by skipping up one // hop along the proto chain when accessing a named (not indexed) property, @@ -8297,20 +8308,6 @@ TraceRecorder::stobj_get_slot(LIns* obj_ins, unsigned slot, LIns*& dslots_ins) return stobj_get_dslot(obj_ins, slot - JS_INITIAL_NSLOTS, dslots_ins); } -JSRecordingStatus -TraceRecorder::native_get(LIns* obj_ins, LIns* pobj_ins, JSScopeProperty* sprop, - LIns*& dslots_ins, LIns*& v_ins) -{ - if (!SPROP_HAS_STUB_GETTER(sprop)) - return JSRS_STOP; - - if (sprop->slot != SPROP_INVALID_SLOT) - v_ins = stobj_get_slot(pobj_ins, sprop->slot, dslots_ins); - else - v_ins = INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID)); - return JSRS_CONTINUE; -} - JS_REQUIRES_STACK LIns* TraceRecorder::box_jsval(jsval v, LIns* v_ins) { @@ -9225,7 +9222,7 @@ TraceRecorder::emitNativePropertyOp(JSScope* scope, JSScopeProperty* sprop, LIns bool setflag, LIns* boxed_ins) { JS_ASSERT(!(sprop->attrs & (setflag ? JSPROP_SETTER : JSPROP_GETTER))); - JS_ASSERT(setflag ? !SPROP_HAS_STUB_SETTER(sprop) : !SPROP_HAS_STUB_GETTER(sprop)); + JS_ASSERT(setflag ? !SPROP_HAS_STUB_SETTER(sprop) : !SPROP_HAS_STUB_GETTER_OR_IS_METHOD(sprop)); enterDeepBailCall(); @@ -9454,8 +9451,8 @@ TraceRecorder::callNative(uintN argc, JSOp mode) switch (argc) { case 1: - if (isNumber(vp[2]) && - (native == js_math_ceil || native == js_math_floor || native == js_math_round)) { + if (isNumber(vp[2]) && + (native == js_math_ceil || native == js_math_floor || native == js_math_round)) { LIns* a = get(&vp[2]); if (isPromote(a)) { set(&vp[0], a); @@ -9464,9 +9461,10 @@ TraceRecorder::callNative(uintN argc, JSOp mode) } } break; + case 2: - if (isNumber(vp[2]) && isNumber(vp[3]) && - (native == js_math_min || native == js_math_max)) { + if (isNumber(vp[2]) && isNumber(vp[3]) && + (native == js_math_min || native == js_math_max)) { LIns* a = get(&vp[2]); LIns* b = get(&vp[3]); if (isPromote(a) && isPromote(b)) { @@ -9939,6 +9937,16 @@ TraceRecorder::nativeSet(JSObject* obj, LIns* obj_ins, JSScopeProperty* sprop, return JSRS_CONTINUE; } +static JSBool FASTCALL +MethodWriteBarrier(JSContext* cx, JSObject* obj, JSScopeProperty* sprop, JSObject* funobj) +{ + JSAutoTempValueRooter tvr(cx, funobj); + + return OBJ_SCOPE(obj)->methodWriteBarrier(cx, sprop, tvr.value()); +} +JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, MethodWriteBarrier, CONTEXT, OBJECT, SCOPEPROP, OBJECT, + 0, 0) + JS_REQUIRES_STACK JSRecordingStatus TraceRecorder::setProp(jsval &l, JSPropCacheEntry* entry, JSScopeProperty* sprop, jsval &v, LIns*& v_ins) @@ -9965,21 +9973,26 @@ TraceRecorder::setProp(jsval &l, JSPropCacheEntry* entry, JSScopeProperty* sprop JS_ASSERT_IF(entry->vcap == PCVCAP_MAKE(entry->kshape, 0, 0), scope->has(sprop)); // Fast path for CallClass. This is about 20% faster than the general case. - if (OBJ_GET_CLASS(cx, obj) == &js_CallClass) { - v_ins = get(&v); + v_ins = get(&v); + if (OBJ_GET_CLASS(cx, obj) == &js_CallClass) return setCallProp(obj, obj_ins, sprop, v_ins, v); - } /* - * Setting a function-valued property might need to rebrand the object; we - * don't trace that case. There's no need to guard on that, though, because - * separating functions into the trace-time type TT_FUNCTION will save the - * day! + * Setting a function-valued property might need to rebrand the object, so + * we emit a call to the method write barrier. There's no need to guard on + * this, because functions have distinct trace-type from other values and + * branded-ness is implied by the shape, which we've already guarded on. */ - if (scope->branded() && VALUE_IS_FUNCTION(cx, v)) - ABORT_TRACE("can't trace function-valued property set in branded scope"); + if (scope->branded() && VALUE_IS_FUNCTION(cx, v) && entry->directHit()) { + if (obj == globalObj) + ABORT_TRACE("can't trace function-valued property set in branded global scope"); - // Find obj2. If entry->adding(), the TAG bits are all 0. + LIns* args[] = { v_ins, INS_CONSTSPROP(sprop), obj_ins, cx_ins }; + LIns* ok_ins = lir->insCall(&MethodWriteBarrier_ci, args); + guard(false, lir->ins_eq0(ok_ins), OOM_EXIT); + } + + // Find obj2. If entry->adding(), the TAG bits are all 0. JSObject* obj2 = obj; for (jsuword i = PCVCAP_TAG(entry->vcap) >> PCVCAP_PROTOBITS; i; i--) obj2 = OBJ_GET_PARENT(cx, obj2); @@ -10008,7 +10021,6 @@ TraceRecorder::setProp(jsval &l, JSPropCacheEntry* entry, JSScopeProperty* sprop guard(false, lir->ins_eq0(ok_ins), OOM_EXIT); } - v_ins = get(&v); return nativeSet(obj, obj_ins, sprop, v, v_ins); } @@ -10062,8 +10074,16 @@ TraceRecorder::record_SetPropHit(JSPropCacheEntry* entry, JSScopeProperty* sprop CHECK_STATUS(setProp(l, entry, sprop, r, v_ins)); jsbytecode* pc = cx->fp->regs->pc; - if (*pc != JSOP_INITPROP && pc[JSOP_SETPROP_LENGTH] != JSOP_POP) - set(&l, v_ins); + switch (*pc) { + case JSOP_SETPROP: + case JSOP_SETNAME: + case JSOP_SETMETHOD: + if (pc[JSOP_SETPROP_LENGTH] != JSOP_POP) + set(&l, v_ins); + break; + + default:; + } return JSRS_CONTINUE; } @@ -10133,7 +10153,7 @@ GetPropertyByName(JSContext* cx, JSObject* obj, JSString** namep, jsval* vp) jsid id; if (!RootedStringToId(cx, namep, &id) || !obj->getProperty(cx, id, vp)) { js_SetBuiltinError(cx); - return JS_FALSE; + return false; } return cx->interpState->builtinStatus == 0; } @@ -10271,8 +10291,9 @@ GetPropertyWithNativeGetter(JSContext* cx, JSObject* obj, JSScopeProperty* sprop obj->dropProperty(cx, prop); #endif - // js_GetSprop contains a special case for With objects. We can elide it - // here because With objects are, we claim, never on the operand stack. + // JSScopeProperty::get contains a special case for With objects. We can + // elide it here because With objects are, we claim, never on the operand + // stack while recording. JS_ASSERT(STOBJ_GET_CLASS(obj) != &js_WithClass); *vp = JSVAL_VOID; @@ -10290,7 +10311,7 @@ TraceRecorder::getPropertyWithNativeGetter(LIns* obj_ins, JSScopeProperty* sprop { JS_ASSERT(!(sprop->attrs & JSPROP_GETTER)); JS_ASSERT(sprop->slot == SPROP_INVALID_SLOT); - JS_ASSERT(!SPROP_HAS_STUB_GETTER(sprop)); + JS_ASSERT(!SPROP_HAS_STUB_GETTER_OR_IS_METHOD(sprop)); // Call GetPropertyWithNativeGetter. See note in getPropertyByName about vp. // FIXME - We should call the getter directly. Using a builtin function for @@ -11156,6 +11177,19 @@ TraceRecorder::name(jsval*& vp, LIns*& ins, NameResult& nr) return JSRS_CONTINUE; } +static JSObject* FASTCALL +MethodReadBarrier(JSContext* cx, JSObject* obj, JSScopeProperty* sprop, JSObject* funobj) +{ + JSAutoTempValueRooter tvr(cx, funobj); + + if (!OBJ_SCOPE(obj)->methodReadBarrier(cx, sprop, tvr.addr())) + return NULL; + JS_ASSERT(VALUE_IS_FUNCTION(cx, tvr.value())); + return JSVAL_TO_OBJECT(tvr.value()); +} +JS_DEFINE_CALLINFO_4(static, OBJECT_FAIL, MethodReadBarrier, CONTEXT, OBJECT, SCOPEPROP, OBJECT, + 0, 0) + /* * Get a property. The current opcode has JOF_ATOM. * @@ -11227,15 +11261,19 @@ TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32 *slotp, LIns** v_insp, uint32 setflags = (cs.format & (JOF_INCDEC | JOF_FOR)); JS_ASSERT(!(cs.format & JOF_SET)); + JSScopeProperty* sprop; uint32 slot; + bool isMethod; + if (PCVAL_IS_SPROP(pcval)) { - JSScopeProperty* sprop = PCVAL_TO_SPROP(pcval); + sprop = PCVAL_TO_SPROP(pcval); + JS_ASSERT(OBJ_SCOPE(obj2)->has(sprop)); if (setflags && !SPROP_HAS_STUB_SETTER(sprop)) ABORT_TRACE("non-stub setter"); if (setflags && (sprop->attrs & JSPROP_READONLY)) ABORT_TRACE("writing to a readonly property"); - if (!SPROP_HAS_STUB_GETTER(sprop)) { + if (!SPROP_HAS_STUB_GETTER_OR_IS_METHOD(sprop)) { if (slotp) ABORT_TRACE("can't trace non-stub getter for this opcode"); if (sprop->attrs & JSPROP_GETTER) @@ -11247,13 +11285,17 @@ TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32 *slotp, LIns** v_insp, if (!SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(obj2))) ABORT_TRACE("no valid slot"); slot = sprop->slot; + isMethod = sprop->isMethod(); + JS_ASSERT_IF(isMethod, OBJ_SCOPE(obj2)->hasMethodBarrier()); } else { if (!PCVAL_IS_SLOT(pcval)) ABORT_TRACE("PCE is not a slot"); slot = PCVAL_TO_SLOT(pcval); + sprop = NULL; + isMethod = false; } - /* We have a slot. */ + /* We have a slot. Check whether it is direct or in a prototype. */ if (obj2 != obj) { if (setflags) ABORT_TRACE("JOF_INCDEC|JOF_FOR opcode hit prototype chain"); @@ -11263,10 +11305,10 @@ TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32 *slotp, LIns** v_insp, * proto slot loads, updating obj as we go, leaving obj set to obj2 with * obj_ins the last proto-load. */ - while (obj != obj2) { + do { obj_ins = stobj_get_proto(obj_ins); obj = STOBJ_GET_PROTO(obj); - } + } while (obj != obj2); } LIns* dslots_ins = NULL; @@ -11274,6 +11316,21 @@ TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32 *slotp, LIns** v_insp, stobj_get_slot(obj_ins, slot, dslots_ins), snapshot(BRANCH_EXIT)); + /* + * Joined function object stored as a method must be cloned when extracted + * as a property value other than a callee. Note that shapes cover method + * value as well as other property attributes and order, so this condition + * is trace-invariant. + * + * We do not impose the method read barrier if in an imacro, assuming any + * property gets it does (e.g., for 'toString' from JSOP_NEW) will not be + * leaked to the calling script. + */ + if (isMethod && !cx->fp->imacpc) { + LIns* args[] = { v_ins, INS_CONSTSPROP(sprop), obj_ins, cx_ins }; + v_ins = lir->insCall(&MethodReadBarrier_ci, args); + } + if (slotp) { *slotp = slot; *v_insp = v_ins; @@ -11590,7 +11647,7 @@ JS_REQUIRES_STACK JSRecordingStatus TraceRecorder::record_JSOP_NEWINIT() { JSProtoKey key = JSProtoKey(GET_INT8(cx->fp->regs->pc)); - LIns *proto_ins; + LIns* proto_ins; CHECK_STATUS(getClassPrototype(key, proto_ins)); LIns* args[] = { proto_ins, cx_ins }; @@ -13272,6 +13329,18 @@ DBG_STUB(JSOP_DEFFUN_DBGFC) DBG_STUB(JSOP_DEFLOCALFUN_DBGFC) DBG_STUB(JSOP_LAMBDA_DBGFC) +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_SETMETHOD() +{ + return record_JSOP_SETPROP(); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_JSOP_INITMETHOD() +{ + return record_JSOP_INITPROP(); +} + #ifdef JS_JIT_SPEW /* * Print information about entry typemaps and unstable exits for all peers diff --git a/js/src/jstracer.h b/js/src/jstracer.h index deb715454472..82f905c05039 100644 --- a/js/src/jstracer.h +++ b/js/src/jstracer.h @@ -762,7 +762,7 @@ class TraceRecorder : public avmplus::GCObject { JS_REQUIRES_STACK JSStackFrame* frameIfInRange(JSObject* obj, unsigned* depthp = NULL) const; JS_REQUIRES_STACK JSRecordingStatus traverseScopeChain(JSObject *obj, nanojit::LIns *obj_ins, JSObject *obj2, nanojit::LIns *&obj2_ins); JS_REQUIRES_STACK JSRecordingStatus scopeChainProp(JSObject* obj, jsval*& vp, nanojit::LIns*& ins, NameResult& nr); - JS_REQUIRES_STACK JSRecordingStatus callProp(JSObject* obj, JSObject* obj2, JSProperty* sprop, jsid id, jsval*& vp, nanojit::LIns*& ins, NameResult& nr); + JS_REQUIRES_STACK JSRecordingStatus callProp(JSObject* obj, JSProperty* sprop, jsid id, jsval*& vp, nanojit::LIns*& ins, NameResult& nr); JS_REQUIRES_STACK nanojit::LIns* arg(unsigned n); JS_REQUIRES_STACK void arg(unsigned n, nanojit::LIns* i); @@ -852,10 +852,6 @@ class TraceRecorder : public avmplus::GCObject { return stobj_get_fslot(obj_ins, JSSLOT_PARENT); } - JSRecordingStatus native_get(nanojit::LIns* obj_ins, nanojit::LIns* pobj_ins, - JSScopeProperty* sprop, nanojit::LIns*& dslots_ins, - nanojit::LIns*& v_ins); - nanojit::LIns* getStringLength(nanojit::LIns* str_ins); JS_REQUIRES_STACK JSRecordingStatus name(jsval*& vp, nanojit::LIns*& ins, NameResult& nr); diff --git a/js/src/jstypes.h b/js/src/jstypes.h index 3a5a958ba8b6..17183e24c5f6 100644 --- a/js/src/jstypes.h +++ b/js/src/jstypes.h @@ -456,6 +456,14 @@ typedef JSUintPtr JSUword; # define JS_DATA_TO_FUNC_PTR(type, ptr) ((type) (void *) (ptr)) #endif +#ifdef __GNUC__ +# define JS_EXTENSION __extension__ +# define JS_EXTENSION_(s) __extension__ ({ s; }) +#else +# define JS_EXTENSION +# define JS_EXTENSION_(s) s +#endif + JS_END_EXTERN_C #endif /* jstypes_h___ */ diff --git a/js/src/jsxdrapi.h b/js/src/jsxdrapi.h index 850bfa36bd0a..8b2a67194a99 100644 --- a/js/src/jsxdrapi.h +++ b/js/src/jsxdrapi.h @@ -204,7 +204,7 @@ JS_XDRFindClassById(JSXDRState *xdr, uint32 id); * before deserialization of bytecode. If the saved version does not match * the current version, abort deserialization and invalidate the file. */ -#define JSXDR_BYTECODE_VERSION (0xb973c0de - 52) +#define JSXDR_BYTECODE_VERSION (0xb973c0de - 53) /* * Library-private functions. diff --git a/js/src/trace-test/tests/basic/testInitMethod.js b/js/src/trace-test/tests/basic/testInitMethod.js new file mode 100644 index 000000000000..abd3f3ec97f3 --- /dev/null +++ b/js/src/trace-test/tests/basic/testInitMethod.js @@ -0,0 +1,9 @@ +for (var i = 0; i < 9; i++) + x = {a: function() {}, b: function() {}}; + +checkStats({ + recorderStarted: 1, + recorderAborted: 0, + traceCompleted: 1, + sideExitIntoInterpreter: 1 +}); diff --git a/js/src/trace-test/tests/basic/testMethodInc.js b/js/src/trace-test/tests/basic/testMethodInc.js new file mode 100644 index 000000000000..d965adfe6b74 --- /dev/null +++ b/js/src/trace-test/tests/basic/testMethodInc.js @@ -0,0 +1,12 @@ +for (var i = 0; i < 9; i++) { + var x = {f: function() {}}; + x.f++; + assertEq(""+x.f, "NaN"); +} + +checkStats({ + recorderStarted: 1, + recorderAborted: 1, + traceCompleted: 0, + sideExitIntoInterpreter: 0 +}); diff --git a/js/src/trace-test/tests/basic/testSetMethod.js b/js/src/trace-test/tests/basic/testSetMethod.js new file mode 100644 index 000000000000..45e940497a75 --- /dev/null +++ b/js/src/trace-test/tests/basic/testSetMethod.js @@ -0,0 +1,13 @@ +function C() { + this.a = function() {}; + this.b = function() {}; +} +for (var i = 0; i < 9; i++) + new C; + +checkStats({ + recorderStarted: 1, + recorderAborted: 0, + traceCompleted: 1, + sideExitIntoInterpreter: 1 +});