mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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:
parent
13c0c5eff8
commit
cce4dce512
@ -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 \
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
||||
/*
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
187
js/src/jsobj.cpp
187
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 (!(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);
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
188
js/src/jsops.cpp
188
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);
|
||||
|
||||
/* 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. */
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
258
js/src/jsscope.h
258
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. */
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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___ */
|
||||
|
@ -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.
|
||||
|
9
js/src/trace-test/tests/basic/testInitMethod.js
Normal file
9
js/src/trace-test/tests/basic/testInitMethod.js
Normal 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
|
||||
});
|
12
js/src/trace-test/tests/basic/testMethodInc.js
Normal file
12
js/src/trace-test/tests/basic/testMethodInc.js
Normal 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
|
||||
});
|
13
js/src/trace-test/tests/basic/testSetMethod.js
Normal file
13
js/src/trace-test/tests/basic/testSetMethod.js
Normal 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
|
||||
});
|
Loading…
Reference in New Issue
Block a user