From 08e0a53af433cd8e3627fe113d18c9df30017caf Mon Sep 17 00:00:00 2001 From: Jason Orendorff Date: Mon, 27 Jul 2009 16:47:34 -0500 Subject: [PATCH] Bug 503408 - Trace native setters. r=brendan. --- js/src/jsbuiltins.cpp | 2 - js/src/jsinterp.cpp | 1 - js/src/jsinterp.h | 4 + js/src/jstracer.cpp | 423 ++++++++++++++++++++++++++++-------------- js/src/jstracer.h | 25 ++- js/src/trace-test.js | 15 ++ 6 files changed, 327 insertions(+), 143 deletions(-) diff --git a/js/src/jsbuiltins.cpp b/js/src/jsbuiltins.cpp index 961e1133127..fe088a03aad 100644 --- a/js/src/jsbuiltins.cpp +++ b/js/src/jsbuiltins.cpp @@ -242,8 +242,6 @@ JSBool FASTCALL js_AddProperty(JSContext* cx, JSObject* obj, JSScopeProperty* sprop) { JS_ASSERT(OBJ_IS_NATIVE(obj)); - JS_ASSERT(SPROP_HAS_STUB_SETTER(sprop)); - JS_LOCK_OBJ(cx, obj); JSScope* scope = OBJ_SCOPE(obj); diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 6f60b4b0821..25506179975 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -4802,7 +4802,6 @@ js_Interpret(JSContext *cx) } JS_UNLOCK_SCOPE(cx, scope); PCMETER(cache->setpcmisses++); - atom = NULL; } } diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index 3848d5498b1..23e77a7171e 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -243,6 +243,10 @@ struct JSPropCacheEntry { jsuword kshape; /* key shape if pc, else obj for atom */ jsuword vcap; /* value capability, see above */ jsuword vword; /* value word, see PCVAL_* below */ + + bool adding() const { + return PCVCAP_TAG(vcap) == 0 && kshape != PCVCAP_SHAPE(vcap); + } }; /* diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index 5d4da83d874..dba2449d3be 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -2950,7 +2950,8 @@ TraceRecorder::snapshot(ExitType exitType) bool resumeAfter = (pendingTraceableNative && JSTN_ERRTYPE(pendingTraceableNative) == FAIL_STATUS); if (resumeAfter) { - JS_ASSERT(*pc == JSOP_CALL || *pc == JSOP_APPLY || *pc == JSOP_NEW); + JS_ASSERT(*pc == JSOP_CALL || *pc == JSOP_APPLY || *pc == JSOP_NEW || + *pc == JSOP_SETPROP || *pc == JSOP_SETNAME); pc += cs.length; regs->pc = pc; MUST_FLOW_THROUGH("restore_pc"); @@ -5410,11 +5411,11 @@ LeaveTree(InterpState& state, VMSideExit* lr) * js_ExecuteTree. We are about to return to the interpreter. Adjust * the top stack frame to resume on the next op. */ - JS_ASSERT(*cx->fp->regs->pc == JSOP_CALL || - *cx->fp->regs->pc == JSOP_APPLY || - *cx->fp->regs->pc == JSOP_NEW); - uintN argc = GET_ARGC(cx->fp->regs->pc); - cx->fp->regs->pc += JSOP_CALL_LENGTH; + jsbytecode *pc = cx->fp->regs->pc; + JS_ASSERT(*pc == JSOP_CALL || *pc == JSOP_APPLY || *pc == JSOP_NEW || + *pc == JSOP_SETPROP || *pc == JSOP_SETNAME); + uintN argc = (js_CodeSpec[*pc].format & JOF_INVOKE) ? GET_ARGC(pc) : 0; + cx->fp->regs->pc += js_CodeSpec[*pc].length; cx->fp->regs->sp -= argc + 1; JS_ASSERT_IF(!cx->fp->imacpc, cx->fp->slots + cx->fp->script->nfixed + @@ -7581,6 +7582,41 @@ TraceRecorder::map_is_native(JSObjectMap* map, LIns* map_ins, LIns*& ops_ins, si return true; } +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::guardNativePropertyOp(JSObject* aobj, LIns* map_ins) +{ + /* + * Interpreter calls to PROPERTY_CACHE_TEST guard on native object ops + * which is required to use native objects (those whose maps are scopes), + * or even more narrow conditions required because the cache miss case + * will call a particular object-op (js_GetProperty, js_SetProperty). + * + * We parameterize using offsetof and guard on match against the hook at + * the given offset in js_ObjectOps. TraceRecorder::record_JSOP_SETPROP + * guards the js_SetProperty case. + */ + uint32 format = js_CodeSpec[*cx->fp->regs->pc].format; + uint32 mode = JOF_MODE(format); + + // No need to guard native-ness of global object. + JS_ASSERT(OBJ_IS_NATIVE(globalObj)); + if (aobj != globalObj) { + size_t op_offset = offsetof(JSObjectOps, objectMap); + if (mode == JOF_PROP || mode == JOF_VARPROP) { + op_offset = (format & JOF_SET) + ? offsetof(JSObjectOps, setProperty) + : offsetof(JSObjectOps, getProperty); + } else { + JS_ASSERT(mode == JOF_NAME); + } + + LIns* ops_ins; + if (!map_is_native(aobj->map, map_ins, ops_ins, op_offset)) + ABORT_TRACE("non-native map"); + } + return JSRS_CONTINUE; +} + JS_REQUIRES_STACK JSRecordingStatus TraceRecorder::test_property_cache(JSObject* obj, LIns* obj_ins, JSObject*& obj2, jsuword& pcval) { @@ -7598,33 +7634,8 @@ TraceRecorder::test_property_cache(JSObject* obj, LIns* obj_ins, JSObject*& obj2 } LIns* map_ins = map(obj_ins); - LIns* ops_ins; - // Interpreter calls to PROPERTY_CACHE_TEST guard on native object ops - // which is required to use native objects (those whose maps are scopes), - // or even more narrow conditions required because the cache miss case - // will call a particular object-op (js_GetProperty, js_SetProperty). - // - // We parameterize using offsetof and guard on match against the hook at - // the given offset in js_ObjectOps. TraceRecorder::record_JSOP_SETPROP - // guards the js_SetProperty case. - uint32 format = js_CodeSpec[*pc].format; - uint32 mode = JOF_MODE(format); - - // No need to guard native-ness of global object. - JS_ASSERT(OBJ_IS_NATIVE(globalObj)); - if (aobj != globalObj) { - size_t op_offset = offsetof(JSObjectOps, objectMap); - if (mode == JOF_PROP || mode == JOF_VARPROP) { - JS_ASSERT(!(format & JOF_SET)); - op_offset = offsetof(JSObjectOps, getProperty); - } else { - JS_ASSERT(mode == JOF_NAME); - } - - if (!map_is_native(aobj->map, map_ins, ops_ins, op_offset)) - ABORT_TRACE("non-native map"); - } + CHECK_STATUS(guardNativePropertyOp(aobj, map_ins)); JSAtom* atom; JSPropCacheEntry* entry; @@ -7692,31 +7703,60 @@ TraceRecorder::test_property_cache(JSObject* obj, LIns* obj_ins, JSObject*& obj2 JS_ASSERT(cx->requestDepth); #endif - // Emit guard(s), common code for both hit and miss cases. + return guardPropertyCacheHit(obj_ins, map_ins, aobj, obj2, entry, pcval); +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::guardPropertyCacheHit(LIns* obj_ins, + LIns* map_ins, + JSObject* aobj, + JSObject* obj2, + JSPropCacheEntry* entry, + jsuword& pcval) +{ + uint32 vshape = PCVCAP_SHAPE(entry->vcap); + // Check for first-level cache hit and guard on kshape if possible. // Otherwise guard on key object exact match. if (PCVCAP_TAG(entry->vcap) <= 1) { if (aobj != globalObj) { LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)), "shape"); - guard(true, addName(lir->ins2i(LIR_eq, shape_ins, entry->kshape), "guard(kshape)(test_property_cache)"), + guard(true, + addName(lir->ins2i(LIR_eq, shape_ins, entry->kshape), "guard_kshape"), BRANCH_EXIT); } + + if (entry->adding()) { + if (aobj == globalObj) + ABORT_TRACE("adding a property to the global object"); + + LIns *vshape_ins = addName( + lir->insLoad(LIR_ld, + addName(lir->insLoad(LIR_ldp, cx_ins, offsetof(JSContext, runtime)), + "runtime"), + offsetof(JSRuntime, protoHazardShape)), + "protoHazardShape"); + guard(true, + addName(lir->ins2i(LIR_eq, vshape_ins, vshape), "guard_protoHazardShape"), + MISMATCH_EXIT); + } } else { #ifdef DEBUG - JSOp op = js_GetOpcode(cx, cx->fp->script, pc); + JSOp op = js_GetOpcode(cx, cx->fp->script, cx->fp->regs->pc); JSAtom *pcatom; if (op == JSOP_LENGTH) { pcatom = cx->runtime->atomState.lengthAtom; } else { ptrdiff_t pcoff = (JOF_TYPE(js_CodeSpec[op].format) == JOF_SLOTATOM) ? SLOTNO_LEN : 0; - GET_ATOM_FROM_BYTECODE(cx->fp->script, pc, pcoff, pcatom); + GET_ATOM_FROM_BYTECODE(cx->fp->script, cx->fp->regs->pc, pcoff, pcatom); } JS_ASSERT(entry->kpc == (jsbytecode *) pcatom); JS_ASSERT(entry->kshape == jsuword(aobj)); #endif if (aobj != globalObj && !obj_ins->isconstp()) { - guard(true, addName(lir->ins2i(LIR_eq, obj_ins, entry->kshape), "guard(kobj)"), + guard(true, + addName(lir->ins2i(LIR_eq, obj_ins, entry->kshape), "guard_kobj"), BRANCH_EXIT); } } @@ -7724,26 +7764,25 @@ TraceRecorder::test_property_cache(JSObject* obj, LIns* obj_ins, JSObject*& obj2 // For any hit that goes up the scope and/or proto chains, we will need to // guard on the shape of the object containing the property. if (PCVCAP_TAG(entry->vcap) >= 1) { - jsuword vcap = entry->vcap; - uint32 vshape = PCVCAP_SHAPE(vcap); JS_ASSERT(OBJ_SHAPE(obj2) == vshape); LIns* obj2_ins; if (PCVCAP_TAG(entry->vcap) == 1) { // Duplicate the special case in PROPERTY_CACHE_TEST. - obj2_ins = stobj_get_fslot(obj_ins, JSSLOT_PROTO); + obj2_ins = addName(stobj_get_fslot(obj_ins, JSSLOT_PROTO), "proto"); guard(false, lir->ins_eq0(obj2_ins), BRANCH_EXIT); } else { obj2_ins = INS_CONSTPTR(obj2); } map_ins = map(obj2_ins); + LIns* ops_ins; if (!map_is_native(obj2->map, map_ins, ops_ins)) ABORT_TRACE("non-native map"); LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)), - "shape"); + "obj2_shape"); guard(true, - addName(lir->ins2i(LIR_eq, shape_ins, vshape), "guard(vshape)(test_property_cache)"), + addName(lir->ins2i(LIR_eq, shape_ins, vshape), "guard_vshape"), BRANCH_EXIT); } @@ -7801,16 +7840,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_set(LIns* obj_ins, JSScopeProperty* sprop, LIns*& dslots_ins, LIns* v_ins) -{ - if (SPROP_HAS_STUB_SETTER(sprop) && sprop->slot != SPROP_INVALID_SLOT) { - stobj_set_slot(obj_ins, sprop->slot, dslots_ins, v_ins); - return JSRS_CONTINUE; - } - ABORT_TRACE("unallocated or non-stub sprop"); -} - JSRecordingStatus TraceRecorder::native_get(LIns* obj_ins, LIns* pobj_ins, JSScopeProperty* sprop, LIns*& dslots_ins, LIns*& v_ins) @@ -8703,6 +8732,83 @@ TraceRecorder::newArray(JSObject* ctor, uint32 argc, jsval* argv, jsval* rval) return JSRS_CONTINUE; } +JS_REQUIRES_STACK void +TraceRecorder::propagateFailureToBuiltinStatus(LIns* ok_ins, LIns*& status_ins) +{ + /* + * Check the boolean return value (ok_ins) of a native JSNative, + * JSFastNative, or JSPropertyOp hook for failure. On failure, set the + * JSBUILTIN_ERROR bit of cx->builtinStatus. + * + * If the return value (ok_ins) is true, status' == status. Otherwise + * status' = status | JSBUILTIN_ERROR. We calculate (rval&1)^1, which is 1 + * if rval is JS_FALSE (error), and then shift that by 1, which is the log2 + * of JSBUILTIN_ERROR. + */ + JS_STATIC_ASSERT(((JS_TRUE & 1) ^ 1) << 1 == 0); + JS_STATIC_ASSERT(((JS_FALSE & 1) ^ 1) << 1 == JSBUILTIN_ERROR); + status_ins = lir->ins2(LIR_or, + status_ins, + lir->ins2i(LIR_lsh, + lir->ins2i(LIR_xor, + lir->ins2i(LIR_and, ok_ins, 1), + 1), + 1)); + lir->insStorei(status_ins, lirbuf->state, (int) offsetof(InterpState, builtinStatus)); +} + +JS_REQUIRES_STACK void +TraceRecorder::emitNativePropertyOp(JSScope* scope, JSScopeProperty* sprop, LIns* obj_ins, + 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)); + + // Take snapshot for js_DeepBail and store it in cx->bailExit. + VMSideExit* exit = snapshot(DEEP_BAIL_EXIT); + lir->insStorei(INS_CONSTPTR(exit), cx_ins, offsetof(JSContext, bailExit)); + + // Tell nanojit not to discard or defer stack writes before this call. + LIns* guardRec = createGuardRecord(exit); + lir->insGuard(LIR_xbarrier, guardRec, guardRec); + + // It is unsafe to pass the address of an object slot as the out parameter, + // because the getter or setter could end up resizing the object's dslots. + // Instead, use a word of stack and root it in nativeVp. + LIns* vp_ins = lir->insAlloc(sizeof(jsval)); + lir->insStorei(vp_ins, cx_ins, offsetof(JSContext, nativeVp)); + lir->insStorei(INS_CONST(1), cx_ins, offsetof(JSContext, nativeVpLen)); + if (setflag) + lir->insStorei(boxed_ins, vp_ins, 0); + + CallInfo* ci = (CallInfo*) lir->insSkip(sizeof(struct CallInfo))->payload(); + ci->_address = uintptr_t(setflag ? sprop->setter : sprop->getter); + ci->_argtypes = ARGSIZE_LO | ARGSIZE_LO << 2 | ARGSIZE_LO << 4 | ARGSIZE_LO << 6 | ARGSIZE_LO << 8; + ci->_cse = ci->_fold = 0; + ci->_abi = ABI_CDECL; +#ifdef DEBUG + ci->_name = "JSPropertyOp"; +#endif + LIns* args[] = { vp_ins, INS_CONSTWORD(SPROP_USERID(sprop)), obj_ins, cx_ins }; + LIns* ok_ins = lir->insCall(ci, args); + + // Unroot the vp. + lir->insStorei(INS_CONSTPTR(NULL), cx_ins, offsetof(JSContext, nativeVp)); + + // Guard that the call succeeded and builtinStatus is still 0. + // If the native op succeeds but we deep-bail here, the result value is + // lost! Therefore this can only be used for setters of shared properties. + // In that case we ignore the result value anyway. + LIns* status_ins = lir->insLoad(LIR_ld, + lirbuf->state, + (int) offsetof(InterpState, builtinStatus)); + propagateFailureToBuiltinStatus(ok_ins, status_ins); + guard(true, lir->ins_eq0(status_ins), STATUS_EXIT); + + // Re-load the value--but this is currently unused, so commented out. + //boxed_ins = lir->insLoad(LIR_ldp, vp_ins, 0); +} + JS_REQUIRES_STACK JSRecordingStatus TraceRecorder::emitNativeCall(JSTraceableNative* known, uintN argc, LIns* args[]) { @@ -9261,30 +9367,90 @@ TraceRecorder::record_JSOP_SETPROP() return JSRS_CONTINUE; } +/* Emit a specialized, inlined copy of js_NativeSet. */ JS_REQUIRES_STACK JSRecordingStatus -TraceRecorder::record_SetPropHit(JSPropCacheEntry* entry, JSScopeProperty* sprop) +TraceRecorder::nativeSet(JSObject* obj, LIns* obj_ins, JSScopeProperty* sprop, + jsval v, LIns* v_ins) +{ + JSScope* scope = OBJ_SCOPE(obj); + uint32 slot = sprop->slot; + + /* + * We do not trace assignment to properties that have both a nonstub 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 + * samples differ. A mere guard is not enough, because you can't just bail + * off trace in the middle of a property assignment without storing the + * value and making the stack right. + * + * If obj is the global object, there are two additional problems. We would + * have to emit still more code to store the result in the object (not the + * native global frame) if the setter returned successfully after + * deep-bailing. And we would have to cope if the run-time type of the + * 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. + */ + JS_ASSERT(SPROP_HAS_STUB_SETTER(sprop) || slot == SPROP_INVALID_SLOT); + + // Box the value to be stored, if necessary. + LIns* boxed_ins = NULL; + if (!SPROP_HAS_STUB_SETTER(sprop) || (slot != SPROP_INVALID_SLOT && obj != globalObj)) { + boxed_ins = v_ins; + box_jsval(v, boxed_ins); + } + + // Call the setter, if any. + if (!SPROP_HAS_STUB_SETTER(sprop)) + 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)); + JS_ASSERT(!(sprop->attrs & JSPROP_SHARED)); + if (obj == globalObj) { + if (!lazilyImportGlobalSlot(slot)) + ABORT_TRACE("lazy import of global slot failed"); + + // If we called a native setter, unbox the result. + if (!SPROP_HAS_STUB_SETTER(sprop)) { + v_ins = boxed_ins; + unbox_jsval(STOBJ_GET_SLOT(obj, slot), v_ins, snapshot(BRANCH_EXIT)); + } + set(&STOBJ_GET_SLOT(obj, slot), v_ins); + } else { + LIns* dslots_ins = NULL; + stobj_set_slot(obj_ins, slot, dslots_ins, boxed_ins); + } + } + + return JSRS_CONTINUE; +} + +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::setProp(jsval &l, JSPropCacheEntry* entry, JSScopeProperty* sprop, + jsval &v, LIns*& v_ins) { if (entry == JS_NO_PROP_CACHE_FILL) ABORT_TRACE("can't trace uncacheable property set"); - if (PCVCAP_TAG(entry->vcap) >= 1) - ABORT_TRACE("can't trace inherited property set"); + JS_ASSERT_IF(PCVCAP_TAG(entry->vcap) >= 1, sprop->attrs & JSPROP_SHARED); + if (!SPROP_HAS_STUB_SETTER(sprop) && sprop->slot != SPROP_INVALID_SLOT) + ABORT_TRACE("can't trace set of property with setter and slot"); + if (sprop->attrs & JSPROP_SETTER) + ABORT_TRACE("can't trace JavaScript function setter"); - jsbytecode* pc = cx->fp->regs->pc; - JS_ASSERT(entry->kpc == pc); - - jsval& r = stackval(-1); - jsval& l = stackval(-2); + // These two cases are actually errors and can't be cached. + JS_ASSERT(!(sprop->attrs & JSPROP_GETTER)); // getter without setter + JS_ASSERT(!(sprop->attrs & JSPROP_READONLY)); JS_ASSERT(!JSVAL_IS_PRIMITIVE(l)); JSObject* obj = JSVAL_TO_OBJECT(l); LIns* obj_ins = get(&l); JSScope* scope = OBJ_SCOPE(obj); - JS_ASSERT(scope->owned()); - JS_ASSERT(scope->has(sprop)); - - if (!isValidSlot(scope, sprop)) - return JSRS_STOP; + JS_ASSERT_IF(entry->vcap == PCVCAP_MAKE(entry->kshape, 0, 0), scope->has(sprop)); /* * Setting a function-valued property might need to rebrand the object; we @@ -9292,57 +9458,54 @@ TraceRecorder::record_SetPropHit(JSPropCacheEntry* entry, JSScopeProperty* sprop * separating functions into the trace-time type TT_FUNCTION will save the * day! */ - if (scope->branded() && VALUE_IS_FUNCTION(cx, r)) + if (scope->branded() && VALUE_IS_FUNCTION(cx, v)) ABORT_TRACE("can't trace function-valued property set in branded scope"); - if (obj == globalObj) { - JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, scope)); - uint32 slot = sprop->slot; - if (!lazilyImportGlobalSlot(slot)) - ABORT_TRACE("lazy import of global slot failed"); + // Find obj2. If entry->adding(), the TAG bits are all 0. + JSObject* obj2 = obj; + for (jsuword i = PCVCAP_TAG(entry->vcap) >> PCVCAP_PROTOBITS; i; i--) + obj2 = OBJ_GET_PARENT(cx, obj2); + for (jsuword j = PCVCAP_TAG(entry->vcap) & PCVCAP_PROTOMASK; j; j--) + obj2 = OBJ_GET_PROTO(cx, obj2); + scope = OBJ_SCOPE(obj2); + JS_ASSERT_IF(entry->adding(), obj2 == obj); - LIns* r_ins = get(&r); - set(&STOBJ_GET_SLOT(obj, slot), r_ins); - - JS_ASSERT(*pc != JSOP_INITPROP); - if (pc[JSOP_SETPROP_LENGTH] != JSOP_POP) - set(&l, r_ins); - return JSRS_CONTINUE; - } - - // The global object's shape is guarded at trace entry, all others need a guard here. + // Guard before anything else. LIns* map_ins = map(obj_ins); - LIns* ops_ins; - if (!map_is_native(obj->map, map_ins, ops_ins, offsetof(JSObjectOps, setProperty))) - ABORT_TRACE("non-native map"); + CHECK_STATUS(guardNativePropertyOp(obj, map_ins)); + jsuword pcval; + CHECK_STATUS(guardPropertyCacheHit(obj_ins, map_ins, obj, obj2, entry, pcval)); + JS_ASSERT(scope->object == obj2); + JS_ASSERT(scope->has(sprop)); + JS_ASSERT_IF(obj2 != obj, sprop->attrs & JSPROP_SHARED); - LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)), "shape"); - guard(true, - addName(lir->ins2i(LIR_eq, shape_ins, entry->kshape), "guard(kshape)(record_SetPropHit)"), - BRANCH_EXIT); - - uint32 vshape = PCVCAP_SHAPE(entry->vcap); - if (entry->kshape != vshape) { - LIns *vshape_ins = lir->insLoad(LIR_ld, - lir->insLoad(LIR_ldp, cx_ins, offsetof(JSContext, runtime)), - offsetof(JSRuntime, protoHazardShape)); - guard(true, - addName(lir->ins2i(LIR_eq, vshape_ins, vshape), "guard(vshape)(record_SetPropHit)"), - MISMATCH_EXIT); + // Add a property to the object if necessary. + if (entry->adding()) { + JS_ASSERT(!(sprop->attrs & JSPROP_SHARED)); + if (obj == globalObj) + ABORT_TRACE("adding a property to the global object"); LIns* args[] = { INS_CONSTPTR(sprop), obj_ins, cx_ins }; LIns* ok_ins = lir->insCall(&js_AddProperty_ci, args); guard(false, lir->ins_eq0(ok_ins), OOM_EXIT); } - LIns* dslots_ins = NULL; - LIns* v_ins = get(&r); - LIns* boxed_ins = v_ins; - box_jsval(r, boxed_ins); - CHECK_STATUS(native_set(obj_ins, sprop, dslots_ins, boxed_ins)); + v_ins = get(&v); + return nativeSet(obj, obj_ins, sprop, v, v_ins); +} +JS_REQUIRES_STACK JSRecordingStatus +TraceRecorder::record_SetPropHit(JSPropCacheEntry* entry, JSScopeProperty* sprop) +{ + jsval& r = stackval(-1); + jsval& l = stackval(-2); + LIns* v_ins; + CHECK_STATUS(setProp(l, entry, sprop, r, v_ins)); + + jsbytecode* pc = cx->fp->regs->pc; if (*pc != JSOP_INITPROP && pc[JSOP_SETPROP_LENGTH] != JSOP_POP) set(&l, v_ins); + return JSRS_CONTINUE; } @@ -10097,22 +10260,24 @@ TraceRecorder::record_NativeCallComplete() jsbytecode* pc = cx->fp->regs->pc; JS_ASSERT(pendingTraceableNative); - JS_ASSERT(*pc == JSOP_CALL || *pc == JSOP_APPLY || *pc == JSOP_NEW); + JS_ASSERT(*pc == JSOP_CALL || *pc == JSOP_APPLY || *pc == JSOP_NEW || *pc == JSOP_SETPROP); jsval& v = stackval(-1); LIns* v_ins = get(&v); - /* At this point the generated code has already called the native function - and we can no longer fail back to the original pc location (JSOP_CALL) - because that would cause the interpreter to re-execute the native - function, which might have side effects. - - Instead, the snapshot() call below sees that we are currently parked on - a traceable native's JSOP_CALL instruction, and it will advance the pc - to restore by the length of the current opcode. If the native's return - type is jsval, snapshot() will also indicate in the type map that the - element on top of the stack is a boxed value which doesn't need to be - boxed if the type guard generated by unbox_jsval() fails. */ + /* + * At this point the generated code has already called the native function + * and we can no longer fail back to the original pc location (JSOP_CALL) + * because that would cause the interpreter to re-execute the native + * function, which might have side effects. + * + * Instead, the snapshot() call below sees that we are currently parked on + * a traceable native's JSOP_CALL instruction, and it will advance the pc + * to restore by the length of the current opcode. If the native's return + * type is jsval, snapshot() will also indicate in the type map that the + * element on top of the stack is a boxed value which doesn't need to be + * boxed if the type guard generated by unbox_jsval() fails. + */ if (JSTN_ERRTYPE(pendingTraceableNative) == FAIL_STATUS) { // Keep cx->bailExit null when it's invalid. @@ -10145,27 +10310,9 @@ TraceRecorder::record_NativeCallComplete() } set(&v, v_ins); - /* - * If this is a generic traceable native invocation, propagate the boolean return - * value of the native into builtinStatus. If the return value (v_ins) - * is true, status' == status. Otherwise status' = status | JSBUILTIN_ERROR. - * We calculate (rval&1)^1, which is 1 if rval is JS_FALSE (error), and then - * shift that by 1 which is JSBUILTIN_ERROR. - */ - JS_STATIC_ASSERT((1 - JS_TRUE) << 1 == 0); - JS_STATIC_ASSERT((1 - JS_FALSE) << 1 == JSBUILTIN_ERROR); - status = lir->ins2(LIR_or, - status, - lir->ins2i(LIR_lsh, - lir->ins2i(LIR_xor, - lir->ins2i(LIR_and, ok_ins, 1), - 1), - 1)); - lir->insStorei(status, lirbuf->state, (int) offsetof(InterpState, builtinStatus)); + propagateFailureToBuiltinStatus(ok_ins, status); } - guard(true, - lir->ins_eq0(status), - STATUS_EXIT); + guard(true, lir->ins_eq0(status), STATUS_EXIT); } JSRecordingStatus ok = JSRS_CONTINUE; @@ -10303,9 +10450,8 @@ TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32& slot, LIns*& v_ins) return JSRS_CONTINUE; } - /* Insist if setting on obj being the directly addressed object. */ - uint32 setflags = (cs.format & (JOF_SET | JOF_INCDEC | JOF_FOR)); - LIns* dslots_ins = NULL; + uint32 setflags = (cs.format & (JOF_INCDEC | JOF_FOR)); + JS_ASSERT(!(cs.format & JOF_SET)); /* Don't trace getter or setter calls, our caller wants a direct slot. */ if (PCVAL_IS_SPROP(pcval)) { @@ -10364,11 +10510,12 @@ TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32& slot, LIns*& v_ins) * obj_ins the last proto-load. */ while (obj != obj2) { - obj_ins = stobj_get_slot(obj_ins, JSSLOT_PROTO, dslots_ins); + obj_ins = stobj_get_fslot(obj_ins, JSSLOT_PROTO); obj = STOBJ_GET_PROTO(obj); } } + LIns* dslots_ins = NULL; v_ins = stobj_get_slot(obj_ins, slot, dslots_ins); unbox_jsval(STOBJ_GET_SLOT(obj, slot), v_ins, snapshot(BRANCH_EXIT)); diff --git a/js/src/jstracer.h b/js/src/jstracer.h index 82a1b13cdb4..61dca54ecd8 100644 --- a/js/src/jstracer.h +++ b/js/src/jstracer.h @@ -687,6 +687,15 @@ class TraceRecorder : public avmplus::GCObject { nanojit::LIns*& ops_ins, size_t op_offset = 0); JS_REQUIRES_STACK JSRecordingStatus test_property_cache(JSObject* obj, nanojit::LIns* obj_ins, JSObject*& obj2, jsuword& pcval); + JS_REQUIRES_STACK JSRecordingStatus guardNativePropertyOp(JSObject* aobj, + nanojit::LIns* map_ins); + JS_REQUIRES_STACK JSRecordingStatus guardPropertyCacheHit(nanojit::LIns* obj_ins, + nanojit::LIns* map_ins, + JSObject* aobj, + JSObject* obj2, + JSPropCacheEntry* entry, + jsuword& pcval); + void stobj_set_fslot(nanojit::LIns *obj_ins, unsigned slot, nanojit::LIns* v_ins, const char *name); void stobj_set_dslot(nanojit::LIns *obj_ins, unsigned slot, nanojit::LIns*& dslots_ins, @@ -704,8 +713,6 @@ class TraceRecorder : public avmplus::GCObject { stobj_get_fslot(obj_ins, JSSLOT_PRIVATE), lir->insImmPtr((void*) ~mask)); } - JSRecordingStatus native_set(nanojit::LIns* obj_ins, JSScopeProperty* sprop, - nanojit::LIns*& dslots_ins, nanojit::LIns* v_ins); JSRecordingStatus native_get(nanojit::LIns* obj_ins, nanojit::LIns* pobj_ins, JSScopeProperty* sprop, nanojit::LIns*& dslots_ins, nanojit::LIns*& v_ins); @@ -722,6 +729,13 @@ class TraceRecorder : public avmplus::GCObject { JS_REQUIRES_STACK JSRecordingStatus getProp(jsval& v); JS_REQUIRES_STACK JSRecordingStatus getThis(nanojit::LIns*& this_ins); + JS_REQUIRES_STACK JSRecordingStatus nativeSet(JSObject* obj, nanojit::LIns* obj_ins, + JSScopeProperty* sprop, + jsval v, nanojit::LIns* v_ins); + JS_REQUIRES_STACK JSRecordingStatus setProp(jsval &l, JSPropCacheEntry* entry, + JSScopeProperty* sprop, + jsval &v, nanojit::LIns*& v_ins); + JS_REQUIRES_STACK void box_jsval(jsval v, nanojit::LIns*& v_ins); JS_REQUIRES_STACK void unbox_jsval(jsval v, nanojit::LIns*& v_ins, VMSideExit* exit); JS_REQUIRES_STACK bool guardClass(JSObject* obj, nanojit::LIns* obj_ins, JSClass* clasp, @@ -748,8 +762,15 @@ class TraceRecorder : public avmplus::GCObject { jsval* rval); JS_REQUIRES_STACK JSRecordingStatus interpretedFunctionCall(jsval& fval, JSFunction* fun, uintN argc, bool constructing); + JS_REQUIRES_STACK void propagateFailureToBuiltinStatus(nanojit::LIns *ok_ins, + nanojit::LIns *&status_ins); JS_REQUIRES_STACK JSRecordingStatus emitNativeCall(JSTraceableNative* known, uintN argc, nanojit::LIns* args[]); + JS_REQUIRES_STACK void emitNativePropertyOp(JSScope* scope, + JSScopeProperty* sprop, + nanojit::LIns* obj_ins, + bool setflag, + nanojit::LIns* boxed_ins); JS_REQUIRES_STACK JSRecordingStatus callTraceableNative(JSFunction* fun, uintN argc, bool constructing); JS_REQUIRES_STACK JSRecordingStatus callNative(uintN argc, JSOp mode); diff --git a/js/src/trace-test.js b/js/src/trace-test.js index df5f7746560..5525267faef 100644 --- a/js/src/trace-test.js +++ b/js/src/trace-test.js @@ -5497,6 +5497,21 @@ testOwnPropertyWithInOperator.jitstats = { }; test(testEliminatedGuardWithinAnchor); +function testNativeSetter() { + var re = /foo/; + var N = RUNLOOP + 10; + for (var i = 0; i < N; i++) + re.lastIndex = i; + assertEq(re.lastIndex, N - 1); +} +testNativeSetter.jitstats = { + recorderStarted: 1, + recorderAborted: 0, + traceTriggered: 1, + sideExitIntoInterpreter: 1 +}; +test(testNativeSetter); + /***************************************************************************** * * * _____ _ _ _____ ______ _____ _______ *