Bug 559653 - Record assignment before the interpreter goes; avoid record_SetPropHit. r=brendan.

This commit is contained in:
Jason Orendorff 2010-06-01 21:18:49 -07:00
parent 3f7c00b2c9
commit bf6f2c259c
11 changed files with 470 additions and 200 deletions

View File

@ -544,7 +544,7 @@ js_SweepWatchPoints(JSContext *cx)
* NB: FindWatchPoint must be called with rt->debuggerLock acquired.
*/
static JSWatchPoint *
FindWatchPoint(JSRuntime *rt, JSScope *scope, jsid id)
FindWatchPoint(JSRuntime *rt, const JSScope *scope, jsid id)
{
JSWatchPoint *wp;
@ -558,7 +558,7 @@ FindWatchPoint(JSRuntime *rt, JSScope *scope, jsid id)
}
JSScopeProperty *
js_FindWatchPoint(JSRuntime *rt, JSScope *scope, jsid id)
js_FindWatchPoint(JSRuntime *rt, const JSScope *scope, jsid id)
{
JSWatchPoint *wp;
JSScopeProperty *sprop;

View File

@ -112,7 +112,7 @@ extern void
js_SweepWatchPoints(JSContext *cx);
extern JSScopeProperty *
js_FindWatchPoint(JSRuntime *rt, JSScope *scope, jsid id);
js_FindWatchPoint(JSRuntime *rt, const JSScope *scope, jsid id);
/*
* NB: callers outside of jsdbgapi.c must pass non-null scope.

View File

@ -4310,9 +4310,7 @@ js_DefineNativeProperty(JSContext *cx, JSObject *obj, jsid id, jsval value,
if (defineHow & JSDNP_CACHE_RESULT) {
JS_ASSERT_NOT_ON_TRACE(cx);
PropertyCacheEntry *entry =
JS_PROPERTY_CACHE(cx).fill(cx, obj, 0, 0, obj, sprop, added);
TRACE_2(SetPropHit, entry, sprop);
JS_PROPERTY_CACHE(cx).fill(cx, obj, 0, 0, obj, sprop, added);
}
if (propp)
*propp = (JSProperty *) sprop;
@ -4966,11 +4964,6 @@ ReportReadOnly(JSContext* cx, jsid id, uintN flags)
}
/*
* Note: all non-error exits in this function must notify the tracer using
* SetPropHit when called from the interpreter, which is detected by testing
* (defineHow & JSDNP_CACHE_RESULT).
*/
JSBool
js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow,
jsval *vp)
@ -5049,8 +5042,6 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow,
if (sprop->isAccessorDescriptor()) {
if (sprop->hasDefaultSetter()) {
JS_UNLOCK_SCOPE(cx, scope);
if (defineHow & JSDNP_CACHE_RESULT)
TRACE_2(SetPropHit, JS_NO_PROP_CACHE_FILL, sprop);
return js_ReportGetterOnlyAssignment(cx);
}
} else {
@ -5060,20 +5051,11 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow,
JS_UNLOCK_SCOPE(cx, scope);
PCMETER((defineHow & JSDNP_CACHE_RESULT) && JS_PROPERTY_CACHE(cx).rofills++);
if (defineHow & JSDNP_CACHE_RESULT) {
JS_ASSERT_NOT_ON_TRACE(cx);
TRACE_2(SetPropHit, JS_NO_PROP_CACHE_FILL, sprop);
}
/* Warn in strict mode, otherwise do nothing. */
if (JS_HAS_STRICT_OPTION(cx))
return ReportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING);
return JS_TRUE;
#ifdef JS_TRACER
error: // TRACE_2 jumps here in case of error.
return JS_FALSE;
#endif
}
}
if (scope->sealed() && !sprop->hasSlot()) {
@ -5096,9 +5078,7 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow,
if (!sprop->hasSlot()) {
if (defineHow & JSDNP_CACHE_RESULT) {
JS_ASSERT_NOT_ON_TRACE(cx);
PropertyCacheEntry *entry =
JS_PROPERTY_CACHE(cx).fill(cx, obj, 0, protoIndex, pobj, sprop);
TRACE_2(SetPropHit, entry, sprop);
JS_PROPERTY_CACHE(cx).fill(cx, obj, 0, protoIndex, pobj, sprop);
}
if (sprop->hasDefaultSetter() && !sprop->hasGetterValue())
@ -5156,8 +5136,7 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow,
* 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) {
if ((defineHow & JSDNP_SET_METHOD) && obj->getClass() == &js_ObjectClass) {
JS_ASSERT(VALUE_IS_FUNCTION(cx, *vp));
JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
@ -5194,8 +5173,7 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow,
if (defineHow & JSDNP_CACHE_RESULT) {
JS_ASSERT_NOT_ON_TRACE(cx);
PropertyCacheEntry *entry = JS_PROPERTY_CACHE(cx).fill(cx, obj, 0, 0, obj, sprop, added);
TRACE_2(SetPropHit, entry, sprop);
JS_PROPERTY_CACHE(cx).fill(cx, obj, 0, 0, obj, sprop, added);
}
if (!js_NativeSet(cx, obj, sprop, added, vp))

View File

@ -619,7 +619,6 @@ END_CASE(JSOP_PICK)
#define NATIVE_SET(cx,obj,sprop,entry,vp) \
JS_BEGIN_MACRO \
TRACE_2(SetPropHit, entry, sprop); \
if (sprop->hasDefaultSetter() && \
(sprop)->slot != SPROP_INVALID_SLOT && \
!(obj)->scope()->brandedOrHasMethodBarrier()) { \
@ -1781,7 +1780,6 @@ BEGIN_CASE(JSOP_SETMETHOD)
* slot's value that might contain a method of a
* branded scope.
*/
TRACE_2(SetPropHit, entry, sprop);
obj->lockedSetSlot(slot, rval);
/*
@ -3250,6 +3248,7 @@ BEGIN_CASE(JSOP_INITMETHOD)
lval = FETCH_OPND(-2);
obj = JSVAL_TO_OBJECT(lval);
JS_ASSERT(obj->isNative());
JS_ASSERT(obj->getClass() == &js_ObjectClass);
JS_ASSERT(!obj->getClass()->reserveSlots);
JSScope *scope = obj->scope();
@ -3303,7 +3302,6 @@ BEGIN_CASE(JSOP_INITMETHOD)
* property, not updating an existing slot's value that might
* contain a method of a branded scope.
*/
TRACE_2(SetPropHit, entry, sprop);
obj->lockedSetSlot(slot, rval);
} else {
PCMETER(JS_PROPERTY_CACHE(cx).inipcmisses++);

View File

@ -787,7 +787,7 @@ JSScope::addProperty(JSContext *cx, jsid id,
* SPROP_CALL_[GS]ETTER macros.
*/
static inline bool
NormalizeGetterAndSetter(JSContext *cx, JSScope *scope,
NormalizeGetterAndSetter(JSContext *cx, const JSScope *scope,
jsid id, uintN attrs, uintN flags,
JSPropertyOp &getter,
JSPropertyOp &setter)
@ -883,6 +883,29 @@ JSScope::addPropertyHelper(JSContext *cx, jsid id,
return NULL;
}
JSScopeProperty *
JSScope::prepareForAddProperty(JSContext *cx, jsid id, JSPropertyOp getter, JSPropertyOp setter,
uint32 slot, uintN attrs, uintN flags, intN shortid) const
{
NormalizeGetterAndSetter(cx, this, id, attrs, flags, getter, setter);
// Find or create a property tree node labeled by our arguments. Do not use
// getChildProperty because it also effectively adds the property to this
// JSScope.
if (inDictionaryMode()) {
JSScopeProperty *sprop = JS_PROPERTY_TREE(cx).newScopeProperty(cx, true);
if (sprop) {
new (sprop) JSScopeProperty(id, getter, setter, slot, attrs, flags, shortid);
sprop->parent = NULL;
sprop->kids = NULL;
sprop->shape = JSObjectMap::SHAPELESS;
}
return sprop;
}
JSScopeProperty child(id, getter, setter, slot, attrs, flags, shortid);
return JS_PROPERTY_TREE(cx).getChild(cx, lastProp, shape, child);
}
JSScopeProperty *
JSScope::putProperty(JSContext *cx, jsid id,
JSPropertyOp getter, JSPropertyOp setter,

View File

@ -279,8 +279,8 @@ struct JSScope : public JSObjectMap
bool changeTable(JSContext *cx, int change);
void reportReadOnlyScope(JSContext *cx);
void setOwnShape() { flags |= OWN_SHAPE; }
void clearOwnShape() { flags &= ~OWN_SHAPE; }
void setOwnShape() { flags |= OWN_SHAPE; }
void clearOwnShape() { flags &= ~OWN_SHAPE; }
void generateOwnShape(JSContext *cx);
JSScopeProperty **searchTable(jsid id, bool adding);
@ -327,6 +327,17 @@ struct JSScope : public JSObjectMap
uint32 slot, uintN attrs,
uintN flags, intN shortid);
/*
* Like addProperty, but just return the new (or cached) JSScopeProperty
* without adding it. This is for use with js_AddProperty.
*
* Note that the return value is not necessarily rooted.
*/
JSScopeProperty *prepareForAddProperty(JSContext *cx, jsid id,
JSPropertyOp getter, JSPropertyOp setter,
uint32 slot, uintN attrs,
uintN flags, intN shortid) const;
/* Add a data property whose id is not yet in this scope. */
JSScopeProperty *addDataProperty(JSContext *cx, jsid id, uint32 slot, uintN attrs) {
JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
@ -400,16 +411,16 @@ struct JSScope : public JSObjectMap
GENERIC = 0x0080
};
bool inDictionaryMode() { return flags & DICTIONARY_MODE; }
void setDictionaryMode() { flags |= DICTIONARY_MODE; }
void clearDictionaryMode() { flags &= ~DICTIONARY_MODE; }
bool inDictionaryMode() const { return flags & DICTIONARY_MODE; }
void setDictionaryMode() { flags |= DICTIONARY_MODE; }
void clearDictionaryMode() { flags &= ~DICTIONARY_MODE; }
/*
* Don't define clearSealed, as it can't be done safely because JS_LOCK_OBJ
* will avoid taking the lock if the object owns its scope and the scope is
* sealed.
*/
bool sealed() { return flags & SEALED; }
bool sealed() const { return flags & SEALED; }
void seal(JSContext *cx) {
JS_ASSERT(!isSharedEmpty());
@ -423,7 +434,7 @@ struct JSScope : public JSObjectMap
* properties without magic getters and setters), and its scope->shape
* evolves whenever a function value changes.
*/
bool branded() { JS_ASSERT(!generic()); return flags & BRANDED; }
bool branded() const { JS_ASSERT(!generic()); return flags & BRANDED; }
bool brand(JSContext *cx, uint32 slot, jsval v) {
JS_ASSERT(!branded());
@ -434,15 +445,15 @@ struct JSScope : public JSObjectMap
return true;
}
bool generic() { return flags & GENERIC; }
void setGeneric() { flags |= GENERIC; }
bool generic() const { return flags & GENERIC; }
void setGeneric() { flags |= GENERIC; }
bool hadIndexedProperties() { return flags & INDEXED_PROPERTIES; }
void setIndexedProperties() { flags |= INDEXED_PROPERTIES; }
bool hadIndexedProperties() const { return flags & INDEXED_PROPERTIES; }
void setIndexedProperties() { flags |= INDEXED_PROPERTIES; }
bool hasOwnShape() { return flags & OWN_SHAPE; }
bool hasOwnShape() const { return flags & OWN_SHAPE; }
bool hasRegenFlag(uint8 regenFlag) { return (flags & SHAPE_REGEN) == regenFlag; }
bool hasRegenFlag(uint8 regenFlag) const { return (flags & SHAPE_REGEN) == regenFlag; }
/*
* A scope has a method barrier when some compiler-created "null closure"
@ -484,8 +495,8 @@ struct JSScope : public JSObjectMap
* METHOD_BARRIER too, and regenerate this scope's shape if the method's
* value is in fact changing.
*/
bool hasMethodBarrier() { return flags & METHOD_BARRIER; }
void setMethodBarrier() { flags |= METHOD_BARRIER; }
bool hasMethodBarrier() const { return flags & METHOD_BARRIER; }
void setMethodBarrier() { flags |= METHOD_BARRIER; }
/*
* Test whether this scope may be branded due to method calls, which means
@ -493,10 +504,9 @@ struct JSScope : public JSObjectMap
* test whether this scope has method properties, which require a method
* write barrier.
*/
bool
brandedOrHasMethodBarrier() { return flags & (BRANDED | METHOD_BARRIER); }
bool brandedOrHasMethodBarrier() const { return flags & (BRANDED | METHOD_BARRIER); }
bool isSharedEmpty() const { return !object; }
bool isSharedEmpty() const { return !object; }
static bool initRuntimeState(JSContext *cx);
static void finishRuntimeState(JSContext *cx);

View File

@ -2452,6 +2452,7 @@ TraceRecorder::insImmVal(jsval val)
inline LIns*
TraceRecorder::insImmObj(JSObject* obj)
{
JS_ASSERT(obj);
tree->gcthings.addUnique(OBJECT_TO_JSVAL(obj));
return lir->insImmP((void*)obj);
}
@ -2459,6 +2460,7 @@ TraceRecorder::insImmObj(JSObject* obj)
inline LIns*
TraceRecorder::insImmFun(JSFunction* fun)
{
JS_ASSERT(fun);
tree->gcthings.addUnique(OBJECT_TO_JSVAL(FUN_OBJECT(fun)));
return lir->insImmP((void*)fun);
}
@ -2466,6 +2468,7 @@ TraceRecorder::insImmFun(JSFunction* fun)
inline LIns*
TraceRecorder::insImmStr(JSString* str)
{
JS_ASSERT(str);
tree->gcthings.addUnique(STRING_TO_JSVAL(str));
return lir->insImmP((void*)str);
}
@ -2473,6 +2476,7 @@ TraceRecorder::insImmStr(JSString* str)
inline LIns*
TraceRecorder::insImmSprop(JSScopeProperty* sprop)
{
JS_ASSERT(sprop);
tree->sprops.addUnique(sprop);
return lir->insImmP((void*)sprop);
}
@ -11185,20 +11189,95 @@ TraceRecorder::record_JSOP_GETPROP()
return getProp(stackval(-1));
}
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_SETPROP()
/*
* If possible, lookup obj[id] without calling any resolve hooks or touching
* any non-native objects or objects that have been shared across contexts,
* store the results in *pobjp and *spropp (NULL if no such property exists),
* and return true. This does not take any locks. That is safe because we
* avoid shared objects.
*
* If a safe lookup is not possible, return false; *pobjp and *spropp are
* undefined.
*/
static bool
SafeLookup(JSContext *cx, JSObject* obj, jsid id, JSObject** pobjp, JSScopeProperty** spropp)
{
jsval& l = stackval(-2);
if (JSVAL_IS_PRIMITIVE(l))
RETURN_STOP_A("primitive this for SETPROP");
do {
// Avoid non-native lookupProperty hooks.
if (obj->map->ops->lookupProperty != js_LookupProperty)
return false;
JSObject* obj = JSVAL_TO_OBJECT(l);
if (obj->map->ops->setProperty != js_SetProperty)
RETURN_STOP_A("non-native JSObjectOps::setProperty");
return ARECORD_CONTINUE;
// Avoid shared objects.
JSScope *scope = obj->scope();
if (!CX_OWNS_SCOPE_TITLE(cx, scope))
return false;
if (JSScopeProperty *sprop = scope->lookup(id)) {
*pobjp = obj;
*spropp = sprop;
return true;
}
// Avoid resolve hooks.
if (obj->getClass()->resolve != JS_ResolveStub)
return false;
} while ((obj = obj->getProto()) != NULL);
*pobjp = NULL;
*spropp = NULL;
return true;
}
/* Emit a specialized, inlined copy of js_NativeSet. */
/*
* Lookup the property for the SETPROP/SETNAME/SETMETHOD instruction at pc.
* Emit guards to ensure that the result at run time is the same.
*/
JS_REQUIRES_STACK RecordingStatus
TraceRecorder::lookupForSetPropertyOp(JSObject* obj, LIns* obj_ins, jsid id,
bool* safep, JSObject** pobjp, JSScopeProperty** spropp)
{
// We could consult the property cache here, but the contract for
// PropertyCache::testForSet is intricate enough that it's a lot less code
// to do a SafeLookup.
*safep = SafeLookup(cx, obj, id, pobjp, spropp);
if (!*safep)
return RECORD_CONTINUE;
VMSideExit *exit = snapshot(BRANCH_EXIT);
if (*spropp) {
if (obj != globalObj)
CHECK_STATUS(guardShape(obj_ins, obj, obj->shape(), "guard_kshape", exit));
if (obj != *pobjp && *pobjp != globalObj) {
CHECK_STATUS(guardShape(INS_CONSTOBJ(*pobjp), *pobjp, (*pobjp)->shape(),
"guard_vshape", exit));
}
} else {
for (;;) {
if (obj != globalObj)
CHECK_STATUS(guardShape(obj_ins, obj, obj->shape(), "guard_proto_chain", exit));
obj = obj->getProto();
if (!obj)
break;
obj_ins = INS_CONSTOBJ(obj);
}
}
return RECORD_CONTINUE;
}
static JSBool FASTCALL
MethodWriteBarrier(JSContext* cx, JSObject* obj, uint32 slot, jsval v)
{
AutoValueRooter tvr(cx, v);
return obj->scope()->methodWriteBarrier(cx, slot, v);
}
JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, MethodWriteBarrier, CONTEXT, OBJECT, UINT32, JSVAL,
0, ACC_STORE_ANY)
/*
* Emit a specialized, inlined copy of js_NativeSet.
*
* Note that since this runs before the interpreter opcode, obj->scope() may
* not actually have the property sprop yet.
*/
JS_REQUIRES_STACK RecordingStatus
TraceRecorder::nativeSet(JSObject* obj, LIns* obj_ins, JSScopeProperty* sprop,
jsval v, LIns* v_ins)
@ -11206,9 +11285,13 @@ TraceRecorder::nativeSet(JSObject* obj, LIns* obj_ins, JSScopeProperty* sprop,
JSScope* scope = obj->scope();
uint32 slot = sprop->slot;
// If obj is the global, it must already have sprop. Otherwise we would
// have gone through addDataProperty, which would have stopped recording.
JS_ASSERT_IF(obj == globalObj, scope->hasProperty(sprop));
/*
* We do not trace assignment to properties that have both a nonstub setter
* and a slot, for several reasons.
* We do not trace assignment to properties that have both a non-default
* setter and a slot, for several reasons.
*
* First, that would require sampling rt->propertyRemovals before and after
* (see js_NativeSet), and even more code to handle the case where the two
@ -11223,8 +11306,18 @@ TraceRecorder::nativeSet(JSObject* obj, LIns* obj_ins, JSScopeProperty* sprop,
* setter's return value differed from the record-time type of v, in which
* case unboxing would fail and, having called a native setter, we could
* not just retry the instruction in the interpreter.
*
* If obj is branded, we would have a similar problem recovering from a
* failed call to MethodWriteBarrier.
*/
JS_ASSERT(sprop->hasDefaultSetter() || slot == SPROP_INVALID_SLOT);
if (!sprop->hasDefaultSetter() && slot != SPROP_INVALID_SLOT)
RETURN_STOP("can't trace set of property with setter and slot");
// These two cases are strict-mode errors and can't be traced.
if (sprop->hasGetterValue() && sprop->hasDefaultSetter())
RETURN_STOP("can't set a property that has only a getter");
if (sprop->isDataDescriptor() && !sprop->writable())
RETURN_STOP("can't assign to readonly property");
// Box the value to be stored, if necessary.
LIns* boxed_ins = NULL;
@ -11232,14 +11325,36 @@ TraceRecorder::nativeSet(JSObject* obj, LIns* obj_ins, JSScopeProperty* sprop,
boxed_ins = box_jsval(v, v_ins);
// Call the setter, if any.
if (!sprop->hasDefaultSetter())
if (!sprop->hasDefaultSetter()) {
if (sprop->hasSetterValue())
RETURN_STOP("can't trace JavaScript function setter yet");
emitNativePropertyOp(scope, sprop, obj_ins, true, boxed_ins);
}
// Store the value, if this property has a slot.
if (slot != SPROP_INVALID_SLOT) {
JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, scope));
if (scope->brandedOrHasMethodBarrier()) {
if (obj == globalObj) {
// Because the trace is type-specialized to the global object's
// slots, no run-time check is needed. Avoid recording a global
// shape change, though.
if (VALUE_IS_FUNCTION(cx, obj->getSlot(slot)))
RETURN_STOP("can't trace set of function-valued property in branded global");
} else {
// Setting a function-valued property might need to rebrand the
// object. Call the method write barrier. Note that even if the
// property is not function-valued now, it might be on trace.
enterDeepBailCall();
LIns* args[] = { boxed_ins, INS_CONST(slot), obj_ins, cx_ins };
LIns* ok_ins = lir->insCall(&MethodWriteBarrier_ci, args);
guard(false, lir->insEqI_0(ok_ins), OOM_EXIT);
leaveDeepBailCall();
}
}
// Store the value.
JS_ASSERT(sprop->hasSlot());
if (obj == globalObj) {
JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, scope));
if (!lazilyImportGlobalSlot(slot))
RETURN_STOP("lazy import of global slot failed");
set(&obj->getSlotRef(slot), v_ins);
@ -11252,90 +11367,67 @@ TraceRecorder::nativeSet(JSObject* obj, LIns* obj_ins, JSScopeProperty* sprop,
return RECORD_CONTINUE;
}
static JSBool FASTCALL
MethodWriteBarrier(JSContext* cx, JSObject* obj, JSScopeProperty* sprop, JSObject* funobj)
{
AutoValueRooter tvr(cx, funobj);
return obj->scope()->methodWriteBarrier(cx, sprop, tvr.value());
}
JS_DEFINE_CALLINFO_4(static, BOOL_FAIL, MethodWriteBarrier, CONTEXT, OBJECT, SCOPEPROP, OBJECT,
0, ACC_STORE_ANY)
JS_REQUIRES_STACK RecordingStatus
TraceRecorder::setProp(jsval &l, PropertyCacheEntry* entry, JSScopeProperty* sprop,
jsval &v, LIns*& v_ins, bool isDefinitelyAtom)
TraceRecorder::addDataProperty(JSObject* obj, LIns* obj_ins,
jsid id, JSPropertyOp getter, JSPropertyOp setter,
uintN flags, intN shortid, jsval v, LIns* v_ins)
{
if (entry == JS_NO_PROP_CACHE_FILL)
RETURN_STOP("can't trace uncacheable property set");
JS_ASSERT_IF(entry->vcapTag() >= 1, !sprop->hasSlot());
if (!sprop->hasDefaultSetter() && sprop->slot != SPROP_INVALID_SLOT)
RETURN_STOP("can't trace set of property with setter and slot");
if (sprop->hasSetterValue())
RETURN_STOP("can't trace JavaScript function setter");
// If obj is the global, the global shape is about to change. Note also
// that since we do not record this case, SETNAME and SETPROP are identical
// as far as the tracer is concerned. (js_CheckUndeclaredVarAssignment
// distinguishes the two, in the interpreter.)
if (obj == globalObj)
RETURN_STOP("set new property of global object"); // global shape change
// These two cases are errors and can't be traced.
if (sprop->hasGetterValue())
RETURN_STOP("can't assign to property with script getter but no setter");
if (!sprop->writable())
RETURN_STOP("can't assign to readonly property");
// js_AddProperty does not call the addProperty hook.
if (obj->getClass()->addProperty != JS_PropertyStub)
RETURN_STOP("set new property of object with addProperty hook");
JS_ASSERT(!JSVAL_IS_PRIMITIVE(l));
JSObject* obj = JSVAL_TO_OBJECT(l);
LIns* obj_ins = get(&l);
// See comment in TR::nativeSet about why we do not support setting a
// property that has both a setter and a slot.
if (setter != JS_PropertyStub)
RETURN_STOP("set new property with setter and slot");
JS_ASSERT_IF(entry->directHit(), obj->scope()->hasProperty(sprop));
// Methods can be defined only on plain Objects.
jsbytecode op = *cx->regs->pc;
if ((op == JSOP_INITMETHOD || op == JSOP_SETMETHOD) && obj->getClass() == &js_ObjectClass) {
JS_ASSERT(VALUE_IS_FUNCTION(cx, v));
// Fast path for CallClass. This is about 20% faster than the general case.
v_ins = get(&v);
if (obj->getClass() == &js_CallClass)
return setCallProp(obj, obj_ins, sprop, v_ins, v);
// Find obj2. If entry->adding(), the TAG bits are all 0.
JSObject* obj2 = obj;
for (jsuword i = entry->scopeIndex(); i; i--)
obj2 = obj2->getParent();
for (jsuword j = entry->protoIndex(); j; j--)
obj2 = obj2->getProto();
JSScope *scope = obj2->scope();
JS_ASSERT_IF(entry->adding(), obj2 == obj);
// Guard before anything else.
PCVal pcval;
CHECK_STATUS(guardPropertyCacheHit(obj_ins, obj, obj2, entry, pcval));
JS_ASSERT(scope->object == obj2);
JS_ASSERT(scope->hasProperty(sprop));
JS_ASSERT_IF(obj2 != obj, !sprop->hasSlot());
/*
* 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->brandedOrHasMethodBarrier() && VALUE_IS_FUNCTION(cx, v) && entry->directHit()) {
if (obj == globalObj)
RETURN_STOP("can't trace function-valued property set in branded global scope");
enterDeepBailCall();
LIns* args[] = { v_ins, INS_CONSTSPROP(sprop), obj_ins, cx_ins };
LIns* ok_ins = lir->insCall(&MethodWriteBarrier_ci, args);
guard(false, lir->insEqI_0(ok_ins), OOM_EXIT);
leaveDeepBailCall();
JSObject *funobj = JSVAL_TO_OBJECT(v);
if (FUN_OBJECT(GET_FUNCTION_PRIVATE(cx, funobj)) == funobj) {
flags |= JSScopeProperty::METHOD;
getter = CastAsPropertyOp(funobj);
}
}
// Add a property to the object if necessary.
if (entry->adding()) {
JS_ASSERT(sprop->hasSlot());
if (obj == globalObj)
RETURN_STOP("adding a property to the global object");
// Determine what slot the interpreter will assign this property.
JSScope *scope;
uint32 slot;
JS_LOCK_OBJ(cx, obj);
scope = js_GetMutableScope(cx, obj);
if (!scope)
RETURN_ERROR("js_GetMutableScope failed");
if (!js_AllocSlot(cx, obj, &slot))
RETURN_ERROR("js_AllocSlot failed");
js_FreeSlot(cx, obj, slot);
JS_UNLOCK_OBJ(cx, obj);
LIns* args[] = { INS_CONSTSPROP(sprop), obj_ins, cx_ins };
const CallInfo *ci = isDefinitelyAtom ? &js_AddAtomProperty_ci : &js_AddProperty_ci;
LIns* ok_ins = lir->insCall(ci, args);
guard(false, lir->insEqI_0(ok_ins), OOM_EXIT);
}
// Find or create an sprop for the new property but avoid adding it to
// obj's scope. The sprop returned by prepareForAddProperty may not be
// rooted, but we immediately pass it to INS_CONSTSPROP, which roots it.
JSScopeProperty* sprop = obj->scope()->prepareForAddProperty(cx, id, getter, setter, slot,
JSPROP_ENUMERATE, flags, shortid);
if (!sprop)
RETURN_ERROR("JSScope::prepareForAddProperty failed");
// On trace, call js_AddProperty to do the dirty work.
LIns* args[] = { INS_CONSTSPROP(sprop), obj_ins, cx_ins };
bool isDefinitelyAtom = (op == JSOP_SETPROP);
const CallInfo *ci = isDefinitelyAtom ? &js_AddAtomProperty_ci : &js_AddProperty_ci;
LIns* ok_ins = lir->insCall(ci, args);
guard(false, lir->insEqI_0(ok_ins), OOM_EXIT);
// Box the value and store it in the new slot.
return nativeSet(obj, obj_ins, sprop, v, v_ins);
}
@ -11486,30 +11578,169 @@ TraceRecorder::setCallProp(JSObject *callobj, LIns *callobj_ins, JSScopeProperty
return RECORD_CONTINUE;
}
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_SetPropHit(PropertyCacheEntry* entry, JSScopeProperty* sprop)
/*
* Emit a specialized, inlined copy of js_SetPropertyHelper for the current
* instruction.
*/
JS_REQUIRES_STACK RecordingStatus
TraceRecorder::setProperty(JSObject* obj, LIns* obj_ins, jsval v, LIns* v_ins)
{
jsval& r = stackval(-1);
jsval& l = stackval(-2);
LIns* v_ins;
JSAtom *atom;
GET_ATOM_FROM_BYTECODE(cx->fp->script, cx->regs->pc, 0, atom);
jsid id = ATOM_TO_JSID(atom);
jsbytecode* pc = cx->regs->pc;
if (obj->map->ops->setProperty != js_SetProperty)
RETURN_STOP("non-native object"); // TODO - fall back on slow path
if (obj->scope()->sealed())
RETURN_STOP("setting property of sealed object"); // this will throw
bool isDefinitelyAtom = (*pc == JSOP_SETPROP);
CHECK_STATUS_A(setProp(l, entry, sprop, r, v_ins, isDefinitelyAtom));
bool safe;
JSObject* pobj;
JSScopeProperty* sprop;
CHECK_STATUS(lookupForSetPropertyOp(obj, obj_ins, id, &safe, &pobj, &sprop));
if (!safe)
RETURN_STOP("setprop: lookup fail"); // TODO - fall back on slow path
switch (*pc) {
case JSOP_SETPROP:
case JSOP_SETNAME:
case JSOP_SETMETHOD:
if (pc[JSOP_SETPROP_LENGTH] != JSOP_POP)
set(&l, v_ins);
break;
// Handle Call objects specially. The Call objects we create on trace are
// bogus. Calling the setter on such an object wouldn't work.
if (obj->getClass() == &js_CallClass)
return setCallProp(obj, obj_ins, sprop, v_ins, v);
default:;
// Handle setting a property that is not found on obj or anywhere on its
// the prototype chain.
if (!sprop) {
JSClass *cls = obj->getClass();
return addDataProperty(obj, obj_ins, id, cls->getProperty, cls->setProperty, 0, 0,
v, v_ins);
}
return ARECORD_CONTINUE;
// Check whether we can assign to/over the existing property.
if (sprop->isAccessorDescriptor()) {
if (sprop->hasDefaultSetter())
RETURN_STOP("setting accessor property with no setter");
} else if (!sprop->writable()) {
RETURN_STOP("setting readonly data property");
}
// Handle setting an existing own property.
if (pobj == obj)
return nativeSet(obj, obj_ins, sprop, v, v_ins);
// Handle setting an inherited non-SHARED property.
if (sprop->hasSlot()) {
JSPropertyOp getter, setter;
uintN flags;
intN shortid;
if (sprop->hasShortID()) {
getter = sprop->getter();
setter = sprop->setter();
flags = JSScopeProperty::HAS_SHORTID;
shortid = sprop->shortid;
} else {
JSClass *cls = obj->getClass();
getter = cls->getProperty;
setter = cls->setProperty;
flags = 0;
shortid = 0;
}
return addDataProperty(obj, obj_ins, id, getter, setter, flags, shortid, v, v_ins);
}
// Handle setting an inherited SHARED property.
if (pobj->scope()->sealed())
RETURN_STOP("setting SHARED property inherited from sealed object");
if (sprop->hasDefaultSetter() && !sprop->hasGetterValue())
return RECORD_CONTINUE; // nothing happens
// Handle setting an inherited SHARED property with a non-default setter.
return nativeSet(obj, obj_ins, sprop, v, v_ins);
}
/*
* Record a JSOP_SET{PROP,NAME,METHOD} instruction or a JSOP_INITPROP
* instruction initializing __proto__.
*/
JS_REQUIRES_STACK RecordingStatus
TraceRecorder::recordSetPropertyOp()
{
jsval& l = stackval(-2);
if (JSVAL_IS_PRIMITIVE(l))
RETURN_STOP("primitive operand object");
JSObject* obj = JSVAL_TO_OBJECT(l);
LIns* obj_ins = get(&l);
jsval& r = stackval(-1);
LIns* r_ins = get(&r);
CHECK_STATUS(setProperty(obj, obj_ins, r, r_ins));
set(&l, r_ins);
return RECORD_CONTINUE;
}
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_SETPROP()
{
return InjectStatus(recordSetPropertyOp());
}
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_SETMETHOD()
{
return InjectStatus(recordSetPropertyOp());
}
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_SETNAME()
{
return InjectStatus(recordSetPropertyOp());
}
JS_REQUIRES_STACK RecordingStatus
TraceRecorder::recordInitPropertyOp()
{
jsval& l = stackval(-2);
JSObject* obj = JSVAL_TO_OBJECT(l);
LIns* obj_ins = get(&l);
JS_ASSERT(obj->getClass() == &js_ObjectClass);
jsval& v = stackval(-1);
LIns* v_ins = get(&v);
JSAtom *atom;
GET_ATOM_FROM_BYTECODE(cx->fp->script, cx->regs->pc, 0, atom);
jsid id = ATOM_TO_JSID(atom);
// If obj already has this property (because the id appears more than once
// in the initializer), just set it.
JSScope* scope = obj->scope();
if (!CX_OWNS_SCOPE_TITLE(cx, scope))
RETURN_STOP("object being initialized is shared among contexts");
if (JSScopeProperty* sprop = scope->lookup(id)) {
JS_ASSERT(sprop->isDataDescriptor());
JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, scope));
JS_ASSERT(sprop->hasDefaultSetter());
return nativeSet(obj, obj_ins, sprop, v, v_ins);
}
// Duplicate the interpreter's special treatment of __proto__.
if (atom == cx->runtime->atomState.protoAtom)
return recordSetPropertyOp();
// Define a new property.
return addDataProperty(obj, obj_ins, id, JS_PropertyStub, JS_PropertyStub, 0, 0, v, v_ins);
}
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_INITPROP()
{
return InjectStatus(recordInitPropertyOp());
}
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_INITMETHOD()
{
return InjectStatus(recordInitPropertyOp());
}
JS_REQUIRES_STACK VMSideExit*
@ -13431,13 +13662,6 @@ TraceRecorder::record_JSOP_ENDINIT()
return ARECORD_CONTINUE;
}
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_INITPROP()
{
// All the action is in record_SetPropHit.
return ARECORD_CONTINUE;
}
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_INITELEM()
{
@ -13964,13 +14188,6 @@ TraceRecorder::record_JSOP_BINDNAME()
return ARECORD_CONTINUE;
}
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_SETNAME()
{
// record_SetPropHit does all the work.
return ARECORD_CONTINUE;
}
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_THROW()
{
@ -15271,18 +15488,6 @@ TraceRecorder::record_JSOP_CONCATN()
return ARECORD_CONTINUE;
}
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_SETMETHOD()
{
return record_JSOP_SETPROP();
}
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_INITMETHOD()
{
return record_JSOP_INITPROP();
}
JSBool FASTCALL
js_Unbrand(JSContext *cx, JSObject *obj)
{

View File

@ -1307,25 +1307,35 @@ class TraceRecorder
nanojit::LIns* obj_ins,
JSScopeProperty* sprop);
JS_REQUIRES_STACK RecordingStatus nativeSet(JSObject* obj, nanojit::LIns* obj_ins,
JSScopeProperty* sprop,
jsval v, nanojit::LIns* v_ins);
JS_REQUIRES_STACK RecordingStatus setProp(jsval &l, PropertyCacheEntry* entry,
JSScopeProperty* sprop,
jsval &v, nanojit::LIns*& v_ins,
bool isDefinitelyAtom);
JS_REQUIRES_STACK RecordingStatus setCallProp(JSObject *callobj, nanojit::LIns *callobj_ins,
JSScopeProperty *sprop, nanojit::LIns *v_ins,
jsval v);
JS_REQUIRES_STACK RecordingStatus initOrSetPropertyByName(nanojit::LIns* obj_ins,
jsval* idvalp, jsval* rvalp,
bool init);
jsval* idvalp, jsval* rvalp,
bool init);
JS_REQUIRES_STACK RecordingStatus initOrSetPropertyByIndex(nanojit::LIns* obj_ins,
nanojit::LIns* index_ins,
jsval* rvalp, bool init);
nanojit::LIns* index_ins,
jsval* rvalp, bool init);
JS_REQUIRES_STACK AbortableRecordingStatus setElem(int lval_spindex, int idx_spindex,
int v_spindex);
JS_REQUIRES_STACK RecordingStatus lookupForSetPropertyOp(JSObject* obj, nanojit::LIns* obj_ins,
jsid id, bool* safep,
JSObject** pobjp,
JSScopeProperty** spropp);
JS_REQUIRES_STACK RecordingStatus nativeSet(JSObject* obj, nanojit::LIns* obj_ins,
JSScopeProperty* sprop,
jsval v, nanojit::LIns* v_ins);
JS_REQUIRES_STACK RecordingStatus addDataProperty(JSObject* obj, nanojit::LIns* obj_ins,
jsid id,
JSPropertyOp getter, JSPropertyOp setter,
uintN flags, intN shortid,
jsval v, nanojit::LIns* v_ins);
JS_REQUIRES_STACK RecordingStatus setCallProp(JSObject *callobj, nanojit::LIns *callobj_ins,
JSScopeProperty *sprop, nanojit::LIns *v_ins,
jsval v);
JS_REQUIRES_STACK RecordingStatus setProperty(JSObject* obj, nanojit::LIns* obj_ins,
jsval v, nanojit::LIns* v_ins);
JS_REQUIRES_STACK RecordingStatus recordSetPropertyOp();
JS_REQUIRES_STACK RecordingStatus recordInitPropertyOp();
JS_REQUIRES_STACK nanojit::LIns* box_jsval(jsval v, nanojit::LIns* v_ins);
JS_REQUIRES_STACK nanojit::LIns* unbox_jsval(jsval v, nanojit::LIns* v_ins, VMSideExit* exit);
JS_REQUIRES_STACK void guardClassHelper(bool cond, nanojit::LIns* obj_ins, JSClass* clasp,
@ -1466,8 +1476,6 @@ public:
JS_REQUIRES_STACK AbortableRecordingStatus monitorRecording(JSOp op);
JS_REQUIRES_STACK AbortableRecordingStatus record_EnterFrame(uintN& inlineCallCount);
JS_REQUIRES_STACK AbortableRecordingStatus record_LeaveFrame();
JS_REQUIRES_STACK AbortableRecordingStatus record_SetPropHit(PropertyCacheEntry* entry,
JSScopeProperty* sprop);
JS_REQUIRES_STACK AbortableRecordingStatus record_DefLocalFunSetSlot(uint32 slot, JSObject* obj);
JS_REQUIRES_STACK AbortableRecordingStatus record_NativeCallComplete();
void forgetGuardedShapesForObject(JSObject* obj);

View File

@ -0,0 +1,4 @@
var x = {p: 0.1, m: function(){}};
x.m(); // the interpreter brands x
for (var i = 0; i < 9; i++)
x.p = 0.1;

View File

@ -0,0 +1,17 @@
function C() {
this.m = function () {}; // JSOP_SETMETHOD
}
var a = [new C, new C, new C, new C, new C, new C, new C, new C, new C];
var b = [new C, new C, new C, new C, new C, new C, a[8], new C, new C];
var thrown = 'none';
try {
for (var i = 0; i < 9; i++) {
a[i].m();
b[i].m = 0.7; // MethodWriteBarrier required here
}
} catch (exc) {
thrown = exc.name;
}
assertEq(thrown, 'TypeError');

View File

@ -0,0 +1,27 @@
function g() {}
function h() {
for (var i = 0; i < 9; i++)
x.f = i;
}
function j() {
x.f();
}
var x = {f: 0.7, g: g};
x.g(); // interpreter brands x
h();
print(shapeOf(x));
x.f = function (){}; // does not change x's shape
j();
print(shapeOf(x));
h(); // should change x's shape
var thrown = 'none';
try {
j(); // should throw since x.f === 8
} catch (exc) {
thrown = exc.name;
}
assertEq(thrown, 'TypeError');