diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index bb601cab3b8d..a42a521d172f 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4581,11 +4581,8 @@ JS_PUBLIC_API(JSBool) JS_CallFunction(JSContext *cx, JSObject *obj, JSFunction *fun, uintN argc, jsval *argv, jsval *rval) { - JSBool ok; - CHECK_REQUEST(cx); - ok = js_InternalCall(cx, obj, OBJECT_TO_JSVAL(FUN_OBJECT(fun)), argc, argv, - rval); + JSBool ok = js_InternalCall(cx, obj, OBJECT_TO_JSVAL(FUN_OBJECT(fun)), argc, argv, rval); LAST_FRAME_CHECKS(cx, ok); return ok; } @@ -4609,10 +4606,8 @@ JS_PUBLIC_API(JSBool) JS_CallFunctionValue(JSContext *cx, JSObject *obj, jsval fval, uintN argc, jsval *argv, jsval *rval) { - JSBool ok; - CHECK_REQUEST(cx); - ok = js_InternalCall(cx, obj, fval, argc, argv, rval); + JSBool ok = js_InternalCall(cx, obj, fval, argc, argv, rval); LAST_FRAME_CHECKS(cx, ok); return ok; } diff --git a/js/src/jsapi.h b/js/src/jsapi.h index d646fd9eec52..ed526d3aef4a 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -864,8 +864,7 @@ JS_InitCTypesClass(JSContext *cx, JSObject *global); */ #define JS_CALLEE(cx,vp) ((vp)[0]) #define JS_ARGV_CALLEE(argv) ((argv)[-2]) -#define JS_THIS(cx,vp) JS_ComputeThis(cx, vp) -#define JS_THIS_OBJECT(cx,vp) ((JSObject *) JS_THIS(cx,vp)) +#define JS_THIS_OBJECT(cx,vp) JSVAL_TO_OBJECT(JS_THIS(cx,vp)) #define JS_ARGV(cx,vp) ((vp) + 2) #define JS_RVAL(cx,vp) (*(vp)) #define JS_SET_RVAL(cx,vp,v) (*(vp) = (v)) @@ -873,6 +872,12 @@ JS_InitCTypesClass(JSContext *cx, JSObject *global); extern JS_PUBLIC_API(jsval) JS_ComputeThis(JSContext *cx, jsval *vp); +static JS_ALWAYS_INLINE jsval +JS_THIS(JSContext *cx, jsval *vp) +{ + return JSVAL_IS_PRIMITIVE(vp[1]) ? JS_ComputeThis(cx, vp) : vp[1]; +} + extern JS_PUBLIC_API(void *) JS_malloc(JSContext *cx, size_t nbytes); diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 94eba049e92e..a024db097c72 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -240,17 +240,6 @@ js_GetPrimitiveThis(JSContext *cx, jsval *vp, JSClass *clasp, jsval *thisvp) return JS_TRUE; } -/* Some objects (e.g., With) delegate 'this' to another object. */ -static inline JSObject * -CallThisObjectHook(JSContext *cx, JSObject *obj, jsval *argv) -{ - JSObject *thisp = obj->thisObject(cx); - if (!thisp) - return NULL; - argv[-1] = OBJECT_TO_JSVAL(thisp); - return thisp; -} - /* * ECMA requires "the global object", but in embeddings such as the browser, * which have multiple top-level objects (windows, frames, etc. in the DOM), @@ -269,36 +258,10 @@ CallThisObjectHook(JSContext *cx, JSObject *obj, jsval *argv) JS_STATIC_INTERPRET JSObject * js_ComputeGlobalThis(JSContext *cx, jsval *argv) { - JSObject *thisp; - - if (JSVAL_IS_PRIMITIVE(argv[-2]) || - !JSVAL_TO_OBJECT(argv[-2])->getParent()) { - thisp = cx->globalObject; - } else { - thisp = JSVAL_TO_OBJECT(argv[-2])->getGlobal(); - } - - return CallThisObjectHook(cx, thisp, argv); -} - -static JSObject * -ComputeThis(JSContext *cx, jsval *argv) -{ - JSObject *thisp; - - JS_ASSERT(!JSVAL_IS_NULL(argv[-1])); - if (!JSVAL_IS_OBJECT(argv[-1])) { - if (!js_PrimitiveToObject(cx, &argv[-1])) - return NULL; - thisp = JSVAL_TO_OBJECT(argv[-1]); - return thisp; - } - - thisp = JSVAL_TO_OBJECT(argv[-1]); - if (thisp->getClass() == &js_CallClass || thisp->getClass() == &js_BlockClass) - return js_ComputeGlobalThis(cx, argv); - - return CallThisObjectHook(cx, thisp, argv); + JSObject *thisp = JSVAL_TO_OBJECT(argv[-2])->getGlobal()->thisObject(cx); + if (thisp) + argv[-1] = OBJECT_TO_JSVAL(thisp); + return thisp; } JSObject * @@ -307,7 +270,28 @@ js_ComputeThis(JSContext *cx, jsval *argv) JS_ASSERT(argv[-1] != JSVAL_HOLE); // check for SynthesizeFrame poisoning if (JSVAL_IS_NULL(argv[-1])) return js_ComputeGlobalThis(cx, argv); - return ComputeThis(cx, argv); + if (!JSVAL_IS_OBJECT(argv[-1])) { + if (!js_PrimitiveToObject(cx, &argv[-1])) + return NULL; + return JSVAL_TO_OBJECT(argv[-1]); + } + + JSObject *obj = JSVAL_TO_OBJECT(argv[-1]); + JS_ASSERT(js_IsSaneThisObject(obj)); + return obj; +} + +JSObject * +JSStackFrame::computeThisObject(JSContext *cx) +{ + JS_ASSERT(JSVAL_IS_PRIMITIVE(thisv)); + JS_ASSERT(fun); + + JSObject *obj = js_ComputeThis(cx, argv); + if (!obj) + return NULL; + thisv = OBJECT_TO_JSVAL(obj); + return obj; } #if JS_HAS_NO_SUCH_METHOD @@ -518,26 +502,6 @@ js_Invoke(JSContext *cx, const InvokeArgsGuard &args, uintN flags) } } - if (flags & JSINVOKE_CONSTRUCT) { - JS_ASSERT(!JSVAL_IS_PRIMITIVE(vp[1])); - } else { - /* - * We must call js_ComputeThis in case we are not called from the - * interpreter, where a prior bytecode has computed an appropriate - * |this| already. - * - * But we need to compute |this| eagerly only for so-called "slow" - * (i.e., not fast) native functions. Fast natives must use either - * JS_THIS or JS_THIS_OBJECT, and scripted functions will go through - * the appropriate this-computing bytecode, e.g., JSOP_THIS. - */ - if (native && (!fun || !(fun->flags & JSFUN_FAST_NATIVE))) { - if (!js_ComputeThis(cx, vp + 2)) - return false; - flags |= JSFRAME_COMPUTED_THIS; - } - } - start_call: if (native && fun && fun->isFastNative()) { #ifdef DEBUG_NOT_THROWING @@ -567,7 +531,6 @@ js_Invoke(JSContext *cx, const InvokeArgsGuard &args, uintN flags) nmissing = (minargs > argc ? minargs - argc : 0) + fun->u.n.extra; nvars = 0; } - } else { nvars = nmissing = 0; } @@ -635,6 +598,27 @@ js_Invoke(JSContext *cx, const InvokeArgsGuard &args, uintN flags) return false; } + /* + * Compute |this|. Currently, this must happen after the frame is pushed + * and fp->scopeChain is correct because the thisObject hook may call + * JS_GetScopeChain. + */ + JS_ASSERT_IF(flags & JSINVOKE_CONSTRUCT, !JSVAL_IS_PRIMITIVE(vp[1])); + if (JSVAL_IS_OBJECT(vp[1]) && !(flags & JSINVOKE_CONSTRUCT)) { + /* + * We must call the thisObject hook in case we are not called from the + * interpreter, where a prior bytecode has computed an appropriate + * |this| already. + */ + JSObject *thisp = JSVAL_TO_OBJECT(vp[1]); + thisp = thisp ? thisp : funobj->getGlobal(); + thisp = thisp->thisObject(cx); + if (!thisp) + return false; + fp->thisv = vp[1] = OBJECT_TO_JSVAL(thisp); + } + JS_ASSERT_IF(!JSVAL_IS_PRIMITIVE(vp[1]), js_IsSaneThisObject(JSVAL_TO_OBJECT(vp[1]))); + /* Call the hook if present after we fully initialized the frame. */ JSInterpreterHook hook = cx->debugHooks->callHook; void *hookData = NULL; @@ -788,7 +772,7 @@ js_Execute(JSContext *cx, JSObject *const chain, JSScript *script, fp->argsobj = down->argsobj; fp->fun = (script->staticLevel > 0) ? down->fun : NULL; fp->thisv = down->thisv; - fp->flags = flags | (down->flags & JSFRAME_COMPUTED_THIS); + fp->flags = flags; fp->argc = down->argc; fp->argv = down->argv; fp->annotation = down->annotation; @@ -809,7 +793,7 @@ js_Execute(JSContext *cx, JSObject *const chain, JSScript *script, fp->argsobj = NULL; fp->fun = NULL; /* Ininitialize fp->thisv after pushExecuteFrame. */ - fp->flags = flags | JSFRAME_COMPUTED_THIS; + fp->flags = flags; fp->argc = 0; fp->argv = NULL; fp->annotation = NULL; @@ -2192,6 +2176,8 @@ js_Interpret(JSContext *cx) script = fp->script; JS_ASSERT(!script->isEmpty()); JS_ASSERT(script->length > 1); + JS_ASSERT(JSVAL_IS_OBJECT(fp->thisv)); + JS_ASSERT_IF(!fp->fun, !JSVAL_IS_NULL(fp->thisv)); /* Count of JS function calls that nest in this C js_Interpret frame. */ inlineCallCount = 0; diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index 760122e53f43..261f143faf8d 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -59,8 +59,7 @@ typedef struct JSFrameRegs { /* JS stack frame flags. */ enum JSFrameFlags { JSFRAME_CONSTRUCTING = 0x01, /* frame is for a constructor invocation */ - JSFRAME_COMPUTED_THIS = 0x02, /* frame.thisv was computed already and - JSVAL_IS_OBJECT(thisv) */ + JSFRAME_OVERRIDE_ARGS = 0x02, /* overridden arguments local variable */ JSFRAME_ASSIGNING = 0x04, /* a complex (not simplex JOF_ASSIGNING) op is currently assigning to a property */ JSFRAME_DEBUGGER = 0x08, /* frame for JS_EvaluateInStackFrame */ @@ -68,7 +67,6 @@ enum JSFrameFlags { JSFRAME_FLOATING_GENERATOR = 0x20, /* frame copy stored in a generator obj */ JSFRAME_YIELDING = 0x40, /* js_Interpret dispatched JSOP_YIELD */ JSFRAME_GENERATOR = 0x80, /* frame belongs to generator-iterator */ - JSFRAME_OVERRIDE_ARGS = 0x100, /* overridden arguments local variable */ JSFRAME_SPECIAL = JSFRAME_DEBUGGER | JSFRAME_EVAL }; @@ -89,7 +87,22 @@ struct JSStackFrame JSVAL_OBJECT */ JSScript *script; /* script being interpreted */ JSFunction *fun; /* function being called or null */ - jsval thisv; /* "this" pointer if in method */ + + /* + * The value of |this| in this stack frame, or JSVAL_NULL if |this| is to + * be computed lazily on demand. + * + * thisv is eagerly initialized for non-function-call frames and qualified + * method calls, but lazily initialized in most unqualified function calls. + * See getThisObject(). + * + * Usually if argv != NULL then thisv == argv[-1], but natives may assign + * to argv[-1]. Also, obj_eval can trigger a special case where two stack + * frames have the same argv. If one of the frames fills in both argv[-1] + * and thisv, the other frame's thisv is left null. + */ + jsval thisv; + uintN argc; /* actual argument count */ jsval *argv; /* base of argument stack slots */ jsval rval; /* function return value */ @@ -211,6 +224,9 @@ struct JSStackFrame } return false; } + +private: + JSObject *computeThisObject(JSContext *cx); }; namespace js { @@ -257,10 +273,9 @@ js_GetPrimitiveThis(JSContext *cx, jsval *vp, JSClass *clasp, jsval *thisvp); /* * For a call with arguments argv including argv[-1] (nominal |this|) and - * argv[-2] (callee) replace null |this| with callee's parent, replace - * primitive values with the equivalent wrapper objects and censor activation - * objects as, per ECMA-262, they may not be referred to by |this|. argv[-1] - * must not be a JSVAL_VOID. + * argv[-2] (callee) replace null |this| with callee's parent and replace + * primitive values with the equivalent wrapper objects. argv[-1] must not be + * JSVAL_VOID or an activation object. */ extern JSObject * js_ComputeThis(JSContext *cx, jsval *argv); @@ -449,14 +464,7 @@ JS_END_EXTERN_C inline JSObject * JSStackFrame::getThisObject(JSContext *cx) { - if (flags & JSFRAME_COMPUTED_THIS) - return JSVAL_TO_OBJECT(thisv); /* JSVAL_COMPUTED_THIS invariant */ - JSObject* obj = js_ComputeThis(cx, argv); - if (!obj) - return NULL; - thisv = OBJECT_TO_JSVAL(obj); - flags |= JSFRAME_COMPUTED_THIS; - return obj; + return JSVAL_IS_PRIMITIVE(thisv) ? computeThisObject(cx) : JSVAL_TO_OBJECT(thisv); } #endif /* jsinterp_h___ */ diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 049dd0349bbf..474a3dc8d52c 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -6404,7 +6404,9 @@ js_DumpAtom(JSAtom *atom) void dumpValue(jsval val) { - if (JSVAL_IS_NULL(val)) { + if ((val & 0xfffffff0) == 0xdadadad0) { + fprintf(stderr, "**uninitialized** %p", (void *) val); + } else if (JSVAL_IS_NULL(val)) { fprintf(stderr, "null"); } else if (JSVAL_IS_VOID(val)) { fprintf(stderr, "undefined"); @@ -6638,8 +6640,6 @@ js_DumpStackFrame(JSContext *cx, JSStackFrame *start) fprintf(stderr, " none"); if (fp->flags & JSFRAME_CONSTRUCTING) fprintf(stderr, " constructing"); - if (fp->flags & JSFRAME_COMPUTED_THIS) - fprintf(stderr, " computed_this"); if (fp->flags & JSFRAME_ASSIGNING) fprintf(stderr, " assigning"); if (fp->flags & JSFRAME_DEBUGGER) diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 5878de5a14b3..66f31de5b0dc 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -1092,6 +1092,25 @@ js_IsCacheableNonGlobalScope(JSObject *obj) return cacheable; } +#ifdef DEBUG +/* + * Used in assertions only. False if obj is a special object which must be + * censored and thus can't be the value of 'this'. + */ +inline bool +js_IsSaneThisObject(JSObject *obj) +{ + extern JS_FRIEND_DATA(JSClass) js_CallClass; + extern JS_FRIEND_DATA(JSClass) js_DeclEnvClass; + + JSClass *clasp = obj->getClass(); + return clasp != &js_CallClass && + clasp != &js_BlockClass && + clasp != &js_DeclEnvClass && + clasp != &js_WithClass; +} +#endif + /* * If cacheResult is false, return JS_NO_PROP_CACHE_FILL on success. */ diff --git a/js/src/jsops.cpp b/js/src/jsops.cpp index 18660e434a7b..15f39002afe9 100644 --- a/js/src/jsops.cpp +++ b/js/src/jsops.cpp @@ -216,8 +216,10 @@ BEGIN_CASE(JSOP_STOP) } JS_ASSERT(regs.sp == StackBase(fp)); - if ((fp->flags & JSFRAME_CONSTRUCTING) && JSVAL_IS_PRIMITIVE(fp->rval)) + if ((fp->flags & JSFRAME_CONSTRUCTING) && JSVAL_IS_PRIMITIVE(fp->rval)) { + JS_ASSERT(!JSVAL_IS_PRIMITIVE(fp->thisv)); fp->rval = fp->thisv; + } ok = JS_TRUE; if (inlineCallCount) inline_return: @@ -268,8 +270,10 @@ BEGIN_CASE(JSOP_STOP) * passed in via |this|, and instrument this constructor invocation. */ if (fp->flags & JSFRAME_CONSTRUCTING) { - if (JSVAL_IS_PRIMITIVE(fp->rval)) + if (JSVAL_IS_PRIMITIVE(fp->rval)) { + JS_ASSERT(!JSVAL_IS_PRIMITIVE(fp->thisv)); fp->rval = fp->thisv; + } JS_RUNTIME_METER(cx->runtime, constructs); } @@ -2066,6 +2070,8 @@ BEGIN_CASE(JSOP_APPLY) *disp = newfp; } JS_ASSERT(!JSFUN_BOUND_METHOD_TEST(fun->flags)); + JS_ASSERT_IF(!JSVAL_IS_PRIMITIVE(vp[1]), + js_IsSaneThisObject(JSVAL_TO_OBJECT(vp[1]))); newfp->thisv = vp[1]; newfp->imacpc = NULL; @@ -2172,6 +2178,29 @@ BEGIN_CASE(JSOP_SETCALL) goto error; END_CASE(JSOP_SETCALL) +#define SLOW_PUSH_THISV(cx, obj) \ + JS_BEGIN_MACRO \ + JSClass *clasp; \ + JSObject *thisp = obj; \ + if (!thisp->getParent() || \ + (clasp = thisp->getClass()) == &js_CallClass || \ + clasp == &js_BlockClass || \ + clasp == &js_DeclEnvClass) { \ + /* Normal case: thisp is global or an activation record. */ \ + /* Callee determines 'this'. */ \ + thisp = NULL; \ + } else { \ + /* thisp is a With object or, even stranger, a plain nonglobal */ \ + /* object on the scope chain. Call the thisObject hook now: */ \ + /* since we are pushing a non-null thisv, the callee will */ \ + /* assume 'this' was computed. */ \ + thisp = thisp->thisObject(cx); \ + if (!thisp) \ + goto error; \ + } \ + PUSH_OPND(OBJECT_TO_JSVAL(thisp)); \ + JS_END_MACRO + BEGIN_CASE(JSOP_NAME) BEGIN_CASE(JSOP_CALLNAME) { @@ -2184,19 +2213,31 @@ BEGIN_CASE(JSOP_CALLNAME) ASSERT_VALID_PROPERTY_CACHE_HIT(0, obj, obj2, entry); if (entry->vword.isObject()) { rval = entry->vword.toJsval(); - goto do_push_rval; - } - - if (entry->vword.isSlot()) { + } else if (entry->vword.isSlot()) { slot = entry->vword.toSlot(); JS_ASSERT(slot < obj2->scope()->freeslot); rval = obj2->lockedGetSlot(slot); - goto do_push_rval; + } else { + JS_ASSERT(entry->vword.isSprop()); + sprop = entry->vword.toSprop(); + NATIVE_GET(cx, obj, obj2, sprop, JSGET_METHOD_BARRIER, &rval); } - JS_ASSERT(entry->vword.isSprop()); - sprop = entry->vword.toSprop(); - goto do_native_get; + // Push results, the same as below, but with a property cache hit there + // is no need to test for the unusual and uncacheable case where the + // caller determines 'this'. +#ifdef DEBUG + JSClass *clasp; + JS_ASSERT(!obj->getParent() || + (clasp = obj->getClass()) == &js_CallClass || + clasp == &js_BlockClass || + clasp == &js_DeclEnvClass); +#endif + PUSH_OPND(rval); + if (op == JSOP_CALLNAME) + PUSH_OPND(JSVAL_NULL); + len = JSOP_NAME_LENGTH; + DO_NEXT_OP(len); } id = ATOM_TO_JSID(atom); @@ -2221,15 +2262,13 @@ BEGIN_CASE(JSOP_CALLNAME) goto error; } else { sprop = (JSScopeProperty *)prop; - do_native_get: NATIVE_GET(cx, obj, obj2, sprop, JSGET_METHOD_BARRIER, &rval); obj2->dropProperty(cx, (JSProperty *) sprop); } - do_push_rval: PUSH_OPND(rval); if (op == JSOP_CALLNAME) - PUSH_OPND(OBJECT_TO_JSVAL(obj)); + SLOW_PUSH_THISV(cx, obj); } END_CASE(JSOP_NAME) @@ -3694,7 +3733,7 @@ BEGIN_CASE(JSOP_XMLNAME) goto error; STORE_OPND(-1, rval); if (op == JSOP_CALLXMLNAME) - PUSH_OPND(OBJECT_TO_JSVAL(obj)); + SLOW_PUSH_THISV(cx, obj); END_CASE(JSOP_XMLNAME) BEGIN_CASE(JSOP_DESCENDANTS) diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index ba55886ef27b..cd578eba0175 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -1820,7 +1820,7 @@ FindReplaceLength(JSContext *cx, ReplaceData &rdata, size_t *sizep) /* Push lambda and its 'this' parameter. */ jsval *sp = rdata.args.getvp(); *sp++ = OBJECT_TO_JSVAL(lambda); - *sp++ = OBJECT_TO_JSVAL(lambda->getParent()); + *sp++ = JSVAL_NULL; /* Push $&, $1, $2, ... */ if (!PushRegExpSubstr(cx, cx->regExpStatics.lastMatch, sp)) diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index b857954f80b3..55cd9e6b79a8 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -3343,8 +3343,6 @@ FlushNativeStackFrame(JSContext* cx, unsigned callDepth, const TraceType* mp, do fp->scopeChain->setPrivate(fp); } fp->thisv = fp->argv[-1]; - if (fp->flags & JSFRAME_CONSTRUCTING) // constructors always compute 'this' - fp->flags |= JSFRAME_COMPUTED_THIS; } } } @@ -9463,78 +9461,54 @@ TraceRecorder::unbox_jsval(jsval v, LIns* v_ins, VMSideExit* exit) JS_REQUIRES_STACK RecordingStatus TraceRecorder::getThis(LIns*& this_ins) { - /* - * JSStackFrame::getThisObject updates cx->fp->argv[-1], so sample it into 'original' first. - */ - jsval original = JSVAL_NULL; - if (cx->fp->argv) { - original = cx->fp->argv[-1]; - if (!JSVAL_IS_PRIMITIVE(original)) { - if (JSVAL_TO_OBJECT(original)->hasClass(&js_WithClass)) - RETURN_STOP("can't trace getThis on With object"); - guardNotClass(get(&cx->fp->argv[-1]), &js_WithClass, snapshot(MISMATCH_EXIT), - ACC_OTHER); - } - } + JSStackFrame *fp = cx->fp; + JS_ASSERT_IF(fp->argv, fp->argv[-1] == fp->thisv); - JSObject* thisObj = cx->fp->getThisObject(cx); - if (!thisObj) - RETURN_ERROR("fp->getThisObject failed"); + if (!fp->fun) { + // Top-level code. It is an invariant of the interpreter that fp->thisv + // is non-null. Furthermore, we would not be recording if globalObj + // were not at the end of the scope chain, so `this` can only be one + // object, which we can burn into the trace. - /* In global code, bake in the global object as 'this' object. */ - if (!cx->fp->callee()) { - JS_ASSERT(callDepth == 0); - this_ins = INS_CONSTOBJ(thisObj); + JS_ASSERT(!fp->argv); + JS_ASSERT(!JSVAL_IS_PRIMITIVE(fp->thisv)); - /* - * We don't have argv[-1] in global code, so we don't update the - * tracker here. - */ +#ifdef DEBUG + JSObject *obj = globalObj->thisObject(cx); + if (!obj) + RETURN_ERROR("thisObject hook failed"); + JS_ASSERT(JSVAL_TO_OBJECT(fp->thisv) == obj); +#endif + + this_ins = INS_CONSTOBJ(JSVAL_TO_OBJECT(fp->thisv)); return RECORD_CONTINUE; } - jsval& thisv = cx->fp->argv[-1]; + jsval thisv = fp->argv[-1]; + JS_ASSERT(thisv == fp->thisv || JSVAL_IS_NULL(fp->thisv)); JS_ASSERT(JSVAL_IS_OBJECT(thisv)); + JS_ASSERT(fp->callee()->getGlobal() == globalObj); - /* - * Traces type-specialize between null and objects, so if we currently see - * a null value in argv[-1], this trace will only match if we see null at - * runtime as well. Bake in the global object as 'this' object, updating - * the tracker as well. We can only detect this condition prior to calling - * JSStackFrame::getThisObject, since it updates the interpreter's copy of - * argv[-1]. - */ - JSClass* clasp = NULL; - if (JSVAL_IS_NULL(original) || - (((clasp = JSVAL_TO_OBJECT(original)->getClass()) == &js_CallClass) || - (clasp == &js_BlockClass))) { - if (clasp) - guardClass(get(&thisv), clasp, snapshot(BRANCH_EXIT), ACC_OTHER); - JS_ASSERT(!JSVAL_IS_PRIMITIVE(thisv)); - if (thisObj != globalObj) - RETURN_STOP("global object was wrapped while recording"); - this_ins = INS_CONSTOBJ(thisObj); - set(&thisv, this_ins); + if (!JSVAL_IS_NULL(thisv)) { + // fp->argv[-1] has already been computed. Since the type- + // specialization of traces distinguishes between null and objects, the + // same will be true at run time (or we won't get this far). + this_ins = get(&fp->argv[-1]); return RECORD_CONTINUE; } - this_ins = get(&thisv); - - JSObject* wrappedGlobal = globalObj->thisObject(cx); - if (!wrappedGlobal) - RETURN_ERROR("globalObj->thisObject hook threw in getThis"); - - /* - * The only unwrapped object that needs to be wrapped that we can get here - * is the global object obtained throught the scope chain. - */ - this_ins = lir->insChoose(lir->insEqP_0(stobj_get_parent(this_ins)), - INS_CONSTOBJ(wrappedGlobal), - this_ins, avmplus::AvmCore::use_cmov()); + // Compute fp->argv[-1] now. The result is globalObj->thisObject(), which + // is trace-constant. getThisObject writes back to fp->argv[-1], so do the + // same on trace. + JSObject *obj = fp->getThisObject(cx); + if (!obj) + RETURN_ERROR("getThisObject failed"); + JS_ASSERT(fp->argv[-1] == OBJECT_TO_JSVAL(obj)); + this_ins = INS_CONSTOBJ(obj); + set(&fp->argv[-1], this_ins); return RECORD_CONTINUE; } - JS_REQUIRES_STACK void TraceRecorder::guardClassHelper(bool cond, LIns* obj_ins, JSClass* clasp, VMSideExit* exit, AccSet accSet) @@ -12392,7 +12366,7 @@ TraceRecorder::record_JSOP_CALLNAME() NameResult nr; CHECK_STATUS_A(scopeChainProp(obj, vp, ins, nr)); stack(0, ins); - stack(1, INS_CONSTOBJ(globalObj)); + stack(1, INS_NULL()); return ARECORD_CONTINUE; } @@ -12408,7 +12382,7 @@ TraceRecorder::record_JSOP_CALLNAME() JS_ASSERT(pcval.toObject()->isFunction()); stack(0, INS_CONSTOBJ(pcval.toObject())); - stack(1, obj_ins); + stack(1, INS_NULL()); return ARECORD_CONTINUE; } @@ -15137,9 +15111,8 @@ TraceRecorder::record_JSOP_GETTHISPROP() /* * It's safe to just use cx->fp->thisv here because getThis() returns - * ARECORD_STOP if thisv is not available. + * ARECORD_STOP or ARECORD_ERROR if thisv is not available. */ - JS_ASSERT(cx->fp->flags & JSFRAME_COMPUTED_THIS); CHECK_STATUS_A(getProp(JSVAL_TO_OBJECT(cx->fp->thisv), this_ins)); return ARECORD_CONTINUE; } diff --git a/js/src/trace-test/tests/basic/testThis1.js b/js/src/trace-test/tests/basic/testThis1.js new file mode 100644 index 000000000000..5f660d8cb5b8 --- /dev/null +++ b/js/src/trace-test/tests/basic/testThis1.js @@ -0,0 +1,5 @@ +var x = this; +var y; +for (var i = 0; i < 9; i++) + y = this; +assertEq(x, y); diff --git a/js/src/trace-test/tests/basic/testThis2.js b/js/src/trace-test/tests/basic/testThis2.js new file mode 100644 index 000000000000..bef3db4aa880 --- /dev/null +++ b/js/src/trace-test/tests/basic/testThis2.js @@ -0,0 +1,4 @@ +var cx = evalcx(""); +evalcx("function f() {var x; for (var i=0;i<9;i++) x=this; return x;}", cx); +var f = cx.f; +assertEq(f(), cx);