Join lambdas assigned or initialized as methods to the compiler-created function object if we can, with a read barrier to clone on method value extractions other than call expressions (471214, r=jorendorff).

This commit is contained in:
Brendan Eich 2009-09-03 14:41:19 -07:00
parent 13c0c5eff8
commit cce4dce512
24 changed files with 913 additions and 384 deletions

View File

@ -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 \

View File

@ -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;

View File

@ -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);

View File

@ -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));
}
}

View File

@ -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)
/*

View File

@ -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,22 +186,37 @@ 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;
if (sprop->isMethod()) {
/*
* A compiler-created function object, AKA a method, already
* memoized in the property tree.
*/
JS_ASSERT(scope->hasMethodBarrier());
v = sprop->methodValue();
JS_ASSERT(VALUE_IS_FUNCTION(cx, v));
JS_ASSERT(v == LOCKED_OBJ_GET_SLOT(pobj, sprop->slot));
vword = JSVAL_OBJECT_TO_PCVAL(v);
break;
}
if (SPROP_HAS_STUB_GETTER(sprop) &&
SPROP_HAS_VALID_SLOT(sprop, scope)) {
v = LOCKED_OBJ_GET_SLOT(pobj, sprop->slot);
if (VALUE_IS_FUNCTION(cx, v)) {
/*
* Great, we have a function-valued prototype property where
* the getter is JS_PropertyStub. The type id in pobj's scope
* does not evolve with changes to property values, however.
* 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.
* 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++);
@ -222,6 +237,7 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj,
break;
}
}
}
/* If getting a value via a stub getter, we can cache the slot. */
if (!(cs->format & (JOF_SET | JOF_INCDEC | JOF_FOR)) &&
@ -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,

View File

@ -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);
}
};
/*

View File

@ -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:

View File

@ -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.

View File

@ -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 (!(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,
if (!AddPropertyHelper(cx, clasp, obj, scope, sprop, &value)) {
scope->remove(cx, id);
goto error);
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,
if (!AddPropertyHelper(cx, clasp, obj, scope, sprop, vp)) {
scope->remove(cx, id);
JS_UNLOCK_SCOPE(cx, scope);
return JS_FALSE);
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);

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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);
/* do-while(0) so we can break instead of using a goto. */
do {
if (FUN_NULL_CLOSURE(fun)) {
obj = js_CloneFunctionObject(cx, fun, fp->scopeChain);
if (!obj)
goto error;
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 (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. */

View File

@ -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;

View File

@ -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)
{
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, "<object> %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

View File

@ -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. */

View File

@ -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();
@ -9464,6 +9461,7 @@ TraceRecorder::callNative(uintN argc, JSOp mode)
}
}
break;
case 2:
if (isNumber(vp[2]) && isNumber(vp[3]) &&
(native == js_math_min || native == js_math_max)) {
@ -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,19 +9973,24 @@ 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);
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");
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;
@ -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)
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

View File

@ -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);

View File

@ -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___ */

View File

@ -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.

View File

@ -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
});

View File

@ -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
});

View File

@ -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
});