From 5f4afe36c260b47f6862064f60650d6c65103ed6 Mon Sep 17 00:00:00 2001 From: "brendan%mozilla.org" Date: Tue, 7 Feb 2006 19:51:29 +0000 Subject: [PATCH] Avoid nesting js_Interpret for heavyweight and other hard-case interpreted function calls (325960, r=mrbkap). --- js/src/jsapi.c | 4 +- js/src/jsinterp.c | 389 ++++++++++++++++++++++++++++------------------ js/src/jsinterp.h | 19 ++- 3 files changed, 249 insertions(+), 163 deletions(-) diff --git a/js/src/jsapi.c b/js/src/jsapi.c index 287dc09c87a2..0d2fb04ffda0 100644 --- a/js/src/jsapi.c +++ b/js/src/jsapi.c @@ -3487,8 +3487,10 @@ js_generic_native_method_dispatcher(JSContext *cx, JSObject *obj, * the 'this' param if no args. */ JS_ASSERT(cx->fp->argv == argv); - if (!js_ComputeThis(cx, JSVAL_TO_OBJECT(argv[-1]), cx->fp)) + tmp = js_ComputeThis(cx, JSVAL_TO_OBJECT(argv[-1]), argv); + if (!tmp) return JS_FALSE; + cx->fp->thisp = tmp; /* * Protect against argc - 1 underflowing below. By calling js_ComputeThis, diff --git a/js/src/jsinterp.c b/js/src/jsinterp.c index 6b3d5ba1d082..25431f545937 100644 --- a/js/src/jsinterp.c +++ b/js/src/jsinterp.c @@ -67,10 +67,6 @@ #include "jsscript.h" #include "jsstr.h" -#if JS_HAS_JIT -#include "jsjit.h" -#endif - #if JS_HAS_XML_SUPPORT #include "jsxml.h" #endif @@ -349,7 +345,7 @@ static JSClass prop_iterator_class = { CHECK_VOID_TOSTRING(cx, v); \ *vp = v; \ } else { \ - SAVE_SP(fp); \ + SAVE_SP_AND_PC(fp); \ CHECK_EAGER_TOSTRING(hint); \ ok = OBJ_DEFAULT_VALUE(cx, JSVAL_TO_OBJECT(v), hint, vp); \ if (!ok) \ @@ -491,8 +487,8 @@ js_SetLocalVariable(JSContext *cx, JSObject *obj, jsval id, jsval *vp) return JS_TRUE; } -JSBool -js_ComputeThis(JSContext *cx, JSObject *thisp, JSStackFrame *fp) +JSObject * +js_ComputeThis(JSContext *cx, JSObject *thisp, jsval *argv) { JSObject *parent; @@ -500,11 +496,7 @@ js_ComputeThis(JSContext *cx, JSObject *thisp, JSStackFrame *fp) /* Some objects (e.g., With) delegate 'this' to another object. */ thisp = OBJ_THIS_OBJECT(cx, thisp); if (!thisp) - return JS_FALSE; - - /* Default return value for a constructor is the new object. */ - if (fp->flags & JSFRAME_CONSTRUCTING) - fp->rval = OBJECT_TO_JSVAL(thisp); + return NULL; } else { /* * ECMA requires "the global object", but in the presence of multiple @@ -522,9 +514,8 @@ js_ComputeThis(JSContext *cx, JSObject *thisp, JSStackFrame *fp) * * The alert should display "true". */ - JS_ASSERT(!(fp->flags & JSFRAME_CONSTRUCTING)); - if (JSVAL_IS_PRIMITIVE(fp->argv[-2]) || - !(parent = OBJ_GET_PARENT(cx, JSVAL_TO_OBJECT(fp->argv[-2])))) { + if (JSVAL_IS_PRIMITIVE(argv[-2]) || + !(parent = OBJ_GET_PARENT(cx, JSVAL_TO_OBJECT(argv[-2])))) { thisp = cx->globalObject; } else { /* walk up to find the top-level object */ @@ -533,11 +524,122 @@ js_ComputeThis(JSContext *cx, JSObject *thisp, JSStackFrame *fp) thisp = parent; } } - fp->thisp = thisp; - fp->argv[-1] = OBJECT_TO_JSVAL(thisp); - return JS_TRUE; + argv[-1] = OBJECT_TO_JSVAL(thisp); + return thisp; } +#if JS_HAS_NO_SUCH_METHOD + +static JSBool +NoSuchMethod(JSContext *cx, JSStackFrame *fp, jsval *vp, uint32 flags, + uintN *argcp) +{ + jsval v, *sp, *newsp; + JSObject *thisp, *argsobj; + jsid id; + jsbytecode *pc; + jsatomid atomIndex; + JSAtom *atom; + uintN argc; + JSArena *a; + + /* + * We must call js_ComputeThis here to censor Call objects. A performance + * hit, since we'll call it again in the normal sequence of invoke events, + * but at least it's idempotent. + * + * Normally, we call ComputeThis after all frame members have been set, + * and in particular, after any revision of the callee value at *vp due + * to clasp->convert (see below). This matters because ComputeThis may + * access *vp via fp->argv[-2], to follow the parent chain to a global + * object to use as the 'this' parameter. + * + * Obviously, here in the JSVAL_IS_PRIMITIVE(v) case, there can't be any + * such defaulting of 'this' to callee (v, *vp) ancestor. + */ + JS_ASSERT(JSVAL_IS_PRIMITIVE(vp[0])); + RESTORE_SP(fp); + thisp = js_ComputeThis(cx, JSVAL_TO_OBJECT(vp[1]), vp + 2); + if (!thisp) + return JS_FALSE; + + id = ATOM_TO_JSID(cx->runtime->atomState.noSuchMethodAtom); + if (OBJECT_IS_XML(cx, thisp)) { + JSXMLObjectOps *ops; + + ops = (JSXMLObjectOps *) thisp->map->ops; + thisp = ops->getMethod(cx, thisp, id, &v); + if (!thisp) + return JS_FALSE; + vp[1] = OBJECT_TO_JSVAL(thisp); + } else { + if (!OBJ_GET_PROPERTY(cx, thisp, id, &v)) + return JS_FALSE; + } + if (JSVAL_IS_PRIMITIVE(v)) + goto bad; + + pc = (jsbytecode *) vp[-(intN)fp->script->depth]; + switch ((JSOp) *pc) { + case JSOP_NAME: + case JSOP_GETPROP: +#if JS_HAS_XML_SUPPORT + case JSOP_GETMETHOD: +#endif + atomIndex = GET_ATOM_INDEX(pc); + atom = js_GetAtom(cx, &fp->script->atomMap, atomIndex); + argc = *argcp; + argsobj = js_NewArrayObject(cx, argc, vp + 2); + if (!argsobj) + return JS_FALSE; + + sp = vp + 4; + if (argc < 2) { + a = cx->stackPool.current; + if ((jsuword)sp > a->limit) { + /* + * Arguments must be contiguous, and must include argv[-1] + * and argv[-2], so allocate more stack, advance sp, and + * set newsp[1] to thisp (vp[1]). The other argv elements + * will be set below, using negative indexing from sp. + */ + newsp = js_AllocRawStack(cx, 4, NULL); + if (!newsp) + return JS_FALSE; + newsp[1] = OBJECT_TO_JSVAL(thisp); + sp = newsp + 4; + } else if ((jsuword)sp > a->avail) { + /* + * Inline, optimized version of JS_ARENA_ALLOCATE to claim + * the small number of words not already allocated as part + * of the caller's operand stack. + */ + JS_ArenaCountAllocation(&cx->stackPool, + (jsuword)sp - a->avail); + a->avail = (jsuword)sp; + } + } + + sp[-4] = v; + JS_ASSERT(sp[-3] == OBJECT_TO_JSVAL(thisp)); + sp[-2] = ATOM_KEY(atom); + sp[-1] = OBJECT_TO_JSVAL(argsobj); + SAVE_SP(fp); + *argcp = 2; + break; + + default: + goto bad; + } + return JS_TRUE; + +bad: + js_ReportIsNotFunction(cx, vp, flags & JSINVOKE_CONSTRUCT); + return JS_FALSE; +} + +#endif /* JS_HAS_NO_SUCH_METHOD */ + #ifdef DUMP_CALL_TABLE #include "jsclist.h" @@ -916,17 +1018,14 @@ js_Invoke(JSContext *cx, uintN argc, uintN flags) * Set vp to the callee value's stack slot (it's where rval goes). * Once vp is set, control should flow through label out2: to return. * Set frame.rval early so native class and object ops can throw and - * return false, causing a goto out2 with ok set to false. Also set - * frame.flags to flags so that ComputeThis can test bits in it. + * return false, causing a goto out2 with ok set to false. */ vp = sp - (2 + argc); v = *vp; frame.rval = JSVAL_VOID; - frame.flags = flags; - thisp = JSVAL_TO_OBJECT(vp[1]); /* - * A callee must be an object reference, unless its |this| parameter + * A callee must be an object reference, unless its 'this' parameter * implements the __noSuchMethod__ method, in which case that method will * be called like so: * @@ -938,114 +1037,21 @@ js_Invoke(JSContext *cx, uintN argc, uintN flags) */ if (JSVAL_IS_PRIMITIVE(v)) { #if JS_HAS_NO_SUCH_METHOD - jsid id; - jsbytecode *pc; - jsatomid atomIndex; - JSAtom *atom; - JSObject *argsobj; - JSArena *a; - if (!fp->script || (flags & JSINVOKE_INTERNAL)) goto bad; - - /* - * We must ComputeThis here to censor Call objects; performance hit, - * but at least it's idempotent. - * - * Normally, we call ComputeThis after all frame members have been - * set, and in particular, after any revision of the callee value at - * *vp due to clasp->convert (see below). This matters because - * ComputeThis may access *vp via fp->argv[-2], to follow the parent - * chain to a global object to use as the |this| parameter. - * - * Obviously, here in the JSVAL_IS_PRIMITIVE(v) case, there can't be - * any such defaulting of |this| to callee (v, *vp) ancestor. - */ - frame.argv = vp + 2; - ok = js_ComputeThis(cx, thisp, &frame); + ok = NoSuchMethod(cx, fp, vp, flags, &argc); if (!ok) goto out2; - thisp = frame.thisp; - - id = ATOM_TO_JSID(cx->runtime->atomState.noSuchMethodAtom); - if (OBJECT_IS_XML(cx, thisp)) { - JSXMLObjectOps *ops; - - ops = (JSXMLObjectOps *) thisp->map->ops; - thisp = ops->getMethod(cx, thisp, id, &v); - if (!thisp) { - ok = JS_FALSE; - goto out2; - } - vp[1] = OBJECT_TO_JSVAL(thisp); - } else { - ok = OBJ_GET_PROPERTY(cx, thisp, id, &v); - } - if (!ok) - goto out2; - if (JSVAL_IS_PRIMITIVE(v)) - goto bad; - - pc = (jsbytecode *) vp[-(intN)fp->script->depth]; - switch ((JSOp) *pc) { - case JSOP_NAME: - case JSOP_GETPROP: -#if JS_HAS_XML_SUPPORT - case JSOP_GETMETHOD: -#endif - atomIndex = GET_ATOM_INDEX(pc); - atom = js_GetAtom(cx, &fp->script->atomMap, atomIndex); - argsobj = js_NewArrayObject(cx, argc, vp + 2); - if (!argsobj) { - ok = JS_FALSE; - goto out2; - } - - sp = vp + 4; - if (argc < 2) { - a = cx->stackPool.current; - if ((jsuword)sp > a->limit) { - /* - * Arguments must be contiguous, and must include argv[-1] - * and argv[-2], so allocate more stack, advance sp, and - * set newsp[1] to thisp (vp[1]). The other argv elements - * will be set below, using negative indexing from sp. - */ - newsp = js_AllocRawStack(cx, 4, NULL); - if (!newsp) { - ok = JS_FALSE; - goto out2; - } - newsp[1] = OBJECT_TO_JSVAL(thisp); - sp = newsp + 4; - } else if ((jsuword)sp > a->avail) { - /* - * Inline, optimized version of JS_ARENA_ALLOCATE to claim - * the small number of words not already allocated as part - * of the caller's operand stack. - */ - JS_ArenaCountAllocation(&cx->stackPool, - (jsuword)sp - a->avail); - a->avail = (jsuword)sp; - } - } - - sp[-4] = v; - JS_ASSERT(sp[-3] == OBJECT_TO_JSVAL(thisp)); - sp[-2] = ATOM_KEY(atom); - sp[-1] = OBJECT_TO_JSVAL(argsobj); - fp->sp = sp; - argc = 2; - break; - - default: - goto bad; - } + RESTORE_SP(fp); + v = *vp; #else goto bad; #endif } + /* Load thisp after potentially calling NoSuchMethod, which may set it. */ + thisp = JSVAL_TO_OBJECT(vp[1]); + funobj = JSVAL_TO_OBJECT(v); parent = OBJ_GET_PARENT(cx, funobj); clasp = OBJ_GET_CLASS(cx, funobj); @@ -1121,13 +1127,20 @@ have_fun: frame.spbase = NULL; frame.sharpDepth = 0; frame.sharpArray = NULL; + frame.flags = flags; frame.dormantNext = NULL; frame.xmlNamespace = NULL; /* Compute the 'this' parameter and store it in frame as frame.thisp. */ - ok = js_ComputeThis(cx, thisp, &frame); - if (!ok) + frame.thisp = js_ComputeThis(cx, thisp, frame.argv); + if (!frame.thisp) { + ok = JS_FALSE; goto out2; + } + + /* Default return value for a constructor is the new object. */ + if (flags & JSINVOKE_CONSTRUCT) + frame.rval = OBJECT_TO_JSVAL(thisp); /* From here on, control must flow through label out: to return. */ cx->fp = &frame; @@ -2167,7 +2180,7 @@ interrupt: } /* Store the return value in the caller's operand frame. */ - vp = fp->argv - 2; + vp = ifp->rvp; *vp = fp->rval; /* Restore cx->fp and release the inline frame's space. */ @@ -3272,7 +3285,7 @@ interrupt: } else { /* * Get the constructor prototype object for this function. - * Use the nominal |this| parameter slot, vp[1], as a local + * Use the nominal 'this' parameter slot, vp[1], as a local * root to protect this prototype, in case it has no other * strong refs. */ @@ -3677,17 +3690,28 @@ interrupt: vp = sp - (argc + 2); lval = *vp; SAVE_SP_AND_PC(fp); +#if JS_HAS_NO_SUCH_METHOD + if (JSVAL_IS_PRIMITIVE(lval)) { + ok = NoSuchMethod(cx, fp, vp, 0, &argc); + if (!ok) + goto out; + RESTORE_SP(fp); + lval = *vp; + } +#endif if (JSVAL_IS_FUNCTION(cx, lval) && (obj = JSVAL_TO_OBJECT(lval), fun = (JSFunction *) JS_GetPrivate(cx, obj), - fun->interpreted && - !(fun->flags & (JSFUN_HEAVYWEIGHT | JSFUN_BOUND_METHOD)) && - argc >= (uintN)fun->nargs)) + fun->interpreted)) /* inline_call: */ { - uintN nframeslots, nvars; + uintN nframeslots, nvars, nslots, missing; + JSArena *a; + jsuword avail, nbytes; + JSBool overflow; void *newmark; + jsval *rvp; JSInlineFrame *newifp; JSInterpreterHook hook; @@ -3699,32 +3723,69 @@ interrupt: goto out; } -#if JS_HAS_JIT - /* ZZZbe should do this only if interpreted often enough. */ - ok = jsjit_Compile(cx, fun); - if (!ok) - goto out; -#endif - - /* Compute the number of stack slots needed for fun. */ - nframeslots = (sizeof(JSInlineFrame) + sizeof(jsval) - 1) - / sizeof(jsval); + /* Compute the total number of stack slots needed for fun. */ + nframeslots = JS_HOWMANY(sizeof(JSInlineFrame), sizeof(jsval)); nvars = fun->u.i.nvars; script = fun->u.i.script; depth = (jsint) script->depth; + nslots = nframeslots + nvars + 2 * depth; - /* Allocate the frame and space for vars and operands. */ - newsp = js_AllocRawStack(cx, nframeslots + nvars + 2 * depth, - &newmark); - if (!newsp) { - ok = JS_FALSE; - goto bad_inline_call; + /* Allocate missing expected args adjacent to actual args. */ + missing = (fun->nargs > argc) ? fun->nargs - argc : 0; + a = cx->stackPool.current; + avail = a->avail; + newmark = (void *) avail; + if (missing) { + newsp = sp + missing; + overflow = (jsuword) newsp > a->limit; + if (overflow) + nslots += 2 + argc + missing; + else if ((jsuword) newsp > avail) + avail = a->avail = (jsuword) newsp; } +#ifdef __GNUC__ + else overflow = JS_FALSE; /* suppress bogus gcc warnings */ +#endif + + /* Allocate the inline frame with its vars and operand slots. */ + newsp = (jsval *) avail; + nbytes = nslots * sizeof(jsval); + avail += nbytes; + if (avail <= a->limit) { + a->avail = avail; + } else { + JS_ARENA_ALLOCATE_CAST(newsp, jsval *, &cx->stackPool, + nbytes); + if (!newsp) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_STACK_OVERFLOW, + (fp && fp->fun) + ? JS_GetFunctionName(fp->fun) + : "script"); + goto bad_inline_call; + } + } + + /* Move args if missing overflow arena a, push missing args. */ + rvp = vp; + if (missing) { + if (overflow) { + memcpy(newsp, vp, (2 + argc) * sizeof(jsval)); + vp = newsp; + sp = vp + 2 + argc; + newsp = sp + missing; + } + do { + PUSH(JSVAL_VOID); + } while (--missing != 0); + } + + /* Claim space for the stack frame and initialize it. */ newifp = (JSInlineFrame *) newsp; newsp += nframeslots; - - /* Initialize the stack frame. */ - memset(newifp, 0, sizeof(JSInlineFrame)); + newifp->frame.callobj = NULL; + newifp->frame.argsobj = NULL; + newifp->frame.varobj = NULL; newifp->frame.script = script; newifp->frame.fun = fun; newifp->frame.argc = argc; @@ -3733,12 +3794,24 @@ interrupt: newifp->frame.nvars = nvars; newifp->frame.vars = newsp; newifp->frame.down = fp; - newifp->frame.scopeChain = OBJ_GET_PARENT(cx, obj); + newifp->frame.annotation = NULL; + newifp->frame.scopeChain = parent = OBJ_GET_PARENT(cx, obj); + newifp->frame.sharpDepth = 0; + newifp->frame.sharpArray = NULL; + newifp->frame.flags = 0; + newifp->frame.dormantNext = NULL; + newifp->frame.xmlNamespace = NULL; + newifp->rvp = rvp; newifp->mark = newmark; /* Compute the 'this' parameter now that argv is set. */ - ok = js_ComputeThis(cx, JSVAL_TO_OBJECT(vp[1]), &newifp->frame); - if (!ok) { + newifp->frame.thisp = + js_ComputeThis(cx, + (fun->flags & JSFUN_BOUND_METHOD) + ? parent + : JSVAL_TO_OBJECT(vp[1]), + newifp->frame.argv); + if (!newifp->frame.thisp) { js_FreeRawStack(cx, newmark); goto bad_inline_call; } @@ -3760,6 +3833,15 @@ interrupt: newifp->hookData = hook(cx, &newifp->frame, JS_TRUE, 0, cx->runtime->callHookData); LOAD_INTERRUPT_HANDLER(rt); + } else { + newifp->hookData = NULL; + } + + /* Scope with a call object parented by the callee's parent. */ + if ((fun->flags & JSFUN_HEAVYWEIGHT) && + !js_GetCallObject(cx, &newifp->frame, parent)) { + ok = JS_FALSE; + goto out; } /* Switch to new version if currentVersion wasn't overridden. */ @@ -3786,6 +3868,7 @@ interrupt: bad_inline_call: script = fp->script; depth = (jsint) script->depth; + ok = JS_FALSE; goto out; } @@ -4539,6 +4622,7 @@ interrupt: /* Lookup id in order to check for redeclaration problems. */ id = ATOM_TO_JSID(atom); + SAVE_SP_AND_PC(fp); ok = js_CheckRedeclaration(cx, obj, id, attrs, &obj2, &prop); if (!ok) goto out; @@ -4664,6 +4748,7 @@ interrupt: * as well as multiple HTML script tags. */ parent = fp->varobj; + SAVE_SP_AND_PC(fp); ok = js_CheckRedeclaration(cx, parent, id, attrs, NULL, NULL); if (ok) { ok = OBJ_DEFINE_PROPERTY(cx, parent, id, rval, diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index 9d6288c3a739..b844afdcd85e 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -48,7 +48,9 @@ JS_BEGIN_EXTERN_C /* - * JS stack frame, allocated on the C stack. + * JS stack frame, may be allocated on the C stack by native callers. Always + * allocated on cx->stackPool for calls from the interpreter to an interpreted + * function. */ struct JSStackFrame { JSObject *callobj; /* lazily created Call object */ @@ -77,6 +79,7 @@ struct JSStackFrame { typedef struct JSInlineFrame { JSStackFrame frame; /* base struct */ + jsval *rvp; /* ptr to caller's return value slot */ void *mark; /* mark before inline frame */ void *hookData; /* debugger call hook data */ JSVersion callerVersion; /* dynamic version of calling script */ @@ -257,18 +260,14 @@ extern void js_DumpCallTable(JSContext *cx); #endif /* - * Compute the 'this' parameter and store it in frame as frame.thisp. + * Compute the 'this' parameter for a call with nominal 'this' given by thisp + * and arguments including argv[-1] (nominal 'this') and argv[-2] (callee). * Activation objects ("Call" objects not created with "new Call()", i.e., * "Call" objects that have private data) may not be referred to by 'this', - * as dictated by ECMA. - * - * N.B.: fp->argv must be set, fp->argv[-1] the nominal 'this' parameter as - * a jsval, and fp->argv[-2] must be the callee object reference, usually a - * function object. Also, fp->flags must contain JSFRAME_CONSTRUCTING if we - * are preparing for a constructor call. + * per ECMA-262, so js_ComputeThis censors them. */ -extern JSBool -js_ComputeThis(JSContext *cx, JSObject *thisp, JSStackFrame *fp); +extern JSObject * +js_ComputeThis(JSContext *cx, JSObject *thisp, jsval *argv); /* * NB: js_Invoke requires that cx is currently running JS (i.e., that cx->fp