From 8c2b7ca1e2a6b57a6cf11a1a161103ee45076256 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Fri, 20 Aug 2010 13:11:04 -0700 Subject: [PATCH 01/31] Bug 585803: Pass TCF_NEED_MUTABLE_SCRIPT when producing scripts returned via JSAPI functions. r=brendan All scripts produced using JSAPI functions should be able to have JS_NewScriptObject applied to them. However, JS_CompileFile and JS_CompileFileHandleForPrincipals fail to pass TCF_NEED_MUTABLE_SCRIPT, and thus will occasionally return JSScript::emptyScript(); applying JS_NewScriptObject to that causes a crash. --- js/src/jsapi.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index eaab526deb60..f53c3cf95044 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4509,7 +4509,7 @@ JS_CompileFile(JSContext *cx, JSObject *obj, const char *filename) } } - tcflags = JS_OPTIONS_TO_TCFLAGS(cx); + tcflags = JS_OPTIONS_TO_TCFLAGS(cx) | TCF_NEED_MUTABLE_SCRIPT; script = Compiler::compileScript(cx, obj, NULL, NULL, tcflags, NULL, 0, fp, filename, 1); if (fp != stdin) @@ -4527,7 +4527,7 @@ JS_CompileFileHandleForPrincipals(JSContext *cx, JSObject *obj, const char *file CHECK_REQUEST(cx); assertSameCompartment(cx, obj, principals); - tcflags = JS_OPTIONS_TO_TCFLAGS(cx); + tcflags = JS_OPTIONS_TO_TCFLAGS(cx) | TCF_NEED_MUTABLE_SCRIPT; script = Compiler::compileScript(cx, obj, NULL, principals, tcflags, NULL, 0, file, filename, 1); LAST_FRAME_CHECKS(cx, script); From d6af3475c1a2018420b20e930bd136ab2e784bfd Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Fri, 20 Aug 2010 13:11:05 -0700 Subject: [PATCH 02/31] Bug 438633: Give new JSScript objects lifetimes like GCThings. r=brendan Attach script objects immediately in all JSAPI script-creating functions; have JS_NewScriptObject simply return the already-allocated object; and make JS_DestroyScript a no-op. Verify that all scripts given to JSAPI script-consuming functions have objects, or are the canonical empty script object. --- js/src/jsapi.cpp | 60 +++++++++++++++++++++++++++++---------------- js/src/jsobj.cpp | 7 ++++-- js/src/jsscript.cpp | 27 ++++++++++++++++++++ js/src/jsscript.h | 21 +++++++++++++++- js/src/jsxdrapi.cpp | 11 ++++++++- 5 files changed, 101 insertions(+), 25 deletions(-) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index f53c3cf95044..bb8d6c3ad487 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4414,6 +4414,10 @@ JS_CompileUCScriptForPrincipals(JSContext *cx, JSObject *obj, JSPrincipals *prin uint32 tcflags = JS_OPTIONS_TO_TCFLAGS(cx) | TCF_NEED_MUTABLE_SCRIPT; JSScript *script = Compiler::compileScript(cx, obj, NULL, principals, tcflags, chars, length, NULL, filename, lineno); + if (script && !js_NewScriptObject(cx, script)) { + js_DestroyScript(cx, script); + script = NULL; + } LAST_FRAME_CHECKS(cx, script); return script; } @@ -4514,6 +4518,10 @@ JS_CompileFile(JSContext *cx, JSObject *obj, const char *filename) NULL, 0, fp, filename, 1); if (fp != stdin) fclose(fp); + if (script && !js_NewScriptObject(cx, script)) { + js_DestroyScript(cx, script); + script = NULL; + } LAST_FRAME_CHECKS(cx, script); return script; } @@ -4530,6 +4538,10 @@ JS_CompileFileHandleForPrincipals(JSContext *cx, JSObject *obj, const char *file tcflags = JS_OPTIONS_TO_TCFLAGS(cx) | TCF_NEED_MUTABLE_SCRIPT; script = Compiler::compileScript(cx, obj, NULL, principals, tcflags, NULL, 0, file, filename, 1); + if (script && !js_NewScriptObject(cx, script)) { + js_DestroyScript(cx, script); + script = NULL; + } LAST_FRAME_CHECKS(cx, script); return script; } @@ -4543,34 +4555,29 @@ JS_CompileFileHandle(JSContext *cx, JSObject *obj, const char *filename, FILE *f JS_PUBLIC_API(JSObject *) JS_NewScriptObject(JSContext *cx, JSScript *script) { - JSObject *obj; - CHECK_REQUEST(cx); assertSameCompartment(cx, script); if (!script) return NewNonFunction(cx, &js_ScriptClass, NULL, NULL); - JS_ASSERT(!script->u.object); - - { - AutoScriptRooter root(cx, script); - - obj = NewNonFunction(cx, &js_ScriptClass, NULL, NULL); - if (obj) { - obj->setPrivate(script); - script->u.object = obj; -#ifdef CHECK_SCRIPT_OWNER - script->owner = NULL; -#endif - } - } - - return obj; + /* + * This function should only ever be applied to JSScripts that had + * script objects allocated for them when they were created, as + * described in the comment for JSScript::u.object. + */ + JS_ASSERT(script->u.object); + return script->u.object; } JS_PUBLIC_API(JSObject *) JS_GetScriptObject(JSScript *script) { + /* + * This function should only ever be applied to JSScripts that had + * script objects allocated for them when they were created, as + * described in the comment for JSScript::u.object. + */ + JS_ASSERT(script->u.object); return script->u.object; } @@ -4578,8 +4585,17 @@ JS_PUBLIC_API(void) JS_DestroyScript(JSContext *cx, JSScript *script) { CHECK_REQUEST(cx); - assertSameCompartment(cx, script); - js_DestroyScript(cx, script); + + /* + * Originally, JSScript lifetimes were managed explicitly, and this function + * was used to free a JSScript. Now, this function does nothing, and the + * garbage collector manages JSScripts; you must root the JSScript's script + * object (obtained via JS_GetScriptObject) to keep it alive. + * + * However, since the script objects have taken over this responsibility, it + * follows that every script passed here must have a script object. + */ + JS_ASSERT(script->u.object); } JS_PUBLIC_API(JSFunction *) @@ -4741,6 +4757,8 @@ JS_ExecuteScript(JSContext *cx, JSObject *obj, JSScript *script, jsval *rval) CHECK_REQUEST(cx); assertSameCompartment(cx, obj, script); + /* This should receive only scripts handed out via the JSAPI. */ + JS_ASSERT(script == JSScript::emptyScript() || script->u.object); ok = Execute(cx, obj, script, NULL, 0, Valueify(rval)); LAST_FRAME_CHECKS(cx, ok); return ok; @@ -4768,7 +4786,7 @@ JS_EvaluateUCScriptForPrincipals(JSContext *cx, JSObject *obj, } ok = Execute(cx, obj, script, NULL, 0, Valueify(rval)); LAST_FRAME_CHECKS(cx, ok); - JS_DestroyScript(cx, script); + js_DestroyScript(cx, script); return ok; } diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 04af3156427c..5bdde363f1fd 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -6081,8 +6081,11 @@ JSObject::getCompartment(JSContext *cx) return cx->runtime->defaultCompartment; } - // Compile-time Function, Block, and RegExp objects are not parented. - if (clasp == &js_FunctionClass || clasp == &js_BlockClass || clasp == &js_RegExpClass) { + /* + * Script objects and compile-time Function, Block, RegExp objects + * are not parented. + */ + if (clasp == &js_FunctionClass || clasp == &js_BlockClass || clasp == &js_RegExpClass || clasp == &js_ScriptClass) { // This is a bogus answer, but it'll do for now. return cx->runtime->defaultCompartment; } diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 80ae693b8e55..4ce4f64bec92 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1277,6 +1277,33 @@ js_TraceScript(JSTracer *trc, JSScript *script) js_MarkScriptFilename(script->filename); } +JSBool +js_NewScriptObject(JSContext *cx, JSScript *script) +{ + AutoScriptRooter root(cx, script); + + JS_ASSERT(!script->u.object); + JS_ASSERT(script != JSScript::emptyScript()); + + JSObject *obj = NewNonFunction(cx, &js_ScriptClass, NULL, NULL); + if (!obj) + return JS_FALSE; + obj->setPrivate(script); + script->u.object = obj; + + /* + * Clear the object's proto, to avoid entraining stuff. Once we no longer use the parent + * for security checks, then we can clear the parent, too. + */ + obj->clearProto(); + +#ifdef CHECK_SCRIPT_OWNER + script->owner = NULL; +#endif + + return JS_TRUE; +} + typedef struct GSNCacheEntry { JSDHashEntryHdr hdr; jsbytecode *pc; diff --git a/js/src/jsscript.h b/js/src/jsscript.h index f2d18a540eee..11637b49dee5 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -184,7 +184,23 @@ struct JSScript { uint16 staticLevel;/* static level for display maintenance */ JSPrincipals *principals;/* principals for this script */ union { - JSObject *object; /* optional Script-class object wrapper */ + /* + * A script object of class js_ScriptClass, to ensure the script is GC'd. + * - All scripts returned by JSAPI functions (JS_CompileScript, + * JS_CompileFile, etc.) have these objects. + * - Function scripts never have script objects; such scripts are owned + * by their function objects. + * - Temporary scripts created by obj_eval, JS_EvaluateScript, and + * similar functions never have these objects; such scripts are + * explicitly destroyed by the code that created them. + * Debugging API functions (JSDebugHooks::newScriptHook; + * JS_GetFunctionScript) may reveal sans-script-object Function and + * temporary scripts to clients, but clients must never call + * JS_NewScriptObject on such scripts: doing so would double-free them, + * once from the explicit call to js_DestroyScript, and once when the + * script object is garbage collected. + */ + JSObject *object; JSScript *nextToGC; /* next to GC in rt->scriptsToGC list */ } u; #ifdef CHECK_SCRIPT_OWNER @@ -375,6 +391,9 @@ js_DestroyScript(JSContext *cx, JSScript *script); extern void js_TraceScript(JSTracer *trc, JSScript *script); +extern JSBool +js_NewScriptObject(JSContext *cx, JSScript *script); + /* * To perturb as little code as possible, we introduce a js_GetSrcNote lookup * cache without adding an explicit cx parameter. Thus js_GetSrcNote becomes diff --git a/js/src/jsxdrapi.cpp b/js/src/jsxdrapi.cpp index b6c52093d8b8..8e3f3f266717 100644 --- a/js/src/jsxdrapi.cpp +++ b/js/src/jsxdrapi.cpp @@ -671,8 +671,17 @@ JS_XDRScript(JSXDRState *xdr, JSScript **scriptp) { if (!js_XDRScript(xdr, scriptp, true, NULL)) return JS_FALSE; - if (xdr->mode == JSXDR_DECODE) + + if (xdr->mode == JSXDR_DECODE) { js_CallNewScriptHook(xdr->cx, *scriptp, NULL); + if (*scriptp != JSScript::emptyScript() && + !js_NewScriptObject(xdr->cx, *scriptp)) { + js_DestroyScript(xdr->cx, *scriptp); + *scriptp = NULL; + return JS_FALSE; + } + } + return JS_TRUE; } From 45c1c787f715d7c07ffea586f1ebd5fd225f39b4 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Fri, 20 Aug 2010 16:54:58 -0700 Subject: [PATCH 03/31] Bug 588558 - fix FrameRegsIter thinko (r=waldo) --- js/src/jscntxtinlines.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/src/jscntxtinlines.h b/js/src/jscntxtinlines.h index e360468e03e6..eef05154c47d 100644 --- a/js/src/jscntxtinlines.h +++ b/js/src/jscntxtinlines.h @@ -327,8 +327,10 @@ JS_REQUIRES_STACK inline FrameRegsIter::FrameRegsIter(JSContext *cx) { curseg = cx->getCurrentSegment(); - if (JS_UNLIKELY(!curseg || !curseg->isActive())) + if (JS_UNLIKELY(!curseg || !curseg->isActive())) { initSlow(); + return; + } JS_ASSERT(cx->fp); curfp = cx->fp; cursp = cx->regs->sp; From e832454eda167decd8ae91528f34751048ace5db Mon Sep 17 00:00:00 2001 From: Gregor Wagner Date: Fri, 20 Aug 2010 17:23:47 -0700 Subject: [PATCH 04/31] Bug 589216 - TM: fix another crossCompartment call in jsapi-tests r=jorendorff --- js/src/jsapi-tests/testContexts.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/src/jsapi-tests/testContexts.cpp b/js/src/jsapi-tests/testContexts.cpp index 6b7226151a8e..20f094495f0e 100644 --- a/js/src/jsapi-tests/testContexts.cpp +++ b/js/src/jsapi-tests/testContexts.cpp @@ -68,6 +68,8 @@ BEGIN_TEST(testContexts_bug561444) JS_BeginRequest(cx); { jsvalRoot v(cx); + JSAutoCrossCompartmentCall crossCall; + crossCall.enter(cx, d->obj); if (!JS_EvaluateScript(cx, d->obj, d->code, strlen(d->code), __FILE__, __LINE__, v.addr())) return; } From 29d9aa115b95a0ce0f97621a571abf9fa007ca05 Mon Sep 17 00:00:00 2001 From: Jacek Caban Date: Sat, 21 Aug 2010 12:21:24 +0200 Subject: [PATCH 05/31] Bug 587321 - YARR landing broke mingw compilation r=cdleary --- js/src/assembler/wtf/Assertions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/assembler/wtf/Assertions.cpp b/js/src/assembler/wtf/Assertions.cpp index cc18067ec49e..7dcba6470c32 100644 --- a/js/src/assembler/wtf/Assertions.cpp +++ b/js/src/assembler/wtf/Assertions.cpp @@ -64,7 +64,7 @@ static void printf_stderr_common(const char* format, ...) static void printCallSite(const char* file, int line, const char* function) { -#if WTF_PLATFORM_WIN && !WTF_PLATFORM_WINCE && defined _DEBUG +#if WTF_COMPILER_MSVC && !WTF_PLATFORM_WINCE && defined _DEBUG _CrtDbgReport(_CRT_WARN, file, line, NULL, "%s\n", function); #else printf_stderr_common("(%s:%d %s)\n", file, line, function); From d4d8b1fc90a7359cac51ed00d941f3e68c9636ec Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Sat, 21 Aug 2010 15:50:14 -0700 Subject: [PATCH 06/31] Bug 589318 - guard that eval-in-function frames don't access args (r=brendan) --- js/src/jsexn.cpp | 4 ++-- js/src/jsinterp.cpp | 3 ++- js/src/jsinterp.h | 4 ++++ js/src/jstracer.cpp | 19 +++++++++++-------- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index 09a3dffd74e4..1a30b52fdf85 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -293,7 +293,7 @@ InitExnPrivate(JSContext *cx, JSObject *exnObject, JSString *message, stackDepth = 0; valueCount = 0; for (fp = js_GetTopStackFrame(cx); fp; fp = fp->down) { - if (fp->hasFunction() && fp->argv) { + if (fp->hasFunction() && fp->argv && !fp->isEvalFrame()) { Value v = NullValue(); if (checkAccess && !checkAccess(cx, fp->callee(), callerid, JSACC_READ, &v)) { @@ -334,7 +334,7 @@ InitExnPrivate(JSContext *cx, JSObject *exnObject, JSString *message, values = GetStackTraceValueBuffer(priv); elem = priv->stackElems; for (fp = js_GetTopStackFrame(cx); fp != fpstop; fp = fp->down) { - if (!fp->hasFunction()) { + if (!fp->hasFunction() || fp->isEvalFrame()) { elem->funName = NULL; elem->argc = 0; } else { diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 71e3b6e6ad9c..67fc847ab96a 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -813,12 +813,13 @@ Execute(JSContext *cx, JSObject *chain, JSScript *script, JSObject *initialVarObj; if (down) { /* Propagate arg state for eval and the debugger API. */ + JS_ASSERT_IF(down->hasFunction(), down->hasCallObj()); fp->setCallObj(down->maybeCallObj()); fp->setArgsObj(NULL); fp->setFunction((script->staticLevel > 0) ? down->maybeFunction() : NULL); fp->setThisValue(down->getThisValue()); fp->flags = flags | (down->flags & JSFRAME_COMPUTED_THIS); - fp->setNumActualArgs(down->numActualArgs()); + fp->setNumActualArgs(0); fp->argv = down->argv; fp->setAnnotation(down->maybeAnnotation()); fp->setScopeChain(chain); diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index c8b948aa2f9f..88b1f3888fac 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -160,6 +160,7 @@ struct JSStackFrame JSObject* getArgsObj() const { JS_ASSERT(hasArgsObj()); + JS_ASSERT(!isEvalFrame()); return argsobj; } @@ -384,6 +385,7 @@ struct JSStackFrame } size_t numFormalArgs() const { + JS_ASSERT(!isEvalFrame()); return getFunction()->nargs; } @@ -426,6 +428,7 @@ struct JSStackFrame /* Argument count accessors */ size_t numActualArgs() const { + JS_ASSERT(!isEvalFrame()); return argc; } @@ -499,6 +502,7 @@ struct JSStackFrame } bool isDummyFrame() const { return !!(flags & JSFRAME_DUMMY); } + bool isEvalFrame() const { return !!(flags & JSFRAME_EVAL); } /* Contains static assertions for member alignment, don't call. */ inline void staticAsserts(); diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index 25c76b69ee2d..48412bedb295 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -1092,7 +1092,13 @@ Tracker::set(const void* v, LIns* i) static inline jsuint argSlots(JSStackFrame* fp) { - return JS_MAX(fp->numActualArgs(), fp->numFormalArgs()); + return fp->isEvalFrame() ? 0 : JS_MAX(fp->numActualArgs(), fp->numFormalArgs()); +} + +static inline jsuint +numEntryFrameArgs(JSStackFrame* fp) +{ + return fp->isEvalFrame() ? 0 : fp->numActualArgs(); } static inline bool @@ -6102,7 +6108,7 @@ AttemptToExtendTree(JSContext* cx, VMSideExit* anchor, VMSideExit* exitedFrom, j } JS_ASSERT(ngslots >= anchor->numGlobalSlots); bool rv = TraceRecorder::startRecorder(cx, anchor, c, stackSlots, ngslots, typeMap, - exitedFrom, outer, cx->fp->numActualArgs(), + exitedFrom, outer, numEntryFrameArgs(cx->fp), Record_Branch, hits < maxHits); #ifdef MOZ_TRACEVIS if (!rv && tvso) @@ -6151,7 +6157,7 @@ TraceRecorder::recordLoopEdge(JSContext* cx, TraceRecorder* r, uintN& inlineCall JS_ASSERT(r->fragment && !r->fragment->lastIns); TreeFragment* root = r->fragment->root; TreeFragment* first = LookupOrAddLoop(tm, cx->regs->pc, root->globalObj, - root->globalShape, cx->fp->numActualArgs()); + root->globalShape, numEntryFrameArgs(cx->fp)); /* * Make sure the shape of the global object still matches (this might flush @@ -6179,7 +6185,7 @@ TraceRecorder::recordLoopEdge(JSContext* cx, TraceRecorder* r, uintN& inlineCall TreeFragment* outerFragment = root; jsbytecode* outer = (jsbytecode*) outerFragment->ip; uint32 outerArgc = outerFragment->argc; - JS_ASSERT(cx->fp->numActualArgs() == first->argc); + JS_ASSERT(numEntryFrameArgs(cx->fp) == first->argc); AbortRecording(cx, "No compatible inner tree"); return RecordingIfTrue(RecordTree(cx, first, outer, outerArgc, globalSlots, Record_Branch)); @@ -7191,7 +7197,7 @@ MonitorLoopEdge(JSContext* cx, uintN& inlineCallCount, RecordReason reason) } jsbytecode* pc = cx->regs->pc; - uint32 argc = cx->fp->numActualArgs(); + uint32 argc = numEntryFrameArgs(cx->fp); TreeFragment* f = LookupOrAddLoop(tm, pc, globalObj, globalShape, argc); @@ -8230,19 +8236,16 @@ TraceRecorder::callProp(JSObject* obj, JSProperty* prop, jsid id, Value*& vp, uintN slot = uint16(sprop->shortid); vp = NULL; - uintN upvar_slot = SPROP_INVALID_SLOT; JSStackFrame* cfp = (JSStackFrame*) obj->getPrivate(); if (cfp) { if (sprop->getterOp() == js_GetCallArg) { JS_ASSERT(slot < cfp->numFormalArgs()); vp = &cfp->argv[slot]; - upvar_slot = slot; nr.v = *vp; } else if (sprop->getterOp() == js_GetCallVar || sprop->getterOp() == js_GetCallVarChecked) { JS_ASSERT(slot < cfp->getSlotCount()); vp = &cfp->slots()[slot]; - upvar_slot = cx->fp->numFormalArgs() + slot; nr.v = *vp; } else { RETURN_STOP("dynamic property of Call object"); From 120f4a1b1ae3d48e0af79edef584178257c68805 Mon Sep 17 00:00:00 2001 From: Bob Clary Date: Sun, 22 Aug 2010 06:29:06 -0700 Subject: [PATCH 07/31] bug 588310 - temporarily disable js1_5/extensions/regress-336410-2.js for browser on Windows, r=brendan (NPOTB). --- js/src/tests/js1_5/extensions/jstests.list | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/tests/js1_5/extensions/jstests.list b/js/src/tests/js1_5/extensions/jstests.list index 9fb416a73baa..545adbbdd1f3 100644 --- a/js/src/tests/js1_5/extensions/jstests.list +++ b/js/src/tests/js1_5/extensions/jstests.list @@ -49,7 +49,7 @@ skip script regress-335700.js # bug xxx - reftest hang, BigO skip-if(!xulRuntime.shell) script regress-336409-1.js # no results reported. skip-if(!xulRuntime.shell&&xulRuntime.XPCOMABI.match(/x86_64/)) script regress-336409-2.js # fails on 64 bit systems for some reason skip-if(!xulRuntime.shell) script regress-336410-1.js # slow -skip-if(!xulRuntime.shell&&xulRuntime.XPCOMABI.match(/x86_64/)) script regress-336410-2.js # fails on 64 bit systems for some reason +skip-if(!xulRuntime.shell&&(xulRuntime.XPCOMABI.match(/x86_64/)||xulRuntime.OS=="WINNT")) script regress-336410-2.js # fails in browser on 64 bit systems or Windows. script regress-338804-01.js script regress-338804-02.js script regress-338804-03.js From 7a0d76cef5955b2c370c50c4cd2ea28ccd33258d Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Sun, 22 Aug 2010 16:00:20 -0700 Subject: [PATCH 08/31] Move cx->fp to cx->regs->fp, bug 588978. r=lw --- .../activex/src/plugin/LegacyPlugin.cpp | 2 +- js/src/jsapi.cpp | 16 +- js/src/jsbuiltins.cpp | 4 +- js/src/jscntxt.cpp | 72 ++- js/src/jscntxt.h | 130 ++--- js/src/jscntxtinlines.h | 49 +- js/src/jsfun.cpp | 8 +- js/src/jsinterp.cpp | 37 +- js/src/jsinterp.h | 1 + js/src/jsiter.cpp | 4 +- js/src/jsobj.cpp | 29 +- js/src/jsobjinlines.h | 8 +- js/src/jsopcode.cpp | 8 +- js/src/jspropertycache.cpp | 17 +- js/src/jsrecursion.cpp | 37 +- js/src/jsreflect.cpp | 2 +- js/src/jstracer.cpp | 447 +++++++++--------- js/src/jstracer.h | 1 + js/src/jswrapper.cpp | 3 +- js/src/shell/js.cpp | 2 +- 20 files changed, 447 insertions(+), 430 deletions(-) diff --git a/embedding/browser/activex/src/plugin/LegacyPlugin.cpp b/embedding/browser/activex/src/plugin/LegacyPlugin.cpp index 20c58f7dce14..9601c5fb9238 100644 --- a/embedding/browser/activex/src/plugin/LegacyPlugin.cpp +++ b/embedding/browser/activex/src/plugin/LegacyPlugin.cpp @@ -359,7 +359,7 @@ MozAxAutoPushJSContext::MozAxAutoPushJSContext(JSContext *cx, // See if there are any scripts on the stack. // If not, we need to add a dummy frame with a principal. PRBool hasScript = PR_FALSE; - JSStackFrame* tempFP = cx->fp; + JSStackFrame* tempFP = cx->fp(); while (tempFP) { if (tempFP->script) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index bb8d6c3ad487..a8dd1f85d872 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -1822,8 +1822,8 @@ JS_GetGlobalForScopeChain(JSContext *cx) */ VOUCH_DOES_NOT_REQUIRE_STACK(); - if (cx->fp) - return cx->fp->getScopeChain()->getGlobal(); + if (cx->hasfp()) + return cx->fp()->getScopeChain()->getGlobal(); JSObject *scope = cx->globalObject; if (!scope) { @@ -4090,8 +4090,8 @@ JS_CloneFunctionObject(JSContext *cx, JSObject *funobj, JSObject *parent) CHECK_REQUEST(cx); assertSameCompartment(cx, parent); // XXX no funobj for now if (!parent) { - if (cx->fp) - parent = js_GetScopeChain(cx, cx->fp); + if (cx->hasfp()) + parent = js_GetScopeChain(cx, cx->fp()); if (!parent) parent = cx->globalObject; JS_ASSERT(parent); @@ -4298,7 +4298,7 @@ js_generic_native_method_dispatcher(JSContext *cx, JSObject *obj, if (!ComputeThisFromArgv(cx, argv)) return JS_FALSE; js_GetTopStackFrame(cx)->setThisValue(argv[-1]); - JS_ASSERT(cx->fp->argv == argv); + JS_ASSERT(cx->fp()->argv == argv); /* Clear the last parameter in case too few arguments were passed. */ argv[--argc].setUndefined(); @@ -4942,9 +4942,9 @@ JS_IsRunning(JSContext *cx) VOUCH_DOES_NOT_REQUIRE_STACK(); #ifdef JS_TRACER - JS_ASSERT_IF(JS_TRACE_MONITOR(cx).tracecx == cx, cx->fp); + JS_ASSERT_IF(JS_TRACE_MONITOR(cx).tracecx == cx, cx->hasfp()); #endif - JSStackFrame *fp = cx->fp; + JSStackFrame *fp = cx->maybefp(); while (fp && fp->isDummyFrame()) fp = fp->down; return fp != NULL; @@ -4972,7 +4972,7 @@ JS_RestoreFrameChain(JSContext *cx, JSStackFrame *fp) { CHECK_REQUEST(cx); JS_ASSERT_NOT_ON_TRACE(cx); - JS_ASSERT(!cx->fp); + JS_ASSERT(!cx->hasfp()); if (!fp) return; cx->restoreSegment(); diff --git a/js/src/jsbuiltins.cpp b/js/src/jsbuiltins.cpp index 37463802213e..3685af033984 100644 --- a/js/src/jsbuiltins.cpp +++ b/js/src/jsbuiltins.cpp @@ -335,8 +335,8 @@ JS_DEFINE_CALLINFO_4(extern, OBJECT, js_NewNullClosure, CONTEXT, OBJECT, OBJECT, JS_REQUIRES_STACK JSBool FASTCALL js_PopInterpFrame(JSContext* cx, TracerState* state) { - JS_ASSERT(cx->fp && cx->fp->down); - JSStackFrame* const fp = cx->fp; + JS_ASSERT(cx->hasfp() && cx->fp()->down); + JSStackFrame* const fp = cx->fp(); /* * Mirror frame popping code from inline_return in js_Interpret. There are diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 2a36d2ce6fd6..7a962b9d889d 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -110,10 +110,12 @@ StackSegment::contains(const JSStackFrame *fp) const JSStackFrame *start; JSStackFrame *stop; if (isActive()) { - start = cx->fp; + JS_ASSERT(cx->hasfp()); + start = cx->fp(); stop = cx->activeSegment()->initialFrame->down; } else { - start = suspendedFrame; + JS_ASSERT(suspendedRegs && suspendedRegs->fp); + start = suspendedRegs->fp; stop = initialFrame->down; } for (JSStackFrame *f = start; f != stop; f = f->down) { @@ -308,7 +310,9 @@ StackSpace::pushExecuteFrame(JSContext *cx, ExecuteFrameGuard &fg, StackSegment *seg = fg.seg; seg->setPreviousInMemory(currentSegment); currentSegment = seg; - cx->pushSegmentAndFrame(seg, fg.fp, regs); + + regs.fp = fg.fp; + cx->pushSegmentAndFrame(seg, regs); seg->setInitialVarObj(initialVarObj); fg.cx = cx; } @@ -328,7 +332,7 @@ ExecuteFrameGuard::~ExecuteFrameGuard() if (!pushed()) return; JS_ASSERT(cx->activeSegment() == seg); - JS_ASSERT(cx->fp == fp); + JS_ASSERT(cx->maybefp() == fp); cx->stack().popExecuteFrame(cx); } @@ -342,14 +346,13 @@ StackSpace::getSynthesizedSlowNativeFrame(JSContext *cx, StackSegment *&seg, JSS } JS_REQUIRES_STACK void -StackSpace::pushSynthesizedSlowNativeFrame(JSContext *cx, StackSegment *seg, JSStackFrame *fp, - JSFrameRegs ®s) +StackSpace::pushSynthesizedSlowNativeFrame(JSContext *cx, StackSegment *seg, JSFrameRegs ®s) { - JS_ASSERT(!fp->hasScript() && FUN_SLOW_NATIVE(fp->getFunction())); - fp->down = cx->fp; + JS_ASSERT(!regs.fp->hasScript() && FUN_SLOW_NATIVE(regs.fp->getFunction())); + regs.fp->down = cx->maybefp(); seg->setPreviousInMemory(currentSegment); currentSegment = seg; - cx->pushSegmentAndFrame(seg, fp, regs); + cx->pushSegmentAndFrame(seg, regs); seg->setInitialVarObj(NULL); } @@ -358,8 +361,8 @@ StackSpace::popSynthesizedSlowNativeFrame(JSContext *cx) { JS_ASSERT(isCurrentAndActive(cx)); JS_ASSERT(cx->hasActiveSegment()); - JS_ASSERT(currentSegment->getInitialFrame() == cx->fp); - JS_ASSERT(!cx->fp->hasScript() && FUN_SLOW_NATIVE(cx->fp->getFunction())); + JS_ASSERT(currentSegment->getInitialFrame() == cx->fp()); + JS_ASSERT(!cx->fp()->hasScript() && FUN_SLOW_NATIVE(cx->fp()->getFunction())); cx->popSegmentAndFrame(); currentSegment = currentSegment->getPreviousInMemory(); } @@ -1893,7 +1896,7 @@ js_GetCurrentBytecodePC(JSContext* cx) pc = cx->regs ? cx->regs->pc : NULL; if (!pc) return NULL; - imacpc = cx->fp->maybeIMacroPC(); + imacpc = cx->fp()->maybeIMacroPC(); } /* @@ -1911,7 +1914,7 @@ js_CurrentPCIsInImacro(JSContext *cx) VOUCH_DOES_NOT_REQUIRE_STACK(); if (JS_ON_TRACE(cx)) return cx->bailExit->imacpc != NULL; - return cx->fp->hasIMacroPC(); + return cx->fp()->hasIMacroPC(); #else return false; #endif @@ -1956,54 +1959,48 @@ DSTOffsetCache::DSTOffsetCache() JSContext::JSContext(JSRuntime *rt) : runtime(rt), compartment(rt->defaultCompartment), - fp(NULL), regs(NULL), regExpStatics(this), busyArrays(this) {} void -JSContext::pushSegmentAndFrame(js::StackSegment *newseg, JSStackFrame *newfp, - JSFrameRegs &newregs) +JSContext::pushSegmentAndFrame(js::StackSegment *newseg, JSFrameRegs &newregs) { if (hasActiveSegment()) { - JS_ASSERT(fp->savedPC == JSStackFrame::sInvalidPC); - fp->savedPC = regs->pc; - currentSegment->suspend(fp, regs); + JS_ASSERT(regs->fp->savedPC == JSStackFrame::sInvalidPC); + regs->fp->savedPC = regs->pc; + currentSegment->suspend(regs); } newseg->setPreviousInContext(currentSegment); currentSegment = newseg; #ifdef DEBUG - newfp->savedPC = JSStackFrame::sInvalidPC; + newregs.fp->savedPC = JSStackFrame::sInvalidPC; #endif - setCurrentFrame(newfp); setCurrentRegs(&newregs); - newseg->joinContext(this, newfp); + newseg->joinContext(this, newregs.fp); } void JSContext::popSegmentAndFrame() { JS_ASSERT(currentSegment->maybeContext() == this); - JS_ASSERT(currentSegment->getInitialFrame() == fp); - JS_ASSERT(fp->savedPC == JSStackFrame::sInvalidPC); + JS_ASSERT(currentSegment->getInitialFrame() == regs->fp); + JS_ASSERT(regs->fp->savedPC == JSStackFrame::sInvalidPC); currentSegment->leaveContext(); currentSegment = currentSegment->getPreviousInContext(); if (currentSegment) { if (currentSegment->isSaved()) { - setCurrentFrame(NULL); setCurrentRegs(NULL); } else { - setCurrentFrame(currentSegment->getSuspendedFrame()); setCurrentRegs(currentSegment->getSuspendedRegs()); currentSegment->resume(); #ifdef DEBUG - fp->savedPC = JSStackFrame::sInvalidPC; + regs->fp->savedPC = JSStackFrame::sInvalidPC; #endif } } else { - JS_ASSERT(fp->down == NULL); - setCurrentFrame(NULL); + JS_ASSERT(regs->fp->down == NULL); setCurrentRegs(NULL); } } @@ -2012,10 +2009,9 @@ void JSContext::saveActiveSegment() { JS_ASSERT(hasActiveSegment()); - currentSegment->save(fp, regs); - JS_ASSERT(fp->savedPC == JSStackFrame::sInvalidPC); - fp->savedPC = regs->pc; - setCurrentFrame(NULL); + currentSegment->save(regs); + JS_ASSERT(regs->fp->savedPC == JSStackFrame::sInvalidPC); + regs->fp->savedPC = regs->pc; setCurrentRegs(NULL); } @@ -2023,11 +2019,10 @@ void JSContext::restoreSegment() { js::StackSegment *ccs = currentSegment; - setCurrentFrame(ccs->getSuspendedFrame()); setCurrentRegs(ccs->getSuspendedRegs()); ccs->restore(); #ifdef DEBUG - fp->savedPC = JSStackFrame::sInvalidPC; + regs->fp->savedPC = JSStackFrame::sInvalidPC; #endif } @@ -2058,10 +2053,11 @@ JSContext::containingSegment(const JSStackFrame *target) if (!seg) return NULL; - /* The active segments's top frame is cx->fp. */ - if (fp) { + /* The active segments's top frame is cx->regs->fp. */ + if (regs) { + JS_ASSERT(regs->fp); JS_ASSERT(activeSegment() == seg); - JSStackFrame *f = fp; + JSStackFrame *f = regs->fp; JSStackFrame *stop = seg->getInitialFrame()->down; for (; f != stop; f = f->down) { if (f == target) diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index bf9dec26f12a..68274b37b846 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -266,16 +266,16 @@ struct GlobalState { * The frames of a non-empty segment must all be in the same context and thus * each non-empty segment is referred to as being "in" a context. Segments in a * context have an additional state of being either "active" or "suspended". A - * suspended segment |ss| has a "suspended frame" which is snapshot of |cx->fp| + * suspended segment |ss| has a "suspended frame" which is snapshot of |cx->regs| * when the segment was suspended and serves as the current frame of |ss|. * There is at most one active segment in a given context. Segments in a * context execute LIFO and are maintained in a stack. The top of this stack * is the context's "current segment". If a context |cx| has an active segment * |ss|, then: * 1. |ss| is |cx|'s current segment, - * 2. |cx->fp != NULL|, and - * 3. |ss|'s current frame is |cx->fp|. - * Moreover, |cx->fp != NULL| iff |cx| has an active segment. + * 2. |cx->regs != NULL|, and + * 3. |ss|'s current frame is |cx->regs->fp|. + * Moreover, |cx->regs != NULL| iff |cx| has an active segment. * * An empty segment is not associated with any context. Empty segments are * created when there is not an active segment for a context at the top of the @@ -306,9 +306,6 @@ class StackSegment /* The first frame executed in this segment. null iff cx is null */ JSStackFrame *initialFrame; - /* If this segment is suspended, the top of the segment. */ - JSStackFrame *suspendedFrame; - /* If this segment is suspended, |cx->regs| when it was suspended. */ JSFrameRegs *suspendedRegs; @@ -318,17 +315,22 @@ class StackSegment /* Whether this segment was suspended by JS_SaveFrameChain. */ bool saved; + /* Align at 8 bytes on all platforms. */ +#if JS_BITS_PER_WORD == 32 + void *padding; +#endif + /* * To make isActive a single null-ness check, this non-null constant is - * assigned to suspendedFrame when !inContext. + * assigned to suspendedRegs when !inContext. */ -#define NON_NULL_SUSPENDED_FRAME ((JSStackFrame *)0x1) +#define NON_NULL_SUSPENDED_REGS ((JSFrameRegs *)0x1) public: StackSegment() : cx(NULL), previousInContext(NULL), previousInMemory(NULL), - initialFrame(NULL), suspendedFrame(NON_NULL_SUSPENDED_FRAME), - suspendedRegs(NULL), initialVarObj(NULL), saved(false) + initialFrame(NULL), suspendedRegs(NON_NULL_SUSPENDED_REGS), + initialVarObj(NULL), saved(false) { JS_ASSERT(!inContext()); } @@ -356,20 +358,20 @@ class StackSegment bool inContext() const { JS_ASSERT(!!cx == !!initialFrame); - JS_ASSERT_IF(!cx, suspendedFrame == NON_NULL_SUSPENDED_FRAME && !saved); + JS_ASSERT_IF(!cx, suspendedRegs == NON_NULL_SUSPENDED_REGS && !saved); return cx; } bool isActive() const { - JS_ASSERT_IF(!suspendedFrame, cx && !saved); - JS_ASSERT_IF(!cx, suspendedFrame == NON_NULL_SUSPENDED_FRAME); - return !suspendedFrame; + JS_ASSERT_IF(!suspendedRegs, cx && !saved); + JS_ASSERT_IF(!cx, suspendedRegs == NON_NULL_SUSPENDED_REGS); + return !suspendedRegs; } bool isSuspended() const { - JS_ASSERT_IF(!cx || !suspendedFrame, !saved); - JS_ASSERT_IF(!cx, suspendedFrame == NON_NULL_SUSPENDED_FRAME); - return cx && suspendedFrame; + JS_ASSERT_IF(!cx || !suspendedRegs, !saved); + JS_ASSERT_IF(!cx, suspendedRegs == NON_NULL_SUSPENDED_REGS); + return cx && suspendedRegs; } /* Substate of suspended, queryable in any state. */ @@ -385,7 +387,7 @@ class StackSegment JS_ASSERT(!inContext()); this->cx = cx; initialFrame = f; - suspendedFrame = NULL; + suspendedRegs = NULL; JS_ASSERT(isActive()); } @@ -393,7 +395,7 @@ class StackSegment JS_ASSERT(isActive()); this->cx = NULL; initialFrame = NULL; - suspendedFrame = NON_NULL_SUSPENDED_FRAME; + suspendedRegs = NON_NULL_SUSPENDED_REGS; JS_ASSERT(!inContext()); } @@ -401,29 +403,28 @@ class StackSegment return cx; } -#undef NON_NULL_SUSPENDED_FRAME +#undef NON_NULL_SUSPENDED_REGS /* Transitioning between isActive <--> isSuspended */ - void suspend(JSStackFrame *fp, JSFrameRegs *regs) { + void suspend(JSFrameRegs *regs) { JS_ASSERT(isActive()); - JS_ASSERT(fp && contains(fp)); - suspendedFrame = fp; - JS_ASSERT(isSuspended()); + JS_ASSERT(regs && regs->fp && contains(regs->fp)); suspendedRegs = regs; + JS_ASSERT(isSuspended()); } void resume() { JS_ASSERT(isSuspended()); - suspendedFrame = NULL; + suspendedRegs = NULL; JS_ASSERT(isActive()); } /* When isSuspended, transitioning isSaved <--> !isSaved */ - void save(JSStackFrame *fp, JSFrameRegs *regs) { + void save(JSFrameRegs *regs) { JS_ASSERT(!isSuspended()); - suspend(fp, regs); + suspend(regs); saved = true; JS_ASSERT(isSaved()); } @@ -442,21 +443,20 @@ class StackSegment return initialFrame; } - inline JSStackFrame *getCurrentFrame() const; inline JSFrameRegs *getCurrentRegs() const; + inline JSStackFrame *getCurrentFrame() const; /* Data available when isSuspended. */ - JSStackFrame *getSuspendedFrame() const { - JS_ASSERT(isSuspended()); - return suspendedFrame; - } - JSFrameRegs *getSuspendedRegs() const { JS_ASSERT(isSuspended()); return suspendedRegs; } + JSStackFrame *getSuspendedFrame() const { + return suspendedRegs->fp; + } + /* JSContext / js::StackSpace bookkeeping. */ void setPreviousInContext(StackSegment *seg) { @@ -525,14 +525,12 @@ class InvokeFrameGuard { friend class StackSpace; JSContext *cx; /* null implies nothing pushed */ - JSStackFrame *fp; JSFrameRegs regs; JSFrameRegs *prevRegs; public: - InvokeFrameGuard() : cx(NULL), fp(NULL) {} + InvokeFrameGuard() : cx(NULL) {} JS_REQUIRES_STACK ~InvokeFrameGuard(); bool pushed() const { return cx != NULL; } - JSStackFrame *getFrame() { return fp; } JSFrameRegs &getRegs() { return regs; } }; @@ -779,8 +777,7 @@ class StackSpace void getSynthesizedSlowNativeFrame(JSContext *cx, StackSegment *&seg, JSStackFrame *&fp); JS_REQUIRES_STACK - void pushSynthesizedSlowNativeFrame(JSContext *cx, StackSegment *seg, JSStackFrame *fp, - JSFrameRegs ®s); + void pushSynthesizedSlowNativeFrame(JSContext *cx, StackSegment *seg, JSFrameRegs ®s); JS_REQUIRES_STACK void popSynthesizedSlowNativeFrame(JSContext *cx); @@ -1887,26 +1884,32 @@ struct JSContext /* GC heap compartment. */ JSCompartment *compartment; - /* Currently executing frame, set by stack operations. */ - JS_REQUIRES_STACK - JSStackFrame *fp; - - /* - * Currently executing frame's regs, set by stack operations. - * |fp != NULL| iff |regs != NULL| (although regs->pc can be NULL) - */ + /* Currently executing frame and regs, set by stack operations. */ JS_REQUIRES_STACK JSFrameRegs *regs; + /* Current frame accessors. */ + + JSStackFrame* fp() { + JS_ASSERT(regs && regs->fp); + return regs->fp; + } + + JSStackFrame* maybefp() { + JS_ASSERT_IF(regs, regs->fp); + return regs ? regs->fp : NULL; + } + + bool hasfp() { + JS_ASSERT_IF(regs, regs->fp); + return !!regs; + } + private: friend class js::StackSpace; friend bool js::Interpret(JSContext *); - /* 'fp' and 'regs' must only be changed by calling these functions. */ - void setCurrentFrame(JSStackFrame *fp) { - this->fp = fp; - } - + /* 'regs' must only be changed by calling this function. */ void setCurrentRegs(JSFrameRegs *regs) { this->regs = regs; } @@ -1958,7 +1961,7 @@ struct JSContext public: void assertSegmentsInSync() const { #ifdef DEBUG - if (fp) { + if (regs) { JS_ASSERT(currentSegment->isActive()); if (js::StackSegment *prev = currentSegment->getPreviousInContext()) JS_ASSERT(!prev->isActive()); @@ -1971,7 +1974,7 @@ struct JSContext /* Return whether this context has an active segment. */ bool hasActiveSegment() const { assertSegmentsInSync(); - return !!fp; + return !!regs; } /* Assuming there is an active segment, return it. */ @@ -1987,8 +1990,7 @@ struct JSContext } /* Add the given segment to the list as the new active segment. */ - void pushSegmentAndFrame(js::StackSegment *newseg, JSStackFrame *newfp, - JSFrameRegs ®s); + void pushSegmentAndFrame(js::StackSegment *newseg, JSFrameRegs ®s); /* Remove the active segment and make the next segment active. */ void popSegmentAndFrame(); @@ -2009,7 +2011,7 @@ struct JSContext * Search the call stack for the nearest frame with static level targetLevel. */ JSStackFrame *findFrameAtLevel(uintN targetLevel) { - JSStackFrame *fp = this->fp; + JSStackFrame *fp = this->regs->fp; while (true) { JS_ASSERT(fp && fp->hasScript()); if (fp->getScript()->staticLevel == targetLevel) @@ -2268,8 +2270,8 @@ struct JSContext #ifdef DEBUG void assertValidStackDepth(uintN depth) { - JS_ASSERT(0 <= regs->sp - fp->base()); - JS_ASSERT(depth <= uintptr_t(regs->sp - fp->base())); + JS_ASSERT(0 <= regs->sp - regs->fp->base()); + JS_ASSERT(depth <= uintptr_t(regs->sp - regs->fp->base())); } #else void assertValidStackDepth(uintN /*depth*/) {} @@ -2309,8 +2311,8 @@ JSStackFrame::varobj(JSContext *cx) const JS_ALWAYS_INLINE jsbytecode * JSStackFrame::pc(JSContext *cx) const { - JS_ASSERT(cx->containingSegment(this) != NULL); - return (cx->fp == this) ? cx->regs->pc : savedPC; + JS_ASSERT(cx->regs && cx->containingSegment(this) != NULL); + return (cx->regs->fp == this) ? cx->regs->pc : savedPC; } #ifdef JS_THREADSAFE @@ -3187,8 +3189,8 @@ SetPendingException(JSContext *cx, const Value &v); } /* namespace js */ /* - * Get the current cx->fp, first lazily instantiating stack frames if needed. - * (Do not access cx->fp directly except in JS_REQUIRES_STACK code.) + * Get the current frame, first lazily instantiating stack frames if needed. + * (Do not access cx->fp() directly except in JS_REQUIRES_STACK code.) * * Defined in jstracer.cpp if JS_TRACER is defined. */ @@ -3196,7 +3198,7 @@ static JS_FORCES_STACK JS_INLINE JSStackFrame * js_GetTopStackFrame(JSContext *cx) { js::LeaveTrace(cx); - return cx->fp; + return cx->maybefp(); } static JS_INLINE JSBool diff --git a/js/src/jscntxtinlines.h b/js/src/jscntxtinlines.h index eef05154c47d..cd89333ce49c 100644 --- a/js/src/jscntxtinlines.h +++ b/js/src/jscntxtinlines.h @@ -56,13 +56,6 @@ JSContext::ensureGeneratorStackSpace() namespace js { -JS_REQUIRES_STACK JS_ALWAYS_INLINE JSStackFrame * -StackSegment::getCurrentFrame() const -{ - JS_ASSERT(inContext()); - return isActive() ? cx->fp : getSuspendedFrame(); -} - JS_REQUIRES_STACK JS_ALWAYS_INLINE JSFrameRegs * StackSegment::getCurrentRegs() const { @@ -70,6 +63,12 @@ StackSegment::getCurrentRegs() const return isActive() ? cx->regs : getSuspendedRegs(); } +JS_REQUIRES_STACK JS_ALWAYS_INLINE JSStackFrame * +StackSegment::getCurrentFrame() const +{ + return getCurrentRegs()->fp; +} + JS_REQUIRES_STACK inline Value * StackSpace::firstUnused() const { @@ -82,7 +81,8 @@ StackSpace::firstUnused() const Value *sp = seg->getCurrentRegs()->sp; if (invokeArgEnd > sp) { JS_ASSERT(invokeSegment == currentSegment); - JS_ASSERT_IF(seg->maybeContext()->fp, invokeFrame == seg->maybeContext()->fp); + JS_ASSERT_IF(seg->maybeContext()->hasfp(), + invokeFrame == seg->maybeContext()->fp()); return invokeArgEnd; } return sp; @@ -169,7 +169,7 @@ StackSpace::pushInvokeArgs(JSContext *cx, uintN argc, InvokeArgsGuard &ag) ag.prevInvokeSegment = invokeSegment; invokeSegment = currentSegment; ag.prevInvokeFrame = invokeFrame; - invokeFrame = cx->fp; + invokeFrame = cx->maybefp(); #endif ag.cx = cx; @@ -188,7 +188,7 @@ StackSpace::popInvokeArgs(const InvokeArgsGuard &ag) JS_ASSERT(isCurrentAndActive(ag.cx)); JS_ASSERT(invokeSegment == currentSegment); - JS_ASSERT(invokeFrame == ag.cx->fp); + JS_ASSERT(invokeFrame == ag.cx->maybefp()); JS_ASSERT(invokeArgEnd == ag.argv() + ag.argc()); #ifdef DEBUG @@ -217,7 +217,7 @@ StackSpace::getInvokeFrame(JSContext *cx, const CallArgs &args, ptrdiff_t nvals = nmissing + VALUES_PER_STACK_FRAME + nfixed; if (!ensureSpace(cx, start, nvals)) return false; - fg.fp = reinterpret_cast(start + nmissing); + fg.regs.fp = reinterpret_cast(start + nmissing); return true; } @@ -227,18 +227,17 @@ StackSpace::pushInvokeFrame(JSContext *cx, const CallArgs &args, { JS_ASSERT(firstUnused() == args.argv() + args.argc()); - JSStackFrame *fp = fg.fp; - JSStackFrame *down = cx->fp; + JSStackFrame *fp = fg.regs.fp; + JSStackFrame *down = cx->maybefp(); fp->down = down; if (JS_UNLIKELY(!currentSegment->inContext())) { - cx->pushSegmentAndFrame(currentSegment, fp, fg.regs); + cx->pushSegmentAndFrame(currentSegment, fg.regs); } else { #ifdef DEBUG fp->savedPC = JSStackFrame::sInvalidPC; JS_ASSERT(down->savedPC == JSStackFrame::sInvalidPC); #endif down->savedPC = cx->regs->pc; - cx->setCurrentFrame(fp); fg.prevRegs = cx->regs; cx->setCurrentRegs(&fg.regs); } @@ -251,18 +250,17 @@ JS_REQUIRES_STACK JS_ALWAYS_INLINE void StackSpace::popInvokeFrame(const InvokeFrameGuard &fg) { JSContext *cx = fg.cx; - JSStackFrame *fp = fg.fp; + JSStackFrame *fp = fg.regs.fp; JS_ASSERT(isCurrentAndActive(cx)); if (JS_UNLIKELY(currentSegment->getInitialFrame() == fp)) { cx->popSegmentAndFrame(); } else { - JS_ASSERT(fp == cx->fp); JS_ASSERT(&fg.regs == cx->regs); - cx->setCurrentFrame(fp->down); + JS_ASSERT(fp->down == fg.prevRegs->fp); cx->setCurrentRegs(fg.prevRegs); #ifdef DEBUG - cx->fp->savedPC = JSStackFrame::sInvalidPC; + cx->fp()->savedPC = JSStackFrame::sInvalidPC; #endif } } @@ -296,14 +294,13 @@ StackSpace::pushInlineFrame(JSContext *cx, JSStackFrame *fp, jsbytecode *pc, JSStackFrame *newfp) { JS_ASSERT(isCurrentAndActive(cx)); - JS_ASSERT(cx->fp == fp && cx->regs->pc == pc); + JS_ASSERT(cx->regs->fp == fp && cx->regs->pc == pc); fp->savedPC = pc; newfp->down = fp; #ifdef DEBUG newfp->savedPC = JSStackFrame::sInvalidPC; #endif - cx->setCurrentFrame(newfp); } JS_REQUIRES_STACK JS_ALWAYS_INLINE void @@ -311,16 +308,16 @@ StackSpace::popInlineFrame(JSContext *cx, JSStackFrame *up, JSStackFrame *down) { JS_ASSERT(isCurrentAndActive(cx)); JS_ASSERT(cx->hasActiveSegment()); - JS_ASSERT(cx->fp == up && up->down == down); + JS_ASSERT(cx->regs->fp == up && up->down == down); JS_ASSERT(up->savedPC == JSStackFrame::sInvalidPC); JS_ASSERT(!up->hasIMacroPC()); JSFrameRegs *regs = cx->regs; + regs->fp = down; regs->pc = down->savedPC; #ifdef DEBUG down->savedPC = JSStackFrame::sInvalidPC; #endif - cx->setCurrentFrame(down); } JS_REQUIRES_STACK inline @@ -331,8 +328,8 @@ FrameRegsIter::FrameRegsIter(JSContext *cx) initSlow(); return; } - JS_ASSERT(cx->fp); - curfp = cx->fp; + JS_ASSERT(cx->regs->fp); + curfp = cx->regs->fp; cursp = cx->regs->sp; curpc = cx->regs->pc; return; @@ -410,7 +407,7 @@ class CompartmentChecker public: explicit CompartmentChecker(JSContext *cx) : context(cx), compartment(cx->compartment) { - check(cx->fp ? JS_GetGlobalForScopeChain(cx) : cx->globalObject); + check(cx->hasfp() ? JS_GetGlobalForScopeChain(cx) : cx->globalObject); VOUCH_DOES_NOT_REQUIRE_STACK(); } diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index b3c54eecd37b..a097f925a5a7 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -2823,7 +2823,7 @@ js_NewFlatClosure(JSContext *cx, JSFunction *fun) * Flat closures can be partial, they may need to search enclosing scope * objects via JSOP_NAME, etc. */ - JSObject *scopeChain = js_GetScopeChain(cx, cx->fp); + JSObject *scopeChain = js_GetScopeChain(cx, cx->fp()); if (!scopeChain) return NULL; @@ -2844,11 +2844,11 @@ js_NewFlatClosure(JSContext *cx, JSFunction *fun) JSObject * js_NewDebuggableFlatClosure(JSContext *cx, JSFunction *fun) { - JS_ASSERT(cx->fp->getFunction()->flags & JSFUN_HEAVYWEIGHT); - JS_ASSERT(!cx->fp->getFunction()->optimizedClosure()); + JS_ASSERT(cx->fp()->getFunction()->flags & JSFUN_HEAVYWEIGHT); + JS_ASSERT(!cx->fp()->getFunction()->optimizedClosure()); JS_ASSERT(FUN_FLAT_CLOSURE(fun)); - return WrapEscapingClosure(cx, cx->fp, fun); + return WrapEscapingClosure(cx, cx->fp(), fun); } JSFunction * diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 67fc847ab96a..1884d5cf9afd 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -490,7 +490,7 @@ InvokeCommon(JSContext *cx, JSFunction *fun, JSScript *script, T native, InvokeFrameGuard frame; if (!cx->stack().getInvokeFrame(cx, args, nmissing, nfixed, frame)) return false; - JSStackFrame *fp = frame.getFrame(); + JSStackFrame *fp = frame.getRegs().fp; /* Initialize missing missing arguments and new local variables. */ Value *missing = args.argv() + args.argc(); @@ -565,7 +565,7 @@ InvokeCommon(JSContext *cx, JSFunction *fun, JSScript *script, T native, ok = callJSNative(cx, native, thisp, fp->numActualArgs(), fp->argv, fp->addressReturnValue()); - JS_ASSERT(cx->fp == fp); + JS_ASSERT(cx->fp() == fp); JS_RUNTIME_METER(cx->runtime, nativeCalls); #ifdef DEBUG_NOT_THROWING if (ok && !alreadyThrowing) @@ -605,7 +605,7 @@ DoConstruct(JSContext *cx, JSObject *obj, uintN argc, Value *argv, Value *rval) static JSBool DoSlowCall(JSContext *cx, uintN argc, Value *vp) { - JSStackFrame *fp = cx->fp; + JSStackFrame *fp = cx->fp(); JSObject *obj = fp->getThisObject(cx); if (!obj) return false; @@ -826,12 +826,12 @@ Execute(JSContext *cx, JSObject *chain, JSScript *script, /* * We want to call |down->varobj()|, but this requires knowing the - * CallStackSegment of |down|. If |down == cx->fp|, the callstack is + * CallStackSegment of |down|. If |down == cx->fp()|, the callstack is * simply the context's active callstack, so we can use - * |down->varobj(cx)|. When |down != cx->fp|, we need to do a slow + * |down->varobj(cx)|. When |down != cx->fp()|, we need to do a slow * linear search. Luckily, this only happens with EvaluateInFrame. */ - initialVarObj = (down == cx->fp) + initialVarObj = (down == cx->maybefp()) ? down->varobj(cx) : down->varobj(cx->containingSegment(down)); } else { @@ -1215,7 +1215,7 @@ ValueToId(JSContext *cx, const Value &v, jsid *idp) JS_STATIC_INTERPRET JS_REQUIRES_STACK JSBool js_EnterWith(JSContext *cx, jsint stackIndex) { - JSStackFrame *fp = cx->fp; + JSStackFrame *fp = cx->fp(); Value *sp = cx->regs->sp; JS_ASSERT(stackIndex < 0); JS_ASSERT(fp->base() <= sp + stackIndex); @@ -1252,11 +1252,11 @@ js_LeaveWith(JSContext *cx) { JSObject *withobj; - withobj = cx->fp->getScopeChain(); + withobj = cx->fp()->getScopeChain(); JS_ASSERT(withobj->getClass() == &js_WithClass); - JS_ASSERT(withobj->getPrivate() == js_FloatingFrameIfGenerator(cx, cx->fp)); + JS_ASSERT(withobj->getPrivate() == js_FloatingFrameIfGenerator(cx, cx->fp())); JS_ASSERT(OBJ_BLOCK_DEPTH(cx, withobj) >= 0); - cx->fp->setScopeChain(withobj->getParent()); + cx->fp()->setScopeChain(withobj->getParent()); withobj->setPrivate(NULL); } @@ -1267,7 +1267,7 @@ js_IsActiveWithOrBlock(JSContext *cx, JSObject *obj, int stackDepth) clasp = obj->getClass(); if ((clasp == &js_WithClass || clasp == &js_BlockClass) && - obj->getPrivate() == js_FloatingFrameIfGenerator(cx, cx->fp) && + obj->getPrivate() == js_FloatingFrameIfGenerator(cx, cx->fp()) && OBJ_BLOCK_DEPTH(cx, obj) >= stackDepth) { return clasp; } @@ -1285,9 +1285,9 @@ js_UnwindScope(JSContext *cx, jsint stackDepth, JSBool normalUnwind) Class *clasp; JS_ASSERT(stackDepth >= 0); - JS_ASSERT(cx->fp->base() + stackDepth <= cx->regs->sp); + JS_ASSERT(cx->fp()->base() + stackDepth <= cx->regs->sp); - JSStackFrame *fp = cx->fp; + JSStackFrame *fp = cx->fp(); for (obj = fp->maybeBlockChain(); obj; obj = obj->getParent()) { JS_ASSERT(obj->getClass() == &js_BlockClass); if (OBJ_BLOCK_DEPTH(cx, obj) < stackDepth) @@ -1375,7 +1375,7 @@ js_TraceOpcode(JSContext *cx) tracefp = (FILE *) cx->tracefp; JS_ASSERT(tracefp); - fp = cx->fp; + fp = cx->fp(); regs = cx->regs; /* @@ -2179,7 +2179,7 @@ Interpret(JSContext *cx) JSRuntime *const rt = cx->runtime; /* Set registerized frame pointer and derived script pointer. */ - JSStackFrame *fp = cx->fp; + JSStackFrame *fp = cx->fp(); JSScript *script = fp->getScript(); JS_ASSERT(!script->isEmpty()); JS_ASSERT(script->length > 1); @@ -2240,7 +2240,7 @@ Interpret(JSContext *cx) #define RESTORE_INTERP_VARS() \ JS_BEGIN_MACRO \ - fp = cx->fp; \ + fp = cx->fp(); \ script = fp->getScript(); \ atoms = FrameAtomBase(cx, fp); \ currentVersion = (JSVersion) script->version; \ @@ -2659,7 +2659,7 @@ BEGIN_CASE(JSOP_STOP) regs.sp[-1] = fp->getReturnValue(); /* Sync interpreter registers. */ - fp = cx->fp; + fp = cx->fp(); script = fp->getScript(); atoms = FrameAtomBase(cx, fp); @@ -4640,11 +4640,12 @@ BEGIN_CASE(JSOP_APPLY) stack.pushInlineFrame(cx, fp, regs.pc, newfp); /* Initializer regs after pushInlineFrame snapshots pc. */ + regs.fp = newfp; regs.pc = newscript->code; regs.sp = newsp; /* Import into locals. */ - JS_ASSERT(newfp == cx->fp); + JS_ASSERT(newfp == cx->fp()); fp = newfp; script = newscript; atoms = script->atomMap.vector; diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index 88b1f3888fac..89682df2a39a 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -51,6 +51,7 @@ #include "jsvalue.h" typedef struct JSFrameRegs { + JSStackFrame *fp; /* active frame */ jsbytecode *pc; /* program counter */ js::Value *sp; /* stack pointer */ } JSFrameRegs; diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index c3c338e1b902..b9fa69304e5e 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -1094,7 +1094,7 @@ js_NewGenerator(JSContext *cx) return NULL; /* Load and compute stack slot counts. */ - JSStackFrame *fp = cx->fp; + JSStackFrame *fp = cx->fp(); uintN argc = fp->numActualArgs(); uintN nargs = JS_MAX(argc, fp->numFormalArgs()); uintN vplen = 2 + nargs; @@ -1230,7 +1230,7 @@ SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj, * the code before pushExecuteFrame must not reenter the interpreter. */ ExecuteFrameGuard frame; - if (!cx->stack().getExecuteFrame(cx, cx->fp, vplen, nfixed, frame)) { + if (!cx->stack().getExecuteFrame(cx, cx->maybefp(), vplen, nfixed, frame)) { gen->state = JSGEN_CLOSED; return JS_FALSE; } diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 5bdde363f1fd..d639cb71c37c 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -2693,10 +2693,10 @@ Detecting(JSContext *cx, jsbytecode *pc) JSOp op; JSAtom *atom; - script = cx->fp->getScript(); + script = cx->fp()->getScript(); endpc = script->code + script->length; for (;; pc += js_CodeSpec[op].length) { - JS_ASSERT_IF(!cx->fp->hasIMacroPC(), script->code <= pc && pc < endpc); + JS_ASSERT_IF(!cx->fp()->hasIMacroPC(), script->code <= pc && pc < endpc); /* General case: a branch or equality op follows the access. */ op = js_GetOpcode(cx, script, pc); @@ -2775,7 +2775,8 @@ js_InferFlags(JSContext *cx, uintN defaultFlags) flags |= JSRESOLVE_ASSIGNING; } else if (cs->length >= 0) { pc += cs->length; - if (pc < cx->fp->getScript()->code + cx->fp->getScript()->length && Detecting(cx, pc)) + JSScript *script = cx->fp()->getScript(); + if (pc < script->code + script->length && Detecting(cx, pc)) flags |= JSRESOLVE_DETECTING; } if (format & JOF_DECLARING) @@ -2892,7 +2893,7 @@ js_NewWithObject(JSContext *cx, JSObject *proto, JSObject *parent, jsint depth) if (!obj) return NULL; obj->init(&js_WithClass, proto, parent, - PrivateValue(js_FloatingFrameIfGenerator(cx, cx->fp))); + PrivateValue(js_FloatingFrameIfGenerator(cx, cx->fp()))); OBJ_SET_BLOCK_DEPTH(cx, obj, depth); obj->map = cx->runtime->emptyWithScope->hold(); @@ -2951,10 +2952,10 @@ js_PutBlockObject(JSContext *cx, JSBool normalUnwind) /* Blocks have one fixed slot available for the first local.*/ JS_STATIC_ASSERT(JS_INITIAL_NSLOTS == JSSLOT_BLOCK_DEPTH + 2); - JSStackFrame *const fp = cx->fp; + JSStackFrame *const fp = cx->fp(); JSObject *obj = fp->getScopeChain(); JS_ASSERT(obj->getClass() == &js_BlockClass); - JS_ASSERT(obj->getPrivate() == js_FloatingFrameIfGenerator(cx, cx->fp)); + JS_ASSERT(obj->getPrivate() == js_FloatingFrameIfGenerator(cx, cx->fp())); JS_ASSERT(OBJ_IS_CLONED_BLOCK(obj)); /* @@ -3729,12 +3730,12 @@ js_FindClassObject(JSContext *cx, JSObject *start, JSProtoKey protoKey, JSScopeProperty *sprop; /* - * Find the global object. Use cx->fp directly to avoid falling off + * Find the global object. Use cx->fp() directly to avoid falling off * trace; all JIT-elided stack frames have the same global object as - * cx->fp. + * cx->fp(). */ VOUCH_DOES_NOT_REQUIRE_STACK(); - if (!start && (fp = cx->fp) != NULL) + if (!start && (fp = cx->maybefp()) != NULL) start = fp->maybeScopeChain(); if (start) { @@ -4775,7 +4776,7 @@ js_GetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN getHow, op = (JSOp) *pc; if (op == JSOP_TRAP) { JS_ASSERT_NOT_ON_TRACE(cx); - op = JS_GetTrapOpcode(cx, cx->fp->getScript(), pc); + op = JS_GetTrapOpcode(cx, cx->fp()->getScript(), pc); } if (op == JSOP_GETXPROP) { flags = JSREPORT_ERROR; @@ -5262,7 +5263,7 @@ js_DeleteProperty(JSContext *cx, JSObject *obj, jsid id, Value *rval) JSFunction *fun = GET_FUNCTION_PRIVATE(cx, funobj); if (fun != funobj) { - for (JSStackFrame *fp = cx->fp; fp; fp = fp->down) { + for (JSStackFrame *fp = cx->maybefp(); fp; fp = fp->down) { if (fp->callee() == fun && fp->getThisValue().isObject() && &fp->getThisValue().toObject() == obj) { @@ -5559,8 +5560,8 @@ js_GetClassPrototype(JSContext *cx, JSObject *scope, JSProtoKey protoKey, if (protoKey != JSProto_Null) { if (!scope) { - if (cx->fp) - scope = cx->fp->maybeScopeChain(); + if (cx->hasfp()) + scope = cx->fp()->maybeScopeChain(); if (!scope) { scope = cx->globalObject; if (!scope) { @@ -6350,7 +6351,7 @@ js_DumpStackFrame(JSContext *cx, JSStackFrame *start) VOUCH_DOES_NOT_REQUIRE_STACK(); if (!start) - start = cx->fp; + start = cx->maybefp(); FrameRegsIter i(cx); while (!i.done() && i.fp() != start) ++i; diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index c57aab6da253..c35f25e85c21 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -547,7 +547,7 @@ NewNativeClassInstance(JSContext *cx, Class *clasp, JSObject *proto, JSObject *p JS_ASSERT(proto->isNative()); JS_ASSERT(parent); - DTrace::ObjectCreationScope objectCreationScope(cx, cx->fp, clasp); + DTrace::ObjectCreationScope objectCreationScope(cx, cx->maybefp(), clasp); /* * Allocate an object from the GC heap and initialize all its fields before @@ -599,13 +599,13 @@ NewBuiltinClassInstance(JSContext *cx, Class *clasp) /* NB: inline-expanded and specialized version of js_GetClassPrototype. */ JSObject *global; - if (!cx->fp) { + if (!cx->hasfp()) { global = cx->globalObject; OBJ_TO_INNER_OBJECT(cx, global); if (!global) return NULL; } else { - global = cx->fp->getScopeChain()->getGlobal(); + global = cx->fp()->getScopeChain()->getGlobal(); } JS_ASSERT(global->getClass()->flags & JSCLASS_IS_GLOBAL); @@ -682,7 +682,7 @@ NewObject(JSContext *cx, js::Class *clasp, JSObject *proto, JSObject *parent) } - DTrace::ObjectCreationScope objectCreationScope(cx, cx->fp, clasp); + DTrace::ObjectCreationScope objectCreationScope(cx, cx->maybefp(), clasp); /* * Allocate an object from the GC heap and initialize all its fields before diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 4d148d3b8c49..721b6ca19b75 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -279,7 +279,7 @@ js_Disassemble(JSContext *cx, JSScript *script, JSBool lines, FILE *fp) JS_FRIEND_API(JSBool) js_DumpPC(JSContext *cx) { - return js_DisassembleAtPC(cx, cx->fp->getScript(), true, stdout, cx->regs->pc); + return js_DisassembleAtPC(cx, cx->fp()->getScript(), true, stdout, cx->regs->pc); } JSBool @@ -5155,7 +5155,8 @@ js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v_in, jsbytecode* savepc = i.pc(); jsbytecode* savedIMacroPC = fp->maybeIMacroPC(); if (savedIMacroPC) { - if (fp == cx->fp) + JS_ASSERT(cx->hasfp()); + if (fp == cx->fp()) cx->regs->pc = savedIMacroPC; else fp->savedPC = savedIMacroPC; @@ -5173,7 +5174,8 @@ js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v_in, name = DecompileExpression(cx, script, fp->maybeFunction(), pc); if (savedIMacroPC) { - if (fp == cx->fp) + JS_ASSERT(cx->hasfp()); + if (fp == cx->fp()) cx->regs->pc = savedIMacroPC; else fp->savedPC = savepc; diff --git a/js/src/jspropertycache.cpp b/js/src/jspropertycache.cpp index 704cb69f3ae8..e54de77a7e48 100644 --- a/js/src/jspropertycache.cpp +++ b/js/src/jspropertycache.cpp @@ -62,7 +62,7 @@ PropertyCache::fill(JSContext *cx, JSObject *obj, uintN scopeIndex, uintN protoI JS_ASSERT(!cx->runtime->gcRunning); /* FIXME bug 489098: consider enabling the property cache for eval. */ - if (js_IsPropertyCacheDisabled(cx) || (cx->fp->flags & JSFRAME_EVAL)) { + if (js_IsPropertyCacheDisabled(cx) || (cx->fp()->flags & JSFRAME_EVAL)) { PCMETER(disfills++); return JS_NO_PROP_CACHE_FILL; } @@ -128,7 +128,7 @@ PropertyCache::fill(JSContext *cx, JSObject *obj, uintN scopeIndex, uintN protoI * opcode format flags. */ pc = cx->regs->pc; - op = js_GetOpcode(cx, cx->fp->getScript(), pc); + op = js_GetOpcode(cx, cx->fp()->getScript(), pc); cs = &js_CodeSpec[op]; kshape = 0; @@ -317,7 +317,7 @@ GetAtomFromBytecode(JSContext *cx, jsbytecode *pc, JSOp op, const JSCodeSpec &cs ptrdiff_t pcoff = (JOF_TYPE(cs.format) == JOF_SLOTATOM) ? SLOTNO_LEN : 0; JSAtom *atom; - GET_ATOM_FROM_BYTECODE(cx->fp->getScript(), pc, pcoff, atom); + GET_ATOM_FROM_BYTECODE(cx->fp()->getScript(), pc, pcoff, atom); return atom; } @@ -328,12 +328,13 @@ PropertyCache::fullTest(JSContext *cx, jsbytecode *pc, JSObject **objp, JSObject JSObject *obj, *pobj, *tmp; uint32 vcap; - JS_ASSERT(this == &JS_PROPERTY_CACHE(cx)); - JS_ASSERT( - uintN((cx->fp->hasIMacroPC() ? cx->fp->getIMacroPC() : pc) - cx->fp->getScript()->code) - < cx->fp->getScript()->length); + JSStackFrame *fp = cx->fp(); - JSOp op = js_GetOpcode(cx, cx->fp->getScript(), pc); + JS_ASSERT(this == &JS_PROPERTY_CACHE(cx)); + JS_ASSERT(uintN((fp->hasIMacroPC() ? fp->getIMacroPC() : pc) - fp->getScript()->code) + < fp->getScript()->length); + + JSOp op = js_GetOpcode(cx, fp->getScript(), pc); const JSCodeSpec &cs = js_CodeSpec[op]; obj = *objp; diff --git a/js/src/jsrecursion.cpp b/js/src/jsrecursion.cpp index d2b1bc6c78a9..17538945ef9f 100644 --- a/js/src/jsrecursion.cpp +++ b/js/src/jsrecursion.cpp @@ -183,10 +183,10 @@ TraceRecorder::downSnapshot(FrameInfo* downFrame) JS_ASSERT(unsigned(exit->calldepth) == callDepth); exit->numGlobalSlots = ngslots; exit->numStackSlots = downPostSlots + 1; - exit->numStackSlotsBelowCurrentFrame = cx->fp->down->argv ? - nativeStackOffset(&cx->fp->argv[-2]) / sizeof(double) : 0; + exit->numStackSlotsBelowCurrentFrame = cx->fp()->down->argv ? + nativeStackOffset(&cx->fp()->argv[-2]) / sizeof(double) : 0; exit->exitType = UNSTABLE_LOOP_EXIT; - exit->block = cx->fp->down->maybeBlockChain(); + exit->block = cx->fp()->down->maybeBlockChain(); exit->pc = downFrame->pc + JSOP_CALL_LENGTH; exit->imacpc = NULL; exit->sp_adj = ((downPostSlots + 1) * sizeof(double)) - tree->nativeStackBase; @@ -205,16 +205,16 @@ DownFrameSP(JSContext *cx) { FrameRegsIter i(cx); ++i; - JS_ASSERT(i.fp() == cx->fp->down); + JS_ASSERT(i.fp() == cx->fp()->down); return i.sp(); } JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::upRecursion() { - JS_ASSERT((JSOp)*cx->fp->down->savedPC == JSOP_CALL); - JS_ASSERT(js_CodeSpec[js_GetOpcode(cx, cx->fp->down->getScript(), - cx->fp->down->savedPC)].length == JSOP_CALL_LENGTH); + JS_ASSERT((JSOp)*cx->fp()->down->savedPC == JSOP_CALL); + JS_ASSERT(js_CodeSpec[js_GetOpcode(cx, cx->fp()->down->getScript(), + cx->fp()->down->savedPC)].length == JSOP_CALL_LENGTH); JS_ASSERT(callDepth == 0); @@ -226,10 +226,10 @@ TraceRecorder::upRecursion() if (anchor && (anchor->exitType == RECURSIVE_EMPTY_RP_EXIT || anchor->exitType == RECURSIVE_SLURP_MISMATCH_EXIT || anchor->exitType == RECURSIVE_SLURP_FAIL_EXIT)) { - return slurpDownFrames(cx->fp->down->savedPC); + return slurpDownFrames(cx->fp()->down->savedPC); } - jsbytecode* return_pc = cx->fp->down->savedPC; + jsbytecode* return_pc = cx->fp()->down->savedPC; jsbytecode* recursive_pc = return_pc + JSOP_CALL_LENGTH; /* @@ -256,11 +256,11 @@ TraceRecorder::upRecursion() * Need to compute this from the down frame, since the stack could have * moved on this one. */ - fi->spdist = DownFrameSP(cx) - cx->fp->down->slots(); - JS_ASSERT(cx->fp->numActualArgs() == cx->fp->down->numActualArgs()); - fi->set_argc(uint16(cx->fp->numActualArgs()), false); + fi->spdist = DownFrameSP(cx) - cx->fp()->down->slots(); + JS_ASSERT(cx->fp()->numActualArgs() == cx->fp()->down->numActualArgs()); + fi->set_argc(uint16(cx->fp()->numActualArgs()), false); fi->callerHeight = downPostSlots; - fi->callerArgc = cx->fp->down->numActualArgs(); + fi->callerArgc = cx->fp()->down->numActualArgs(); if (anchor && anchor->exitType == RECURSIVE_MISMATCH_EXIT) { /* @@ -390,7 +390,7 @@ JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::slurpDownFrames(jsbytecode* return_pc) { /* Missing - no go */ - if (cx->fp->numActualArgs() != cx->fp->numFormalArgs()) + if (cx->fp()->numActualArgs() != cx->fp()->numFormalArgs()) RETURN_STOP_A("argc != nargs"); LIns* argv_ins; @@ -398,8 +398,7 @@ TraceRecorder::slurpDownFrames(jsbytecode* return_pc) unsigned downPostSlots; FrameRegsIter i(cx); - LIns* fp_ins = - addName(lir->insLoad(LIR_ldp, cx_ins, offsetof(JSContext, fp), ACCSET_OTHER), "fp"); + LIns* fp_ins = addName(entryFrameIns(), "fp"); /* * When first emitting slurp code, do so against the down frame. After @@ -434,7 +433,7 @@ TraceRecorder::slurpDownFrames(jsbytecode* return_pc) addName(lir->insLoad(LIR_ldp, fp_ins, JSStackFrame::offsetScript(), ACCSET_OTHER), "script"), - INS_CONSTPTR(cx->fp->down->getScript())), + INS_CONSTPTR(cx->fp()->down->getScript())), RECURSIVE_LOOP_EXIT); } @@ -453,7 +452,7 @@ TraceRecorder::slurpDownFrames(jsbytecode* return_pc) addName(lir->insLoad(LIR_ldi, fp_ins, JSStackFrame::offsetNumActualArgs(), ACCSET_OTHER), "argc"), - INS_CONST(cx->fp->numActualArgs())), + INS_CONST(cx->fp()->numActualArgs())), MISMATCH_EXIT); /* Pop the interpreter frame. */ @@ -672,7 +671,7 @@ public: JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::downRecursion() { - JSStackFrame* fp = cx->fp; + JSStackFrame* fp = cx->fp(); JSScript *script = fp->getScript(); if ((jsbytecode*)fragment->ip < script->code || (jsbytecode*)fragment->ip >= script->code + script->length) { diff --git a/js/src/jsreflect.cpp b/js/src/jsreflect.cpp index 0026c387173f..03cb3f84549a 100644 --- a/js/src/jsreflect.cpp +++ b/js/src/jsreflect.cpp @@ -2340,7 +2340,7 @@ ASTSerializer::literal(JSParseNode *pn, Value *dst) LOCAL_ASSERT(re1 && re1->isRegExp()); JSObject *proto; - if (!js_GetClassPrototype(cx, cx->fp->getScopeChain(), JSProto_RegExp, &proto)) + if (!js_GetClassPrototype(cx, cx->fp()->getScopeChain(), JSProto_RegExp, &proto)) return false; JSObject *re2 = js_CloneRegExpObject(cx, re1, proto); diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index 48412bedb295..0e03b608de07 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -1192,7 +1192,7 @@ static JS_REQUIRES_STACK inline int StackSlotHash(JSContext* cx, unsigned slot, const void* pc) { uintptr_t h = HASH_SEED; - HashAccum(h, uintptr_t(cx->fp->getScript()), ORACLE_MASK); + HashAccum(h, uintptr_t(cx->fp()->getScript()), ORACLE_MASK); HashAccum(h, uintptr_t(pc), ORACLE_MASK); HashAccum(h, uintptr_t(slot), ORACLE_MASK); return int(h); @@ -1202,7 +1202,7 @@ static JS_REQUIRES_STACK inline int GlobalSlotHash(JSContext* cx, unsigned slot) { uintptr_t h = HASH_SEED; - JSStackFrame* fp = cx->fp; + JSStackFrame* fp = cx->fp(); while (fp->down) fp = fp->down; @@ -1536,14 +1536,14 @@ TreeFragment::initialize(JSContext* cx, SlotList *globalSlots, bool speculate) /* Capture the coerced type of each active slot in the type map. */ this->typeMap.captureTypes(cx, globalObj, *globalSlots, 0 /* callDepth */, speculate); this->nStackTypes = this->typeMap.length() - globalSlots->length(); - this->spOffsetAtEntry = cx->regs->sp - cx->fp->base(); + this->spOffsetAtEntry = cx->regs->sp - cx->fp()->base(); #ifdef DEBUG - this->treeFileName = cx->fp->getScript()->filename; - this->treeLineNumber = js_FramePCToLineNumber(cx, cx->fp); - this->treePCOffset = FramePCOffset(cx, cx->fp); + this->treeFileName = cx->fp()->getScript()->filename; + this->treeLineNumber = js_FramePCToLineNumber(cx, cx->fp()); + this->treePCOffset = FramePCOffset(cx, cx->fp()); #endif - this->script = cx->fp->getScript(); + this->script = cx->fp()->getScript(); this->recursion = Recursion_None; this->gcthings.clear(); this->sprops.clear(); @@ -1551,7 +1551,7 @@ TreeFragment::initialize(JSContext* cx, SlotList *globalSlots, bool speculate) this->sideExits.clear(); /* Determine the native frame layout at the entry point. */ - this->nativeStackBase = (nStackTypes - (cx->regs->sp - cx->fp->base())) * + this->nativeStackBase = (nStackTypes - (cx->regs->sp - cx->fp()->base())) * sizeof(double); this->maxNativeStackSlots = nStackTypes; this->maxCallDepth = 0; @@ -1880,7 +1880,7 @@ template static JS_REQUIRES_STACK JS_ALWAYS_INLINE void VisitGlobalSlots(Visitor &visitor, JSContext *cx, SlotList &gslots) { - VisitGlobalSlots(visitor, cx, cx->fp->getScopeChain()->getGlobal(), + VisitGlobalSlots(visitor, cx, cx->fp()->getScopeChain()->getGlobal(), gslots.length(), gslots.data()); } @@ -1899,7 +1899,7 @@ static JS_REQUIRES_STACK JS_ALWAYS_INLINE void VisitSlots(Visitor& visitor, JSContext* cx, unsigned callDepth, unsigned ngslots, uint16* gslots) { - VisitSlots(visitor, cx, cx->fp->getScopeChain()->getGlobal(), + VisitSlots(visitor, cx, cx->fp()->getScopeChain()->getGlobal(), callDepth, ngslots, gslots); } @@ -1917,7 +1917,7 @@ static JS_REQUIRES_STACK JS_ALWAYS_INLINE void VisitSlots(Visitor &visitor, JSContext *cx, unsigned callDepth, const SlotList& slots) { - VisitSlots(visitor, cx, cx->fp->getScopeChain()->getGlobal(), + VisitSlots(visitor, cx, cx->fp()->getScopeChain()->getGlobal(), callDepth, slots.length(), slots.data()); } @@ -2231,7 +2231,7 @@ TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* anchor, VMFragment* frag globalObj(tree->globalObj), outer(outer), outerArgc(outerArgc), - lexicalBlock(cx->fp->maybeBlockChain()), + lexicalBlock(cx->fp()->maybeBlockChain()), anchor(anchor), lir(NULL), cx_ins(NULL), @@ -2246,8 +2246,10 @@ TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* anchor, VMFragment* frag nativeFrameTracker(), global_dslots(NULL), callDepth(anchor ? anchor->calldepth : 0), - atoms(FrameAtomBase(cx, cx->fp)), - consts(cx->fp->getScript()->constOffset ? cx->fp->getScript()->consts()->vector : NULL), + atoms(FrameAtomBase(cx, cx->fp())), + consts(cx->fp()->getScript()->constOffset + ? cx->fp()->getScript()->consts()->vector + : NULL), cfgMerges(&tempAlloc()), trashSelf(false), whichTreesToTrash(&tempAlloc()), @@ -2262,7 +2264,7 @@ TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* anchor, VMFragment* frag generatedSpecializedNative(), tempTypeMap(cx) { - JS_ASSERT(globalObj == cx->fp->getScopeChain()->getGlobal()); + JS_ASSERT(globalObj == cx->fp()->getScopeChain()->getGlobal()); JS_ASSERT(globalObj->scope()->hasOwnShape()); JS_ASSERT(cx->regs->pc == (jsbytecode*)fragment->ip); @@ -2492,9 +2494,9 @@ TraceRecorder::finishAbort(const char* reason) tree->treeFileName, tree->treeLineNumber, tree->treePCOffset, - cx->fp->getScript()->filename, - js_FramePCToLineNumber(cx, cx->fp), - FramePCOffset(cx, cx->fp), + cx->fp()->getScript()->filename, + js_FramePCToLineNumber(cx, cx->fp()), + FramePCOffset(cx, cx->fp()), reason); #endif Backoff(cx, (jsbytecode*) fragment->root->ip, fragment->root); @@ -2637,7 +2639,7 @@ TraceRecorder::nativeStackOffsetImpl(const void* p) const */ if (!visitor.stopped()) { const Value *vp = (const Value *)p; - JS_ASSERT(size_t(vp - cx->fp->slots()) < cx->fp->getSlotCount()); + JS_ASSERT(size_t(vp - cx->fp()->slots()) < cx->fp()->getSlotCount()); offset += size_t(vp - cx->regs->sp) * sizeof(double); } return offset; @@ -3310,8 +3312,8 @@ GetFromClosure(JSContext* cx, JSObject* call, const ClosureVarInfo* cv, double* */ uint32 slot = cv->slot; VOUCH_DOES_NOT_REQUIRE_STACK(); - if (cx->fp->maybeCallObj() == call) { - slot = T::adj_slot(cx->fp, slot); + if (cx->fp()->maybeCallObj() == call) { + slot = T::adj_slot(cx->fp(), slot); *result = state->stackBase[slot]; return state->callstackBase[0]->get_typemap()[slot]; } @@ -3418,7 +3420,7 @@ GetClosureVar(JSContext* cx, JSObject* callee, const ClosureVarInfo* cv, double* * is infallible. * * @param callDepth the distance between the entry frame into our trace and - * cx->fp when we make this call. If this is not called as a + * cx->fp() when we make this call. If this is not called as a * result of a nested exit, callDepth is 0. * @param mp an array of JSValueType that indicate what the types of the things * on the stack are. @@ -3443,7 +3445,7 @@ FlushNativeStackFrame(JSContext* cx, unsigned callDepth, const JSValueType* mp, // Scope to keep |fp| from leaking into the macros we're using. { unsigned n = callDepth+1; // +1 to make sure we restore the entry frame - JSStackFrame* fp = cx->fp; + JSStackFrame* fp = cx->fp(); if (stopFrame) { for (; fp != stopFrame; fp = fp->down) { JS_ASSERT(n != 0); @@ -3824,7 +3826,7 @@ TraceRecorder::attemptImport(const Value* p) CountSlotsVisitor countVisitor(p); VisitStackSlots(countVisitor, cx, callDepth); - if (countVisitor.stopped() || size_t(p - cx->fp->slots()) < cx->fp->getSlotCount()) + if (countVisitor.stopped() || size_t(p - cx->fp()->slots()) < cx->fp()->getSlotCount()) return get(p); return NULL; @@ -3858,7 +3860,7 @@ TraceRecorder::getImpl(const void *p) JSValueType type = importTypeMap[slot]; JS_ASSERT(type != JSVAL_TYPE_UNINITIALIZED); importImpl(lirbuf->sp, -tree->nativeStackBase + slot * sizeof(jsdouble), - p, type, "stack", slot, cx->fp); + p, type, "stack", slot, cx->fp()); } JS_ASSERT(knownImpl(p)); return tracker.get(p); @@ -3874,7 +3876,7 @@ TraceRecorder::get(const Value *p) bool TraceRecorder::isValidFrameObjPtr(JSObject **p) { - JSStackFrame *fp = cx->fp; + JSStackFrame *fp = cx->fp(); for (; fp; fp = fp->down) { if (fp->addressScopeChain() == p || fp->addressArgsObj() == p) return true; @@ -4142,8 +4144,8 @@ TreevisLogExit(JSContext* cx, VMSideExit* exit) { debug_only_printf(LC_TMTreeVis, "TREEVIS ADDEXIT EXIT=%p TYPE=%s FRAG=%p PC=%p FILE=\"%s\"" " LINE=%d OFFS=%d", (void*)exit, getExitName(exit->exitType), - (void*)exit->from, (void*)cx->regs->pc, cx->fp->getScript()->filename, - js_FramePCToLineNumber(cx, cx->fp), FramePCOffset(cx, cx->fp)); + (void*)exit->from, (void*)cx->regs->pc, cx->fp()->getScript()->filename, + js_FramePCToLineNumber(cx, cx->fp()), FramePCOffset(cx, cx->fp())); debug_only_print0(LC_TMTreeVis, " STACK=\""); for (unsigned i = 0; i < exit->numStackSlots; i++) debug_only_printf(LC_TMTreeVis, "%c", TypeToChar(exit->stackTypeMap()[i])); @@ -4157,7 +4159,7 @@ TreevisLogExit(JSContext* cx, VMSideExit* exit) JS_REQUIRES_STACK VMSideExit* TraceRecorder::snapshot(ExitType exitType) { - JSStackFrame* const fp = cx->fp; + JSStackFrame* const fp = cx->fp(); JSFrameRegs* const regs = cx->regs; jsbytecode* pc = regs->pc; @@ -4282,8 +4284,8 @@ TraceRecorder::snapshot(ExitType exitType) exit->calldepth = callDepth; exit->numGlobalSlots = ngslots; exit->numStackSlots = stackSlots; - exit->numStackSlotsBelowCurrentFrame = cx->fp->argv ? - nativeStackOffset(&cx->fp->argv[-2]) / sizeof(double) : + exit->numStackSlotsBelowCurrentFrame = cx->fp()->argv ? + nativeStackOffset(&cx->fp()->argv[-2]) / sizeof(double) : 0; exit->exitType = exitType; exit->block = fp->maybeBlockChain(); @@ -4497,10 +4499,10 @@ TraceRecorder::compile() /* :TODO: windows support */ #if defined DEBUG && !defined WIN32 /* Associate a filename and line number with the fragment. */ - const char* filename = cx->fp->getScript()->filename; + const char* filename = cx->fp()->getScript()->filename; char* label = (char*)js_malloc((filename ? strlen(filename) : 7) + 16); sprintf(label, "%s:%u", filename ? filename : "", - js_FramePCToLineNumber(cx, cx->fp)); + js_FramePCToLineNumber(cx, cx->fp())); lirbuf->printer->addrNameMap->addAddrRange(fragment, sizeof(Fragment), 0, label); js_free(label); #endif @@ -4880,7 +4882,7 @@ TraceRecorder::closeLoop(SlotMap& slotMap, VMSideExit* exit) */ JS_ASSERT((*cx->regs->pc == JSOP_TRACE || *cx->regs->pc == JSOP_NOP || *cx->regs->pc == JSOP_RETURN || *cx->regs->pc == JSOP_STOP) && - !cx->fp->hasIMacroPC()); + !cx->fp()->hasIMacroPC()); if (callDepth != 0) { debug_only_print0(LC_TMTracer, @@ -5009,9 +5011,9 @@ TraceRecorder::closeLoop(SlotMap& slotMap, VMSideExit* exit) #ifdef JS_JIT_SPEW debug_only_printf(LC_TMMinimal, "Recording completed at %s:%u@%u via closeLoop (FragID=%06u)\n", - cx->fp->getScript()->filename, - js_FramePCToLineNumber(cx, cx->fp), - FramePCOffset(cx, cx->fp), + cx->fp()->getScript()->filename, + js_FramePCToLineNumber(cx, cx->fp()), + FramePCOffset(cx, cx->fp()), fragment->profFragID); debug_only_print0(LC_TMMinimal, "\n"); #endif @@ -5183,9 +5185,9 @@ TraceRecorder::endLoop(VMSideExit* exit) #ifdef JS_JIT_SPEW debug_only_printf(LC_TMMinimal, "Recording completed at %s:%u@%u via endLoop (FragID=%06u)\n", - cx->fp->getScript()->filename, - js_FramePCToLineNumber(cx, cx->fp), - FramePCOffset(cx, cx->fp), + cx->fp()->getScript()->filename, + js_FramePCToLineNumber(cx, cx->fp()), + FramePCOffset(cx, cx->fp()), fragment->profFragID); debug_only_print0(LC_TMTracer, "\n"); #endif @@ -5212,7 +5214,7 @@ TraceRecorder::prepareTreeCall(TreeFragment* inner) * compensate for any outer frames that the inner tree doesn't expect * but the outer tree has. */ - ptrdiff_t sp_adj = nativeStackOffset(&cx->fp->argv[-2]); + ptrdiff_t sp_adj = nativeStackOffset(&cx->fp()->argv[-2]); /* Calculate the amount we have to lift the call stack by. */ ptrdiff_t rp_adj = callDepth * sizeof(FrameInfo*); @@ -5422,7 +5424,7 @@ TraceRecorder::trackCfgMerges(jsbytecode* pc) { /* If we hit the beginning of an if/if-else, then keep track of the merge point after it. */ JS_ASSERT((*pc == JSOP_IFEQ) || (*pc == JSOP_IFEQX)); - jssrcnote* sn = js_GetSrcNote(cx->fp->getScript(), pc); + jssrcnote* sn = js_GetSrcNote(cx->fp()->getScript(), pc); if (sn != NULL) { if (SN_TYPE(sn) == SRC_IF) { cfgMerges.add((*pc == JSOP_IFEQ) @@ -5495,11 +5497,11 @@ TraceRecorder::checkTraceEnd(jsbytecode *pc) * pointer and pretend we have reached the loop header. */ if (pendingLoop) { - JS_ASSERT(!cx->fp->hasIMacroPC() && (pc == cx->regs->pc || pc == cx->regs->pc + 1)); + JS_ASSERT(!cx->fp()->hasIMacroPC() && (pc == cx->regs->pc || pc == cx->regs->pc + 1)); JSFrameRegs orig = *cx->regs; cx->regs->pc = (jsbytecode*)tree->ip; - cx->regs->sp = cx->fp->base() + tree->spOffsetAtEntry; + cx->regs->sp = cx->fp()->base() + tree->spOffsetAtEntry; JSContext* localcx = cx; AbortableRecordingStatus ars = closeLoop(); @@ -5614,7 +5616,7 @@ TraceRecorder::startRecorder(JSContext* cx, VMSideExit* anchor, VMFragment* f, { TraceMonitor *tm = &JS_TRACE_MONITOR(cx); JS_ASSERT(!tm->needFlush); - JS_ASSERT_IF(cx->fp->hasIMacroPC(), f->root != f); + JS_ASSERT_IF(cx->fp()->hasIMacroPC(), f->root != f); tm->recorder = new TraceRecorder(cx, anchor, f, stackSlots, ngslots, typeMap, expectedInnerExit, outer, outerArgc, recordReason, @@ -5668,13 +5670,13 @@ SynthesizeFrame(JSContext* cx, const FrameInfo& fi, JSObject* callee) JSFunction* fun = GET_FUNCTION_PRIVATE(cx, callee); JS_ASSERT(FUN_INTERPRETED(fun)); - /* Assert that we have a correct sp distance from cx->fp->slots in fi. */ - JSStackFrame* const fp = cx->fp; + /* Assert that we have a correct sp distance from cx->fp()->slots in fi. */ + JSStackFrame* const fp = cx->fp(); JS_ASSERT_IF(!fi.imacpc, js_ReconstructStackDepth(cx, fp->getScript(), fi.pc) == uintN(fi.spdist - fp->getFixedCount())); - /* Simulate js_Interpret locals for when |cx->fp == fp|. */ + /* Simulate js_Interpret locals for when |cx->fp() == fp|. */ JSScript* newscript = fun->u.i.script; Value* sp = fp->slots() + fi.spdist; uintN argc = fi.get_argc(); @@ -5739,6 +5741,7 @@ SynthesizeFrame(JSContext* cx, const FrameInfo& fi, JSObject* callee) stack.pushInlineFrame(cx, fp, fi.pc, newfp); /* Initialize regs after pushInlineFrame snapshots pc. */ + cx->regs->fp = newfp; cx->regs->pc = newscript->code; cx->regs->sp = newfp->base(); @@ -5794,17 +5797,16 @@ SynthesizeSlowNativeFrame(TracerState& state, JSContext *cx, VMSideExit *exit) fp->setFunction(GET_FUNCTION_PRIVATE(cx, fp->callee())); fp->clearReturnValue(); fp->setAnnotation(NULL); - fp->setScopeChain(cx->fp->getScopeChain()); + fp->setScopeChain(cx->fp()->getScopeChain()); fp->setBlockChain(NULL); fp->flags = exit->constructing() ? JSFRAME_CONSTRUCTING : 0; JS_ASSERT(!fp->hasIMacroPC()); - state.bailedSlowNativeRegs = *cx->regs; - - cx->stack().pushSynthesizedSlowNativeFrame(cx, seg, fp, state.bailedSlowNativeRegs); - + state.bailedSlowNativeRegs.fp = fp; state.bailedSlowNativeRegs.pc = NULL; state.bailedSlowNativeRegs.sp = fp->slots(); + + cx->stack().pushSynthesizedSlowNativeFrame(cx, seg, state.bailedSlowNativeRegs); } static JS_REQUIRES_STACK bool @@ -5857,7 +5859,7 @@ RecordTree(JSContext* cx, TreeFragment* first, jsbytecode* outer, #ifdef JS_JIT_SPEW debug_only_printf(LC_TMTreeVis, "TREEVIS CREATETREE ROOT=%p PC=%p FILE=\"%s\" LINE=%d OFFS=%d", (void*)f, f->ip, f->treeFileName, f->treeLineNumber, - FramePCOffset(cx, cx->fp)); + FramePCOffset(cx, cx->fp())); debug_only_print0(LC_TMTreeVis, " STACK=\""); for (unsigned i = 0; i < f->nStackTypes; i++) debug_only_printf(LC_TMTreeVis, "%c", TypeToChar(f->typeMap[i])); @@ -5981,7 +5983,7 @@ AttemptToStabilizeTree(JSContext* cx, JSObject* globalObj, VMSideExit* exit, jsb } if (exit->recursive_pc != cx->regs->pc) return false; - from = LookupLoop(tm, exit->recursive_pc, globalObj, globalShape, cx->fp->numActualArgs()); + from = LookupLoop(tm, exit->recursive_pc, globalObj, globalShape, cx->fp()->numActualArgs()); if (!from) return false; /* use stale TI for RecordTree - since from might not have one anymore. */ @@ -6010,9 +6012,9 @@ CreateBranchFragment(JSContext* cx, TreeFragment* root, VMSideExit* anchor) debug_only_printf(LC_TMTreeVis, "TREEVIS CREATEBRANCH ROOT=%p FRAG=%p PC=%p FILE=\"%s\"" " LINE=%d ANCHOR=%p OFFS=%d\n", - (void*)root, (void*)f, (void*)cx->regs->pc, cx->fp->getScript()->filename, - js_FramePCToLineNumber(cx, cx->fp), (void*)anchor, - FramePCOffset(cx, cx->fp)); + (void*)root, (void*)f, (void*)cx->regs->pc, cx->fp()->getScript()->filename, + js_FramePCToLineNumber(cx, cx->fp()), (void*)anchor, + FramePCOffset(cx, cx->fp())); verbose_only( tm->branches = new (*tm->dataAlloc) Seq(f, tm->branches); ) f->root = root; @@ -6108,7 +6110,7 @@ AttemptToExtendTree(JSContext* cx, VMSideExit* anchor, VMSideExit* exitedFrom, j } JS_ASSERT(ngslots >= anchor->numGlobalSlots); bool rv = TraceRecorder::startRecorder(cx, anchor, c, stackSlots, ngslots, typeMap, - exitedFrom, outer, numEntryFrameArgs(cx->fp), + exitedFrom, outer, numEntryFrameArgs(cx->fp()), Record_Branch, hits < maxHits); #ifdef MOZ_TRACEVIS if (!rv && tvso) @@ -6140,7 +6142,7 @@ JS_REQUIRES_STACK MonitorResult TraceRecorder::recordLoopEdge(JSContext* cx, TraceRecorder* r, uintN& inlineCallCount) { #ifdef JS_THREADSAFE - if (cx->fp->getScopeChain()->getGlobal()->scope()->title.ownercx != cx) { + if (cx->fp()->getScopeChain()->getGlobal()->scope()->title.ownercx != cx) { AbortRecording(cx, "Global object not owned by this context"); return MONITOR_NOT_RECORDING; /* we stay away from shared global objects */ } @@ -6157,13 +6159,13 @@ TraceRecorder::recordLoopEdge(JSContext* cx, TraceRecorder* r, uintN& inlineCall JS_ASSERT(r->fragment && !r->fragment->lastIns); TreeFragment* root = r->fragment->root; TreeFragment* first = LookupOrAddLoop(tm, cx->regs->pc, root->globalObj, - root->globalShape, numEntryFrameArgs(cx->fp)); + root->globalShape, numEntryFrameArgs(cx->fp())); /* * Make sure the shape of the global object still matches (this might flush * the JIT cache). */ - JSObject* globalObj = cx->fp->getScopeChain()->getGlobal(); + JSObject* globalObj = cx->fp()->getScopeChain()->getGlobal(); uint32 globalShape = -1; SlotList* globalSlots = NULL; if (!CheckGlobalObjectShape(cx, tm, globalObj, &globalShape, &globalSlots)) { @@ -6173,9 +6175,9 @@ TraceRecorder::recordLoopEdge(JSContext* cx, TraceRecorder* r, uintN& inlineCall debug_only_printf(LC_TMTracer, "Looking for type-compatible peer (%s:%d@%d)\n", - cx->fp->getScript()->filename, - js_FramePCToLineNumber(cx, cx->fp), - FramePCOffset(cx, cx->fp)); + cx->fp()->getScript()->filename, + js_FramePCToLineNumber(cx, cx->fp()), + FramePCOffset(cx, cx->fp())); // Find a matching inner tree. If none can be found, compile one. TreeFragment* f = r->findNestedCompatiblePeer(first); @@ -6185,7 +6187,7 @@ TraceRecorder::recordLoopEdge(JSContext* cx, TraceRecorder* r, uintN& inlineCall TreeFragment* outerFragment = root; jsbytecode* outer = (jsbytecode*) outerFragment->ip; uint32 outerArgc = outerFragment->argc; - JS_ASSERT(numEntryFrameArgs(cx->fp) == first->argc); + JS_ASSERT(numEntryFrameArgs(cx->fp()) == first->argc); AbortRecording(cx, "No compatible inner tree"); return RecordingIfTrue(RecordTree(cx, first, outer, outerArgc, globalSlots, Record_Branch)); @@ -6216,9 +6218,9 @@ TraceRecorder::attemptTreeCall(TreeFragment* f, uintN& inlineCallCount) * In the interim, just do tree calls knowing that they won't go into * recursive trees that can pop parent frames. */ - if (f->script == cx->fp->getScript()) { + if (f->script == cx->fp()->getScript()) { if (f->recursion >= Recursion_Unwinds) { - Blacklist(cx->fp->getScript()->code); + Blacklist(cx->fp()->getScript()->code); AbortRecording(cx, "Inner tree is an unsupported type of recursion"); return ARECORD_ABORTED; } @@ -6669,7 +6671,7 @@ ExecuteTrace(JSContext* cx, Fragment* f, TracerState& state) static JS_REQUIRES_STACK JS_ALWAYS_INLINE bool ScopeChainCheck(JSContext* cx, TreeFragment* f) { - JS_ASSERT(f->globalObj == cx->fp->getScopeChain()->getGlobal()); + JS_ASSERT(f->globalObj == cx->fp()->getScopeChain()->getGlobal()); /* * The JIT records and expects to execute with two scope-chain @@ -6687,7 +6689,7 @@ ScopeChainCheck(JSContext* cx, TreeFragment* f) * class types; once a global is found, it's checked for #1. Failing * either check causes an early return from execution. */ - JSObject* child = cx->fp->getScopeChain(); + JSObject* child = cx->fp()->getScopeChain(); while (JSObject* parent = child->getParent()) { if (!js_IsCacheableNonGlobalScope(child)) { debug_only_print0(LC_TMTracer,"Blacklist: non-cacheable object on scope chain.\n"); @@ -6747,9 +6749,9 @@ ExecuteTree(JSContext* cx, TreeFragment* f, uintN& inlineCallCount, AUDIT(traceTriggered); debug_only_printf(LC_TMTracer, "entering trace at %s:%u@%u, native stack slots: %u code: %p\n", - cx->fp->getScript()->filename, - js_FramePCToLineNumber(cx, cx->fp), - FramePCOffset(cx, cx->fp), + cx->fp()->getScript()->filename, + js_FramePCToLineNumber(cx, cx->fp()), + FramePCOffset(cx, cx->fp()), f->maxNativeStackSlots, f->code()); @@ -6866,11 +6868,11 @@ LeaveTree(TraceMonitor *tm, TracerState& state, VMSideExit* lr) * * First, if we just returned from a slow native, pop its stack frame. */ - if (!cx->fp->hasScript()) { + if (!cx->fp()->hasScript()) { JS_ASSERT(cx->regs == &state.bailedSlowNativeRegs); cx->stack().popSynthesizedSlowNativeFrame(cx); } - JS_ASSERT(cx->fp->hasScript()); + JS_ASSERT(cx->fp()->hasScript()); if (!(bs & BUILTIN_ERROR)) { /* @@ -6915,9 +6917,9 @@ LeaveTree(TraceMonitor *tm, TracerState& state, VMSideExit* lr) regs->sp -= (cs.format & JOF_INVOKE) ? GET_ARGC(regs->pc) + 2 : cs.nuses; regs->sp += cs.ndefs; regs->pc += cs.length; - JS_ASSERT_IF(!cx->fp->hasIMacroPC(), - cx->fp->slots() + cx->fp->getFixedCount() + - js_ReconstructStackDepth(cx, cx->fp->getScript(), regs->pc) == + JS_ASSERT_IF(!cx->fp()->hasIMacroPC(), + cx->fp()->slots() + cx->fp()->getFixedCount() + + js_ReconstructStackDepth(cx, cx->fp()->getScript(), regs->pc) == regs->sp); /* @@ -6971,9 +6973,9 @@ LeaveTree(TraceMonitor *tm, TracerState& state, VMSideExit* lr) */ SynthesizeFrame(cx, *fi, callee); int slots = FlushNativeStackFrame(cx, 1 /* callDepth */, (*callstack)->get_typemap(), - stack, cx->fp, 0); + stack, cx->fp(), 0); #ifdef DEBUG - JSStackFrame* fp = cx->fp; + JSStackFrame* fp = cx->fp(); debug_only_printf(LC_TMTracer, "synthesized deep frame for %s:%u@%u, slots=%d, fi=%p\n", fp->getScript()->filename, @@ -7009,7 +7011,7 @@ LeaveTree(TraceMonitor *tm, TracerState& state, VMSideExit* lr) calldepth_slots += SynthesizeFrame(cx, *callstack[n], callee); ++*state.inlineCallCountp; #ifdef DEBUG - JSStackFrame* fp = cx->fp; + JSStackFrame* fp = cx->fp(); debug_only_printf(LC_TMTracer, "synthesized shallow frame for %s:%u@%u\n", fp->getScript()->filename, js_FramePCToLineNumber(cx, fp), @@ -7024,7 +7026,7 @@ LeaveTree(TraceMonitor *tm, TracerState& state, VMSideExit* lr) * first we recover fp->blockChain, which comes from the side exit * struct. */ - JSStackFrame* const fp = cx->fp; + JSStackFrame* const fp = cx->fp(); fp->setBlockChain(innermost->block); @@ -7119,7 +7121,7 @@ LeaveTree(TraceMonitor *tm, TracerState& state, VMSideExit* lr) FlushNativeGlobalFrame(cx, globalObj, state.eos, ngslots, gslots, globalTypeMap); #ifdef DEBUG /* Verify that our state restoration worked. */ - for (JSStackFrame* fp = cx->fp; fp; fp = fp->down) { + for (JSStackFrame* fp = cx->fp(); fp; fp = fp->down) { JS_ASSERT_IF(fp->argv, fp->argv[-1].isObjectOrNull()); } #endif @@ -7179,7 +7181,7 @@ MonitorLoopEdge(JSContext* cx, uintN& inlineCallCount, RecordReason reason) * Make sure the shape of the global object still matches (this might flush * the JIT cache). */ - JSObject* globalObj = cx->fp->getScopeChain()->getGlobal(); + JSObject* globalObj = cx->fp()->getScopeChain()->getGlobal(); uint32 globalShape = -1; SlotList* globalSlots = NULL; @@ -7197,7 +7199,7 @@ MonitorLoopEdge(JSContext* cx, uintN& inlineCallCount, RecordReason reason) } jsbytecode* pc = cx->regs->pc; - uint32 argc = numEntryFrameArgs(cx->fp); + uint32 argc = numEntryFrameArgs(cx->fp()); TreeFragment* f = LookupOrAddLoop(tm, pc, globalObj, globalShape, argc); @@ -7236,8 +7238,8 @@ MonitorLoopEdge(JSContext* cx, uintN& inlineCallCount, RecordReason reason) debug_only_printf(LC_TMTracer, "Looking for compat peer %d@%d, from %p (ip: %p)\n", - js_FramePCToLineNumber(cx, cx->fp), - FramePCOffset(cx, cx->fp), (void*)f, f->ip); + js_FramePCToLineNumber(cx, cx->fp()), + FramePCOffset(cx, cx->fp()), (void*)f, f->ip); uintN count; TreeFragment* match = FindVMCompatiblePeer(cx, globalObj, f, count); @@ -7398,10 +7400,10 @@ TraceRecorder::monitorRecording(JSOp op) debug_only_stmt( if (LogController.lcbits & LC_TMRecorder) { - js_Disassemble1(cx, cx->fp->getScript(), cx->regs->pc, - cx->fp->hasIMacroPC() - ? 0 : cx->regs->pc - cx->fp->getScript()->code, - !cx->fp->hasIMacroPC(), stdout); + js_Disassemble1(cx, cx->fp()->getScript(), cx->regs->pc, + cx->fp()->hasIMacroPC() + ? 0 : cx->regs->pc - cx->fp()->getScript()->code, + !cx->fp()->hasIMacroPC(), stdout); } ) @@ -7414,7 +7416,7 @@ TraceRecorder::monitorRecording(JSOp op) AbortableRecordingStatus status; #ifdef DEBUG - bool wasInImacro = (cx->fp->hasIMacroPC()); + bool wasInImacro = (cx->fp()->hasIMacroPC()); #endif switch (op) { default: @@ -7433,7 +7435,7 @@ TraceRecorder::monitorRecording(JSOp op) if (!JSOP_IS_IMACOP(op)) { JS_ASSERT(status != ARECORD_IMACRO); - JS_ASSERT_IF(!wasInImacro, !localcx->fp->hasIMacroPC()); + JS_ASSERT_IF(!wasInImacro, !localcx->fp()->hasIMacroPC()); } if (localtm.recorder) { @@ -8055,15 +8057,15 @@ DeepBail(JSContext *cx) JS_REQUIRES_STACK Value& TraceRecorder::argval(unsigned n) const { - JS_ASSERT(n < cx->fp->numFormalArgs()); - return cx->fp->argv[n]; + JS_ASSERT(n < cx->fp()->numFormalArgs()); + return cx->fp()->argv[n]; } JS_REQUIRES_STACK Value& TraceRecorder::varval(unsigned n) const { - JS_ASSERT(n < cx->fp->getSlotCount()); - return cx->fp->slots()[n]; + JS_ASSERT(n < cx->fp()->getSlotCount()); + return cx->fp()->slots()[n]; } JS_REQUIRES_STACK Value& @@ -8075,10 +8077,10 @@ TraceRecorder::stackval(int n) const JS_REQUIRES_STACK void TraceRecorder::updateAtoms() { - atoms = FrameAtomBase(cx, cx->fp); - consts = cx->fp->hasIMacroPC() || cx->fp->getScript()->constOffset == 0 + atoms = FrameAtomBase(cx, cx->fp()); + consts = cx->fp()->hasIMacroPC() || cx->fp()->getScript()->constOffset == 0 ? 0 - : cx->fp->getScript()->consts()->vector; + : cx->fp()->getScript()->consts()->vector; } JS_REQUIRES_STACK void @@ -8094,8 +8096,8 @@ TraceRecorder::updateAtoms(JSScript *script) JS_REQUIRES_STACK LIns* TraceRecorder::scopeChain() { - return cx->fp->callee() - ? getFrameObjPtr(cx->fp->addressScopeChain()) + return cx->fp()->callee() + ? getFrameObjPtr(cx->fp()->addressScopeChain()) : entryScopeChain(); } @@ -8107,21 +8109,30 @@ TraceRecorder::scopeChain() JS_REQUIRES_STACK LIns* TraceRecorder::entryScopeChain() const { - return lir->insLoad(LIR_ldp, - lir->insLoad(LIR_ldp, cx_ins, offsetof(JSContext, fp), ACCSET_OTHER), + return lir->insLoad(LIR_ldp, entryFrameIns(), JSStackFrame::offsetScopeChain(), ACCSET_OTHER); } +/* + * Generate LIR to compute the stack frame on entry to the trace. + */ +JS_REQUIRES_STACK LIns* +TraceRecorder::entryFrameIns() const +{ + LIns *regs_ins = lir->insLoad(LIR_ldp, cx_ins, offsetof(JSContext, regs), ACCSET_OTHER); + return lir->insLoad(LIR_ldp, regs_ins, offsetof(JSFrameRegs, fp), ACCSET_OTHER); +} + /* * Return the frame of a call object if that frame is part of the current * trace. |depthp| is an optional outparam: if it is non-null, it will be - * filled in with the depth of the call object's frame relevant to cx->fp. + * filled in with the depth of the call object's frame relevant to cx->fp(). */ JS_REQUIRES_STACK JSStackFrame* TraceRecorder::frameIfInRange(JSObject* obj, unsigned* depthp) const { JSStackFrame* ofp = (JSStackFrame*) obj->getPrivate(); - JSStackFrame* fp = cx->fp; + JSStackFrame* fp = cx->fp(); for (unsigned depth = 0; depth <= callDepth; ++depth) { if (fp == ofp) { if (depthp) @@ -8151,7 +8162,7 @@ JS_DEFINE_CALLINFO_4(extern, UINT32, GetClosureArg, CONTEXT, OBJECT, CVIPTR, DOU JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::scopeChainProp(JSObject* chainHead, Value*& vp, LIns*& ins, NameResult& nr) { - JS_ASSERT(chainHead == cx->fp->getScopeChain()); + JS_ASSERT(chainHead == cx->fp()->getScopeChain()); JS_ASSERT(chainHead != globalObj); TraceMonitor &localtm = *traceMonitor; @@ -8175,12 +8186,12 @@ TraceRecorder::scopeChainProp(JSObject* chainHead, Value*& vp, LIns*& ins, NameR // the creation of properties that shadow the property in the middle // of the scope chain. LIns *head_ins; - if (cx->fp->argv) { + if (cx->fp()->argv) { // Skip any Call object when inside a function. Any reference to a // Call name the compiler resolves statically and we do not need // to match shapes of the Call objects. - chainHead = cx->fp->callee()->getParent(); - head_ins = stobj_get_parent(get(&cx->fp->argv[-2])); + chainHead = cx->fp()->callee()->getParent(); + head_ins = stobj_get_parent(get(&cx->fp()->argv[-2])); } else { head_ins = scopeChain(); } @@ -8277,8 +8288,8 @@ TraceRecorder::callProp(JSObject* obj, JSProperty* prop, jsid id, Value*& vp, } LIns* obj_ins; - JSObject* parent = cx->fp->callee()->getParent(); - LIns* parent_ins = stobj_get_parent(get(&cx->fp->argv[-2])); + JSObject* parent = cx->fp()->callee()->getParent(); + LIns* parent_ins = stobj_get_parent(get(&cx->fp()->argv[-2])); CHECK_STATUS(traverseScopeChain(parent, parent_ins, obj, obj_ins)); LIns* call_ins; @@ -8639,7 +8650,7 @@ JS_REQUIRES_STACK bool TraceRecorder::canCallImacro() const { /* We cannot nest imacros. */ - return !cx->fp->hasIMacroPC(); + return !cx->fp()->hasIMacroPC(); } JS_REQUIRES_STACK RecordingStatus @@ -8651,7 +8662,7 @@ TraceRecorder::callImacro(jsbytecode* imacro) JS_REQUIRES_STACK RecordingStatus TraceRecorder::callImacroInfallibly(jsbytecode* imacro) { - JSStackFrame* fp = cx->fp; + JSStackFrame* fp = cx->fp(); JS_ASSERT(!fp->hasIMacroPC()); JSFrameRegs* regs = cx->regs; fp->setIMacroPC(regs->pc); @@ -9556,7 +9567,7 @@ TraceRecorder::guardPropertyCacheHit(LIns* obj_ins, if (entry->adding()) RETURN_STOP("adding a property to the global object"); - JSOp op = js_GetOpcode(cx, cx->fp->getScript(), cx->regs->pc); + JSOp op = js_GetOpcode(cx, cx->fp()->getScript(), cx->regs->pc); if (JOF_OPMODE(op) != JOF_NAME) { guard(true, addName(lir->ins2(LIR_eqp, obj_ins, INS_CONSTOBJ(globalObj)), "guard_global"), @@ -10059,25 +10070,25 @@ JS_REQUIRES_STACK RecordingStatus TraceRecorder::getThis(LIns*& this_ins) { /* - * JSStackFrame::getThisObject updates cx->fp->argv[-1], so sample it into 'original' first. + * JSStackFrame::getThisObject updates cx->fp()->argv[-1], so sample it into 'original' first. */ Value original = NullValue(); - if (cx->fp->argv) { - original = cx->fp->argv[-1]; + if (cx->fp()->argv) { + original = cx->fp()->argv[-1]; if (!original.isPrimitive()) { if (original.toObject().hasClass(&js_WithClass)) RETURN_STOP("can't trace getThis on With object"); - guardNotClass(get(&cx->fp->argv[-1]), &js_WithClass, snapshot(MISMATCH_EXIT), + guardNotClass(get(&cx->fp()->argv[-1]), &js_WithClass, snapshot(MISMATCH_EXIT), LOAD_NORMAL); } } - JSObject* thisObj = cx->fp->getThisObject(cx); + JSObject* thisObj = cx->fp()->getThisObject(cx); if (!thisObj) RETURN_ERROR("fp->getThisObject failed"); /* In global code, bake in the global object as 'this' object. */ - if (!cx->fp->callee()) { + if (!cx->fp()->callee()) { JS_ASSERT(callDepth == 0); this_ins = INS_CONSTOBJ(thisObj); @@ -10088,7 +10099,7 @@ TraceRecorder::getThis(LIns*& this_ins) return RECORD_CONTINUE; } - Value& thisv = cx->fp->argv[-1]; + Value& thisv = cx->fp()->argv[-1]; JS_ASSERT(thisv.isObject()); /* @@ -10267,7 +10278,7 @@ TraceRecorder::clearFrameSlotsFromTracker(Tracker& which, JSStackFrame* fp, unsi JS_REQUIRES_STACK JSStackFrame* TraceRecorder::entryFrame() const { - JSStackFrame *fp = cx->fp; + JSStackFrame *fp = cx->fp(); for (unsigned i = 0; i < callDepth; ++i) fp = fp->down; return fp; @@ -10286,7 +10297,7 @@ JS_REQUIRES_STACK void TraceRecorder::clearCurrentFrameSlotsFromTracker(Tracker& which) { // Clear out all local slots. - clearFrameSlotsFromTracker(which, cx->fp, cx->fp->getSlotCount()); + clearFrameSlotsFromTracker(which, cx->fp(), cx->fp()->getSlotCount()); } /* @@ -10297,23 +10308,25 @@ TraceRecorder::clearCurrentFrameSlotsFromTracker(Tracker& which) JS_REQUIRES_STACK void TraceRecorder::putActivationObjects() { - bool have_args = cx->fp->hasArgsObj() && - !cx->fp->getArgsObj()->isStrictArguments() && - cx->fp->numActualArgs() > 0; - bool have_call = cx->fp->hasFunction() && - JSFUN_HEAVYWEIGHT_TEST(cx->fp->getFunction()->flags) && - cx->fp->getFunction()->countArgsAndVars(); + JSStackFrame *fp = cx->fp(); + + bool have_args = fp->hasArgsObj() && + !fp->getArgsObj()->isStrictArguments() && + fp->numActualArgs() > 0; + bool have_call = fp->hasFunction() && + JSFUN_HEAVYWEIGHT_TEST(fp->getFunction()->flags) && + fp->getFunction()->countArgsAndVars(); if (!have_args && !have_call) return; - int nargs = have_args ? argSlots(cx->fp) : cx->fp->numFormalArgs(); + int nargs = have_args ? argSlots(fp) : fp->numFormalArgs(); LIns* args_ins; if (nargs) { args_ins = lir->insAlloc(sizeof(Value) * nargs); for (int i = 0; i < nargs; ++i) { - box_value_into(cx->fp->argv[i], get(&cx->fp->argv[i]), args_ins, i * sizeof(Value), + box_value_into(fp->argv[i], get(&fp->argv[i]), args_ins, i * sizeof(Value), ACCSET_OTHER); } } else { @@ -10321,27 +10334,27 @@ TraceRecorder::putActivationObjects() } if (have_args) { - LIns* argsobj_ins = getFrameObjPtr(cx->fp->addressArgsObj()); + LIns* argsobj_ins = getFrameObjPtr(fp->addressArgsObj()); LIns* args[] = { args_ins, argsobj_ins, cx_ins }; lir->insCall(&js_PutArguments_ci, args); } if (have_call) { - int nslots = cx->fp->getFunction()->countVars(); + int nslots = fp->getFunction()->countVars(); LIns* slots_ins; if (nslots) { slots_ins = lir->insAlloc(sizeof(Value) * nslots); for (int i = 0; i < nslots; ++i) { - box_value_into(cx->fp->slots()[i], get(&cx->fp->slots()[i]), slots_ins, + box_value_into(fp->slots()[i], get(&fp->slots()[i]), slots_ins, i * sizeof(Value), ACCSET_OTHER); } } else { slots_ins = INS_CONSTPTR(0); } - LIns* scopeChain_ins = getFrameObjPtr(cx->fp->addressScopeChain()); + LIns* scopeChain_ins = getFrameObjPtr(fp->addressScopeChain()); LIns* args[] = { slots_ins, INS_CONST(nslots), args_ins, - INS_CONST(cx->fp->numFormalArgs()), scopeChain_ins, cx_ins }; + INS_CONST(fp->numFormalArgs()), scopeChain_ins, cx_ins }; lir->insCall(&js_PutCallObjectOnTrace_ci, args); } } @@ -10349,8 +10362,8 @@ TraceRecorder::putActivationObjects() static JS_REQUIRES_STACK inline bool IsTraceableRecursion(JSContext *cx) { - JSStackFrame *fp = cx->fp; - JSStackFrame *down = cx->fp->down; + JSStackFrame *fp = cx->fp(); + JSStackFrame *down = fp->down; if (!down) return false; if (down->maybeScript() != fp->maybeScript()) @@ -10373,17 +10386,17 @@ IsTraceableRecursion(JSContext *cx) JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_EnterFrame(uintN& inlineCallCount) { - JSStackFrame* const fp = cx->fp; + JSStackFrame* const fp = cx->fp(); if (++callDepth >= MAX_CALLDEPTH) RETURN_STOP_A("exceeded maximum call depth"); debug_only_printf(LC_TMTracer, "EnterFrame %s, callDepth=%d\n", - js_AtomToPrintableString(cx, cx->fp->getFunction()->atom), + js_AtomToPrintableString(cx, cx->fp()->getFunction()->atom), callDepth); debug_only_stmt( if (LogController.lcbits & LC_TMRecorder) { - js_Disassemble(cx, cx->fp->getScript(), JS_TRUE, stdout); + js_Disassemble(cx, cx->fp()->getScript(), JS_TRUE, stdout); debug_only_print0(LC_TMTracer, "----\n"); } ) @@ -10425,18 +10438,18 @@ TraceRecorder::record_EnterFrame(uintN& inlineCallCount) for (; vp < vpstop; ++vp) nativeFrameTracker.set(vp, NULL); - LIns* callee_ins = get(&cx->fp->argv[-2]); + LIns* callee_ins = get(&cx->fp()->argv[-2]); LIns* scopeChain_ins = stobj_get_parent(callee_ins); - if (cx->fp->hasFunction() && JSFUN_HEAVYWEIGHT_TEST(cx->fp->getFunction()->flags)) { + if (cx->fp()->hasFunction() && JSFUN_HEAVYWEIGHT_TEST(cx->fp()->getFunction()->flags)) { // We need to make sure every part of the frame is known to the tracker // before taking a snapshot. setFrameObjPtr(fp->addressScopeChain(), INS_NULL()); - if (js_IsNamedLambda(cx->fp->getFunction())) + if (js_IsNamedLambda(cx->fp()->getFunction())) RETURN_STOP_A("can't call named lambda heavyweight on trace"); - LIns* fun_ins = INS_CONSTPTR(cx->fp->getFunction()); + LIns* fun_ins = INS_CONSTPTR(cx->fp()->getFunction()); LIns* args[] = { scopeChain_ins, callee_ins, fun_ins, cx_ins }; LIns* call_ins = lir->insCall(&js_CreateCallObjectOnTrace_ci, args); @@ -10454,10 +10467,10 @@ TraceRecorder::record_EnterFrame(uintN& inlineCallCount) * concerned about. That should pass through below to not regress pre-recursion * functionality. */ - if (IsTraceableRecursion(cx) && tree->script == cx->fp->getScript()) { + if (IsTraceableRecursion(cx) && tree->script == cx->fp()->getScript()) { if (tree->recursion == Recursion_Disallowed) RETURN_STOP_A("recursion not allowed in this tree"); - if (tree->script != cx->fp->getScript()) + if (tree->script != cx->fp()->getScript()) RETURN_STOP_A("recursion does not match original tree"); return InjectStatus(downRecursion()); } @@ -10489,7 +10502,7 @@ TraceRecorder::record_EnterFrame(uintN& inlineCallCount) JSContext* _cx = cx; SlotList* globalSlots = tree->globalSlots; AbortRecording(cx, "trying to compile inner recursive tree"); - JS_ASSERT(_cx->fp->numActualArgs() == first->argc); + JS_ASSERT(_cx->fp()->numActualArgs() == first->argc); RecordTree(_cx, first, NULL, 0, globalSlots, Record_EnterFrame); break; } @@ -10500,7 +10513,7 @@ TraceRecorder::record_EnterFrame(uintN& inlineCallCount) * Make sure the shape of the global object still matches (this might * flush the JIT cache). */ - JSObject* globalObj = cx->fp->getScopeChain()->getGlobal(); + JSObject* globalObj = cx->fp()->getScopeChain()->getGlobal(); uint32 globalShape = -1; SlotList* globalSlots = NULL; if (!CheckGlobalObjectShape(cx, traceMonitor, globalObj, &globalShape, &globalSlots)) @@ -10515,21 +10528,21 @@ JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_LeaveFrame() { debug_only_stmt( - if (cx->fp->hasFunction()) + if (cx->fp()->hasFunction()) debug_only_printf(LC_TMTracer, "LeaveFrame (back to %s), callDepth=%d\n", - js_AtomToPrintableString(cx, cx->fp->getFunction()->atom), + js_AtomToPrintableString(cx, cx->fp()->getFunction()->atom), callDepth); ); - JS_ASSERT(js_CodeSpec[js_GetOpcode(cx, cx->fp->getScript(), + JS_ASSERT(js_CodeSpec[js_GetOpcode(cx, cx->fp()->getScript(), cx->regs->pc)].length == JSOP_CALL_LENGTH); if (callDepth-- <= 0) RETURN_STOP_A("returned out of a loop we started tracing"); // LeaveFrame gets called after the interpreter popped the frame and - // stored rval, so cx->fp not cx->fp->down, and -1 not 0. + // stored rval, so cx->fp() not cx->fp()->down, and -1 not 0. updateAtoms(); set(&stackval(-1), rval_ins); return ARECORD_CONTINUE; @@ -10547,10 +10560,10 @@ TraceRecorder::record_JSOP_POPV() { Value& rval = stackval(-1); - // Store it in cx->fp->rval. NB: Tricky dependencies. cx->fp is the right + // Store it in cx->fp()->rval. NB: Tricky dependencies. cx->fp() is the right // frame because POPV appears only in global and eval code and we don't // trace JSOP_EVAL or leaving the frame where tracing started. - LIns *fp_ins = lir->insLoad(LIR_ldp, cx_ins, offsetof(JSContext, fp), ACCSET_OTHER); + LIns *fp_ins = entryFrameIns(); box_value_into(rval, get(&rval), fp_ins, JSStackFrame::offsetReturnValue(), ACCSET_OTHER); return ARECORD_CONTINUE; } @@ -10589,7 +10602,7 @@ TraceRecorder::record_JSOP_RETURN() /* A return from callDepth 0 terminates the current loop, except for recursion. */ if (callDepth == 0) { if (IsTraceableRecursion(cx) && tree->recursion != Recursion_Disallowed && - tree->script == cx->fp->getScript()) { + tree->script == cx->fp()->getScript()) { return InjectStatus(upRecursion()); } else { AUDIT(returnLoopExits); @@ -10601,7 +10614,7 @@ TraceRecorder::record_JSOP_RETURN() #ifdef MOZ_TRACE_JSCALLS if (cx->functionCallback) { - LIns* args[] = { INS_CONST(0), INS_CONSTPTR(cx->fp->getFunction()), cx_ins }; + LIns* args[] = { INS_CONST(0), INS_CONSTPTR(cx->fp()->getFunction()), cx_ins }; LIns* call_ins = lir->insCall(&functionProbe_ci, args); guard(false, lir->insEqI_0(call_ins), MISMATCH_EXIT); } @@ -10609,8 +10622,8 @@ TraceRecorder::record_JSOP_RETURN() /* If we inlined this function call, make the return value available to the caller code. */ Value& rval = stackval(-1); - JSStackFrame *fp = cx->fp; - if ((cx->fp->flags & JSFRAME_CONSTRUCTING) && rval.isPrimitive()) { + JSStackFrame *fp = cx->fp(); + if ((fp->flags & JSFRAME_CONSTRUCTING) && rval.isPrimitive()) { JS_ASSERT(fp->getThisValue() == fp->argv[-1]); rval_ins = get(&fp->argv[-1]); } else { @@ -10618,7 +10631,7 @@ TraceRecorder::record_JSOP_RETURN() } debug_only_printf(LC_TMTracer, "returning from %s\n", - js_AtomToPrintableString(cx, cx->fp->getFunction()->atom)); + js_AtomToPrintableString(cx, fp->getFunction()->atom)); clearCurrentFrameSlotsFromTracker(nativeFrameTracker); return ARECORD_CONTINUE; @@ -10632,7 +10645,7 @@ TraceRecorder::record_JSOP_GOTO() * generate an always-taken loop exit guard. For other downward gotos * (like if/else) continue recording. */ - jssrcnote* sn = js_GetSrcNote(cx->fp->getScript(), cx->regs->pc); + jssrcnote* sn = js_GetSrcNote(cx->fp()->getScript(), cx->regs->pc); if (sn && (SN_TYPE(sn) == SRC_BREAK || SN_TYPE(sn) == SRC_CONT2LABEL)) { AUDIT(breakLoopExits); @@ -10658,14 +10671,14 @@ LIns* TraceRecorder::newArguments(LIns* callee_ins, bool strict) { LIns* global_ins = INS_CONSTOBJ(globalObj); - LIns* argc_ins = INS_CONST(cx->fp->numActualArgs()); + LIns* argc_ins = INS_CONST(cx->fp()->numActualArgs()); LIns* args[] = { callee_ins, argc_ins, global_ins, cx_ins }; LIns* call_ins = lir->insCall(&js_Arguments_ci, args); guard(false, lir->insEqP_0(call_ins), OOM_EXIT); if (strict) { - JSStackFrame* fp = cx->fp; + JSStackFrame* fp = cx->fp(); uintN argc = fp->numActualArgs(); LIns* argsSlots_ins = NULL; for (uintN i = 0; i < argc; i++) @@ -10678,15 +10691,15 @@ TraceRecorder::newArguments(LIns* callee_ins, bool strict) JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_ARGUMENTS() { - /* In an eval, 'arguments' will be a BINDNAME, which we don't trace. */ - JS_ASSERT(!(cx->fp->flags & JSFRAME_EVAL)); + JSStackFrame* const fp = cx->fp(); - if (cx->fp->flags & JSFRAME_OVERRIDE_ARGS) + /* In an eval, 'arguments' will be a BINDNAME, which we don't trace. */ + JS_ASSERT(!(fp->flags & JSFRAME_EVAL)); + + if (fp->flags & JSFRAME_OVERRIDE_ARGS) RETURN_STOP_A("Can't trace |arguments| if |arguments| is assigned to"); - JSStackFrame* const fp = cx->fp; - - LIns* a_ins = getFrameObjPtr(cx->fp->addressArgsObj()); + LIns* a_ins = getFrameObjPtr(fp->addressArgsObj()); LIns* args_ins; LIns* callee_ins = get(&fp->argv[-2]); bool strict = fp->getFunction()->inStrictMode(); @@ -10751,7 +10764,7 @@ TraceRecorder::record_JSOP_PICK() { Value* sp = cx->regs->sp; jsint n = cx->regs->pc[1]; - JS_ASSERT(sp - (n+1) >= cx->fp->base()); + JS_ASSERT(sp - (n+1) >= cx->fp()->base()); LIns* top = get(sp - (n+1)); for (jsint i = 0; i < n; ++i) set(sp - (n+1) + i, get(sp - n + i)); @@ -11275,7 +11288,7 @@ JS_REQUIRES_STACK RecordingStatus TraceRecorder::callSpecializedNative(JSNativeTraceInfo *trcinfo, uintN argc, bool constructing) { - JSStackFrame* const fp = cx->fp; + JSStackFrame* const fp = cx->fp(); jsbytecode *pc = cx->regs->pc; Value& fval = stackval(0 - (2 + argc)); @@ -11628,7 +11641,7 @@ JS_REQUIRES_STACK RecordingStatus TraceRecorder::functionCall(uintN argc, JSOp mode) { Value& fval = stackval(0 - (2 + argc)); - JS_ASSERT(&fval >= cx->fp->base()); + JS_ASSERT(&fval >= cx->fp()->base()); if (!IsFunctionObject(fval)) RETURN_STOP("callee is not a function"); @@ -12182,7 +12195,7 @@ TraceRecorder::setCallProp(JSObject *callobj, LIns *callobj_ins, JSScopeProperty // entry frame. In that case, we must store to the native stack area for // that frame. - LIns *fp_ins = lir->insLoad(LIR_ldp, cx_ins, offsetof(JSContext, fp), ACCSET_OTHER); + LIns *fp_ins = entryFrameIns(); LIns *fpcallobj_ins = lir->insLoad(LIR_ldp, fp_ins, JSStackFrame::offsetCallObj(), ACCSET_OTHER); LIns *br1 = lir->insBranch(LIR_jf, lir->ins2(LIR_eqp, fpcallobj_ins, callobj_ins), NULL); @@ -13113,7 +13126,7 @@ TraceRecorder::record_JSOP_SETELEM() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_CALLNAME() { - JSObject* obj = cx->fp->getScopeChain(); + JSObject* obj = cx->fp()->getScopeChain(); if (obj != globalObj) { Value* vp; LIns* ins; @@ -13247,7 +13260,7 @@ JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_GETUPVAR() { uintN index = GET_UINT16(cx->regs->pc); - JSScript *script = cx->fp->getScript(); + JSScript *script = cx->fp()->getScript(); JSUpvarArray* uva = script->upvars(); JS_ASSERT(index < uva->length); @@ -13270,8 +13283,8 @@ TraceRecorder::record_JSOP_CALLUPVAR() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_GETDSLOT() { - JSObject* callee = cx->fp->callee(); - LIns* callee_ins = get(&cx->fp->argv[-2]); + JSObject* callee = cx->fp()->callee(); + LIns* callee_ins = get(&cx->fp()->argv[-2]); unsigned index = GET_UINT16(cx->regs->pc); LIns* dslots_ins = lir->insLoad(LIR_ldp, callee_ins, offsetof(JSObject, dslots), ACCSET_OTHER); @@ -13395,7 +13408,7 @@ TraceRecorder::interpretedFunctionCall(Value& fval, JSFunction* fun, uintN argc, if (fval.toObject().getGlobal() != globalObj) RETURN_STOP("JSOP_CALL or JSOP_NEW crosses global scopes"); - JSStackFrame* const fp = cx->fp; + JSStackFrame* const fp = cx->fp(); // Generate a type map for the outgoing frame and stash it in the LIR unsigned stackSlots = NativeStackSlots(cx, 0 /* callDepth */); @@ -13445,7 +13458,7 @@ TraceRecorder::record_JSOP_CALL() uintN argc = GET_ARGC(cx->regs->pc); cx->assertValidStackDepth(argc + 2); return InjectStatus(functionCall(argc, - (cx->fp->hasIMacroPC() && *cx->fp->getIMacroPC() == JSOP_APPLY) + (cx->fp()->hasIMacroPC() && *cx->fp()->getIMacroPC() == JSOP_APPLY) ? JSOP_APPLY : JSOP_CALL)); } @@ -13486,7 +13499,7 @@ TraceRecorder::record_JSOP_APPLY() JSObject* aobj = NULL; LIns* aobj_ins = NULL; - JS_ASSERT(!cx->fp->hasIMacroPC()); + JS_ASSERT(!cx->fp()->hasIMacroPC()); if (!IsFunctionObject(vp[0])) return record_JSOP_CALL(); @@ -13655,7 +13668,7 @@ TraceRecorder::record_NativeCallComplete() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::name(Value*& vp, LIns*& ins, NameResult& nr) { - JSObject* obj = cx->fp->getScopeChain(); + JSObject* obj = cx->fp()->getScopeChain(); if (obj != globalObj) return scopeChainProp(obj, vp, ins, nr); @@ -13871,7 +13884,7 @@ TraceRecorder::propTail(JSObject* obj, LIns* obj_ins, JSObject* obj2, PCVal pcva * property gets it does (e.g., for 'toString' from JSOP_NEW) will not be * leaked to the calling script. */ - if (isMethod && !cx->fp->hasIMacroPC()) { + if (isMethod && !cx->fp()->hasIMacroPC()) { enterDeepBailCall(); LIns* args[] = { v_ins, INS_CONSTSPROP(sprop), obj_ins, cx_ins }; v_ins = lir->insCall(&MethodReadBarrier_ci, args); @@ -14181,7 +14194,7 @@ TraceRecorder::record_JSOP_STRICTNE() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_OBJECT() { - JSStackFrame* const fp = cx->fp; + JSStackFrame* const fp = cx->fp(); JSScript* script = fp->getScript(); unsigned index = atoms - script->atomMap.vector + GET_INDEX(cx->regs->pc); @@ -14347,7 +14360,7 @@ TraceRecorder::record_JSOP_LOCALDEC() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_IMACOP() { - JS_ASSERT(cx->fp->hasIMacroPC()); + JS_ASSERT(cx->fp()->hasIMacroPC()); return ARECORD_CONTINUE; } @@ -14753,7 +14766,7 @@ TraceRecorder::traverseScopeChain(JSObject *obj, LIns *obj_ins, JSObject *target JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_BINDNAME() { - JSStackFrame* const fp = cx->fp; + JSStackFrame* const fp = cx->fp(); JSObject *obj; if (!fp->hasFunction()) { @@ -14806,7 +14819,7 @@ TraceRecorder::record_JSOP_BINDNAME() // We don't have the scope chain on trace, so instead we get a start object // that is on the scope chain and doesn't skip the target object (the one // that contains the property). - Value *callee = &cx->fp->argv[-2]; + Value *callee = &cx->fp()->argv[-2]; obj = callee->toObject().getParent(); if (obj == globalObj) { stack(0, INS_CONSTOBJ(obj)); @@ -15056,7 +15069,7 @@ jsatomid TraceRecorder::getFullIndex(ptrdiff_t pcoff) { jsatomid index = GET_INDEX(cx->regs->pc + pcoff); - index += atoms - cx->fp->getScript()->atomMap.vector; + index += atoms - cx->fp()->getScript()->atomMap.vector; return index; } @@ -15064,7 +15077,7 @@ JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_LAMBDA() { JSFunction* fun; - fun = cx->fp->getScript()->getFunction(getFullIndex()); + fun = cx->fp()->getScript()->getFunction(getFullIndex()); /* * Emit code to clone a null closure parented by this recorder's global @@ -15163,7 +15176,7 @@ JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_LAMBDA_FC() { JSFunction* fun; - fun = cx->fp->getScript()->getFunction(getFullIndex()); + fun = cx->fp()->getScript()->getFunction(getFullIndex()); if (FUN_OBJECT(fun)->getParent() != globalObj) return ARECORD_STOP; @@ -15198,7 +15211,7 @@ TraceRecorder::record_JSOP_LAMBDA_FC() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_CALLEE() { - stack(0, get(&cx->fp->argv[-2])); + stack(0, get(&cx->fp()->argv[-2])); return ARECORD_CONTINUE; } @@ -15246,12 +15259,12 @@ TraceRecorder::record_JSOP_NOP() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_ARGSUB() { - JSStackFrame* const fp = cx->fp; + JSStackFrame* const fp = cx->fp(); if (!(fp->getFunction()->flags & JSFUN_HEAVYWEIGHT)) { uintN slot = GET_ARGNO(cx->regs->pc); if (slot >= fp->numActualArgs()) RETURN_STOP_A("can't trace out-of-range arguments"); - stack(0, get(&cx->fp->argv[slot])); + stack(0, get(&cx->fp()->argv[slot])); return ARECORD_CONTINUE; } RETURN_STOP_A("can't trace JSOP_ARGSUB hard case"); @@ -15271,7 +15284,9 @@ TraceRecorder::guardArgsLengthNotAssigned(LIns* argsobj_ins) JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_ARGCNT() { - if (cx->fp->getFunction()->flags & JSFUN_HEAVYWEIGHT) + JSStackFrame * const fp = cx->fp(); + + if (fp->getFunction()->flags & JSFUN_HEAVYWEIGHT) RETURN_STOP_A("can't trace heavyweight JSOP_ARGCNT"); // argc is fixed on trace, so ideally we would simply generate LIR for @@ -15280,16 +15295,16 @@ TraceRecorder::record_JSOP_ARGCNT() // We also have to check that arguments.length has not been mutated // at record time, because if so we will generate incorrect constant // LIR, which will assert in alu(). - if (cx->fp->hasArgsObj() && cx->fp->getArgsObj()->isArgsLengthOverridden()) + if (fp->hasArgsObj() && fp->getArgsObj()->isArgsLengthOverridden()) RETURN_STOP_A("can't trace JSOP_ARGCNT if arguments.length has been modified"); - LIns *a_ins = getFrameObjPtr(cx->fp->addressArgsObj()); + LIns *a_ins = getFrameObjPtr(fp->addressArgsObj()); if (callDepth == 0) { LIns *br = lir->insBranch(LIR_jt, lir->insEqP_0(a_ins), NULL); guardArgsLengthNotAssigned(a_ins); LIns *label = lir->ins0(LIR_label); br->setTarget(label); } - stack(0, lir->insImmD(cx->fp->numActualArgs())); + stack(0, lir->insImmD(fp->numActualArgs())); return ARECORD_CONTINUE; } @@ -15418,7 +15433,7 @@ TraceRecorder::record_JSOP_RETRVAL() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_GETGVAR() { - Value slotval = cx->fp->slots()[GET_SLOTNO(cx->regs->pc)]; + Value slotval = cx->fp()->slots()[GET_SLOTNO(cx->regs->pc)]; if (slotval.isNull()) return ARECORD_CONTINUE; // We will see JSOP_NAME from the interpreter's jump, so no-op here. @@ -15434,7 +15449,7 @@ TraceRecorder::record_JSOP_GETGVAR() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_SETGVAR() { - Value slotval = cx->fp->slots()[GET_SLOTNO(cx->regs->pc)]; + Value slotval = cx->fp()->slots()[GET_SLOTNO(cx->regs->pc)]; if (slotval.isNull()) return ARECORD_CONTINUE; // We will see JSOP_NAME from the interpreter's jump, so no-op here. @@ -15450,7 +15465,7 @@ TraceRecorder::record_JSOP_SETGVAR() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_INCGVAR() { - Value slotval = cx->fp->slots()[GET_SLOTNO(cx->regs->pc)]; + Value slotval = cx->fp()->slots()[GET_SLOTNO(cx->regs->pc)]; if (slotval.isNull()) // We will see JSOP_INCNAME from the interpreter's jump, so no-op here. return ARECORD_CONTINUE; @@ -15466,7 +15481,7 @@ TraceRecorder::record_JSOP_INCGVAR() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_DECGVAR() { - Value slotval = cx->fp->slots()[GET_SLOTNO(cx->regs->pc)]; + Value slotval = cx->fp()->slots()[GET_SLOTNO(cx->regs->pc)]; if (slotval.isNull()) // We will see JSOP_INCNAME from the interpreter's jump, so no-op here. return ARECORD_CONTINUE; @@ -15482,7 +15497,7 @@ TraceRecorder::record_JSOP_DECGVAR() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_GVARINC() { - Value slotval = cx->fp->slots()[GET_SLOTNO(cx->regs->pc)]; + Value slotval = cx->fp()->slots()[GET_SLOTNO(cx->regs->pc)]; if (slotval.isNull()) // We will see JSOP_INCNAME from the interpreter's jump, so no-op here. return ARECORD_CONTINUE; @@ -15498,7 +15513,7 @@ TraceRecorder::record_JSOP_GVARINC() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_GVARDEC() { - Value slotval = cx->fp->slots()[GET_SLOTNO(cx->regs->pc)]; + Value slotval = cx->fp()->slots()[GET_SLOTNO(cx->regs->pc)]; if (slotval.isNull()) // We will see JSOP_INCNAME from the interpreter's jump, so no-op here. return ARECORD_CONTINUE; @@ -15514,7 +15529,7 @@ TraceRecorder::record_JSOP_GVARDEC() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_REGEXP() { - JSStackFrame* const fp = cx->fp; + JSStackFrame* const fp = cx->fp(); JSScript* script = fp->getScript(); unsigned index = atoms - script->atomMap.vector + GET_INDEX(cx->regs->pc); @@ -15799,10 +15814,10 @@ TraceRecorder::record_JSOP_STOP() { if (callDepth == 0 && IsTraceableRecursion(cx) && tree->recursion != Recursion_Disallowed && - tree->script == cx->fp->getScript()) { + tree->script == cx->fp()->getScript()) { return InjectStatus(upRecursion()); } - JSStackFrame *fp = cx->fp; + JSStackFrame *fp = cx->fp(); if (fp->hasIMacroPC()) { /* @@ -15818,7 +15833,7 @@ TraceRecorder::record_JSOP_STOP() #ifdef MOZ_TRACE_JSCALLS if (cx->functionCallback) { - LIns* args[] = { INS_CONST(0), INS_CONSTPTR(cx->fp->getFunction()), cx_ins }; + LIns* args[] = { INS_CONST(0), INS_CONSTPTR(cx->fp()->getFunction()), cx_ins }; LIns* call_ins = lir->insCall(&functionProbe_ci, args); guard(false, lir->insEqI_0(call_ins), MISMATCH_EXIT); } @@ -15873,7 +15888,7 @@ JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_ENTERBLOCK() { JSObject* obj; - obj = cx->fp->getScript()->getObject(getFullIndex(0)); + obj = cx->fp()->getScript()->getObject(getFullIndex(0)); LIns* void_ins = INS_UNDEFINED(); for (int i = 0, n = OBJ_BLOCK_COUNT(cx, obj); i < n; i++) @@ -15885,7 +15900,7 @@ JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_LEAVEBLOCK() { /* We mustn't exit the lexical block we began recording in. */ - if (cx->fp->getBlockChain() == lexicalBlock) + if (cx->fp()->getBlockChain() == lexicalBlock) return ARECORD_STOP; return ARECORD_CONTINUE; } @@ -15906,9 +15921,9 @@ JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_ARRAYPUSH() { uint32_t slot = GET_UINT16(cx->regs->pc); - JS_ASSERT(cx->fp->getFixedCount() <= slot); - JS_ASSERT(cx->fp->slots() + slot < cx->regs->sp - 1); - Value &arrayval = cx->fp->slots()[slot]; + JS_ASSERT(cx->fp()->getFixedCount() <= slot); + JS_ASSERT(cx->fp()->slots() + slot < cx->regs->sp - 1); + Value &arrayval = cx->fp()->slots()[slot]; JS_ASSERT(arrayval.isObject()); JS_ASSERT(arrayval.toObject().isDenseArray()); LIns *array_ins = get(&arrayval); @@ -15944,11 +15959,11 @@ TraceRecorder::record_JSOP_GETTHISPROP() CHECK_STATUS_A(getThis(this_ins)); /* - * It's safe to just use cx->fp->thisv here because getThis() returns + * It's safe to just use cx->fp()->thisv here because getThis() returns * ARECORD_STOP if thisv is not available. */ - JS_ASSERT(cx->fp->flags & JSFRAME_COMPUTED_THIS); - CHECK_STATUS_A(getProp(&cx->fp->getThisValue().toObject(), this_ins)); + JS_ASSERT(cx->fp()->flags & JSFRAME_COMPUTED_THIS); + CHECK_STATUS_A(getProp(&cx->fp()->getThisValue().toObject(), this_ins)); return ARECORD_CONTINUE; } @@ -15988,7 +16003,7 @@ TraceRecorder::record_JSOP_INDEXBASE3() JS_REQUIRES_STACK AbortableRecordingStatus TraceRecorder::record_JSOP_CALLGVAR() { - Value slotval = cx->fp->slots()[GET_SLOTNO(cx->regs->pc)]; + Value slotval = cx->fp()->slots()[GET_SLOTNO(cx->regs->pc)]; if (slotval.isNull()) // We will see JSOP_CALLNAME from the interpreter's jump, so no-op here. return ARECORD_CONTINUE; diff --git a/js/src/jstracer.h b/js/src/jstracer.h index 540ea6ae6d18..d09e8d03a4f2 100644 --- a/js/src/jstracer.h +++ b/js/src/jstracer.h @@ -1136,6 +1136,7 @@ class TraceRecorder JS_REQUIRES_STACK nanojit::LIns* scopeChain(); JS_REQUIRES_STACK nanojit::LIns* entryScopeChain() const; + JS_REQUIRES_STACK nanojit::LIns* entryFrameIns() const; JS_REQUIRES_STACK JSStackFrame* frameIfInRange(JSObject* obj, unsigned* depthp = NULL) const; JS_REQUIRES_STACK RecordingStatus traverseScopeChain(JSObject *obj, nanojit::LIns *obj_ins, JSObject *obj2, nanojit::LIns *&obj2_ins); JS_REQUIRES_STACK AbortableRecordingStatus scopeChainProp(JSObject* obj, Value*& vp, nanojit::LIns*& ins, NameResult& nr); diff --git a/js/src/jswrapper.cpp b/js/src/jswrapper.cpp index 1c54921ce22f..791a56b832cd 100644 --- a/js/src/jswrapper.cpp +++ b/js/src/jswrapper.cpp @@ -381,7 +381,8 @@ JSCompartment::wrap(JSContext *cx, Value *vp) * we parent all wrappers to the global object in their home compartment. * This loses us some transparency, and is generally very cheesy. */ - JSObject *global = cx->fp ? cx->fp->getScopeChain()->getGlobal() : cx->globalObject; + JSObject *global = + cx->hasfp() ? cx->fp()->getScopeChain()->getGlobal() : cx->globalObject; wrapper->setParent(global); return true; } diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 9736e7a6c233..328eb4601a1a 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -3113,7 +3113,7 @@ EvalInFrame(JSContext *cx, uintN argc, jsval *vp) ? !!(JSVAL_TO_BOOLEAN(argv[2])) : false; - JS_ASSERT(cx->fp); + JS_ASSERT(cx->hasfp()); FrameRegsIter fi(cx); for (uint32 i = 0; i < upCount; ++i, ++fi) { From e285d6151b2438ef1da4e9db9797a44d20e09f3c Mon Sep 17 00:00:00 2001 From: Robert Sayre Date: Mon, 23 Aug 2010 16:28:36 -0400 Subject: [PATCH 09/31] Bug 587257 - Make Array.prototype.join faster. r=lw --- js/src/jsarray.cpp | 158 ++++++++++++++++++++++++++---------------- js/src/jsstr.cpp | 9 +-- js/src/jsstrinlines.h | 10 +++ 3 files changed, 111 insertions(+), 66 deletions(-) diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index f6ce5483a92a..894ed7102b2f 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -106,6 +106,7 @@ #include "jsatominlines.h" #include "jsobjinlines.h" #include "jscntxtinlines.h" +#include "jsstrinlines.h" using namespace js; @@ -1254,37 +1255,88 @@ array_toSource(JSContext *cx, uintN argc, Value *vp) } #endif +typedef HashSet ObjSet; + +class AutoArrayCycleDetector +{ + public: + AutoArrayCycleDetector(JSContext *cx, JSObject *obj JS_GUARD_OBJECT_NOTIFIER_PARAM) + : context(cx), object(obj), genBefore(0), + hashPointer(context->busyArrays.lookupForAdd(object)), cycle(!!hashPointer) + { + JS_GUARD_OBJECT_NOTIFIER_INIT; + } + + ~AutoArrayCycleDetector() { + JS_ASSERT(context); + if (!cycle && hashPointer) { + if (genBefore == context->busyArrays.generation()) + context->busyArrays.remove(hashPointer); + else + context->busyArrays.remove(object); + } + } + + bool foundCycle() { + return cycle; + } + + bool update() { + JS_ASSERT(!hashPointer); + if (!context->busyArrays.add(hashPointer, object)) + return false; + genBefore = context->busyArrays.generation(); + return true; + } + + protected: + JSContext *context; + JSObject *object; + uint32 genBefore; + ObjSet::AddPtr hashPointer; + JSBool cycle; + JS_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +static inline JSBool +GetElementCharacters(JSContext *cx, JSObject *obj, JSBool locale, JSCharBuffer *cb, Value *rval) +{ + if (locale) { + /* Work on obj.toLocalString() instead. */ + JSObject *robj; + + if (!js_ValueToObjectOrNull(cx, *rval, &robj)) + return false; + rval->setObjectOrNull(robj); + JSAtom *atom = cx->runtime->atomState.toLocaleStringAtom; + if (!js_TryMethod(cx, robj, atom, 0, NULL, rval)) + return false; + } + + /* avoid calling js_ValueToCharBuffer if possible */ + if (rval->isString()) + return js_StringValueToCharBuffer(cx, *rval, *cb); + + return js_ValueToCharBuffer(cx, *rval, *cb); +} + static JSBool array_toString_sub(JSContext *cx, JSObject *obj, JSBool locale, JSString *sepstr, Value *rval) { JS_CHECK_RECURSION(cx, return false); - /* - * Use HashTable entry as the cycle indicator. On first visit, create the - * entry, and, when leaving, remove the entry. - */ - typedef js::HashSet ObjSet; - ObjSet::AddPtr hashp = cx->busyArrays.lookupForAdd(obj); - uint32 genBefore; - if (!hashp) { - /* Not in hash table, so not a cycle. */ - if (!cx->busyArrays.add(hashp, obj)) { - JS_ReportOutOfMemory(cx); - return false; - } - genBefore = cx->busyArrays.generation(); - } else { - /* Cycle, so return empty string. */ + AutoArrayCycleDetector detector(cx, obj); + + if (detector.foundCycle()) { rval->setString(ATOM_TO_STRING(cx->runtime->atomState.emptyAtom)); return true; } - AutoObjectRooter tvr(cx, obj); + if (!detector.update()) + return false; - /* After this point, all paths exit through the 'out' label. */ - MUST_FLOW_THROUGH("out"); - bool ok = false; + AutoObjectRooter tvr(cx, obj); /* Get characters to use for the separator. */ static const jschar comma = ','; @@ -1305,53 +1357,41 @@ array_toString_sub(JSContext *cx, JSObject *obj, JSBool locale, jsuint length; if (!js_GetLengthProperty(cx, obj, &length)) - goto out; + return false; - for (jsuint index = 0; index < length; index++) { - /* Use rval to locally root each element value. */ - JSBool hole; - if (!JS_CHECK_OPERATION_LIMIT(cx) || - !GetArrayElement(cx, obj, index, &hole, rval)) { - goto out; + /* avoid extra checks inside the loop if possible */ + if (seplen == 0 && obj->isDenseArray()) { + jsuint len = JS_MIN(length, obj->getDenseArrayCapacity()); + for (Value *vp = obj->getDenseArrayElements(), *end = vp + len; vp != end; ++vp) { + if (!JS_CHECK_OPERATION_LIMIT(cx)) + return false; + *rval = *vp; + if (!rval->isMagic(JS_ARRAY_HOLE) && !rval->isNullOrUndefined()) { + if (!GetElementCharacters(cx, obj, locale, &cb, rval)) + return false; + } } + } else { + JSBool hole; + for (jsuint index = 0; index < length; index++) { + if (!JS_CHECK_OPERATION_LIMIT(cx) || !GetArrayElement(cx, obj, index, &hole, rval)) + return false; - /* Get element's character string. */ - if (!(hole || rval->isNullOrUndefined())) { - if (locale) { - /* Work on obj.toLocalString() instead. */ - JSObject *robj; - - if (!js_ValueToObjectOrNull(cx, *rval, &robj)) - goto out; - rval->setObjectOrNull(robj); - JSAtom *atom = cx->runtime->atomState.toLocaleStringAtom; - if (!js_TryMethod(cx, robj, atom, 0, NULL, rval)) - goto out; + if (!hole && !rval->isNullOrUndefined()) { + if (!GetElementCharacters(cx, obj, locale, &cb, rval)) + return false; } - if (!js_ValueToCharBuffer(cx, *rval, cb)) - goto out; - } - - /* Append the separator. */ - if (index + 1 != length) { - if (!cb.append(sep, seplen)) - goto out; + /* Append the separator. */ + if (index + 1 != length) { + if (!cb.append(sep, seplen)) + return false; + } } } /* Finalize the buffer. */ - if (!BufferToString(cx, cb, rval)) - goto out; - - ok = true; - - out: - if (genBefore == cx->busyArrays.generation()) - cx->busyArrays.remove(hashp); - else - cx->busyArrays.remove(obj); - return ok; + return BufferToString(cx, cb, rval); } /* ES5 15.4.4.2. NB: The algorithm here differs from the one in ES3. */ @@ -2632,7 +2672,7 @@ array_indexOfHelper(JSContext *cx, JSBool isLast, uintN argc, Value *vp) return JS_FALSE; } if (!hole && StrictlyEqual(cx, *vp, tosearch)) { - vp->setNumber(i); + vp->setNumber(i); return JS_TRUE; } if (i == stop) diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 013075d979b6..cbf075fa5eea 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -3479,13 +3479,8 @@ js_ValueToCharBuffer(JSContext *cx, const Value &arg, JSCharBuffer &cb) Value v = arg; if (v.isObject() && !DefaultValue(cx, &v.toObject(), JSTYPE_STRING, &v)) return JS_FALSE; - - if (v.isString()) { - const jschar *chars; - size_t length; - v.toString()->getCharsAndLength(chars, length); - return cb.append(chars, length); - } + if (v.isString()) + return js_StringValueToCharBuffer(cx, v, cb); if (v.isNumber()) return js_NumberValueToCharBuffer(cx, v, cb); if (v.isBoolean()) diff --git a/js/src/jsstrinlines.h b/js/src/jsstrinlines.h index c42a8e98bc10..a5dec13ed7c6 100644 --- a/js/src/jsstrinlines.h +++ b/js/src/jsstrinlines.h @@ -80,4 +80,14 @@ JSRopeBuilder::JSRopeBuilder(JSContext *cx) { mStr = cx->runtime->emptyString; } +inline JSBool +js_StringValueToCharBuffer(JSContext *cx, const js::Value &arg, JSCharBuffer &cb) +{ + JS_ASSERT(arg.isString()); + const jschar *chars; + size_t length; + arg.toString()->getCharsAndLength(chars, length); + return cb.append(chars, length); +} + #endif /* jsstrinlines_h___ */ From 204c819344a998c30a25b89ac0ddb5856b4390f2 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Mon, 23 Aug 2010 14:36:40 -0700 Subject: [PATCH 10/31] Accessor functions for cx->fp, bug 588978. r=lw --- js/src/jsdbgapi.cpp | 2 +- js/src/jsdtracef.cpp | 4 ++-- js/src/jspropertycache.cpp | 13 +++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/js/src/jsdbgapi.cpp b/js/src/jsdbgapi.cpp index 854845e9bde8..9b9395884fe2 100644 --- a/js/src/jsdbgapi.cpp +++ b/js/src/jsdbgapi.cpp @@ -2241,7 +2241,7 @@ jstv_Lineno(JSContext *cx, JSStackFrame *fp) JS_FRIEND_API(void) js::StoreTraceVisState(JSContext *cx, TraceVisState s, TraceVisExitReason r) { - JSStackFrame *fp = cx->fp; + JSStackFrame *fp = cx->fp(); char *script_file = jstv_Filename(fp); JSHashNumber hash = JS_HashString(script_file); diff --git a/js/src/jsdtracef.cpp b/js/src/jsdtracef.cpp index 292cf67bf651..2254142d762f 100644 --- a/js/src/jsdtracef.cpp +++ b/js/src/jsdtracef.cpp @@ -217,8 +217,8 @@ DTrace::ObjectCreationScope::handleCreationEnd() void DTrace::ObjectCreationScope::handleCreationImpl(JSObject *obj) { - JAVASCRIPT_OBJECT_CREATE(jsdtrace_filename(cx->fp), (char *)clasp->name, (uintptr_t)obj, - jsdtrace_frame_linenumber(cx, cx->fp)); + JAVASCRIPT_OBJECT_CREATE(jsdtrace_filename(fp), (char *)clasp->name, (uintptr_t)obj, + jsdtrace_frame_linenumber(cx, fp)); } void diff --git a/js/src/jspropertycache.cpp b/js/src/jspropertycache.cpp index e54de77a7e48..87c87bd390c1 100644 --- a/js/src/jspropertycache.cpp +++ b/js/src/jspropertycache.cpp @@ -345,18 +345,19 @@ PropertyCache::fullTest(JSContext *cx, jsbytecode *pc, JSObject **objp, JSObject JSAtom *atom = GetAtomFromBytecode(cx, pc, op, cs); #ifdef DEBUG_notme + JSScript *script = cx->fp()->getScript(); fprintf(stderr, "id miss for %s from %s:%u" " (pc %u, kpc %u, kshape %u, shape %u)\n", js_AtomToPrintableString(cx, atom), - cx->fp->script->filename, - js_PCToLineNumber(cx, cx->fp->script, pc), - pc - cx->fp->script->code, - entry->kpc - cx->fp->script->code, + script->filename, + js_PCToLineNumber(cx, script, pc), + pc - script->code, + entry->kpc - script->code, entry->kshape, obj->shape()); - js_Disassemble1(cx, cx->fp->script, pc, - pc - cx->fp->script->code, + js_Disassemble1(cx, script, pc, + pc - script->code, JS_FALSE, stderr); #endif From 459593df3b559edd0c39bad0482e6b586f9b38d8 Mon Sep 17 00:00:00 2001 From: Jason Orendorff Date: Mon, 23 Aug 2010 18:14:38 -0400 Subject: [PATCH 11/31] Bug 583232 - Add rewrapping to jsapi.h. r=gal --- js/src/jsapi.cpp | 14 ++++++++++++++ js/src/jsapi.h | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index a8dd1f85d872..bbaa2e54fd14 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -1208,6 +1208,20 @@ JS_GetCompartmentPrivate(JSContext *cx, JSCompartment *compartment) return compartment->data; } +JS_PUBLIC_API(JSBool) +JS_WrapObject(JSContext *cx, JSObject **objp) +{ + CHECK_REQUEST(cx); + return cx->compartment->wrap(cx, objp); +} + +JS_PUBLIC_API(JSBool) +JS_WrapValue(JSContext *cx, jsval *vp) +{ + CHECK_REQUEST(cx); + return cx->compartment->wrap(cx, Valueify(vp)); +} + JS_PUBLIC_API(JSObject *) JS_GetGlobalObject(JSContext *cx) { diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 8cb6425a7406..591d3826475d 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -941,6 +941,12 @@ JS_SetCompartmentPrivate(JSContext *cx, JSCompartment *compartment, void *data); extern JS_PUBLIC_API(void *) JS_GetCompartmentPrivate(JSContext *cx, JSCompartment *compartment); +extern JS_PUBLIC_API(JSBool) +JS_RewrapObject(JSContext *cx, JSObject **objp); + +extern JS_PUBLIC_API(JSBool) +JS_RewrapValue(JSContext *cx, jsval *p); + #ifdef __cplusplus JS_END_EXTERN_C From 9bea7fb2be43f331d9775484afaae1142a53d004 Mon Sep 17 00:00:00 2001 From: Robert Sayre Date: Mon, 23 Aug 2010 19:20:46 -0400 Subject: [PATCH 12/31] Backed out changeset b404ad209cb9. (Bug 587257 - Make Array.prototype.join faster. r=lw) --- js/src/jsarray.cpp | 158 ++++++++++++++++-------------------------- js/src/jsstr.cpp | 9 ++- js/src/jsstrinlines.h | 10 --- 3 files changed, 66 insertions(+), 111 deletions(-) diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 894ed7102b2f..f6ce5483a92a 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -106,7 +106,6 @@ #include "jsatominlines.h" #include "jsobjinlines.h" #include "jscntxtinlines.h" -#include "jsstrinlines.h" using namespace js; @@ -1255,89 +1254,38 @@ array_toSource(JSContext *cx, uintN argc, Value *vp) } #endif -typedef HashSet ObjSet; - -class AutoArrayCycleDetector -{ - public: - AutoArrayCycleDetector(JSContext *cx, JSObject *obj JS_GUARD_OBJECT_NOTIFIER_PARAM) - : context(cx), object(obj), genBefore(0), - hashPointer(context->busyArrays.lookupForAdd(object)), cycle(!!hashPointer) - { - JS_GUARD_OBJECT_NOTIFIER_INIT; - } - - ~AutoArrayCycleDetector() { - JS_ASSERT(context); - if (!cycle && hashPointer) { - if (genBefore == context->busyArrays.generation()) - context->busyArrays.remove(hashPointer); - else - context->busyArrays.remove(object); - } - } - - bool foundCycle() { - return cycle; - } - - bool update() { - JS_ASSERT(!hashPointer); - if (!context->busyArrays.add(hashPointer, object)) - return false; - genBefore = context->busyArrays.generation(); - return true; - } - - protected: - JSContext *context; - JSObject *object; - uint32 genBefore; - ObjSet::AddPtr hashPointer; - JSBool cycle; - JS_DECL_USE_GUARD_OBJECT_NOTIFIER -}; - -static inline JSBool -GetElementCharacters(JSContext *cx, JSObject *obj, JSBool locale, JSCharBuffer *cb, Value *rval) -{ - if (locale) { - /* Work on obj.toLocalString() instead. */ - JSObject *robj; - - if (!js_ValueToObjectOrNull(cx, *rval, &robj)) - return false; - rval->setObjectOrNull(robj); - JSAtom *atom = cx->runtime->atomState.toLocaleStringAtom; - if (!js_TryMethod(cx, robj, atom, 0, NULL, rval)) - return false; - } - - /* avoid calling js_ValueToCharBuffer if possible */ - if (rval->isString()) - return js_StringValueToCharBuffer(cx, *rval, *cb); - - return js_ValueToCharBuffer(cx, *rval, *cb); -} - static JSBool array_toString_sub(JSContext *cx, JSObject *obj, JSBool locale, JSString *sepstr, Value *rval) { JS_CHECK_RECURSION(cx, return false); - AutoArrayCycleDetector detector(cx, obj); - - if (detector.foundCycle()) { + /* + * Use HashTable entry as the cycle indicator. On first visit, create the + * entry, and, when leaving, remove the entry. + */ + typedef js::HashSet ObjSet; + ObjSet::AddPtr hashp = cx->busyArrays.lookupForAdd(obj); + uint32 genBefore; + if (!hashp) { + /* Not in hash table, so not a cycle. */ + if (!cx->busyArrays.add(hashp, obj)) { + JS_ReportOutOfMemory(cx); + return false; + } + genBefore = cx->busyArrays.generation(); + } else { + /* Cycle, so return empty string. */ rval->setString(ATOM_TO_STRING(cx->runtime->atomState.emptyAtom)); return true; } - if (!detector.update()) - return false; - AutoObjectRooter tvr(cx, obj); + /* After this point, all paths exit through the 'out' label. */ + MUST_FLOW_THROUGH("out"); + bool ok = false; + /* Get characters to use for the separator. */ static const jschar comma = ','; const jschar *sep; @@ -1357,41 +1305,53 @@ array_toString_sub(JSContext *cx, JSObject *obj, JSBool locale, jsuint length; if (!js_GetLengthProperty(cx, obj, &length)) - return false; + goto out; - /* avoid extra checks inside the loop if possible */ - if (seplen == 0 && obj->isDenseArray()) { - jsuint len = JS_MIN(length, obj->getDenseArrayCapacity()); - for (Value *vp = obj->getDenseArrayElements(), *end = vp + len; vp != end; ++vp) { - if (!JS_CHECK_OPERATION_LIMIT(cx)) - return false; - *rval = *vp; - if (!rval->isMagic(JS_ARRAY_HOLE) && !rval->isNullOrUndefined()) { - if (!GetElementCharacters(cx, obj, locale, &cb, rval)) - return false; - } - } - } else { + for (jsuint index = 0; index < length; index++) { + /* Use rval to locally root each element value. */ JSBool hole; - for (jsuint index = 0; index < length; index++) { - if (!JS_CHECK_OPERATION_LIMIT(cx) || !GetArrayElement(cx, obj, index, &hole, rval)) - return false; + if (!JS_CHECK_OPERATION_LIMIT(cx) || + !GetArrayElement(cx, obj, index, &hole, rval)) { + goto out; + } - if (!hole && !rval->isNullOrUndefined()) { - if (!GetElementCharacters(cx, obj, locale, &cb, rval)) - return false; + /* Get element's character string. */ + if (!(hole || rval->isNullOrUndefined())) { + if (locale) { + /* Work on obj.toLocalString() instead. */ + JSObject *robj; + + if (!js_ValueToObjectOrNull(cx, *rval, &robj)) + goto out; + rval->setObjectOrNull(robj); + JSAtom *atom = cx->runtime->atomState.toLocaleStringAtom; + if (!js_TryMethod(cx, robj, atom, 0, NULL, rval)) + goto out; } - /* Append the separator. */ - if (index + 1 != length) { - if (!cb.append(sep, seplen)) - return false; - } + if (!js_ValueToCharBuffer(cx, *rval, cb)) + goto out; + } + + /* Append the separator. */ + if (index + 1 != length) { + if (!cb.append(sep, seplen)) + goto out; } } /* Finalize the buffer. */ - return BufferToString(cx, cb, rval); + if (!BufferToString(cx, cb, rval)) + goto out; + + ok = true; + + out: + if (genBefore == cx->busyArrays.generation()) + cx->busyArrays.remove(hashp); + else + cx->busyArrays.remove(obj); + return ok; } /* ES5 15.4.4.2. NB: The algorithm here differs from the one in ES3. */ @@ -2672,7 +2632,7 @@ array_indexOfHelper(JSContext *cx, JSBool isLast, uintN argc, Value *vp) return JS_FALSE; } if (!hole && StrictlyEqual(cx, *vp, tosearch)) { - vp->setNumber(i); + vp->setNumber(i); return JS_TRUE; } if (i == stop) diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index cbf075fa5eea..013075d979b6 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -3479,8 +3479,13 @@ js_ValueToCharBuffer(JSContext *cx, const Value &arg, JSCharBuffer &cb) Value v = arg; if (v.isObject() && !DefaultValue(cx, &v.toObject(), JSTYPE_STRING, &v)) return JS_FALSE; - if (v.isString()) - return js_StringValueToCharBuffer(cx, v, cb); + + if (v.isString()) { + const jschar *chars; + size_t length; + v.toString()->getCharsAndLength(chars, length); + return cb.append(chars, length); + } if (v.isNumber()) return js_NumberValueToCharBuffer(cx, v, cb); if (v.isBoolean()) diff --git a/js/src/jsstrinlines.h b/js/src/jsstrinlines.h index a5dec13ed7c6..c42a8e98bc10 100644 --- a/js/src/jsstrinlines.h +++ b/js/src/jsstrinlines.h @@ -80,14 +80,4 @@ JSRopeBuilder::JSRopeBuilder(JSContext *cx) { mStr = cx->runtime->emptyString; } -inline JSBool -js_StringValueToCharBuffer(JSContext *cx, const js::Value &arg, JSCharBuffer &cb) -{ - JS_ASSERT(arg.isString()); - const jschar *chars; - size_t length; - arg.toString()->getCharsAndLength(chars, length); - return cb.append(chars, length); -} - #endif /* jsstrinlines_h___ */ From 262b256df660cf5d27f098d3f6c734604b56c3b9 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 23 Aug 2010 18:13:09 -0700 Subject: [PATCH 13/31] Bug 578216 - Make eval(json-like string) fast. r=sayrer. --- js/src/jsobj.cpp | 25 +++++++++++++- js/src/json.cpp | 84 +++++++++++++++++++++--------------------------- js/src/json.h | 2 +- 3 files changed, 61 insertions(+), 50 deletions(-) diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index d639cb71c37c..19e79a59c386 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -76,6 +76,7 @@ #include "jsstr.h" #include "jstracer.h" #include "jsdbgapi.h" +#include "json.h" #include "jsscopeinlines.h" #include "jsscriptinlines.h" @@ -1129,6 +1130,28 @@ obj_eval(JSContext *cx, uintN argc, Value *vp) JSString *str = argv[0].toString(); JSScript *script = NULL; + + const jschar *chars; + size_t length; + str->getCharsAndLength(chars, length); + + /* + * If the eval string starts with '(' and ends with ')', it may be JSON. + * Try the JSON parser first because it's much faster. If the eval string + * isn't JSON, JSON parsing will probably fail quickly, so little time + * will be lost. + */ + if (length > 2 && chars[0] == '(' && chars[length-1] == ')') { + JSONParser *jp = js_BeginJSONParse(cx, vp, /* suppressErrors = */true); + JSBool ok = jp != NULL; + if (ok) { + /* Run JSON-parser on string inside ( and ). */ + ok = js_ConsumeJSONText(cx, jp, chars+1, length-2); + ok &= js_FinishJSONParse(cx, jp, NullValue()); + if (ok) + return JS_TRUE; + } + } /* * Cache local eval scripts indexed by source qualified by scope. @@ -1216,7 +1239,7 @@ obj_eval(JSContext *cx, uintN argc, Value *vp) script = Compiler::compileScript(cx, scopeobj, callerFrame, principals, TCF_COMPILE_N_GO | TCF_NEED_MUTABLE_SCRIPT, - str->chars(), str->length(), + chars, length, NULL, file, line, str, staticLevel); if (!script) return JS_FALSE; diff --git a/js/src/json.cpp b/js/src/json.cpp index f489a7de7867..ba86fa43523b 100644 --- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -77,7 +77,7 @@ struct JSONParser { JSONParser(JSContext *cx) : hexChar(), numHex(), statep(), stateStack(), rootVal(), objectStack(), - objectKey(cx), buffer(cx) + objectKey(cx), buffer(cx), suppressErrors(false) {} /* Used while handling \uNNNN in strings */ @@ -90,6 +90,7 @@ struct JSONParser JSObject *objectStack; js::Vector objectKey; js::Vector buffer; + bool suppressErrors; }; #ifdef _MSC_VER @@ -655,6 +656,14 @@ Walk(JSContext *cx, jsid id, JSObject *holder, const Value &reviver, Value *vp) return true; } +static JSBool +JSONParseError(JSContext *cx, JSONParser *jp) +{ + if (!jp->suppressErrors) + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; +} + static bool Revive(JSContext *cx, const Value &reviver, Value *vp) { @@ -673,7 +682,7 @@ Revive(JSContext *cx, const Value &reviver, Value *vp) } JSONParser * -js_BeginJSONParse(JSContext *cx, Value *rootVal) +js_BeginJSONParse(JSContext *cx, Value *rootVal, bool suppressErrors /*= true*/) { if (!cx) return NULL; @@ -693,6 +702,7 @@ js_BeginJSONParse(JSContext *cx, Value *rootVal) jp->statep = jp->stateStack; *jp->statep = JSON_PARSE_STATE_INIT; jp->rootVal = rootVal; + jp->suppressErrors = suppressErrors; return jp; @@ -734,10 +744,8 @@ js_FinishJSONParse(JSContext *cx, JSONParser *jp, const Value &reviver) if (!early_ok) return false; - if (!ok) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return false; - } + if (!ok) + return !!JSONParseError(cx, jp); if (reviver.isObject() && reviver.toObject().isCallable()) ok = Revive(cx, reviver, vp); @@ -750,15 +758,13 @@ PushState(JSContext *cx, JSONParser *jp, JSONParserState state) { if (*jp->statep == JSON_PARSE_STATE_FINISHED) { // extra input - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } jp->statep++; if ((uint32)(jp->statep - jp->stateStack) >= JS_ARRAY_LENGTH(jp->stateStack)) { // too deep - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } *jp->statep = state; @@ -772,8 +778,7 @@ PopState(JSContext *cx, JSONParser *jp) jp->statep--; if (jp->statep < jp->stateStack) { jp->statep = jp->stateStack; - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } if (*jp->statep == JSON_PARSE_STATE_INIT) @@ -811,10 +816,8 @@ PushObject(JSContext *cx, JSONParser *jp, JSObject *obj) jsuint len; if (!js_GetLengthProperty(cx, jp->objectStack, &len)) return JS_FALSE; - if (len >= JSON_MAX_DEPTH) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; - } + if (len >= JSON_MAX_DEPTH) + return JSONParseError(cx, jp); AutoObjectRooter tvr(cx, obj); Value v = ObjectOrNullValue(obj); @@ -917,8 +920,7 @@ HandleNumber(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len) return JS_FALSE; if (ep != buf + len) { // bad number input - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } return PushPrimitive(cx, jp, DoubleValue(val)); @@ -941,8 +943,7 @@ HandleKeyword(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len) TokenKind tt = js_CheckKeyword(buf, len); if (tt != TOK_PRIMARY) { // bad keyword - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } if (buf[0] == 'n') { @@ -952,8 +953,7 @@ HandleKeyword(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len) } else if (buf[0] == 'f') { keyword.setBoolean(false); } else { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } return PushPrimitive(cx, jp, keyword); @@ -1006,10 +1006,8 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!PopState(cx, jp)) return JS_FALSE; - if (*jp->statep != JSON_PARSE_STATE_ARRAY) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; - } + if (*jp->statep != JSON_PARSE_STATE_ARRAY) + return JSONParseError(cx, jp); if (!CloseArray(cx, jp) || !PopState(cx, jp)) return JS_FALSE; @@ -1019,8 +1017,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (c == '}') { // we should only find these in OBJECT_KEY state - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } if (c == '"') { @@ -1053,8 +1050,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!OpenArray(cx, jp) || !PushState(cx, jp, JSON_PARSE_STATE_VALUE)) return JS_FALSE; } else if (!JS_ISXMLSPACE(c)) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } break; @@ -1066,12 +1062,11 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!PushState(cx, jp, JSON_PARSE_STATE_OBJECT_PAIR)) return JS_FALSE; } else if (c == ']' || !JS_ISXMLSPACE(c)) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } break; - case JSON_PARSE_STATE_ARRAY : + case JSON_PARSE_STATE_ARRAY: if (c == ']') { if (!CloseArray(cx, jp) || !PopState(cx, jp)) return JS_FALSE; @@ -1079,12 +1074,11 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!PushState(cx, jp, JSON_PARSE_STATE_VALUE)) return JS_FALSE; } else if (!JS_ISXMLSPACE(c)) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } break; - case JSON_PARSE_STATE_OBJECT_PAIR : + case JSON_PARSE_STATE_OBJECT_PAIR: if (c == '"') { // we want to be waiting for a : when the string has been read *jp->statep = JSON_PARSE_STATE_OBJECT_IN_PAIR; @@ -1095,8 +1089,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!CloseObject(cx, jp) || !PopState(cx, jp) || !PopState(cx, jp)) return JS_FALSE; } else if (c == ']' || !JS_ISXMLSPACE(c)) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } break; @@ -1104,8 +1097,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (c == ':') { *jp->statep = JSON_PARSE_STATE_VALUE; } else if (!JS_ISXMLSPACE(c)) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } break; @@ -1126,8 +1118,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len } else if (c < 31) { // The JSON lexical grammer does not allow a JSONStringCharacter to be // any of the Unicode characters U+0000 thru U+001F (control characters). - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } else { if (!jp->buffer.append(c)) return JS_FALSE; @@ -1152,8 +1143,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len *jp->statep = JSON_PARSE_STATE_STRING_HEX; continue; } else { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } } @@ -1170,8 +1160,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len } else if (('A' <= c) && (c <= 'F')) { jp->hexChar = (jp->hexChar << 4) | (c - 'A' + 0x0a); } else { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } if (++(jp->numHex) == 4) { @@ -1215,8 +1204,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len case JSON_PARSE_STATE_FINISHED: if (!JS_ISXMLSPACE(c)) { // extra input - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(cx, jp); } break; diff --git a/js/src/json.h b/js/src/json.h index 0e6a996691f0..1bf3123fdc92 100644 --- a/js/src/json.h +++ b/js/src/json.h @@ -111,7 +111,7 @@ enum JSONDataType { struct JSONParser; extern JSONParser * -js_BeginJSONParse(JSContext *cx, js::Value *rootVal); +js_BeginJSONParse(JSContext *cx, js::Value *rootVal, bool suppressErrors = false); extern JSBool js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len); From 33950eeba9b136ea67660475c90b87f40a758a41 Mon Sep 17 00:00:00 2001 From: Robert Sayre Date: Mon, 23 Aug 2010 23:36:21 -0400 Subject: [PATCH 14/31] delete obvious comment. --- js/src/json.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/js/src/json.cpp b/js/src/json.cpp index ba86fa43523b..d1e6d227008f 100644 --- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -918,10 +918,8 @@ HandleNumber(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len) double val; if (!js_strtod(cx, buf, buf + len, &ep, &val)) return JS_FALSE; - if (ep != buf + len) { - // bad number input + if (ep != buf + len) return JSONParseError(cx, jp); - } return PushPrimitive(cx, jp, DoubleValue(val)); } From 5584010a0b870edb91809a33e55cc04b84cd26b9 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 23 Aug 2010 22:38:19 -0700 Subject: [PATCH 15/31] Backed out changeset 74ce80e8782e --- js/src/jsobj.cpp | 25 +------------- js/src/json.cpp | 84 +++++++++++++++++++++++++++--------------------- js/src/json.h | 2 +- 3 files changed, 50 insertions(+), 61 deletions(-) diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 19e79a59c386..d639cb71c37c 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -76,7 +76,6 @@ #include "jsstr.h" #include "jstracer.h" #include "jsdbgapi.h" -#include "json.h" #include "jsscopeinlines.h" #include "jsscriptinlines.h" @@ -1130,28 +1129,6 @@ obj_eval(JSContext *cx, uintN argc, Value *vp) JSString *str = argv[0].toString(); JSScript *script = NULL; - - const jschar *chars; - size_t length; - str->getCharsAndLength(chars, length); - - /* - * If the eval string starts with '(' and ends with ')', it may be JSON. - * Try the JSON parser first because it's much faster. If the eval string - * isn't JSON, JSON parsing will probably fail quickly, so little time - * will be lost. - */ - if (length > 2 && chars[0] == '(' && chars[length-1] == ')') { - JSONParser *jp = js_BeginJSONParse(cx, vp, /* suppressErrors = */true); - JSBool ok = jp != NULL; - if (ok) { - /* Run JSON-parser on string inside ( and ). */ - ok = js_ConsumeJSONText(cx, jp, chars+1, length-2); - ok &= js_FinishJSONParse(cx, jp, NullValue()); - if (ok) - return JS_TRUE; - } - } /* * Cache local eval scripts indexed by source qualified by scope. @@ -1239,7 +1216,7 @@ obj_eval(JSContext *cx, uintN argc, Value *vp) script = Compiler::compileScript(cx, scopeobj, callerFrame, principals, TCF_COMPILE_N_GO | TCF_NEED_MUTABLE_SCRIPT, - chars, length, + str->chars(), str->length(), NULL, file, line, str, staticLevel); if (!script) return JS_FALSE; diff --git a/js/src/json.cpp b/js/src/json.cpp index ba86fa43523b..f489a7de7867 100644 --- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -77,7 +77,7 @@ struct JSONParser { JSONParser(JSContext *cx) : hexChar(), numHex(), statep(), stateStack(), rootVal(), objectStack(), - objectKey(cx), buffer(cx), suppressErrors(false) + objectKey(cx), buffer(cx) {} /* Used while handling \uNNNN in strings */ @@ -90,7 +90,6 @@ struct JSONParser JSObject *objectStack; js::Vector objectKey; js::Vector buffer; - bool suppressErrors; }; #ifdef _MSC_VER @@ -656,14 +655,6 @@ Walk(JSContext *cx, jsid id, JSObject *holder, const Value &reviver, Value *vp) return true; } -static JSBool -JSONParseError(JSContext *cx, JSONParser *jp) -{ - if (!jp->suppressErrors) - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; -} - static bool Revive(JSContext *cx, const Value &reviver, Value *vp) { @@ -682,7 +673,7 @@ Revive(JSContext *cx, const Value &reviver, Value *vp) } JSONParser * -js_BeginJSONParse(JSContext *cx, Value *rootVal, bool suppressErrors /*= true*/) +js_BeginJSONParse(JSContext *cx, Value *rootVal) { if (!cx) return NULL; @@ -702,7 +693,6 @@ js_BeginJSONParse(JSContext *cx, Value *rootVal, bool suppressErrors /*= true*/) jp->statep = jp->stateStack; *jp->statep = JSON_PARSE_STATE_INIT; jp->rootVal = rootVal; - jp->suppressErrors = suppressErrors; return jp; @@ -744,8 +734,10 @@ js_FinishJSONParse(JSContext *cx, JSONParser *jp, const Value &reviver) if (!early_ok) return false; - if (!ok) - return !!JSONParseError(cx, jp); + if (!ok) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return false; + } if (reviver.isObject() && reviver.toObject().isCallable()) ok = Revive(cx, reviver, vp); @@ -758,13 +750,15 @@ PushState(JSContext *cx, JSONParser *jp, JSONParserState state) { if (*jp->statep == JSON_PARSE_STATE_FINISHED) { // extra input - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } jp->statep++; if ((uint32)(jp->statep - jp->stateStack) >= JS_ARRAY_LENGTH(jp->stateStack)) { // too deep - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } *jp->statep = state; @@ -778,7 +772,8 @@ PopState(JSContext *cx, JSONParser *jp) jp->statep--; if (jp->statep < jp->stateStack) { jp->statep = jp->stateStack; - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } if (*jp->statep == JSON_PARSE_STATE_INIT) @@ -816,8 +811,10 @@ PushObject(JSContext *cx, JSONParser *jp, JSObject *obj) jsuint len; if (!js_GetLengthProperty(cx, jp->objectStack, &len)) return JS_FALSE; - if (len >= JSON_MAX_DEPTH) - return JSONParseError(cx, jp); + if (len >= JSON_MAX_DEPTH) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; + } AutoObjectRooter tvr(cx, obj); Value v = ObjectOrNullValue(obj); @@ -920,7 +917,8 @@ HandleNumber(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len) return JS_FALSE; if (ep != buf + len) { // bad number input - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } return PushPrimitive(cx, jp, DoubleValue(val)); @@ -943,7 +941,8 @@ HandleKeyword(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len) TokenKind tt = js_CheckKeyword(buf, len); if (tt != TOK_PRIMARY) { // bad keyword - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } if (buf[0] == 'n') { @@ -953,7 +952,8 @@ HandleKeyword(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len) } else if (buf[0] == 'f') { keyword.setBoolean(false); } else { - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } return PushPrimitive(cx, jp, keyword); @@ -1006,8 +1006,10 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!PopState(cx, jp)) return JS_FALSE; - if (*jp->statep != JSON_PARSE_STATE_ARRAY) - return JSONParseError(cx, jp); + if (*jp->statep != JSON_PARSE_STATE_ARRAY) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; + } if (!CloseArray(cx, jp) || !PopState(cx, jp)) return JS_FALSE; @@ -1017,7 +1019,8 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (c == '}') { // we should only find these in OBJECT_KEY state - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } if (c == '"') { @@ -1050,7 +1053,8 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!OpenArray(cx, jp) || !PushState(cx, jp, JSON_PARSE_STATE_VALUE)) return JS_FALSE; } else if (!JS_ISXMLSPACE(c)) { - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } break; @@ -1062,11 +1066,12 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!PushState(cx, jp, JSON_PARSE_STATE_OBJECT_PAIR)) return JS_FALSE; } else if (c == ']' || !JS_ISXMLSPACE(c)) { - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } break; - case JSON_PARSE_STATE_ARRAY: + case JSON_PARSE_STATE_ARRAY : if (c == ']') { if (!CloseArray(cx, jp) || !PopState(cx, jp)) return JS_FALSE; @@ -1074,11 +1079,12 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!PushState(cx, jp, JSON_PARSE_STATE_VALUE)) return JS_FALSE; } else if (!JS_ISXMLSPACE(c)) { - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } break; - case JSON_PARSE_STATE_OBJECT_PAIR: + case JSON_PARSE_STATE_OBJECT_PAIR : if (c == '"') { // we want to be waiting for a : when the string has been read *jp->statep = JSON_PARSE_STATE_OBJECT_IN_PAIR; @@ -1089,7 +1095,8 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!CloseObject(cx, jp) || !PopState(cx, jp) || !PopState(cx, jp)) return JS_FALSE; } else if (c == ']' || !JS_ISXMLSPACE(c)) { - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } break; @@ -1097,7 +1104,8 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (c == ':') { *jp->statep = JSON_PARSE_STATE_VALUE; } else if (!JS_ISXMLSPACE(c)) { - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } break; @@ -1118,7 +1126,8 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len } else if (c < 31) { // The JSON lexical grammer does not allow a JSONStringCharacter to be // any of the Unicode characters U+0000 thru U+001F (control characters). - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } else { if (!jp->buffer.append(c)) return JS_FALSE; @@ -1143,7 +1152,8 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len *jp->statep = JSON_PARSE_STATE_STRING_HEX; continue; } else { - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } } @@ -1160,7 +1170,8 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len } else if (('A' <= c) && (c <= 'F')) { jp->hexChar = (jp->hexChar << 4) | (c - 'A' + 0x0a); } else { - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } if (++(jp->numHex) == 4) { @@ -1204,7 +1215,8 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len case JSON_PARSE_STATE_FINISHED: if (!JS_ISXMLSPACE(c)) { // extra input - return JSONParseError(cx, jp); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; } break; diff --git a/js/src/json.h b/js/src/json.h index 1bf3123fdc92..0e6a996691f0 100644 --- a/js/src/json.h +++ b/js/src/json.h @@ -111,7 +111,7 @@ enum JSONDataType { struct JSONParser; extern JSONParser * -js_BeginJSONParse(JSContext *cx, js::Value *rootVal, bool suppressErrors = false); +js_BeginJSONParse(JSContext *cx, js::Value *rootVal); extern JSBool js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len); From 796b3e7c28e4ff9d7cf53e4862180a0784e22035 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 24 Aug 2010 11:50:54 -0400 Subject: [PATCH 16/31] Bug 578216 - Make eval(json-like string) fast. r=sayrer --- js/src/jsobj.cpp | 25 ++++++++++++- js/src/json.cpp | 95 +++++++++++++++++++++--------------------------- js/src/json.h | 2 +- 3 files changed, 66 insertions(+), 56 deletions(-) diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index d639cb71c37c..19e79a59c386 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -76,6 +76,7 @@ #include "jsstr.h" #include "jstracer.h" #include "jsdbgapi.h" +#include "json.h" #include "jsscopeinlines.h" #include "jsscriptinlines.h" @@ -1129,6 +1130,28 @@ obj_eval(JSContext *cx, uintN argc, Value *vp) JSString *str = argv[0].toString(); JSScript *script = NULL; + + const jschar *chars; + size_t length; + str->getCharsAndLength(chars, length); + + /* + * If the eval string starts with '(' and ends with ')', it may be JSON. + * Try the JSON parser first because it's much faster. If the eval string + * isn't JSON, JSON parsing will probably fail quickly, so little time + * will be lost. + */ + if (length > 2 && chars[0] == '(' && chars[length-1] == ')') { + JSONParser *jp = js_BeginJSONParse(cx, vp, /* suppressErrors = */true); + JSBool ok = jp != NULL; + if (ok) { + /* Run JSON-parser on string inside ( and ). */ + ok = js_ConsumeJSONText(cx, jp, chars+1, length-2); + ok &= js_FinishJSONParse(cx, jp, NullValue()); + if (ok) + return JS_TRUE; + } + } /* * Cache local eval scripts indexed by source qualified by scope. @@ -1216,7 +1239,7 @@ obj_eval(JSContext *cx, uintN argc, Value *vp) script = Compiler::compileScript(cx, scopeobj, callerFrame, principals, TCF_COMPILE_N_GO | TCF_NEED_MUTABLE_SCRIPT, - str->chars(), str->length(), + chars, length, NULL, file, line, str, staticLevel); if (!script) return JS_FALSE; diff --git a/js/src/json.cpp b/js/src/json.cpp index f489a7de7867..5cc1da0d9292 100644 --- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -77,7 +77,7 @@ struct JSONParser { JSONParser(JSContext *cx) : hexChar(), numHex(), statep(), stateStack(), rootVal(), objectStack(), - objectKey(cx), buffer(cx) + objectKey(cx), buffer(cx), suppressErrors(false) {} /* Used while handling \uNNNN in strings */ @@ -90,6 +90,7 @@ struct JSONParser JSObject *objectStack; js::Vector objectKey; js::Vector buffer; + bool suppressErrors; }; #ifdef _MSC_VER @@ -655,6 +656,14 @@ Walk(JSContext *cx, jsid id, JSObject *holder, const Value &reviver, Value *vp) return true; } +static JSBool +JSONParseError(JSONParser *jp, JSContext *cx) +{ + if (!jp->suppressErrors) + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); + return JS_FALSE; +} + static bool Revive(JSContext *cx, const Value &reviver, Value *vp) { @@ -673,7 +682,7 @@ Revive(JSContext *cx, const Value &reviver, Value *vp) } JSONParser * -js_BeginJSONParse(JSContext *cx, Value *rootVal) +js_BeginJSONParse(JSContext *cx, Value *rootVal, bool suppressErrors /*= true*/) { if (!cx) return NULL; @@ -693,6 +702,7 @@ js_BeginJSONParse(JSContext *cx, Value *rootVal) jp->statep = jp->stateStack; *jp->statep = JSON_PARSE_STATE_INIT; jp->rootVal = rootVal; + jp->suppressErrors = suppressErrors; return jp; @@ -729,18 +739,15 @@ js_FinishJSONParse(JSContext *cx, JSONParser *jp, const Value &reviver) bool ok = *jp->statep == JSON_PARSE_STATE_FINISHED; Value *vp = jp->rootVal; - cx->destroy(jp); - - if (!early_ok) - return false; - - if (!ok) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return false; + if (!early_ok) { + ok = false; + } else if (!ok) { + JSONParseError(jp, cx); + } else if (reviver.isObject() && reviver.toObject().isCallable()) { + ok = Revive(cx, reviver, vp); } - if (reviver.isObject() && reviver.toObject().isCallable()) - ok = Revive(cx, reviver, vp); + cx->destroy(jp); return ok; } @@ -750,15 +757,13 @@ PushState(JSContext *cx, JSONParser *jp, JSONParserState state) { if (*jp->statep == JSON_PARSE_STATE_FINISHED) { // extra input - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } jp->statep++; if ((uint32)(jp->statep - jp->stateStack) >= JS_ARRAY_LENGTH(jp->stateStack)) { // too deep - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } *jp->statep = state; @@ -772,8 +777,7 @@ PopState(JSContext *cx, JSONParser *jp) jp->statep--; if (jp->statep < jp->stateStack) { jp->statep = jp->stateStack; - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } if (*jp->statep == JSON_PARSE_STATE_INIT) @@ -811,10 +815,8 @@ PushObject(JSContext *cx, JSONParser *jp, JSObject *obj) jsuint len; if (!js_GetLengthProperty(cx, jp->objectStack, &len)) return JS_FALSE; - if (len >= JSON_MAX_DEPTH) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; - } + if (len >= JSON_MAX_DEPTH) + return JSONParseError(jp, cx); AutoObjectRooter tvr(cx, obj); Value v = ObjectOrNullValue(obj); @@ -917,8 +919,7 @@ HandleNumber(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len) return JS_FALSE; if (ep != buf + len) { // bad number input - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } return PushPrimitive(cx, jp, DoubleValue(val)); @@ -941,8 +942,7 @@ HandleKeyword(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len) TokenKind tt = js_CheckKeyword(buf, len); if (tt != TOK_PRIMARY) { // bad keyword - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } if (buf[0] == 'n') { @@ -952,8 +952,7 @@ HandleKeyword(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len) } else if (buf[0] == 'f') { keyword.setBoolean(false); } else { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } return PushPrimitive(cx, jp, keyword); @@ -1006,10 +1005,8 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!PopState(cx, jp)) return JS_FALSE; - if (*jp->statep != JSON_PARSE_STATE_ARRAY) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; - } + if (*jp->statep != JSON_PARSE_STATE_ARRAY) + return JSONParseError(jp, cx); if (!CloseArray(cx, jp) || !PopState(cx, jp)) return JS_FALSE; @@ -1019,8 +1016,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (c == '}') { // we should only find these in OBJECT_KEY state - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } if (c == '"') { @@ -1053,8 +1049,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!OpenArray(cx, jp) || !PushState(cx, jp, JSON_PARSE_STATE_VALUE)) return JS_FALSE; } else if (!JS_ISXMLSPACE(c)) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } break; @@ -1066,12 +1061,11 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!PushState(cx, jp, JSON_PARSE_STATE_OBJECT_PAIR)) return JS_FALSE; } else if (c == ']' || !JS_ISXMLSPACE(c)) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } break; - case JSON_PARSE_STATE_ARRAY : + case JSON_PARSE_STATE_ARRAY: if (c == ']') { if (!CloseArray(cx, jp) || !PopState(cx, jp)) return JS_FALSE; @@ -1079,12 +1073,11 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!PushState(cx, jp, JSON_PARSE_STATE_VALUE)) return JS_FALSE; } else if (!JS_ISXMLSPACE(c)) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } break; - case JSON_PARSE_STATE_OBJECT_PAIR : + case JSON_PARSE_STATE_OBJECT_PAIR: if (c == '"') { // we want to be waiting for a : when the string has been read *jp->statep = JSON_PARSE_STATE_OBJECT_IN_PAIR; @@ -1095,8 +1088,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (!CloseObject(cx, jp) || !PopState(cx, jp) || !PopState(cx, jp)) return JS_FALSE; } else if (c == ']' || !JS_ISXMLSPACE(c)) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } break; @@ -1104,8 +1096,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len if (c == ':') { *jp->statep = JSON_PARSE_STATE_VALUE; } else if (!JS_ISXMLSPACE(c)) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } break; @@ -1126,8 +1117,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len } else if (c < 31) { // The JSON lexical grammer does not allow a JSONStringCharacter to be // any of the Unicode characters U+0000 thru U+001F (control characters). - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } else { if (!jp->buffer.append(c)) return JS_FALSE; @@ -1152,8 +1142,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len *jp->statep = JSON_PARSE_STATE_STRING_HEX; continue; } else { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } } @@ -1170,8 +1159,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len } else if (('A' <= c) && (c <= 'F')) { jp->hexChar = (jp->hexChar << 4) | (c - 'A' + 0x0a); } else { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } if (++(jp->numHex) == 4) { @@ -1215,8 +1203,7 @@ js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len case JSON_PARSE_STATE_FINISHED: if (!JS_ISXMLSPACE(c)) { // extra input - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); - return JS_FALSE; + return JSONParseError(jp, cx); } break; diff --git a/js/src/json.h b/js/src/json.h index 0e6a996691f0..1bf3123fdc92 100644 --- a/js/src/json.h +++ b/js/src/json.h @@ -111,7 +111,7 @@ enum JSONDataType { struct JSONParser; extern JSONParser * -js_BeginJSONParse(JSContext *cx, js::Value *rootVal); +js_BeginJSONParse(JSContext *cx, js::Value *rootVal, bool suppressErrors = false); extern JSBool js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len); From ca108ec11e93754496990280835fad534289893a Mon Sep 17 00:00:00 2001 From: Paul Biggar Date: Tue, 24 Aug 2010 12:16:17 -0400 Subject: [PATCH 17/31] bug 492688 - use -fomit-frame-pointer on mac. r=sayrer --- js/src/Makefile.in | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 1011f1bd9faa..72d537df1f95 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -88,11 +88,7 @@ MODULE_OPTIMIZE_FLAGS = -O2 -ip #XXX: do we want different INTERP_OPTIMIZER flags here? endif else # not INTEL_CXX -MODULE_OPTIMIZE_FLAGS = -O3 -fstrict-aliasing $(MOZ_OPTIMIZE_SIZE_TWEAK) -ifeq ($(OS_ARCH),Linux) -#TODO: move this up a line when we fix OS X (bug 517832) -MODULE_OPTIMIZE_FLAGS += -fomit-frame-pointer -endif +MODULE_OPTIMIZE_FLAGS = -O3 -fstrict-aliasing -fomit-frame-pointer $(MOZ_OPTIMIZE_SIZE_TWEAK) # Special optimization flags for jsinterp.c INTERP_OPTIMIZER = -O3 -fstrict-aliasing endif From 7f06d5d65eda90b473bf7162cb972dd7a269aea8 Mon Sep 17 00:00:00 2001 From: Chris Leary Date: Tue, 24 Aug 2010 10:18:34 -0700 Subject: [PATCH 18/31] Bug 586530: non-list cmdline display for trace-test. (r=dmandelin) --- js/src/trace-test/trace-test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/trace-test/trace-test.py b/js/src/trace-test/trace-test.py index cf3d1338f30b..57cef5f44a38 100644 --- a/js/src/trace-test/trace-test.py +++ b/js/src/trace-test/trace-test.py @@ -135,7 +135,7 @@ def run_test(test, lib_dir): cmd = valgrind_prefix + cmd if OPTIONS.show_cmd: - print(cmd) + print(subprocess.list2cmdline(cmd)) # close_fds is not supported on Windows and will cause a ValueError. close_fds = sys.platform != 'win32' p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=close_fds, env=env) From 9d2320048685ce5284dbd2617e025034089a85ca Mon Sep 17 00:00:00 2001 From: Chris Leary Date: Tue, 24 Aug 2010 14:46:19 -0700 Subject: [PATCH 19/31] Bug 587366: regexp failure for flat text replace. (r=lw) --- js/src/jsstr.cpp | 461 +++++++++++++++------ js/src/jsstr.h | 10 +- js/src/jsstrinlines.h | 5 +- js/src/tests/ecma_5/String/15.5.4.11-01.js | 66 +++ js/src/tests/ecma_5/String/browser.js | 1 + js/src/tests/ecma_5/String/jstests.list | 2 + js/src/tests/ecma_5/String/shell.js | 0 js/src/tests/ecma_5/jstests.list | 1 + js/src/trace-test/tests/basic/bug587366.js | 3 + 9 files changed, 409 insertions(+), 140 deletions(-) create mode 100644 js/src/tests/ecma_5/String/15.5.4.11-01.js create mode 100644 js/src/tests/ecma_5/String/browser.js create mode 100644 js/src/tests/ecma_5/String/jstests.list create mode 100644 js/src/tests/ecma_5/String/shell.js create mode 100644 js/src/trace-test/tests/basic/bug587366.js diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 013075d979b6..740027632edf 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -1631,119 +1631,153 @@ str_trimRight(JSContext *cx, uintN argc, Value *vp) * Perl-inspired string functions. */ +/* Result of a successfully performed flat match. */ +class FlatMatch +{ + JSString *patstr; + const jschar *pat; + size_t patlen; + int32 match_; + + friend class RegExpGuard; + + public: + JSString *pattern() const { return patstr; } + size_t patternLength() const { return patlen; } + + /* + * @note The match is -1 when the match is performed successfully, + * but no match is found. + */ + int32 match() const { return match_; } +}; + +/* A regexp and optional associated object. */ +class RegExpPair +{ + JSObject *reobj_; + RegExp *re_; + + explicit RegExpPair(RegExp *re): re_(re) {} + friend class RegExpGuard; + + public: + /* @note May be null. */ + JSObject *reobj() const { return reobj_; } + RegExp &re() const { JS_ASSERT(re_); return *re_; } +}; + /* - * RegExpGuard factors logic out of String regexp operations. After each - * operation completes, RegExpGuard data members become available, according to - * the comments below. + * RegExpGuard factors logic out of String regexp operations. * - * Notes on parameters to RegExpGuard member functions: - * - 'optarg' indicates in which argument position RegExp flags will be found, - * if present. This is a Mozilla extension and not part of any ECMA spec. - * - 'flat' indicates that the given pattern string will not be interpreted as - * a regular expression, hence regexp meta-characters are ignored. + * @param optarg Indicates in which argument position RegExp + * flags will be found, if present. This is a Mozilla + * extension and not part of any ECMA spec. */ class RegExpGuard { RegExpGuard(const RegExpGuard &); void operator=(const RegExpGuard &); - JSContext *mCx; - JSObject *mReobj; - js::RegExp *mRe; - - public: - RegExpGuard(JSContext *cx) : mCx(cx), mRe(NULL) {} - - ~RegExpGuard() { - if (mRe) - mRe->decref(mCx); - } - - JSContext* cx() const { return mCx; } - - /* init must succeed in order to call tryFlatMatch or normalizeRegExp. */ - bool - init(uintN argc, Value *vp) - { - if (argc != 0 && VALUE_IS_REGEXP(mCx, vp[2])) { - mReobj = &vp[2].toObject(); - mRe = static_cast(mReobj->getPrivate()); - mRe->incref(mCx); - } else { - patstr = ArgToRootedString(mCx, argc, vp, 0); - if (!patstr) - return false; - } - return true; - } + JSContext *cx; + RegExpPair rep; + FlatMatch fm; /* * Upper bound on the number of characters we are willing to potentially * waste on searching for RegExp meta-characters. */ - static const size_t sMaxFlatPatLen = 256; + static const size_t MAX_FLAT_PAT_LEN = 256; + + public: + explicit RegExpGuard(JSContext *cx) : cx(cx), rep(NULL) {} + + ~RegExpGuard() { + if (rep.re_) + rep.re_->decref(cx); + } + + /* init must succeed in order to call tryFlatMatch or normalizeRegExp. */ + bool + init(uintN argc, Value *vp) + { + if (argc != 0 && VALUE_IS_REGEXP(cx, vp[2])) { + rep.reobj_ = &vp[2].toObject(); + rep.re_ = RegExp::extractFrom(rep.reobj_); + rep.re_->incref(cx); + } else { + fm.patstr = ArgToRootedString(cx, argc, vp, 0); + if (!fm.patstr) + return false; + } + return true; + } /* - * Attempt to match |patstr| with |textstr|. Return false if flat matching - * could not be used. + * Attempt to match |patstr| to |textstr|. A flags argument, metachars in the + * pattern string, or a lengthy pattern string can thwart this process. + * + * @param checkMetaChars Look for regexp metachars in the pattern string. + * @return Whether flat matching could be used. */ - bool - tryFlatMatch(JSString *textstr, bool flat, uintN optarg, uintN argc) + const FlatMatch * + tryFlatMatch(JSString *textstr, uintN optarg, uintN argc, bool checkMetaChars = true) { - if (mRe) - return false; - patstr->getCharsAndLength(pat, patlen); - if (optarg < argc || - (!flat && - (patlen > sMaxFlatPatLen || RegExp::hasMetaChars(pat, patlen)))) { - return false; + if (rep.re_) + return NULL; + + fm.patstr->getCharsAndLength(fm.pat, fm.patlen); + + if (optarg < argc) + return NULL; + + if (checkMetaChars && + (fm.patlen > MAX_FLAT_PAT_LEN || RegExp::hasMetaChars(fm.pat, fm.patlen))) { + return NULL; } + /* * textstr could be a rope, so we want to avoid flattening it for as * long as possible. */ if (textstr->isTopNode()) { - match = RopeMatch(textstr, pat, patlen); + fm.match_ = RopeMatch(textstr, fm.pat, fm.patlen); } else { const jschar *text; size_t textlen; textstr->getCharsAndLength(text, textlen); - match = StringMatch(text, textlen, pat, patlen); + fm.match_ = StringMatch(text, textlen, fm.pat, fm.patlen); } - return true; + return &fm; } - /* Data available on successful return from |tryFlatMatch|. */ - JSString *patstr; - const jschar *pat; - size_t patlen; - jsint match; - /* If the pattern is not already a regular expression, make it so. */ - bool + const RegExpPair * normalizeRegExp(bool flat, uintN optarg, uintN argc, Value *vp) { /* If we don't have a RegExp, build RegExp from pattern string. */ - if (mRe) - return true; + if (rep.re_) + return &rep; + JSString *opt; if (optarg < argc) { - opt = js_ValueToString(mCx, vp[2 + optarg]); + opt = js_ValueToString(cx, vp[2 + optarg]); if (!opt) - return false; + return NULL; } else { opt = NULL; } - mRe = RegExp::createFlagged(mCx, patstr, opt); - if (!mRe) - return false; - mReobj = NULL; - return true; + + rep.re_ = RegExp::createFlagged(cx, fm.patstr, opt); + if (!rep.re_) + return NULL; + rep.reobj_ = NULL; + return &rep; } - /* Data available on successful return from |normalizeRegExp|. */ - JSObject *reobj() const { return mReobj; } /* nullable */ - js::RegExp *re() const { return mRe; } /* non-null */ +#if DEBUG + bool hasRegExpPair() const { return rep.re_; } +#endif }; /* js_ExecuteRegExp indicates success in two ways, based on the 'test' flag. */ @@ -1771,15 +1805,15 @@ enum MatchControlFlags { /* Factor out looping and matching logic. */ static bool -DoMatch(JSContext *cx, Value *vp, JSString *str, const RegExpGuard &g, +DoMatch(JSContext *cx, Value *vp, JSString *str, const RegExpPair &rep, DoMatchCallback callback, void *data, MatchControlFlags flags) { - RegExp &re = *g.re(); + RegExp &re = rep.re(); if (re.global()) { /* global matching ('g') */ bool testGlobal = flags & TEST_GLOBAL_BIT; - if (g.reobj()) - g.reobj()->zeroRegExpLastIndex(); + if (rep.reobj()) + rep.reobj()->zeroRegExpLastIndex(); for (size_t count = 0, i = 0, length = str->length(); i <= length; ++count) { if (!re.execute(cx, str, &i, testGlobal, vp)) return false; @@ -1804,10 +1838,9 @@ DoMatch(JSContext *cx, Value *vp, JSString *str, const RegExpGuard &g, } static bool -BuildFlatMatchArray(JSContext *cx, JSString *textstr, const RegExpGuard &g, - Value *vp) +BuildFlatMatchArray(JSContext *cx, JSString *textstr, const FlatMatch &fm, Value *vp) { - if (g.match < 0) { + if (fm.match() < 0) { vp->setNull(); return true; } @@ -1818,9 +1851,9 @@ BuildFlatMatchArray(JSContext *cx, JSString *textstr, const RegExpGuard &g, return false; vp->setObject(*obj); - return obj->defineProperty(cx, INT_TO_JSID(0), StringValue(g.patstr)) && + return obj->defineProperty(cx, INT_TO_JSID(0), StringValue(fm.pattern())) && obj->defineProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.indexAtom), - Int32Value(g.match)) && + Int32Value(fm.match())) && obj->defineProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.inputAtom), StringValue(textstr)); } @@ -1860,18 +1893,20 @@ str_match(JSContext *cx, uintN argc, Value *vp) RegExpGuard g(cx); if (!g.init(argc, vp)) return false; - if (g.tryFlatMatch(str, false, 1, argc)) - return BuildFlatMatchArray(cx, str, g, vp); - if (!g.normalizeRegExp(false, 1, argc, vp)) + if (const FlatMatch *fm = g.tryFlatMatch(str, 1, argc)) + return BuildFlatMatchArray(cx, str, *fm, vp); + + const RegExpPair *rep = g.normalizeRegExp(false, 1, argc, vp); + if (!rep) return false; AutoObjectRooter array(cx); MatchArgType arg = array.addr(); - if (!DoMatch(cx, vp, str, g, MatchCallback, arg, MATCH_ARGS)) + if (!DoMatch(cx, vp, str, *rep, MatchCallback, arg, MATCH_ARGS)) return false; /* When not global, DoMatch will leave |RegExp.exec()| in *vp. */ - if (g.re()->global()) + if (rep->re().global()) vp->setObjectOrNull(array.object()); return true; } @@ -1885,15 +1920,16 @@ str_search(JSContext *cx, uintN argc, Value *vp) RegExpGuard g(cx); if (!g.init(argc, vp)) return false; - if (g.tryFlatMatch(str, false, 1, argc)) { - vp->setInt32(g.match); + if (const FlatMatch *fm = g.tryFlatMatch(str, 1, argc)) { + vp->setInt32(fm->match()); return true; } - if (!g.normalizeRegExp(false, 1, argc, vp)) + const RegExpPair *rep = g.normalizeRegExp(false, 1, argc, vp); + if (!rep) return false; size_t i = 0; - if (!g.re()->execute(cx, str, &i, true, vp)) + if (!rep->re().execute(cx, str, &i, true, vp)) return false; if (vp->isTrue()) @@ -2056,8 +2092,8 @@ FindReplaceLength(JSContext *cx, ReplaceData &rdata, size_t *sizep) } /* Push match index and input string. */ - sp++->setInt32(statics.get(0, 0)); - sp++->setString(rdata.str); + sp[0].setInt32(statics.get(0, 0)); + sp[1].setString(rdata.str); if (!Invoke(cx, rdata.args, 0)) return false; @@ -2151,16 +2187,11 @@ ReplaceCallback(JSContext *cx, size_t count, void *p) static bool BuildFlatReplacement(JSContext *cx, JSString *textstr, JSString *repstr, - const RegExpGuard &g, Value *vp) + const FlatMatch &fm, Value *vp) { - if (g.match == -1) { - vp->setString(textstr); - return true; - } - JSRopeBuilder builder(cx); - size_t match = g.match; /* Avoid signed/unsigned warnings. */ - size_t matchEnd = match + g.patlen; + size_t match = fm.match(); /* Avoid signed/unsigned warnings. */ + size_t matchEnd = match + fm.patternLength(); if (textstr->isTopNode()) { /* @@ -2186,8 +2217,8 @@ BuildFlatReplacement(JSContext *cx, JSString *textstr, JSString *repstr, */ JSString *leftSide = js_NewDependentString(cx, str, 0, match - pos); if (!leftSide || - !builder.append(cx, leftSide) || - !builder.append(cx, repstr)) { + !builder.append(leftSide) || + !builder.append(repstr)) { return false; } } @@ -2199,11 +2230,11 @@ BuildFlatReplacement(JSContext *cx, JSString *textstr, JSString *repstr, if (strEnd > matchEnd) { JSString *rightSide = js_NewDependentString(cx, str, matchEnd - pos, strEnd - matchEnd); - if (!rightSide || !builder.append(cx, rightSide)) + if (!rightSide || !builder.append(rightSide)) return false; } } else { - if (!builder.append(cx, str)) + if (!builder.append(str)) return false; } pos += str->length(); @@ -2212,12 +2243,12 @@ BuildFlatReplacement(JSContext *cx, JSString *textstr, JSString *repstr, JSString *leftSide = js_NewDependentString(cx, textstr, 0, match); if (!leftSide) return false; - JSString *rightSide = js_NewDependentString(cx, textstr, match + g.patlen, - textstr->length() - match - g.patlen); + JSString *rightSide = js_NewDependentString(cx, textstr, match + fm.patternLength(), + textstr->length() - match - fm.patternLength()); if (!rightSide || - !builder.append(cx, leftSide) || - !builder.append(cx, repstr) || - !builder.append(cx, rightSide)) { + !builder.append(leftSide) || + !builder.append(repstr) || + !builder.append(rightSide)) { return false; } } @@ -2226,6 +2257,172 @@ BuildFlatReplacement(JSContext *cx, JSString *textstr, JSString *repstr, return true; } +/* + * Perform a linear-scan dollar substitution on the replacement text, + * constructing a result string that looks like: + * + * newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:] + */ +static inline bool +BuildDollarReplacement(JSContext *cx, JSString *textstr, JSString *repstr, + const jschar *firstDollar, const FlatMatch &fm, Value *vp) +{ + JS_ASSERT(repstr->chars() <= firstDollar && firstDollar < repstr->chars() + repstr->length()); + size_t matchStart = fm.match(); + size_t matchLimit = matchStart + fm.patternLength(); + JSCharBuffer newReplaceChars(cx); + + /* + * Most probably: + * + * len(newstr) >= len(orig) - len(match) + len(replacement) + * + * Note that dollar vars _could_ make the resulting text smaller than this. + */ + if (!newReplaceChars.reserve(textstr->length() - fm.patternLength() + repstr->length())) + return false; + + /* Move the pre-dollar chunk in bulk. */ + JS_ALWAYS_TRUE(newReplaceChars.append(repstr->chars(), firstDollar)); + + /* Move the rest char-by-char, interpreting dollars as we encounter them. */ +#define ENSURE(__cond) if (!(__cond)) return false; + const jschar *repstrLimit = repstr->chars() + repstr->length(); + for (const jschar *it = firstDollar; it < repstrLimit; ++it) { + if (*it != '$' || it == repstrLimit - 1) { + ENSURE(newReplaceChars.append(*it)); + continue; + } + + switch (*(it + 1)) { + case '$': /* Eat one of the dollars. */ + ENSURE(newReplaceChars.append(*it)); + break; + case '&': + ENSURE(newReplaceChars.append(textstr->chars() + matchStart, + textstr->chars() + matchLimit)); + break; + case '`': + ENSURE(newReplaceChars.append(textstr->chars(), textstr->chars() + matchStart)); + break; + case '\'': + ENSURE(newReplaceChars.append(textstr->chars() + matchLimit, + textstr->chars() + textstr->length())); + break; + default: /* The dollar we saw was not special (no matter what its mother told it). */ + ENSURE(newReplaceChars.append(*it)); + continue; + } + ++it; /* We always eat an extra char in the above switch. */ + } + + JSString *leftSide = js_NewDependentString(cx, textstr, 0, matchStart); + ENSURE(leftSide); + + JSString *newReplace = js_NewStringFromCharBuffer(cx, newReplaceChars); + ENSURE(newReplace); + + JS_ASSERT(textstr->length() >= matchLimit); + JSString *rightSide = js_NewDependentString(cx, textstr, matchLimit, + textstr->length() - matchLimit); + ENSURE(rightSide); + + JSRopeBuilder builder(cx); + ENSURE(builder.append(leftSide) && + builder.append(newReplace) && + builder.append(rightSide)); +#undef ENSURE + + vp->setString(builder.getStr()); + return true; +} + +static inline bool +str_replace_regexp(JSContext *cx, uintN argc, Value *vp, ReplaceData &rdata) +{ + const RegExpPair *rep = rdata.g.normalizeRegExp(true, 2, argc, vp); + if (!rep) + return false; + + rdata.index = 0; + rdata.leftIndex = 0; + rdata.calledBack = false; + + if (!DoMatch(cx, vp, rdata.str, *rep, ReplaceCallback, &rdata, REPLACE_ARGS)) + return false; + + if (!rdata.calledBack) { + /* Didn't match, so the string is unmodified. */ + vp->setString(rdata.str); + return true; + } + + JSSubString sub; + cx->regExpStatics.getRightContext(&sub); + if (!rdata.cb.append(sub.chars, sub.length)) + return false; + + JSString *retstr = js_NewStringFromCharBuffer(cx, rdata.cb); + if (!retstr) + return false; + + vp->setString(retstr); + return true; +} + +static inline bool +str_replace_flat_lambda(JSContext *cx, uintN argc, Value *vp, ReplaceData &rdata, + const FlatMatch &fm) +{ + JS_ASSERT(fm.match() >= 0); + LeaveTrace(cx); + + JSString *matchStr = js_NewDependentString(cx, rdata.str, fm.match(), fm.patternLength()); + if (!matchStr) + return false; + + /* lambda(matchStr, matchStart, textstr) */ + static const uint32 lambdaArgc = 3; + if (!cx->stack().pushInvokeArgs(cx, lambdaArgc, rdata.args)) + return false; + + CallArgs &args = rdata.args; + args.callee().setObject(*rdata.lambda); + args.thisv().setObjectOrNull(rdata.lambda->getParent()); + + Value *sp = args.argv(); + sp[0].setString(matchStr); + sp[1].setInt32(fm.match()); + sp[2].setString(rdata.str); + + if (!Invoke(cx, rdata.args, 0)) + return false; + + JSString *repstr = js_ValueToString(cx, args.rval()); + if (!repstr) + return false; + + JSString *leftSide = js_NewDependentString(cx, rdata.str, 0, fm.match()); + if (!leftSide) + return false; + + size_t matchLimit = fm.match() + fm.patternLength(); + JSString *rightSide = js_NewDependentString(cx, rdata.str, matchLimit, + rdata.str->length() - matchLimit); + if (!rightSide) + return false; + + JSRopeBuilder builder(cx); + if (!(builder.append(leftSide) && + builder.append(repstr) && + builder.append(rightSide))) { + return false; + } + + vp->setString(builder.getStr()); + return true; +} + JSBool js::str_replace(JSContext *cx, uintN argc, Value *vp) { @@ -2253,37 +2450,39 @@ js::str_replace(JSContext *cx, uintN argc, Value *vp) if (!rdata.g.init(argc, vp)) return false; - if (!rdata.dollar && !rdata.lambda && - rdata.g.tryFlatMatch(rdata.str, true, 2, argc)) { - return BuildFlatReplacement(cx, rdata.str, rdata.repstr, rdata.g, vp); + + /* + * Unlike its |String.prototype| brethren, |replace| doesn't convert + * its input to a regular expression. (Even if it contains metachars.) + * + * However, if the user invokes our (non-standard) |flags| argument + * extension then we revert to creating a regular expression. Note that + * this is observable behavior through the side-effect mutation of the + * |RegExp| statics. + */ + + const FlatMatch *fm = rdata.g.tryFlatMatch(rdata.str, 2, argc, false); + if (!fm) { + JS_ASSERT_IF(!rdata.g.hasRegExpPair(), argc > 2); + return str_replace_regexp(cx, argc, vp, rdata); } - if (!rdata.g.normalizeRegExp(true, 2, argc, vp)) - return false; - rdata.index = 0; - rdata.leftIndex = 0; - rdata.calledBack = false; - - if (!DoMatch(cx, vp, rdata.str, rdata.g, ReplaceCallback, &rdata, REPLACE_ARGS)) - return false; - - if (!rdata.calledBack) { - /* Didn't match, so the string is unmodified. */ + if (fm->match() < 0) { vp->setString(rdata.str); return true; } - JSSubString sub; - cx->regExpStatics.getRightContext(&sub); - if (!rdata.cb.append(sub.chars, sub.length)) - return false; + if (rdata.lambda) + return str_replace_flat_lambda(cx, argc, vp, rdata, *fm); - JSString *retstr = js_NewStringFromCharBuffer(cx, rdata.cb); - if (!retstr) - return false; + /* + * Note: we could optimize the text.length == pattern.length case if we wanted, + * even in the presence of dollar metachars. + */ + if (rdata.dollar) + return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, *fm, vp); - vp->setString(retstr); - return true; + return BuildFlatReplacement(cx, rdata.str, rdata.repstr, *fm, vp); } /* diff --git a/js/src/jsstr.h b/js/src/jsstr.h index 93accd952448..6375e2207201 100644 --- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -678,17 +678,15 @@ class JSRopeLeafIterator { }; class JSRopeBuilder { - private: - JSString *mStr; + JSContext * const cx; + JSString *mStr; public: JSRopeBuilder(JSContext *cx); - inline bool append(JSContext *cx, JSString *str) { + inline bool append(JSString *str) { mStr = js_ConcatStrings(cx, mStr, str); - if (!mStr) - return false; - return true; + return !!mStr; } inline JSString *getStr() { diff --git a/js/src/jsstrinlines.h b/js/src/jsstrinlines.h index c42a8e98bc10..893871f55737 100644 --- a/js/src/jsstrinlines.h +++ b/js/src/jsstrinlines.h @@ -76,8 +76,7 @@ JSString::intString(jsint i) } inline -JSRopeBuilder::JSRopeBuilder(JSContext *cx) { - mStr = cx->runtime->emptyString; -} +JSRopeBuilder::JSRopeBuilder(JSContext *cx) + : cx(cx), mStr(cx->runtime->emptyString) {} #endif /* jsstrinlines_h___ */ diff --git a/js/src/tests/ecma_5/String/15.5.4.11-01.js b/js/src/tests/ecma_5/String/15.5.4.11-01.js new file mode 100644 index 000000000000..2125cbf8298c --- /dev/null +++ b/js/src/tests/ecma_5/String/15.5.4.11-01.js @@ -0,0 +1,66 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var BUGNUMBER = 587366; +var summary = "String.prototype.replace with non-regexp searchValue"; + +print(BUGNUMBER + ": " + summary); + +/************** + * BEGIN TEST * + **************/ + +/* + * Check that regexp statics are preserved across the whole test. + * If the engine is trying to cheat by turning stuff into regexps, + * we should catch it! + */ +/(a|(b)|c)+/.exec('abcabc'); +var before = { + "source" : RegExp.source, + "$`": RegExp.leftContext, + "$'": RegExp.rightContext, + "$&": RegExp.lastMatch, + "$1": RegExp.$1, + "$2": RegExp.$2 +}; + +var text = 'I once was lost but now am found.'; +var searchValue = 'found'; +var replaceValue; + +/* Lambda substitution. */ +replaceValue = function(matchStr, matchStart, textStr) { + assertEq(matchStr, searchValue); + assertEq(matchStart, 27); + assertEq(textStr, text); + return 'not watching that show anymore'; +} +var result = text.replace(searchValue, replaceValue); +assertEq(result, 'I once was lost but now am not watching that show anymore.'); + +/* Dollar substitution. */ +replaceValue = "...wait, where was I again? And where is all my $$$$$$? Oh right, $`$&$'" + + " But with no $$$$$$"; /* Note the dot is not replaced and trails the end. */ +result = text.replace(searchValue, replaceValue); +assertEq(result, 'I once was lost but now am ...wait, where was I again?' + + ' And where is all my $$$? Oh right, I once was lost but now am found.' + + ' But with no $$$.'); + +/* Missing capture group dollar substitution. */ +replaceValue = "$1$&$2$'$3"; +result = text.replace(searchValue, replaceValue); +assertEq(result, 'I once was lost but now am $1found$2.$3.'); + +/* Check RegExp statics haven't been mutated. */ +for (var ident in before) + assertEq(RegExp[ident], before[ident]); + +/******************************************************************************/ + +if (typeof reportCompare === "function") + reportCompare(true, true); + +print("All tests passed!"); diff --git a/js/src/tests/ecma_5/String/browser.js b/js/src/tests/ecma_5/String/browser.js new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/js/src/tests/ecma_5/String/browser.js @@ -0,0 +1 @@ + diff --git a/js/src/tests/ecma_5/String/jstests.list b/js/src/tests/ecma_5/String/jstests.list new file mode 100644 index 000000000000..c1d93c71be98 --- /dev/null +++ b/js/src/tests/ecma_5/String/jstests.list @@ -0,0 +1,2 @@ +url-prefix ../../jsreftest.html?test=ecma_5/String/ +script 15.5.4.11-01.js diff --git a/js/src/tests/ecma_5/String/shell.js b/js/src/tests/ecma_5/String/shell.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/js/src/tests/ecma_5/jstests.list b/js/src/tests/ecma_5/jstests.list index 6db045dd39bf..141295cfda81 100644 --- a/js/src/tests/ecma_5/jstests.list +++ b/js/src/tests/ecma_5/jstests.list @@ -6,6 +6,7 @@ include Global/jstests.list include JSON/jstests.list include Object/jstests.list include RegExp/jstests.list +include String/jstests.list include Types/jstests.list include extensions/jstests.list include misc/jstests.list diff --git a/js/src/trace-test/tests/basic/bug587366.js b/js/src/trace-test/tests/basic/bug587366.js new file mode 100644 index 000000000000..9934d987a24f --- /dev/null +++ b/js/src/trace-test/tests/basic/bug587366.js @@ -0,0 +1,3 @@ +// Test flat string replacement, per ECMAScriptv5 15.5.4.11. +assertEq("1+2".replace("1+2", "$&+3"), "1+2+3"); +assertEq(")".replace(")","*$&*"), "*)*"); From 502c97c9240f9411988cf5a7a30335eb6f15151c Mon Sep 17 00:00:00 2001 From: Chris Leary Date: Tue, 24 Aug 2010 14:53:02 -0700 Subject: [PATCH 20/31] Bug 587346: fix PCRE code length assertion failure. (r=gal) --- js/src/trace-test/tests/basic/bug587346-regexp-01.js | 1 + js/src/yarr/pcre/pcre_compile.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 js/src/trace-test/tests/basic/bug587346-regexp-01.js diff --git a/js/src/trace-test/tests/basic/bug587346-regexp-01.js b/js/src/trace-test/tests/basic/bug587346-regexp-01.js new file mode 100644 index 000000000000..cf341a4f63ad --- /dev/null +++ b/js/src/trace-test/tests/basic/bug587346-regexp-01.js @@ -0,0 +1 @@ +var re = /(?:){1,60}/ diff --git a/js/src/yarr/pcre/pcre_compile.cpp b/js/src/yarr/pcre/pcre_compile.cpp index 643c76203473..fa8f5fc152ae 100644 --- a/js/src/yarr/pcre/pcre_compile.cpp +++ b/js/src/yarr/pcre/pcre_compile.cpp @@ -84,6 +84,7 @@ static const short escapes[] = { 0, 0, 0 /* x - z */ }; static const unsigned OPCODE_LEN = 1; +static const unsigned BRAZERO_LEN = OPCODE_LEN; static const unsigned BRA_NEST_SIZE = 2; static const unsigned BRA_LEN = OPCODE_LEN + LINK_SIZE + BRA_NEST_SIZE; static const unsigned KET_LEN = OPCODE_LEN + LINK_SIZE; @@ -2485,7 +2486,7 @@ static int calculateCompiledPatternLength(const UChar* pattern, int patternLengt } length += repeatsLength; if (maxRepeats > minRepeats) { /* Need this test as maxRepeats=-1 means no limit */ - repeatsLength = multiplyWithOverflowCheck(maxRepeats - minRepeats, duplength + BRA_LEN + KET_LEN); + repeatsLength = multiplyWithOverflowCheck(maxRepeats - minRepeats, duplength + BRAZERO_LEN + BRA_LEN + KET_LEN); if (repeatsLength < 0) { errorcode = ERR16; return -1; From 802248748e0450ea92c9964028925f931dbb9142 Mon Sep 17 00:00:00 2001 From: William Maddox Date: Tue, 24 Aug 2010 11:30:07 -0700 Subject: [PATCH 21/31] Bug 568737 - Fix incorrect overflow tests generated for MIPS (r=wmaddox+) Patch submitted by Chris Dearman (chris@mips.com). --HG-- extra : convert_revision : 69bf0aeb6fda2a5071bbf904d61801e94316000a --- js/src/nanojit/NativeMIPS.cpp | 130 ++++++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 5 deletions(-) diff --git a/js/src/nanojit/NativeMIPS.cpp b/js/src/nanojit/NativeMIPS.cpp index 051201fd31af..599666ee7aa8 100644 --- a/js/src/nanojit/NativeMIPS.cpp +++ b/js/src/nanojit/NativeMIPS.cpp @@ -955,17 +955,75 @@ namespace nanojit switch (op) { case LIR_addxovi: case LIR_addjovi: - SLT(AT, rr, ra); + // add with overflow result into $at + // overflow is indicated by ((sign(rr)^sign(ra)) & (sign(rr)^sign(rhsc)) + + // [move $t,$ra] if (rr==ra) + // addiu $rr,$ra,rhsc + // [xor $at,$rr,$ra] if (rr!=ra) + // [xor $at,$rr,$t] if (rr==ra) + // [not $t,$rr] if (rhsc < 0) + // [and $at,$at,$t] if (rhsc < 0) + // [and $at,$at,$rr] if (rhsc >= 0) + // srl $at,$at,31 + + t = registerAllocTmp(allow); + SRL(AT, AT, 31); + if (rhsc < 0) { + AND(AT, AT, t); + NOT(t, rr); + } + else + AND(AT, AT, rr); + if (rr == ra) + XOR(AT, rr, t); + else + XOR(AT, rr, ra); ADDIU(rr, ra, rhsc); + if (rr == ra) + MOVE(t, ra); goto done; case LIR_addi: ADDIU(rr, ra, rhsc); goto done; case LIR_subxovi: case LIR_subjovi: + // subtract with overflow result into $at + // overflow is indicated by (sign(ra)^sign(rhsc)) & (sign(rr)^sign(ra)) + + // [move $t,$ra] if (rr==ra) + // addiu $rr,$ra,-rhsc + // [xor $at,$rr,$ra] if (rr!=ra) + // [xor $at,$rr,$t] if (rr==ra) + // [and $at,$at,$ra] if (rhsc >= 0 && rr!=ra) + // [and $at,$at,$t] if (rhsc >= 0 && rr==ra) + // [not $t,$ra] if (rhsc < 0 && rr!=ra) + // [not $t,$t] if (rhsc < 0 && rr==ra) + // [and $at,$at,$t] if (rhsc < 0) + // srl $at,$at,31 if (isS16(-rhsc)) { - SLT(AT, ra, rr); + t = registerAllocTmp(allow); + SRL(AT,AT,31); + if (rhsc < 0) { + AND(AT, AT, t); + if (rr == ra) + NOT(t, t); + else + NOT(t, ra); + } + else { + if (rr == ra) + AND(AT, AT, t); + else + AND(AT, AT, ra); + } + if (rr == ra) + XOR(AT, rr, t); + else + XOR(AT, rr, ra); ADDIU(rr, ra, -rhsc); + if (rr == ra) + MOVE(t, ra); goto done; } break; @@ -1025,11 +1083,44 @@ namespace nanojit NanoAssert(deprecated_isKnownReg(rb)); allow &= ~rmask(rb); + // The register allocator will have set up one of these 4 cases + // rr==ra && ra==rb r0 = r0 op r0 + // rr==ra && ra!=rb r0 = r0 op r1 + // rr!=ra && ra==rb r0 = r1 op r1 + // rr!=ra && ra!=rb && rr!=rb r0 = r1 op r2 + NanoAssert(ra == rb || rr != rb); + switch (op) { case LIR_addxovi: case LIR_addjovi: - SLT(AT, rr, ra); + // add with overflow result into $at + // overflow is indicated by (sign(rr)^sign(ra)) & (sign(rr)^sign(rb)) + + // [move $t,$ra] if (rr==ra) + // addu $rr,$ra,$rb + // ; Generate sign($rr)^sign($ra) + // [xor $at,$rr,$t] sign($at)=sign($rr)^sign($t) if (rr==ra) + // [xor $at,$rr,$ra] sign($at)=sign($rr)^sign($ra) if (rr!=ra) + // ; Generate sign($rr)^sign($rb) if $ra!=$rb + // [xor $t,$rr,$rb] if (ra!=rb) + // [and $at,$t] if (ra!=rb) + // srl $at,31 + + t = ZERO; + if (rr == ra || ra != rb) + t = registerAllocTmp(allow); + SRL(AT, AT, 31); + if (ra != rb) { + AND(AT, AT, t); + XOR(t, rr, rb); + } + if (rr == ra) + XOR(AT, rr, t); + else + XOR(AT, rr, ra); ADDU(rr, ra, rb); + if (rr == ra) + MOVE(t, ra); break; case LIR_addi: ADDU(rr, ra, rb); @@ -1045,8 +1136,37 @@ namespace nanojit break; case LIR_subxovi: case LIR_subjovi: - SLT(AT,ra,rr); - SUBU(rr, ra, rb); + // subtract with overflow result into $at + // overflow is indicated by (sign(ra)^sign(rb)) & (sign(rr)^sign(ra)) + + // [move $t,$ra] if (rr==ra) + // ; Generate sign($at)=sign($ra)^sign($rb) + // xor $at,$ra,$rb + // subu $rr,$ra,$rb + // ; Generate sign($t)=sign($rr)^sign($ra) + // [xor $t,$rr,$ra] if (rr!=ra) + // [xor $t,$rr,$t] if (rr==ra) + // and $at,$at,$t + // srl $at,$at,31 + + if (ra == rb) { + // special case for (ra == rb) which can't overflow + MOVE(AT, ZERO); + SUBU(rr, ra, rb); + } + else { + t = registerAllocTmp(allow); + SRL(AT, AT, 31); + AND(AT, AT, t); + if (rr == ra) + XOR(t, rr, t); + else + XOR(t, rr, ra); + SUBU(rr, ra, rb); + XOR(AT, ra, rb); + if (rr == ra) + MOVE(t, ra); + } break; case LIR_subi: SUBU(rr, ra, rb); From c060eb3d5b749d5e27c6363cfd35e2f9bc56f124 Mon Sep 17 00:00:00 2001 From: William Maddox Date: Tue, 24 Aug 2010 11:44:17 -0700 Subject: [PATCH 22/31] Bug 587916 - Cleanup of X87 FP stack code (r=nnethercote+) 1) The "register" FST0 is the sole member of the x87regs register class. In many places, however, the code is written so as to strongly suggest that there might be multiple such registers. This patch removes such conceits, replacing expressions such as (rmask(r) & x87regs) with (r == FST0), etc. 2) prepareResultReg() has been slightly refactored to make the x87 stack fiddling a bit easier to follow and to remove a fragile assumption. 3) Do not pass the "pop" argument to asm_spill() on non-IA32 platforms. 4) Remove redundant normalization of boolean values. 5) Comment the FPU stack depth consistency check. --HG-- extra : convert_revision : 04a3292575e6af31578914f7f3b9478b5cad2a1c --- js/src/nanojit/Assembler.cpp | 84 ++++++++++++++++++++-------------- js/src/nanojit/Assembler.h | 12 +++-- js/src/nanojit/NativeARM.cpp | 5 +- js/src/nanojit/NativeMIPS.cpp | 7 ++- js/src/nanojit/NativePPC.cpp | 2 +- js/src/nanojit/NativeSparc.cpp | 2 +- js/src/nanojit/NativeX64.cpp | 2 +- js/src/nanojit/Nativei386.cpp | 27 ++++++----- 8 files changed, 78 insertions(+), 63 deletions(-) diff --git a/js/src/nanojit/Assembler.cpp b/js/src/nanojit/Assembler.cpp index 16315ee310e3..0a8b03d09055 100755 --- a/js/src/nanojit/Assembler.cpp +++ b/js/src/nanojit/Assembler.cpp @@ -351,14 +351,21 @@ namespace nanojit void Assembler::resourceConsistencyCheck() { NanoAssert(!error()); - #ifdef NANOJIT_IA32 + // Within the expansion of a single LIR instruction, we may use the x87 + // stack for unmanaged temporaries. Otherwise, we do not use the x87 stack + // as such, but use the top element alone as a single allocatable FP register. + // Compensation code must be inserted to keep the stack balanced and avoid + // overflow, and the mechanisms for this are rather fragile and IA32-specific. + // The predicate below should hold between any pair of instructions within + // a basic block, at labels, and just after a conditional branch. Currently, + // we enforce this condition between all pairs of instructions, but this is + // overly restrictive, and would fail if we did not generate unreachable x87 + // stack pops following unconditional branches. NanoAssert((_allocator.active[FST0] && _fpuStkDepth == -1) || - (!_allocator.active[FST0] && _fpuStkDepth == 0)); + (!_allocator.active[FST0] && _fpuStkDepth == 0)); #endif - _activation.checkForResourceConsistency(_allocator); - registerConsistencyCheck(); } @@ -633,46 +640,52 @@ namespace nanojit // Register Assembler::prepareResultReg(LIns *ins, RegisterMask allow) { - // At this point, we know the result of 'ins' result has a use later - // in the code. (Exception: if 'ins' is a call to an impure function - // the return value may not be used, but 'ins' will still be present - // because it has side-effects.) It may have had to be evicted, in - // which case the restore will have already been generated, so we now - // generate the spill (unless the restore was actually a - // rematerialize, in which case it's not necessary). + // At this point, we know the result of 'ins' is used later in the + // code, unless it is a call to an impure function that must be + // included for effect even though its result is ignored. It may have + // had to be evicted, in which case the restore will have already been + // generated, so we now generate the spill. QUERY: Is there any attempt + // to elide the spill if we know that all restores can be rematerialized? #ifdef NANOJIT_IA32 - // If 'allow' includes FST0 we have to pop if 'ins' isn't in FST0 in - // the post-regstate. This could be because 'ins' is unused, 'ins' is - // in a spill slot, or 'ins' is in an XMM register. - const bool pop = (allow & rmask(FST0)) && - (!ins->isInReg() || ins->getReg() != FST0); -#else - const bool pop = false; -#endif + const bool notInFST0 = (!ins->isInReg() || ins->getReg() != FST0); Register r = findRegFor(ins, allow); - asm_maybe_spill(ins, pop); -#ifdef NANOJIT_IA32 - if (!ins->isInAr() && pop && r == FST0) { - // This can only happen with a LIR_calld to an impure function - // whose return value was ignored (ie. if ins->isInReg() was false - // prior to the findRegFor() call). - FSTP(FST0); // pop the fpu result since it isn't used + // If the result register is FST0, but FST0 is not in the post-regstate, + // then we must pop the x87 stack. This may occur because the result is + // unused, or because it has been stored to a spill slot or an XMM register. + const bool needPop = notInFST0 && (r == FST0); + const bool didSpill = asm_maybe_spill(ins, needPop); + if (!didSpill && needPop) { + // If the instruction is spilled, then the pop will have already + // been performed by the store to the stack slot. Otherwise, we + // must pop now. This may occur when the result of a LIR_calld + // to an impure (side-effecting) function is not used. + FSTP(FST0); } +#else + Register r = findRegFor(ins, allow); + asm_maybe_spill(ins, false); #endif return r; } - void Assembler::asm_maybe_spill(LIns* ins, bool pop) + bool Assembler::asm_maybe_spill(LIns* ins, bool pop) { - int d = ins->isInAr() ? arDisp(ins) : 0; - Register r = ins->getReg(); if (ins->isInAr()) { + int d = arDisp(ins); + Register r = ins->getReg(); verbose_only( RefBuf b; if (_logc->lcbits & LC_Native) { setOutputForEOL(" <= spill %s", _thisfrag->lirbuf->printer->formatRef(&b, ins)); } ) - asm_spill(r, d, pop, ins->isQorD()); +#ifdef NANOJIT_IA32 + asm_spill(r, d, pop); +#else + (void)pop; + asm_spill(r, d, ins->isQorD()); +#endif + return true; } + return false; } // XXX: This function is error-prone and should be phased out; see bug 513615. @@ -2358,9 +2371,9 @@ namespace nanojit } #ifdef NANOJIT_IA32 - if (savedins && (rmask(r) & x87Regs)) { + if (savedins && r == FST0) { verbose_only( shouldMention=true; ) - FSTP(r); + FSTP(FST0); } #endif } @@ -2414,12 +2427,13 @@ namespace nanojit } #ifdef NANOJIT_IA32 - if (rmask(r) & x87Regs) { + if (r == FST0) { if (savedins) { - FSTP(r); + // Discard top of x87 stack. + FSTP(FST0); } else if (curins) { - // saved state did not have fpu reg allocated, + // Saved state did not have fpu reg allocated, // so we must evict here to keep x87 stack balanced. evict(curins); } diff --git a/js/src/nanojit/Assembler.h b/js/src/nanojit/Assembler.h index 65a13315c99e..8053437c68ea 100644 --- a/js/src/nanojit/Assembler.h +++ b/js/src/nanojit/Assembler.h @@ -429,8 +429,12 @@ namespace nanojit // Otherwise, register allocation decisions will be suboptimal. void asm_restore(LIns*, Register); - void asm_maybe_spill(LIns* ins, bool pop); - void asm_spill(Register rr, int d, bool pop, bool quad); + bool asm_maybe_spill(LIns* ins, bool pop); +#ifdef NANOJIT_IA32 + void asm_spill(Register rr, int d, bool pop); +#else + void asm_spill(Register rr, int d, bool quad); +#endif void asm_load64(LIns* ins); void asm_ret(LIns* ins); #ifdef NANOJIT_64BIT @@ -502,10 +506,10 @@ namespace nanojit // since we generate backwards the depth is negative inline void fpu_push() { - debug_only( ++_fpuStkDepth; NanoAssert(_fpuStkDepth<=0); ) + debug_only( ++_fpuStkDepth; NanoAssert(_fpuStkDepth <= 0); ) } inline void fpu_pop() { - debug_only( --_fpuStkDepth; NanoAssert(_fpuStkDepth<=0); ) + debug_only( --_fpuStkDepth; NanoAssert(_fpuStkDepth >= -7); ) } #endif const Config& _config; diff --git a/js/src/nanojit/NativeARM.cpp b/js/src/nanojit/NativeARM.cpp index f6143c880f59..f53fb94b6aa5 100644 --- a/js/src/nanojit/NativeARM.cpp +++ b/js/src/nanojit/NativeARM.cpp @@ -1299,9 +1299,8 @@ Assembler::asm_restore(LIns* i, Register r) } void -Assembler::asm_spill(Register rr, int d, bool pop, bool quad) +Assembler::asm_spill(Register rr, int d, bool quad) { - (void) pop; (void) quad; NanoAssert(d); // The following registers should never be spilled: @@ -1570,7 +1569,7 @@ Assembler::asm_immd(LIns* ins) if (_config.arm_vfp && deprecated_isKnownReg(rr)) { if (d) - asm_spill(rr, d, false, true); + asm_spill(rr, d, true); underrunProtect(4*4); asm_immd_nochk(rr, ins->immDlo(), ins->immDhi()); diff --git a/js/src/nanojit/NativeMIPS.cpp b/js/src/nanojit/NativeMIPS.cpp index 599666ee7aa8..a343c243af11 100644 --- a/js/src/nanojit/NativeMIPS.cpp +++ b/js/src/nanojit/NativeMIPS.cpp @@ -630,7 +630,7 @@ namespace nanojit if (cpu_has_fpu && deprecated_isKnownReg(rr)) { if (d) - asm_spill(rr, d, false, true); + asm_spill(rr, d, true); asm_li_d(rr, ins->immDhi(), ins->immDlo()); } else { @@ -1649,9 +1649,8 @@ namespace nanojit } void - Assembler::asm_spill(Register rr, int d, bool pop, bool quad) + Assembler::asm_spill(Register rr, int d, bool quad) { - USE(pop); USE(quad); NanoAssert(d); if (IsFpReg(rr)) { @@ -1662,7 +1661,7 @@ namespace nanojit NanoAssert(!quad); asm_ldst(OP_SW, rr, d, FP); } - TAG("asm_spill(rr=%d, d=%d, pop=%d, quad=%d)", rr, d, pop, quad); + TAG("asm_spill(rr=%d, d=%d, quad=%d)", rr, d, quad); } void diff --git a/js/src/nanojit/NativePPC.cpp b/js/src/nanojit/NativePPC.cpp index 9d9ca8b5bb32..6653e1b7c9f4 100644 --- a/js/src/nanojit/NativePPC.cpp +++ b/js/src/nanojit/NativePPC.cpp @@ -827,7 +827,7 @@ namespace nanojit } } - void Assembler::asm_spill(Register rr, int d, bool /* pop */, bool quad) { + void Assembler::asm_spill(Register rr, int d, bool quad) { (void)quad; NanoAssert(d); if (IsFpReg(rr)) { diff --git a/js/src/nanojit/NativeSparc.cpp b/js/src/nanojit/NativeSparc.cpp index 6e89b264333f..e331952cee17 100644 --- a/js/src/nanojit/NativeSparc.cpp +++ b/js/src/nanojit/NativeSparc.cpp @@ -330,7 +330,7 @@ namespace nanojit } } - void Assembler::asm_spill(Register rr, int d, bool pop, bool quad) + void Assembler::asm_spill(Register rr, int d, bool quad) { underrunProtect(24); (void)quad; diff --git a/js/src/nanojit/NativeX64.cpp b/js/src/nanojit/NativeX64.cpp index 760fcdf5961b..f626cfbcda72 100644 --- a/js/src/nanojit/NativeX64.cpp +++ b/js/src/nanojit/NativeX64.cpp @@ -1844,7 +1844,7 @@ namespace nanojit endOpRegs(ins, rr, ra); } - void Assembler::asm_spill(Register rr, int d, bool /*pop*/, bool quad) { + void Assembler::asm_spill(Register rr, int d, bool quad) { NanoAssert(d); if (!IsFpReg(rr)) { if (quad) diff --git a/js/src/nanojit/Nativei386.cpp b/js/src/nanojit/Nativei386.cpp index 6d287212314f..fb352a33fadb 100644 --- a/js/src/nanojit/Nativei386.cpp +++ b/js/src/nanojit/Nativei386.cpp @@ -1215,7 +1215,7 @@ namespace nanojit if (rmask(r) & XmmRegs) { SSE_LDQ(r, d, FP); } else { - NanoAssert(rmask(r) & x87Regs); + NanoAssert(r == FST0); FLDQ(d, FP); } } @@ -1275,17 +1275,16 @@ namespace nanojit } } - void Assembler::asm_spill(Register rr, int d, bool pop, bool quad) + void Assembler::asm_spill(Register rr, int d, bool pop) { - (void)quad; NanoAssert(d); if (rmask(rr) & GpRegs) { ST(FP, d, rr); } else if (rmask(rr) & XmmRegs) { SSE_STQ(d, FP, rr); } else { - NanoAssert(rmask(rr) & x87Regs); - FSTQ((pop?1:0), d, FP); + NanoAssert(rr == FST0); + FSTQ(pop, d, FP); } } @@ -1313,7 +1312,7 @@ namespace nanojit if (rmask(rr) & XmmRegs) { SSE_LDQ(rr, db, rb); } else { - NanoAssert(rmask(rr) & x87Regs); + NanoAssert(rr == FST0); FLDQ(db, rb); } break; @@ -1324,7 +1323,7 @@ namespace nanojit SSE_LDSS(rr, db, rb); SSE_XORPDr(rr,rr); } else { - NanoAssert(rmask(rr) & x87Regs); + NanoAssert(rr == FST0); FLD32(db, rb); } break; @@ -1379,7 +1378,7 @@ namespace nanojit SSE_XORPDr(rt, rt); // zero dest to ensure no dependency stalls } else { - FST32(pop?1:0, dr, rb); + FST32(pop, dr, rb); } } else if (value->isImmD()) { @@ -1413,7 +1412,7 @@ namespace nanojit if (rmask(rv) & XmmRegs) { SSE_STQ(dr, rb, rv); } else { - FSTQ(pop?1:0, dr, rb); + FSTQ(pop, dr, rb); } } } @@ -2558,12 +2557,12 @@ namespace nanojit Register ra = findRegFor(lhs, XmmRegs); SSE_CVTSD2SI(rr, ra); } else { - int pop = !lhs->isInReg(); + bool pop = !lhs->isInReg(); findSpecificRegFor(lhs, FST0); if (ins->isInReg()) evict(ins); int d = findMemFor(ins); - FIST((pop?1:0), d, FP); + FIST(pop, d, FP); } freeResourcesOf(ins); @@ -2760,7 +2759,7 @@ namespace nanojit } evictIfActive(EAX); - int pop = !lhs->isInReg(); + bool pop = !lhs->isInReg(); findSpecificRegFor(lhs, FST0); if (lhs == rhs) { @@ -2778,12 +2777,12 @@ namespace nanojit if (rhs->isImmD()) { const uint64_t* p = findImmDFromPool(rhs->immDasQ()); - FCOMdm((pop?1:0), (const double*)p); + FCOMdm(pop, (const double*)p); } else { int d = findMemFor(rhs); - FCOM((pop?1:0), d, FP); + FCOM(pop, d, FP); } } } From 81565e4ef5f4f95c45389a33626f5926b82d445d Mon Sep 17 00:00:00 2001 From: William Maddox Date: Tue, 24 Aug 2010 13:41:15 -0700 Subject: [PATCH 23/31] Bug 570214 - MIPS variable shift instructions corrupts registers (r=stejohns+) Remove unnecessary masking of shift count. Patch submitted by Chris Dearman (chris@mips.com). --HG-- extra : convert_revision : 8986dba933c63d68c3b0498af53b9cdd6c99c69d --- js/src/nanojit/NativeMIPS.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/src/nanojit/NativeMIPS.cpp b/js/src/nanojit/NativeMIPS.cpp index a343c243af11..3e1a30047dcc 100644 --- a/js/src/nanojit/NativeMIPS.cpp +++ b/js/src/nanojit/NativeMIPS.cpp @@ -1172,16 +1172,16 @@ namespace nanojit SUBU(rr, ra, rb); break; case LIR_lshi: + // SLLV uses the low-order 5 bits of rb for the shift amount so no masking required SLLV(rr, ra, rb); - ANDI(rb, rb, 31); break; case LIR_rshi: + // SRAV uses the low-order 5 bits of rb for the shift amount so no masking required SRAV(rr, ra, rb); - ANDI(rb, rb, 31); break; case LIR_rshui: + // SRLV uses the low-order 5 bits of rb for the shift amount so no masking required SRLV(rr, ra, rb); - ANDI(rb, rb, 31); break; case LIR_mulxovi: case LIR_muljovi: From 83da35462cacf9fe0ed13d047d3c57a5155028d9 Mon Sep 17 00:00:00 2001 From: Rick Reitmaier Date: Tue, 24 Aug 2010 14:14:53 -0700 Subject: [PATCH 24/31] Bug 542891 - nanojit X64 backend errors on 64bit conditional jumps (r+nnethercote) For conditional jumps that are larger than 32bits, invert the branch logic so that it jumps around an unconditional 64bit branch to the target. --HG-- extra : convert_revision : ada7f685d84394abc19d909a021957e25043a722 --- js/src/nanojit/NativeX64.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/js/src/nanojit/NativeX64.cpp b/js/src/nanojit/NativeX64.cpp index f626cfbcda72..3b70577206eb 100644 --- a/js/src/nanojit/NativeX64.cpp +++ b/js/src/nanojit/NativeX64.cpp @@ -1181,12 +1181,21 @@ namespace nanojit } NIns* Assembler::asm_branch(bool onFalse, LIns *cond, NIns *target) { - if (target && !isTargetWithinS32(target)) { - setError(ConditionalBranchTooFar); - NanoAssert(0); - } NanoAssert(cond->isCmp()); LOpcode condop = cond->opcode(); + + if (target && !isTargetWithinS32(target)) { + // conditional jumps beyond 32bit range, so invert the branch/compare + // and emit an unconditional jump to the target + // j(inverted) B1 + // jmp target + // B1: + NIns* shortTarget = _nIns; + JMP(target); + target = shortTarget; + + onFalse = !onFalse; + } if (isCmpDOpcode(condop)) return asm_branchd(onFalse, cond, target); From 4dd85d4007dea78f2c20a29d181a5ca525b8c59d Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Tue, 24 Aug 2010 16:31:48 -0700 Subject: [PATCH 25/31] Bug 586387 - Problem with Typed Float32 Arrays and canonicalizeNaNs() / LIR_cmovd. r=nnethercote,edwsmith. --HG-- extra : convert_revision : 2e44b58e0662f140ab49064b26dfbe15d64bc061 --- js/src/nanojit/NativeARM.cpp | 10 +++++++--- js/src/nanojit/NativeX64.cpp | 10 +++++++--- js/src/nanojit/Nativei386.cpp | 10 +++++++--- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/js/src/nanojit/NativeARM.cpp b/js/src/nanojit/NativeARM.cpp index f53fb94b6aa5..4cbff9e053b1 100644 --- a/js/src/nanojit/NativeARM.cpp +++ b/js/src/nanojit/NativeARM.cpp @@ -2758,13 +2758,14 @@ Assembler::asm_cmov(LIns* ins) Register rf = findRegFor(iffalse, allow & ~rmask(rr)); - // If 'iftrue' isn't in a register, it can be clobbered by 'ins'. - Register rt = iftrue->isInReg() ? iftrue->getReg() : rr; - if (ins->isop(LIR_cmovd)) { NIns* target = _nIns; asm_nongp_copy(rr, rf); asm_branch(false, condval, target); + + // If 'iftrue' isn't in a register, it can be clobbered by 'ins'. + Register rt = iftrue->isInReg() ? iftrue->getReg() : rr; + if (rr != rt) asm_nongp_copy(rr, rt); freeResourcesOf(ins); @@ -2775,6 +2776,9 @@ Assembler::asm_cmov(LIns* ins) return; } + // If 'iftrue' isn't in a register, it can be clobbered by 'ins'. + Register rt = iftrue->isInReg() ? iftrue->getReg() : rr; + // WARNING: We cannot generate any code that affects the condition // codes between the MRcc generation here and the asm_cmp() call // below. See asm_cmp() for more details. diff --git a/js/src/nanojit/NativeX64.cpp b/js/src/nanojit/NativeX64.cpp index 3b70577206eb..17979688de4a 100644 --- a/js/src/nanojit/NativeX64.cpp +++ b/js/src/nanojit/NativeX64.cpp @@ -1119,13 +1119,14 @@ namespace nanojit Register rf = findRegFor(iffalse, allow & ~rmask(rr)); - // If 'iftrue' isn't in a register, it can be clobbered by 'ins'. - Register rt = iftrue->isInReg() ? iftrue->getReg() : rr; - if (ins->isop(LIR_cmovd)) { NIns* target = _nIns; asm_nongp_copy(rr, rf); asm_branch(false, cond, target); + + // If 'iftrue' isn't in a register, it can be clobbered by 'ins'. + Register rt = iftrue->isInReg() ? iftrue->getReg() : rr; + if (rr != rt) asm_nongp_copy(rr, rt); freeResourcesOf(ins); @@ -1136,6 +1137,9 @@ namespace nanojit return; } + // If 'iftrue' isn't in a register, it can be clobbered by 'ins'. + Register rt = iftrue->isInReg() ? iftrue->getReg() : rr; + // WARNING: We cannot generate any code that affects the condition // codes between the MRcc generation here and the asm_cmp() call // below. See asm_cmp() for more details. diff --git a/js/src/nanojit/Nativei386.cpp b/js/src/nanojit/Nativei386.cpp index fb352a33fadb..f6546137ec5f 100644 --- a/js/src/nanojit/Nativei386.cpp +++ b/js/src/nanojit/Nativei386.cpp @@ -2057,13 +2057,14 @@ namespace nanojit Register rf = findRegFor(iffalse, allow & ~rmask(rr)); - // If 'iftrue' isn't in a register, it can be clobbered by 'ins'. - Register rt = iftrue->isInReg() ? iftrue->getReg() : rr; - if (ins->isop(LIR_cmovd)) { NIns* target = _nIns; asm_nongp_copy(rr, rf); asm_branch(false, condval, target); + + // If 'iftrue' isn't in a register, it can be clobbered by 'ins'. + Register rt = iftrue->isInReg() ? iftrue->getReg() : rr; + if (rr != rt) asm_nongp_copy(rr, rt); freeResourcesOf(ins); @@ -2074,6 +2075,9 @@ namespace nanojit return; } + // If 'iftrue' isn't in a register, it can be clobbered by 'ins'. + Register rt = iftrue->isInReg() ? iftrue->getReg() : rr; + NanoAssert(ins->isop(LIR_cmovi)); // WARNING: We cannot generate any code that affects the condition From 2b732bd20bf55803d25870cc0e8e0b01bb410b1a Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 24 Aug 2010 16:48:24 -0700 Subject: [PATCH 26/31] Update nanojit-import-rev stamp. --- js/src/nanojit-import-rev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/nanojit-import-rev b/js/src/nanojit-import-rev index 2ff6a927019b..4f4faea250fe 100644 --- a/js/src/nanojit-import-rev +++ b/js/src/nanojit-import-rev @@ -1 +1 @@ -c7009f5cd83ea028b98f59e1f8830a76ba27c1dd +2e44b58e0662f140ab49064b26dfbe15d64bc061 From f2297d001769935d85069dc332e09b803766a836 Mon Sep 17 00:00:00 2001 From: David Humphrey Date: Tue, 24 Aug 2010 17:00:56 -0700 Subject: [PATCH 27/31] Bug 589727 - Typed Float32 Arrays broken on mozilla-central trunk (adding a test only, the bug itself was fixed in bug 586387). r=sayrer. --- js/src/trace-test/tests/basic/test586387.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 js/src/trace-test/tests/basic/test586387.js diff --git a/js/src/trace-test/tests/basic/test586387.js b/js/src/trace-test/tests/basic/test586387.js new file mode 100644 index 000000000000..5ddc2d72660a --- /dev/null +++ b/js/src/trace-test/tests/basic/test586387.js @@ -0,0 +1,14 @@ +function testFloatArray() { + var v = new Float32Array(32); + + for (var i = 0; i < v.length; ++i) + v[i] = i; + + var t = 0; + for (var i = 0; i < v.length; ++i) + t += v[i]; + + return t; +} + +assertEq(testFloatArray(), 496); From c4e2721fbdd8495e6f1aedb5e1921a2c6a17d32e Mon Sep 17 00:00:00 2001 From: Gregor Wagner Date: Tue, 24 Aug 2010 18:57:14 -0700 Subject: [PATCH 28/31] Bug 589262 - TM: Move GCStats into separate file. r=igor --- js/src/Makefile.in | 2 + js/src/jscntxt.h | 1 + js/src/jsgc.cpp | 377 +++---------------------------------------- js/src/jsgc.h | 86 +--------- js/src/jsgcstats.cpp | 375 ++++++++++++++++++++++++++++++++++++++++++ js/src/jsgcstats.h | 190 ++++++++++++++++++++++ 6 files changed, 590 insertions(+), 441 deletions(-) create mode 100644 js/src/jsgcstats.cpp create mode 100644 js/src/jsgcstats.h diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 72d537df1f95..0c286b3edb37 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -139,6 +139,7 @@ CPPSRCS = \ jsfun.cpp \ jsgc.cpp \ jsgcchunk.cpp \ + jsgcstats.cpp \ jshash.cpp \ jsinterp.cpp \ jsinvoke.cpp \ @@ -198,6 +199,7 @@ INSTALLED_HEADERS = \ jsfun.h \ jsgc.h \ jsgcchunk.h \ + jsgcstats.h \ jshash.h \ jsinterp.h \ jsinttypes.h \ diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 68274b37b846..5ad30e40bd69 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -1609,6 +1609,7 @@ struct JSRuntime { #ifdef JS_GCMETER JSGCStats gcStats; + JSGCArenaStats gcArenaStats[FINALIZE_LIMIT]; #endif #ifdef DEBUG diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 3f86b278151c..8e06f7ed0229 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -240,6 +240,14 @@ JS_STATIC_ASSERT(sizeof(JSFunction) % GC_CELL_SIZE == 0); JS_STATIC_ASSERT(sizeof(JSXML) % GC_CELL_SIZE == 0); #endif +#ifdef JS_GCMETER +# define METER(x) ((void) (x)) +# define METER_IF(condition, x) ((void) ((condition) && (x))) +#else +# define METER(x) ((void) 0) +# define METER_IF(condition, x) ((void) 0) +#endif + struct JSGCArenaInfo { /* * Allocation list for the arena. @@ -532,7 +540,7 @@ MarkIfUnmarkedGCThing(void *thing, uint32 color = BLACK) return true; } -inline size_t +size_t ThingsPerArena(size_t thingSize) { JS_ASSERT(!(thingSize & GC_CELL_MASK)); @@ -595,22 +603,6 @@ MakeNewArenaFreeList(JSGCArena *a, size_t thingSize) return reinterpret_cast(thingsStart); } -#ifdef JS_GCMETER -# define METER(x) ((void) (x)) -# define METER_IF(condition, x) ((void) ((condition) && (x))) -#else -# define METER(x) ((void) 0) -# define METER_IF(condition, x) ((void) 0) -#endif - -#define METER_UPDATE_MAX(maxLval, rval) \ - METER_IF((maxLval) < (rval), (maxLval) = (rval)) - -#ifdef MOZ_GCTIMER -static jsrefcount newChunkCount = 0; -static jsrefcount destroyChunkCount = 0; -#endif - inline jsuword GetGCChunk(JSRuntime *rt) { @@ -1081,30 +1073,6 @@ IsGCThingWord(JSRuntime *rt, jsuword w) return IsGCThingWord(rt, w, thing, traceKind); } - -#if defined(JS_DUMP_CONSERVATIVE_GC_ROOTS) || defined(JS_GCMETER) - -void -ConservativeGCStats::dump(FILE *fp) -{ - size_t words = 0; - for (size_t i = 0; i != JS_ARRAY_LENGTH(counter); ++i) - words += counter[i]; - -#define ULSTAT(x) ((unsigned long)(x)) - fprintf(fp, "CONSERVATIVE STACK SCANNING:\n"); - fprintf(fp, " number of stack words: %lu\n", ULSTAT(words)); - fprintf(fp, " excluded, low bit set: %lu\n", ULSTAT(counter[CGCT_LOWBITSET])); - fprintf(fp, " not withing a chunk: %lu\n", ULSTAT(counter[CGCT_NOTCHUNK])); - fprintf(fp, " not within arena range: %lu\n", ULSTAT(counter[CGCT_NOTARENA])); - fprintf(fp, " points to free arena: %lu\n", ULSTAT(counter[CGCT_FREEARENA])); - fprintf(fp, " excluded, wrong tag: %lu\n", ULSTAT(counter[CGCT_WRONGTAG])); - fprintf(fp, " excluded, not live: %lu\n", ULSTAT(counter[CGCT_NOTLIVE])); - fprintf(fp, " valid GC things: %lu\n", ULSTAT(counter[CGCT_VALID])); -#undef ULSTAT -} -#endif - static void MarkWordConservatively(JSTracer *trc, jsuword w) { @@ -1211,158 +1179,6 @@ ConservativeGCThreadData::disable() } /* namespace js */ - -#ifdef JS_GCMETER - -static void -UpdateArenaStats(JSGCArenaStats *st, uint32 nlivearenas, uint32 nkilledArenas, - uint32 nthings) -{ - size_t narenas; - - narenas = nlivearenas + nkilledArenas; - JS_ASSERT(narenas >= st->livearenas); - - st->newarenas = narenas - st->livearenas; - st->narenas = narenas; - st->livearenas = nlivearenas; - if (st->maxarenas < narenas) - st->maxarenas = narenas; - st->totalarenas += narenas; - - st->nthings = nthings; - if (st->maxthings < nthings) - st->maxthings = nthings; - st->totalthings += nthings; -} - -JS_FRIEND_API(void) -js_DumpGCStats(JSRuntime *rt, FILE *fp) -{ - static const char *const GC_ARENA_NAMES[] = { - "object", - "function", -#if JS_HAS_XML_SUPPORT - "xml", -#endif - "short string", - "string", - "external_string_0", - "external_string_1", - "external_string_2", - "external_string_3", - "external_string_4", - "external_string_5", - "external_string_6", - "external_string_7", - }; - JS_STATIC_ASSERT(JS_ARRAY_LENGTH(GC_ARENA_NAMES) == FINALIZE_LIMIT); - - fprintf(fp, "\nGC allocation statistics:\n\n"); - -#define UL(x) ((unsigned long)(x)) -#define ULSTAT(x) UL(rt->gcStats.x) -#define PERCENT(x,y) (100.0 * (double) (x) / (double) (y)) - - size_t sumArenas = 0; - size_t sumTotalArenas = 0; - size_t sumThings = 0; - size_t sumMaxThings = 0; - size_t sumThingSize = 0; - size_t sumTotalThingSize = 0; - size_t sumArenaCapacity = 0; - size_t sumTotalArenaCapacity = 0; - size_t sumAlloc = 0; - size_t sumLocalAlloc = 0; - size_t sumFail = 0; - size_t sumRetry = 0; - for (int i = 0; i < (int) FINALIZE_LIMIT; i++) { - size_t thingSize, thingsPerArena; - JSGCArenaStats *st; - thingSize = rt->gcArenaList[i].thingSize; - thingsPerArena = ThingsPerArena(thingSize); - st = &rt->gcStats.arenaStats[i]; - if (st->maxarenas == 0) - continue; - fprintf(fp, - "%s arenas (thing size %lu, %lu things per arena):", - GC_ARENA_NAMES[i], UL(thingSize), UL(thingsPerArena)); - putc('\n', fp); - fprintf(fp, " arenas before GC: %lu\n", UL(st->narenas)); - fprintf(fp, " new arenas before GC: %lu (%.1f%%)\n", - UL(st->newarenas), PERCENT(st->newarenas, st->narenas)); - fprintf(fp, " arenas after GC: %lu (%.1f%%)\n", - UL(st->livearenas), PERCENT(st->livearenas, st->narenas)); - fprintf(fp, " max arenas: %lu\n", UL(st->maxarenas)); - fprintf(fp, " things: %lu\n", UL(st->nthings)); - fprintf(fp, " GC cell utilization: %.1f%%\n", - PERCENT(st->nthings, thingsPerArena * st->narenas)); - fprintf(fp, " average cell utilization: %.1f%%\n", - PERCENT(st->totalthings, thingsPerArena * st->totalarenas)); - fprintf(fp, " max things: %lu\n", UL(st->maxthings)); - fprintf(fp, " alloc attempts: %lu\n", UL(st->alloc)); - fprintf(fp, " alloc without locks: %lu (%.1f%%)\n", - UL(st->localalloc), PERCENT(st->localalloc, st->alloc)); - sumArenas += st->narenas; - sumTotalArenas += st->totalarenas; - sumThings += st->nthings; - sumMaxThings += st->maxthings; - sumThingSize += thingSize * st->nthings; - sumTotalThingSize += size_t(thingSize * st->totalthings); - sumArenaCapacity += thingSize * thingsPerArena * st->narenas; - sumTotalArenaCapacity += thingSize * thingsPerArena * st->totalarenas; - sumAlloc += st->alloc; - sumLocalAlloc += st->localalloc; - sumFail += st->fail; - sumRetry += st->retry; - putc('\n', fp); - } - - fputs("Never used arenas:\n", fp); - for (int i = 0; i < (int) FINALIZE_LIMIT; i++) { - size_t thingSize, thingsPerArena; - JSGCArenaStats *st; - thingSize = rt->gcArenaList[i].thingSize; - thingsPerArena = ThingsPerArena(thingSize); - st = &rt->gcStats.arenaStats[i]; - if (st->maxarenas != 0) - continue; - fprintf(fp, - "%s (thing size %lu, %lu things per arena)\n", - GC_ARENA_NAMES[i], UL(thingSize), UL(thingsPerArena)); - } - fprintf(fp, "\nTOTAL STATS:\n"); - fprintf(fp, " bytes allocated: %lu\n", UL(rt->gcBytes)); - fprintf(fp, " total GC arenas: %lu\n", UL(sumArenas)); - fprintf(fp, " max allocated arenas: %lu\n", ULSTAT(maxnallarenas)); - fprintf(fp, " max allocated chunks: %lu\n", ULSTAT(maxnchunks)); - fprintf(fp, " total GC things: %lu\n", UL(sumThings)); - fprintf(fp, " max total GC things: %lu\n", UL(sumMaxThings)); - fprintf(fp, " GC cell utilization: %.1f%%\n", - PERCENT(sumThingSize, sumArenaCapacity)); - fprintf(fp, " average cell utilization: %.1f%%\n", - PERCENT(sumTotalThingSize, sumTotalArenaCapacity)); - fprintf(fp, "allocation retries after GC: %lu\n", UL(sumRetry)); - fprintf(fp, " alloc attempts: %lu\n", UL(sumAlloc)); - fprintf(fp, " alloc without locks: %lu (%.1f%%)\n", - UL(sumLocalAlloc), PERCENT(sumLocalAlloc, sumAlloc)); - fprintf(fp, " allocation failures: %lu\n", UL(sumFail)); - fprintf(fp, " valid lock calls: %lu\n", ULSTAT(lock)); - fprintf(fp, " valid unlock calls: %lu\n", ULSTAT(unlock)); - fprintf(fp, " delayed tracing calls: %lu\n", ULSTAT(unmarked)); -#ifdef DEBUG - fprintf(fp, " max trace later count: %lu\n", ULSTAT(maxunmarked)); -#endif - fprintf(fp, "potentially useful GC calls: %lu\n", ULSTAT(poke)); - fprintf(fp, " thing arenas freed so far: %lu\n", ULSTAT(afree)); - rt->gcStats.conservative.dump(fp); - -#undef UL -#undef ULSTAT -#undef PERCENT -} -#endif - #ifdef DEBUG static void CheckLeakedRoots(JSRuntime *rt); @@ -1628,7 +1444,7 @@ RefillFinalizableFreeList(JSContext *cx, unsigned thingKind) for (;;) { if (doGC) { LastDitchGC(cx); - METER(cx->runtime->gcStats.arenaStats[thingKind].retry++); + METER(cx->runtime->gcArenaStats[thingKind].retry++); canGC = false; /* @@ -1655,7 +1471,7 @@ RefillFinalizableFreeList(JSContext *cx, unsigned thingKind) if (a) break; if (!canGC) { - METER(cx->runtime->gcStats.arenaStats[thingKind].fail++); + METER(cx->runtime->gcArenaStats[thingKind].fail++); return NULL; } doGC = true; @@ -1702,7 +1518,7 @@ js_NewFinalizableGCThing(JSContext *cx, unsigned thingKind) #endif /* Updates of metering counters here may not be thread-safe. */ - METER(cx->runtime->gcStats.arenaStats[thingKind].alloc++); + METER(cx->runtime->gcArenaStats[thingKind].alloc++); JSGCThing **freeListp = JS_THREAD_DATA(cx)->gcFreeLists.finalizables + thingKind; @@ -1710,7 +1526,7 @@ js_NewFinalizableGCThing(JSContext *cx, unsigned thingKind) if (thing) { *freeListp = thing->link; CheckGCFreeListLink(thing); - METER(cx->runtime->gcStats.arenaStats[thingKind].localalloc++); + METER(cx->runtime->gcArenaStats[thingKind].localalloc++); return thing; } @@ -1878,64 +1694,6 @@ GCMarker::~GCMarker() #endif } -#ifdef JS_DUMP_CONSERVATIVE_GC_ROOTS -void -GCMarker::dumpConservativeRoots() -{ - if (!conservativeDumpFileName) - return; - - FILE *fp; - if (!strcmp(conservativeDumpFileName, "stdout")) { - fp = stdout; - } else if (!strcmp(conservativeDumpFileName, "stderr")) { - fp = stderr; - } else if (!(fp = fopen(conservativeDumpFileName, "aw"))) { - fprintf(stderr, - "Warning: cannot open %s to dump the conservative roots\n", - conservativeDumpFileName); - return; - } - - conservativeStats.dump(fp); - - for (ConservativeRoot *i = conservativeRoots.begin(); - i != conservativeRoots.end(); - ++i) { - fprintf(fp, " %p: ", i->thing); - switch (i->traceKind) { - default: - JS_NOT_REACHED("Unknown trace kind"); - - case JSTRACE_OBJECT: { - JSObject *obj = (JSObject *) i->thing; - fprintf(fp, "object %s", obj->getClass()->name); - break; - } - case JSTRACE_STRING: { - JSString *str = (JSString *) i->thing; - char buf[50]; - js_PutEscapedString(buf, sizeof buf, str, '"'); - fprintf(fp, "string %s", buf); - break; - } -# if JS_HAS_XML_SUPPORT - case JSTRACE_XML: { - JSXML *xml = (JSXML *) i->thing; - fprintf(fp, "xml %u", (unsigned)xml->xml_class); - break; - } -# endif - } - fputc('\n', fp); - } - fputc('\n', fp); - - if (fp != stdout && fp != stderr) - fclose(fp); -} -#endif /* JS_DUMP_CONSERVATIVE_GC_ROOTS */ - void GCMarker::delayMarkingChildren(void *thing) { @@ -2785,89 +2543,10 @@ FinalizeArenaList(JSContext *cx, unsigned thingKind) } arenaList->cursor = arenaList->head; - METER(UpdateArenaStats(&cx->runtime->gcStats.arenaStats[thingKind], + METER(UpdateArenaStats(&cx->runtime->gcArenaStats[thingKind], nlivearenas, nkilledarenas, nthings)); } -#ifdef MOZ_GCTIMER - -const bool JS_WANT_GC_SUITE_PRINT = true; //false for gnuplot output - -struct GCTimer { - uint64 enter; - uint64 startMark; - uint64 startSweep; - uint64 sweepObjectEnd; - uint64 sweepStringEnd; - uint64 sweepDestroyEnd; - uint64 end; - - GCTimer() { - getFirstEnter(); - memset(this, 0, sizeof(GCTimer)); - enter = rdtsc(); - } - - static uint64 getFirstEnter() { - static uint64 firstEnter = rdtsc(); - return firstEnter; - } - - void finish(bool lastGC) { - end = rdtsc(); - - if (startMark > 0) { - if (JS_WANT_GC_SUITE_PRINT) { - fprintf(stderr, "%f %f %f\n", - (double)(end - enter) / 1e6, - (double)(startSweep - startMark) / 1e6, - (double)(sweepDestroyEnd - startSweep) / 1e6); - } else { - static FILE *gcFile; - - if (!gcFile) { - gcFile = fopen("gcTimer.dat", "w"); - - fprintf(gcFile, " AppTime, Total, Mark, Sweep, FinObj,"); - fprintf(gcFile, " FinStr, Destroy, newChunks, destoyChunks\n"); - } - JS_ASSERT(gcFile); - fprintf(gcFile, "%12.1f, %6.1f, %6.1f, %6.1f, %6.1f, %6.1f, %7.1f, ", - (double)(enter - getFirstEnter()) / 1e6, - (double)(end - enter) / 1e6, - (double)(startSweep - startMark) / 1e6, - (double)(sweepDestroyEnd - startSweep) / 1e6, - (double)(sweepObjectEnd - startSweep) / 1e6, - (double)(sweepStringEnd - sweepObjectEnd) / 1e6, - (double)(sweepDestroyEnd - sweepStringEnd) / 1e6); - fprintf(gcFile, "%10d, %10d \n", newChunkCount, - destroyChunkCount); - fflush(gcFile); - - if (lastGC) { - fclose(gcFile); - gcFile = NULL; - } - } - } - newChunkCount = 0; - destroyChunkCount = 0; - } -}; - -# define GCTIMER_PARAM , GCTimer &gcTimer -# define GCTIMER_ARG , gcTimer -# define TIMESTAMP(x) (gcTimer.x = rdtsc()) -# define GCTIMER_BEGIN() GCTimer gcTimer -# define GCTIMER_END(last) (gcTimer.finish(last)) -#else -# define GCTIMER_PARAM -# define GCTIMER_ARG -# define TIMESTAMP(x) ((void) 0) -# define GCTIMER_BEGIN() ((void) 0) -# define GCTIMER_END(last) ((void) 0) -#endif - #ifdef JS_THREADSAFE namespace js { @@ -3128,32 +2807,12 @@ GC(JSContext *cx GCTIMER_PARAM) #endif #ifdef JS_SCOPE_DEPTH_METER - { static FILE *fp; - if (!fp) - fp = fopen("/tmp/scopedepth.stats", "w"); - - if (fp) { - JS_DumpBasicStats(&rt->protoLookupDepthStats, "proto-lookup depth", fp); - JS_DumpBasicStats(&rt->scopeSearchDepthStats, "scope-search depth", fp); - JS_DumpBasicStats(&rt->hostenvScopeDepthStats, "hostenv scope depth", fp); - JS_DumpBasicStats(&rt->lexicalScopeDepthStats, "lexical scope depth", fp); - - putc('\n', fp); - fflush(fp); - } - } -#endif /* JS_SCOPE_DEPTH_METER */ + DumpScopeDepthMeter(rt); +#endif #ifdef JS_DUMP_LOOP_STATS - { static FILE *lsfp; - if (!lsfp) - lsfp = fopen("/tmp/loopstats", "w"); - if (lsfp) { - JS_DumpBasicStats(&rt->loopStats, "loops", lsfp); - fflush(lsfp); - } - } -#endif /* JS_DUMP_LOOP_STATS */ + DumpLoopStats(rt); +#endif } #ifdef JS_THREADSAFE diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 8fc92f3bb0c2..d32aaa0b899e 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -56,17 +56,7 @@ #include "jsversion.h" #include "jsobj.h" #include "jsfun.h" - -#if !defined JS_DUMP_CONSERVATIVE_GC_ROOTS && defined DEBUG -# define JS_DUMP_CONSERVATIVE_GC_ROOTS 1 -#endif - -#if defined JS_GCMETER -const bool JS_WANT_GC_METER_PRINT = true; -#elif defined DEBUG -# define JS_GCMETER 1 -const bool JS_WANT_GC_METER_PRINT = false; -#endif +#include "jsgcstats.h" #define JSTRACE_XML 2 @@ -87,6 +77,9 @@ js_GetExternalStringGCType(JSString *str); extern JS_FRIEND_API(uint32) js_GetGCThingTraceKind(void *thing); +extern size_t +ThingsPerArena(size_t thingSize); + /* * The sole purpose of the function is to preserve public API compatibility * in JS_GetStringBytes which takes only single JSString* argument. @@ -440,33 +433,6 @@ struct ConservativeGCThreadData { bool isEnabled() const { return enableCount > 0; } }; -/* - * The conservative GC test for a word shows that it is either a valid GC - * thing or is not for one of the following reasons. - */ -enum ConservativeGCTest { - CGCT_VALID, - CGCT_LOWBITSET, /* excluded because one of the low bits was set */ - CGCT_NOTARENA, /* not within arena range in a chunk */ - CGCT_NOTCHUNK, /* not within a valid chunk */ - CGCT_FREEARENA, /* within arena containing only free things */ - CGCT_WRONGTAG, /* tagged pointer but wrong type */ - CGCT_NOTLIVE, /* gcthing is not allocated */ - CGCT_END -}; - -struct ConservativeGCStats { - uint32 counter[CGCT_END]; /* ConservativeGCTest classification - counters */ - - void add(const ConservativeGCStats &another) { - for (size_t i = 0; i != JS_ARRAY_LENGTH(counter); ++i) - counter[i] += another.counter[i]; - } - - void dump(FILE *fp); -}; - struct GCMarker : public JSTracer { private: /* The color is only applied to objects, functions and xml. */ @@ -522,50 +488,6 @@ struct GCMarker : public JSTracer { extern void js_FinalizeStringRT(JSRuntime *rt, JSString *str); -#ifdef JS_GCMETER - -struct JSGCArenaStats { - uint32 alloc; /* allocation attempts */ - uint32 localalloc; /* allocations from local lists */ - uint32 retry; /* allocation retries after running the GC */ - uint32 fail; /* allocation failures */ - uint32 nthings; /* live GC things */ - uint32 maxthings; /* maximum of live GC cells */ - double totalthings; /* live GC things the GC scanned so far */ - uint32 narenas; /* number of arena in list before the GC */ - uint32 newarenas; /* new arenas allocated before the last GC */ - uint32 livearenas; /* number of live arenas after the last GC */ - uint32 maxarenas; /* maximum of allocated arenas */ - uint32 totalarenas; /* total number of arenas with live things that - GC scanned so far */ -}; - -struct JSGCStats { - uint32 lock; /* valid lock calls */ - uint32 unlock; /* valid unlock calls */ - uint32 unmarked; /* number of times marking of GC thing's children were - delayed due to a low C stack */ -#ifdef DEBUG - uint32 maxunmarked;/* maximum number of things with children to mark - later */ -#endif - uint32 poke; /* number of potentially useful GC calls */ - uint32 afree; /* thing arenas freed so far */ - uint32 nallarenas; /* number of all allocated arenas */ - uint32 maxnallarenas; /* maximum number of all allocated arenas */ - uint32 nchunks; /* number of allocated chunks */ - uint32 maxnchunks; /* maximum number of allocated chunks */ - - JSGCArenaStats arenaStats[FINALIZE_LIMIT]; - - js::ConservativeGCStats conservative; -}; - -extern JS_FRIEND_API(void) -js_DumpGCStats(JSRuntime *rt, FILE *fp); - -#endif /* JS_GCMETER */ - /* * This function is defined in jsdbgapi.cpp but is declared here to avoid * polluting jsdbgapi.h, a public API header, with internal functions. diff --git a/js/src/jsgcstats.cpp b/js/src/jsgcstats.cpp new file mode 100644 index 000000000000..9cd7b6c73ec7 --- /dev/null +++ b/js/src/jsgcstats.cpp @@ -0,0 +1,375 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99 ft=cpp: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * June 30, 2010 + * + * The Initial Developer of the Original Code is + * the Mozilla Corporation. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "jstypes.h" +#include "jscntxt.h" +#include "jsgcstats.h" +#include "jsgc.h" +#include "jsxml.h" +#include "jsbuiltins.h" + +using namespace js; + +#if defined(JS_DUMP_CONSERVATIVE_GC_ROOTS) || defined(JS_GCMETER) + +void +ConservativeGCStats::dump(FILE *fp) +{ + size_t words = 0; + for (size_t i = 0; i != JS_ARRAY_LENGTH(counter); ++i) + words += counter[i]; + +#define ULSTAT(x) ((unsigned long)(x)) + fprintf(fp, "CONSERVATIVE STACK SCANNING:\n"); + fprintf(fp, " number of stack words: %lu\n", ULSTAT(words)); + fprintf(fp, " excluded, low bit set: %lu\n", ULSTAT(counter[CGCT_LOWBITSET])); + fprintf(fp, " not withing a chunk: %lu\n", ULSTAT(counter[CGCT_NOTCHUNK])); + fprintf(fp, " not within arena range: %lu\n", ULSTAT(counter[CGCT_NOTARENA])); + fprintf(fp, " points to free arena: %lu\n", ULSTAT(counter[CGCT_FREEARENA])); + fprintf(fp, " excluded, wrong tag: %lu\n", ULSTAT(counter[CGCT_WRONGTAG])); + fprintf(fp, " excluded, not live: %lu\n", ULSTAT(counter[CGCT_NOTLIVE])); + fprintf(fp, " valid GC things: %lu\n", ULSTAT(counter[CGCT_VALID])); +#undef ULSTAT +} +#endif + +#ifdef JS_DUMP_CONSERVATIVE_GC_ROOTS +void +GCMarker::dumpConservativeRoots() +{ + if (!conservativeDumpFileName) + return; + + FILE *fp; + if (!strcmp(conservativeDumpFileName, "stdout")) { + fp = stdout; + } else if (!strcmp(conservativeDumpFileName, "stderr")) { + fp = stderr; + } else if (!(fp = fopen(conservativeDumpFileName, "aw"))) { + fprintf(stderr, + "Warning: cannot open %s to dump the conservative roots\n", + conservativeDumpFileName); + return; + } + + conservativeStats.dump(fp); + + for (ConservativeRoot *i = conservativeRoots.begin(); + i != conservativeRoots.end(); + ++i) { + fprintf(fp, " %p: ", i->thing); + switch (i->traceKind) { + default: + JS_NOT_REACHED("Unknown trace kind"); + + case JSTRACE_OBJECT: { + JSObject *obj = (JSObject *) i->thing; + fprintf(fp, "object %s", obj->getClass()->name); + break; + } + case JSTRACE_STRING: { + JSString *str = (JSString *) i->thing; + char buf[50]; + js_PutEscapedString(buf, sizeof buf, str, '"'); + fprintf(fp, "string %s", buf); + break; + } +# if JS_HAS_XML_SUPPORT + case JSTRACE_XML: { + JSXML *xml = (JSXML *) i->thing; + fprintf(fp, "xml %u", (unsigned)xml->xml_class); + break; + } +# endif + } + fputc('\n', fp); + } + fputc('\n', fp); + + if (fp != stdout && fp != stderr) + fclose(fp); +} +#endif /* JS_DUMP_CONSERVATIVE_GC_ROOTS */ + +#ifdef JS_GCMETER + +void +UpdateArenaStats(JSGCArenaStats *st, uint32 nlivearenas, uint32 nkilledArenas, + uint32 nthings) +{ + size_t narenas; + + narenas = nlivearenas + nkilledArenas; + JS_ASSERT(narenas >= st->livearenas); + + st->newarenas = narenas - st->livearenas; + st->narenas = narenas; + st->livearenas = nlivearenas; + if (st->maxarenas < narenas) + st->maxarenas = narenas; + st->totalarenas += narenas; + + st->nthings = nthings; + if (st->maxthings < nthings) + st->maxthings = nthings; + st->totalthings += nthings; +} + +JS_FRIEND_API(void) +js_DumpGCStats(JSRuntime *rt, FILE *fp) +{ + static const char *const GC_ARENA_NAMES[] = { + "object", + "function", +#if JS_HAS_XML_SUPPORT + "xml", +#endif + "short string", + "string", + "external_string_0", + "external_string_1", + "external_string_2", + "external_string_3", + "external_string_4", + "external_string_5", + "external_string_6", + "external_string_7", + }; + + fprintf(fp, "\nGC allocation statistics:\n\n"); + +#define UL(x) ((unsigned long)(x)) +#define ULSTAT(x) UL(rt->gcStats.x) +#define PERCENT(x,y) (100.0 * (double) (x) / (double) (y)) + + size_t sumArenas = 0; + size_t sumTotalArenas = 0; + size_t sumThings = 0; + size_t sumMaxThings = 0; + size_t sumThingSize = 0; + size_t sumTotalThingSize = 0; + size_t sumArenaCapacity = 0; + size_t sumTotalArenaCapacity = 0; + size_t sumAlloc = 0; + size_t sumLocalAlloc = 0; + size_t sumFail = 0; + size_t sumRetry = 0; + for (int i = 0; i < (int) FINALIZE_LIMIT; i++) { + size_t thingSize, thingsPerArena; + JSGCArenaStats *st; + thingSize = rt->gcArenaList[i].thingSize; + thingsPerArena = ThingsPerArena(thingSize); + st = &rt->gcArenaStats[i]; + if (st->maxarenas == 0) + continue; + fprintf(fp, + "%s arenas (thing size %lu, %lu things per arena):", + GC_ARENA_NAMES[i], UL(thingSize), UL(thingsPerArena)); + putc('\n', fp); + fprintf(fp, " arenas before GC: %lu\n", UL(st->narenas)); + fprintf(fp, " new arenas before GC: %lu (%.1f%%)\n", + UL(st->newarenas), PERCENT(st->newarenas, st->narenas)); + fprintf(fp, " arenas after GC: %lu (%.1f%%)\n", + UL(st->livearenas), PERCENT(st->livearenas, st->narenas)); + fprintf(fp, " max arenas: %lu\n", UL(st->maxarenas)); + fprintf(fp, " things: %lu\n", UL(st->nthings)); + fprintf(fp, " GC cell utilization: %.1f%%\n", + PERCENT(st->nthings, thingsPerArena * st->narenas)); + fprintf(fp, " average cell utilization: %.1f%%\n", + PERCENT(st->totalthings, thingsPerArena * st->totalarenas)); + fprintf(fp, " max things: %lu\n", UL(st->maxthings)); + fprintf(fp, " alloc attempts: %lu\n", UL(st->alloc)); + fprintf(fp, " alloc without locks: %lu (%.1f%%)\n", + UL(st->localalloc), PERCENT(st->localalloc, st->alloc)); + sumArenas += st->narenas; + sumTotalArenas += st->totalarenas; + sumThings += st->nthings; + sumMaxThings += st->maxthings; + sumThingSize += thingSize * st->nthings; + sumTotalThingSize += size_t(thingSize * st->totalthings); + sumArenaCapacity += thingSize * thingsPerArena * st->narenas; + sumTotalArenaCapacity += thingSize * thingsPerArena * st->totalarenas; + sumAlloc += st->alloc; + sumLocalAlloc += st->localalloc; + sumFail += st->fail; + sumRetry += st->retry; + putc('\n', fp); + } + + fputs("Never used arenas:\n", fp); + for (int i = 0; i < (int) FINALIZE_LIMIT; i++) { + size_t thingSize, thingsPerArena; + JSGCArenaStats *st; + thingSize = rt->gcArenaList[i].thingSize; + thingsPerArena = ThingsPerArena(thingSize); + st = &rt->gcArenaStats[i]; + if (st->maxarenas != 0) + continue; + fprintf(fp, + "%s (thing size %lu, %lu things per arena)\n", + GC_ARENA_NAMES[i], UL(thingSize), UL(thingsPerArena)); + } + fprintf(fp, "\nTOTAL STATS:\n"); + fprintf(fp, " bytes allocated: %lu\n", UL(rt->gcBytes)); + fprintf(fp, " total GC arenas: %lu\n", UL(sumArenas)); + fprintf(fp, " max allocated arenas: %lu\n", ULSTAT(maxnallarenas)); + fprintf(fp, " max allocated chunks: %lu\n", ULSTAT(maxnchunks)); + fprintf(fp, " total GC things: %lu\n", UL(sumThings)); + fprintf(fp, " max total GC things: %lu\n", UL(sumMaxThings)); + fprintf(fp, " GC cell utilization: %.1f%%\n", + PERCENT(sumThingSize, sumArenaCapacity)); + fprintf(fp, " average cell utilization: %.1f%%\n", + PERCENT(sumTotalThingSize, sumTotalArenaCapacity)); + fprintf(fp, "allocation retries after GC: %lu\n", UL(sumRetry)); + fprintf(fp, " alloc attempts: %lu\n", UL(sumAlloc)); + fprintf(fp, " alloc without locks: %lu (%.1f%%)\n", + UL(sumLocalAlloc), PERCENT(sumLocalAlloc, sumAlloc)); + fprintf(fp, " allocation failures: %lu\n", UL(sumFail)); + fprintf(fp, " valid lock calls: %lu\n", ULSTAT(lock)); + fprintf(fp, " valid unlock calls: %lu\n", ULSTAT(unlock)); + fprintf(fp, " delayed tracing calls: %lu\n", ULSTAT(unmarked)); +#ifdef DEBUG + fprintf(fp, " max trace later count: %lu\n", ULSTAT(maxunmarked)); +#endif + fprintf(fp, "potentially useful GC calls: %lu\n", ULSTAT(poke)); + fprintf(fp, " thing arenas freed so far: %lu\n", ULSTAT(afree)); + rt->gcStats.conservative.dump(fp); + +#undef UL +#undef ULSTAT +#undef PERCENT +} +#endif + +#ifdef MOZ_GCTIMER + +namespace js { + +jsrefcount newChunkCount = 0; +jsrefcount destroyChunkCount = 0; + +GCTimer::GCTimer() { + getFirstEnter(); + memset(this, 0, sizeof(GCTimer)); + enter = rdtsc(); +} + +uint64 +GCTimer::getFirstEnter() { + static uint64 firstEnter = rdtsc(); + return firstEnter; +} + +void +GCTimer::finish(bool lastGC) { + end = rdtsc(); + + if (startMark > 0 && JS_WANT_GC_TIMER_PRINT) { + if (JS_WANT_GC_SUITE_PRINT) { + fprintf(stderr, "%f %f %f\n", + (double)(end - enter) / 1e6, + (double)(startSweep - startMark) / 1e6, + (double)(sweepDestroyEnd - startSweep) / 1e6); + } else { + static FILE *gcFile; + + if (!gcFile) { + gcFile = fopen("gcTimer.dat", "w"); + + fprintf(gcFile, " AppTime, Total, Mark, Sweep, FinObj,"); + fprintf(gcFile, " FinStr, Destroy, newChunks, destoyChunks\n"); + } + JS_ASSERT(gcFile); + fprintf(gcFile, "%12.1f, %6.1f, %6.1f, %6.1f, %6.1f, %6.1f, %7.1f, ", + (double)(enter - getFirstEnter()) / 1e6, + (double)(end - enter) / 1e6, + (double)(startSweep - startMark) / 1e6, + (double)(sweepDestroyEnd - startSweep) / 1e6, + (double)(sweepObjectEnd - startSweep) / 1e6, + (double)(sweepStringEnd - sweepObjectEnd) / 1e6, + (double)(sweepDestroyEnd - sweepStringEnd) / 1e6); + fprintf(gcFile, "%10d, %10d \n", newChunkCount, + destroyChunkCount); + fflush(gcFile); + + if (lastGC) { + fclose(gcFile); + gcFile = NULL; + } + } + } + newChunkCount = 0; + destroyChunkCount = 0; +} + +#ifdef JS_SCOPE_DEPTH_METER +void +DumpScopeDepthMeter(JSRuntime *rt) +{ + static FILE *fp; + if (!fp) + fp = fopen("/tmp/scopedepth.stats", "w"); + + if (fp) { + JS_DumpBasicStats(&rt->protoLookupDepthStats, "proto-lookup depth", fp); + JS_DumpBasicStats(&rt->scopeSearchDepthStats, "scope-search depth", fp); + JS_DumpBasicStats(&rt->hostenvScopeDepthStats, "hostenv scope depth", fp); + JS_DumpBasicStats(&rt->lexicalScopeDepthStats, "lexical scope depth", fp); + + putc('\n', fp); + fflush(fp); + } +} +#endif + +#ifdef JS_DUMP_LOOP_STATS +void +DumpLoopStats(JSRuntime *rt) +{ + static FILE *lsfp; + if (!lsfp) + lsfp = fopen("/tmp/loopstats", "w"); + if (lsfp) { + JS_DumpBasicStats(&rt->loopStats, "loops", lsfp); + fflush(lsfp); + } +} +#endif + +} /* namespace js */ +#endif diff --git a/js/src/jsgcstats.h b/js/src/jsgcstats.h new file mode 100644 index 000000000000..cd0e7e269829 --- /dev/null +++ b/js/src/jsgcstats.h @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99 ft=cpp: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * June 30, 2010 + * + * The Initial Developer of the Original Code is + * the Mozilla Corporation. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsgcstats_h___ +#define jsgcstats_h___ + +#if !defined JS_DUMP_CONSERVATIVE_GC_ROOTS && defined DEBUG +# define JS_DUMP_CONSERVATIVE_GC_ROOTS 1 +#endif + +/* Define JS_GCMETER here if wanted */ +#if defined JS_GCMETER +const bool JS_WANT_GC_METER_PRINT = true; +#elif defined DEBUG +# define JS_GCMETER 1 +const bool JS_WANT_GC_METER_PRINT = false; +#endif + +/* MOZ_GCTIMER is a compile flag. --enable-gctimer */ +#if defined MOZ_GCTIMER +const bool JS_WANT_GC_TIMER_PRINT = true; +#elif defined DEBUG +# define MOZ_GCTIMER 1 +const bool JS_WANT_GC_TIMER_PRINT = false; +#endif + +#define METER_UPDATE_MAX(maxLval, rval) \ + METER_IF((maxLval) < (rval), (maxLval) = (rval)) + +namespace js { + +/* + * The conservative GC test for a word shows that it is either a valid GC + * thing or is not for one of the following reasons. + */ +enum ConservativeGCTest { + CGCT_VALID, + CGCT_LOWBITSET, /* excluded because one of the low bits was set */ + CGCT_NOTARENA, /* not within arena range in a chunk */ + CGCT_NOTCHUNK, /* not within a valid chunk */ + CGCT_FREEARENA, /* within arena containing only free things */ + CGCT_WRONGTAG, /* tagged pointer but wrong type */ + CGCT_NOTLIVE, /* gcthing is not allocated */ + CGCT_END +}; + +struct ConservativeGCStats { + uint32 counter[CGCT_END]; /* ConservativeGCTest classification + counters */ + + void add(const ConservativeGCStats &another) { + for (size_t i = 0; i != JS_ARRAY_LENGTH(counter); ++i) + counter[i] += another.counter[i]; + } + + void dump(FILE *fp); +}; + +} /* namespace js */ + +#ifdef JS_GCMETER + +struct JSGCArenaStats { + uint32 alloc; /* allocation attempts */ + uint32 localalloc; /* allocations from local lists */ + uint32 retry; /* allocation retries after running the GC */ + uint32 fail; /* allocation failures */ + uint32 nthings; /* live GC things */ + uint32 maxthings; /* maximum of live GC cells */ + double totalthings; /* live GC things the GC scanned so far */ + uint32 narenas; /* number of arena in list before the GC */ + uint32 newarenas; /* new arenas allocated before the last GC */ + uint32 livearenas; /* number of live arenas after the last GC */ + uint32 maxarenas; /* maximum of allocated arenas */ + uint32 totalarenas; /* total number of arenas with live things that + GC scanned so far */ +}; + +struct JSGCStats { + uint32 lock; /* valid lock calls */ + uint32 unlock; /* valid unlock calls */ + uint32 unmarked; /* number of times marking of GC thing's children were + delayed due to a low C stack */ +#ifdef DEBUG + uint32 maxunmarked;/* maximum number of things with children to mark + later */ +#endif + uint32 poke; /* number of potentially useful GC calls */ + uint32 afree; /* thing arenas freed so far */ + uint32 nallarenas; /* number of all allocated arenas */ + uint32 maxnallarenas; /* maximum number of all allocated arenas */ + uint32 nchunks; /* number of allocated chunks */ + uint32 maxnchunks; /* maximum number of allocated chunks */ + + js::ConservativeGCStats conservative; +}; + +extern JS_FRIEND_API(void) +js_DumpGCStats(JSRuntime *rt, FILE *fp); + +extern void +UpdateArenaStats(JSGCArenaStats *st, uint32 nlivearenas, uint32 nkilledArenas, + uint32 nthings); + +#endif /* JS_GCMETER */ + +namespace js { + +#ifdef MOZ_GCTIMER + +extern jsrefcount newChunkCount; +extern jsrefcount destroyChunkCount; + +const bool JS_WANT_GC_SUITE_PRINT = false; //false for gnuplot output + +struct GCTimer { + uint64 enter; + uint64 startMark; + uint64 startSweep; + uint64 sweepObjectEnd; + uint64 sweepStringEnd; + uint64 sweepDestroyEnd; + uint64 end; + + GCTimer(); + static uint64 getFirstEnter(); + void finish(bool lastGC); +}; + +# define GCTIMER_PARAM , GCTimer &gcTimer +# define GCTIMER_ARG , gcTimer +# define TIMESTAMP(x) (gcTimer.x = rdtsc()) +# define GCTIMER_BEGIN() GCTimer gcTimer +# define GCTIMER_END(last) (gcTimer.finish(last)) +#else +# define GCTIMER_PARAM +# define GCTIMER_ARG +# define TIMESTAMP(x) ((void) 0) +# define GCTIMER_BEGIN() ((void) 0) +# define GCTIMER_END(last) ((void) 0) +#endif + +#ifdef JS_SCOPE_DEPTH_METER +extern void +DumpScopeDepthMeter(JSRuntime *rt); +#endif + +#ifdef JS_DUMP_LOOP_STATS +extern void +DumpLoopStats(JSRuntime *rt); +#endif + +} /* namepsace js */ + +#endif /* jsgcstats_h___ */ From ae564d6e66706ba0c256a43097deb3c07ca1cc9a Mon Sep 17 00:00:00 2001 From: Shu-yu Guo Date: Tue, 24 Aug 2010 19:45:21 -0700 Subject: [PATCH 29/31] Refactor structured property names to pseudo-namespaces using bind --- js/narcissus/jsexec.js | 8 +- js/narcissus/jsparse.js | 1826 ++++++++++++++++++++------------------- 2 files changed, 934 insertions(+), 900 deletions(-) diff --git a/js/narcissus/jsexec.js b/js/narcissus/jsexec.js index 777ed4e48b8a..0ab9b233f420 100644 --- a/js/narcissus/jsexec.js +++ b/js/narcissus/jsexec.js @@ -85,7 +85,7 @@ Narcissus.interpreter = (function() { x2.callee = x.callee; x2.scope = x.scope; try { - x2.execute(parser.parse(new parser.VanillaBuilder, s)); + x2.execute(parser.parse(new parser.DefaultBuilder, s)); return x2.result; } catch (e if e instanceof SyntaxError || isStackOverflow(e)) { /* @@ -119,7 +119,7 @@ Narcissus.interpreter = (function() { // NB: Use the STATEMENT_FORM constant since we don't want to push this // function onto the fake compilation context. - var x = { builder: new parser.VanillaBuilder }; + var x = { builder: new parser.DefaultBuilder }; var f = parser.FunctionDefinition(t, x, false, parser.STATEMENT_FORM); var s = {object: global, parent: null}; return newFunction(f,{scope:s}); @@ -1023,7 +1023,7 @@ Narcissus.interpreter = (function() { return s; var x = new ExecutionContext(GLOBAL_CODE); - x.execute(parser.parse(new parser.VanillaBuilder, s, f, l)); + x.execute(parser.parse(new parser.DefaultBuilder, s, f, l)); return x.result; } @@ -1059,7 +1059,7 @@ Narcissus.interpreter = (function() { } } - var b = new parser.VanillaBuilder; + var b = new parser.DefaultBuilder; var x = new ExecutionContext(GLOBAL_CODE); ExecutionContext.current = x; diff --git a/js/narcissus/jsparse.js b/js/narcissus/jsparse.js index e1da444505bd..f5918bd43fe2 100644 --- a/js/narcissus/jsparse.js +++ b/js/narcissus/jsparse.js @@ -51,740 +51,771 @@ Narcissus.parser = (function() { // Set constants in the local scope. eval(definitions.consts); - /* - * The vanilla AST builder. - */ - function VanillaBuilder() { + /* + * Function.prototype.bind is not yet implemented in many browsers. + * The following definition will be removed when it is. + * + * Similar to Prototype's implementation. + */ + + function bindMethod(method, context) { + if (arguments.length < 3 && arguments[0] === undefined) + return method; + var slice = Array.prototype.slice; + var args = slice.call(arguments, 2); + // Optimization for when there's no currying. + if (args.length === 0) { + return function() { + return method.apply(context, arguments); + } + } + + return function() { + var a = slice.call(args, 0); + for (var i = 0, j = arguments.length; i < j; i++) { + a.push(arguments[i]); + } + return method.apply(context, a); + } } - VanillaBuilder.prototype = { - IF$build: function(t) { - return new Node(t, IF); - }, + function bindSubBuilders(builder, proto) { + for (var ns in proto) { + var unbound = proto[ns]; + // We do not want to bind functions like setHoists. + if (typeof unbound !== "object") + continue; + + /* + * We store the bound sub-builder as builder's own property + * so that we can have multiple builders at the same time. + */ + var bound = builder[ns] = {}; + for (var m in unbound) { + bound[m] = bindMethod(unbound[m], builder); + } + } + } + + /* + * The vanilla AST builder. + */ + + function DefaultBuilder() { + bindSubBuilders(this, DefaultBuilder.prototype); + } + + function mkBinopBuilder(type) { + return { + build: !type ? function(t) { return new Node(t); } + : function(t) { return new Node(t, type); }, + addOperand: function(n, n2) { n.push(n2); }, + finish: function(n) { } + }; + } + + DefaultBuilder.prototype = { + IF: { + build: function(t) { + return new Node(t, IF); + }, + + setCondition: function(n, e) { + n.condition = e; + }, + + setThenPart: function(n, s) { + n.thenPart = s; + }, + + setElsePart: function(n, s) { + n.elsePart = s; + }, + + finish: function(n) { + } + }, + + SWITCH: { + build: function(t) { + var n = new Node(t, SWITCH); + n.cases = []; + n.defaultIndex = -1; + return n; + }, + + setDiscriminant: function(n, e) { + n.discriminant = e; + }, + + setDefaultIndex: function(n, i) { + n.defaultIndex = i; + }, + + addCase: function(n, n2) { + n.cases.push(n2); + }, + + finish: function(n) { + } + }, + + CASE: { + build: function(t) { + return new Node(t, CASE); + }, + + setLabel: function(n, e) { + n.caseLabel = e; + }, + + initializeStatements: function(n, t) { + n.statements = new Node(t, BLOCK); + }, + + addStatement: function(n, s) { + n.statements.push(s); + }, + + finish: function(n) { + } + }, + + DEFAULT: { + build: function(t, p) { + return new Node(t, DEFAULT); + }, + + initializeStatements: function(n, t) { + n.statements = new Node(t, BLOCK); + }, + + addStatement: function(n, s) { + n.statements.push(s); + }, - IF$setCondition: function(n, e) { - n.condition = e; + finish: function(n) { + } }, - IF$setThenPart: function(n, s) { - n.thenPart = s; - }, + FOR: { + build: function(t) { + var n = new Node(t, FOR); + n.isLoop = true; + n.isEach = false; + return n; + }, - IF$setElsePart: function(n, s) { - n.elsePart = s; - }, + rebuildForEach: function(n) { + n.isEach = true; + }, - IF$finish: function(n) { - }, + // NB. This function is called after rebuildForEach, if that's called + // at all. + rebuildForIn: function(n) { + n.type = FOR_IN; + }, - SWITCH$build: function(t) { - var n = new Node(t, SWITCH); - n.cases = []; - n.defaultIndex = -1; - return n; - }, + setCondition: function(n, e) { + n.condition = e; + }, - SWITCH$setDiscriminant: function(n, e) { - n.discriminant = e; - }, + setSetup: function(n, e) { + n.setup = e || null; + }, - SWITCH$setDefaultIndex: function(n, i) { - n.defaultIndex = i; - }, + setUpdate: function(n, e) { + n.update = e; + }, - SWITCH$addCase: function(n, n2) { - n.cases.push(n2); - }, + setObject: function(n, e) { + n.object = e; + }, - SWITCH$finish: function(n) { - }, + setIterator: function(n, e, e2) { + n.iterator = e; + n.varDecl = e2; + }, - CASE$build: function(t) { - return new Node(t, CASE); - }, + setBody: function(n, s) { + n.body = s; + }, - CASE$setLabel: function(n, e) { - n.caseLabel = e; + finish: function(n) { + } }, - CASE$initializeStatements: function(n, t) { - n.statements = new Node(t, BLOCK); - }, + WHILE: { + build: function(t) { + var n = new Node(t, WHILE); + n.isLoop = true; + return n; + }, - CASE$addStatement: function(n, s) { - n.statements.push(s); - }, + setCondition: function(n, e) { + n.condition = e; + }, - CASE$finish: function(n) { - }, + setBody: function(n, s) { + n.body = s; + }, - DEFAULT$build: function(t, p) { - return new Node(t, DEFAULT); + finish: function(n) { + } }, - DEFAULT$initializeStatements: function(n, t) { - n.statements = new Node(t, BLOCK); - }, + DO: { + build: function(t) { + var n = new Node(t, DO); + n.isLoop = true; + return n; + }, - DEFAULT$addStatement: function(n, s) { - n.statements.push(s); - }, + setCondition: function(n, e) { + n.condition = e; + }, - DEFAULT$finish: function(n) { - }, + setBody: function(n, s) { + n.body = s; + }, - FOR$build: function(t) { - var n = new Node(t, FOR); - n.isLoop = true; - n.isEach = false; - return n; + finish: function(n) { + } }, - FOR$rebuildForEach: function(n) { - n.isEach = true; - }, + BREAK: { + build: function(t) { + return new Node(t, BREAK); + }, - // NB. This function is called after rebuildForEach, if that's called - // at all. - FOR$rebuildForIn: function(n) { - n.type = FOR_IN; - }, + setLabel: function(n, v) { + n.label = v; + }, - FOR$setCondition: function(n, e) { - n.condition = e; - }, + setTarget: function(n, n2) { + n.target = n2; + }, - FOR$setSetup: function(n, e) { - n.setup = e || null; + finish: function(n) { + } }, - FOR$setUpdate: function(n, e) { - n.update = e; - }, + CONTINUE: { + build: function(t) { + return new Node(t, CONTINUE); + }, - FOR$setObject: function(n, e) { - n.object = e; - }, + setLabel: function(n, v) { + n.label = v; + }, - FOR$setIterator: function(n, e, e2) { - n.iterator = e; - n.varDecl = e2; - }, + setTarget: function(n, n2) { + n.target = n2; + }, - FOR$setBody: function(n, s) { - n.body = s; + finish: function(n) { + } }, - FOR$finish: function(n) { - }, + TRY: { + build: function(t) { + var n = new Node(t, TRY); + n.catchClauses = []; + return n; + }, - WHILE$build: function(t) { - var n = new Node(t, WHILE); - n.isLoop = true; - return n; - }, + setTryBlock: function(n, s) { + n.tryBlock = s; + }, - WHILE$setCondition: function(n, e) { - n.condition = e; - }, + addCatch: function(n, n2) { + n.catchClauses.push(n2); + }, - WHILE$setBody: function(n, s) { - n.body = s; - }, + finishCatches: function(n) { + }, - WHILE$finish: function(n) { - }, + setFinallyBlock: function(n, s) { + n.finallyBlock = s; + }, - DO$build: function(t) { - var n = new Node(t, DO); - n.isLoop = true; - return n; + finish: function(n) { + } }, - DO$setCondition: function(n, e) { - n.condition = e; - }, + CATCH: { + build: function(t) { + var n = new Node(t, CATCH); + n.guard = null; + return n; + }, - DO$setBody: function(n, s) { - n.body = s; - }, + setVarName: function(n, v) { + n.varName = v; + }, - DO$finish: function(n) { - }, + setGuard: function(n, e) { + n.guard = e; + }, - BREAK$build: function(t) { - return new Node(t, BREAK); - }, + setBlock: function(n, s) { + n.block = s; + }, - BREAK$setLabel: function(n, v) { - n.label = v; + finish: function(n) { + } }, - BREAK$setTarget: function(n, n2) { - n.target = n2; - }, + THROW: { + build: function(t) { + return new Node(t, THROW); + }, - BREAK$finish: function(n) { - }, + setException: function(n, e) { + n.exception = e; + }, - CONTINUE$build: function(t) { - return new Node(t, CONTINUE); + finish: function(n) { + } }, - CONTINUE$setLabel: function(n, v) { - n.label = v; - }, + RETURN: { + build: function(t) { + return new Node(t, RETURN); + }, - CONTINUE$setTarget: function(n, n2) { - n.target = n2; - }, + setValue: function(n, e) { + n.value = e; + }, - CONTINUE$finish: function(n) { + finish: function(n) { + } }, - TRY$build: function(t) { - var n = new Node(t, TRY); - n.catchClauses = []; - return n; - }, + YIELD: { + build: function(t) { + return new Node(t, YIELD); + }, - TRY$setTryBlock: function(n, s) { - n.tryBlock = s; - }, + setValue: function(n, e) { + n.value = e; + }, - TRY$addCatch: function(n, n2) { - n.catchClauses.push(n2); + finish: function(n) { + } }, - TRY$finishCatches: function(n) { - }, + GENERATOR: { + build: function(t) { + return new Node(t, GENERATOR); + }, - TRY$setFinallyBlock: function(n, s) { - n.finallyBlock = s; - }, + setExpression: function(n, e) { + n.expression = e; + }, - TRY$finish: function(n) { - }, + setTail: function(n, n2) { + n.tail = n2; + }, - CATCH$build: function(t) { - var n = new Node(t, CATCH); - n.guard = null; - return n; + finish: function(n) { + } }, - CATCH$setVarName: function(n, v) { - n.varName = v; - }, + WITH: { + build: function(t) { + return new Node(t, WITH); + }, - CATCH$setGuard: function(n, e) { - n.guard = e; - }, + setObject: function(n, e) { + n.object = e; + }, - CATCH$setBlock: function(n, s) { - n.block = s; - }, + setBody: function(n, s) { + n.body = s; + }, - CATCH$finish: function(n) { + finish: function(n) { + } }, - THROW$build: function(t) { - return new Node(t, THROW); + DEBUGGER: { + build: function(t) { + return new Node(t, DEBUGGER); + } }, - THROW$setException: function(n, e) { - n.exception = e; - }, + SEMICOLON: { + build: function(t) { + return new Node(t, SEMICOLON); + }, - THROW$finish: function(n) { - }, + setExpression: function(n, e) { + n.expression = e; + }, - RETURN$build: function(t) { - return new Node(t, RETURN); + finish: function(n) { + } }, - RETURN$setValue: function(n, e) { - n.value = e; - }, + LABEL: { + build: function(t) { + return new Node(t, LABEL); + }, - RETURN$finish: function(n) { - }, + setLabel: function(n, e) { + n.label = e; + }, - YIELD$build: function(t) { - return new Node(t, YIELD); - }, + setStatement: function(n, s) { + n.statement = s; + }, - YIELD$setValue: function(n, e) { - n.value = e; + finish: function(n) { + } }, - YIELD$finish: function(n) { - }, + FUNCTION: { + build: function(t) { + var n = new Node(t); + if (n.type !== FUNCTION) + n.type = (n.value === "get") ? GETTER : SETTER; + n.params = []; + return n; + }, - GENERATOR$build: function(t) { - return new Node(t, GENERATOR); - }, + setName: function(n, v) { + n.name = v; + }, - GENERATOR$setExpression: function(n, e) { - n.expression = e; - }, + addParam: function(n, v) { + n.params.push(v); + }, - GENERATOR$setTail: function(n, n2) { - n.tail = n2; - }, + setBody: function(n, s) { + n.body = s; + }, - GENERATOR$finish: function(n) { - }, + hoistVars: function(x) { + }, - WITH$build: function(t) { - return new Node(t, WITH); + finish: function(n, x) { + } }, - WITH$setObject: function(n, e) { - n.object = e; - }, + VAR: { + build: function(t) { + return new Node(t, VAR); + }, - WITH$setBody: function(n, s) { - n.body = s; - }, + addDecl: function(n, n2, x) { + n.push(n2); + }, - WITH$finish: function(n) { + finish: function(n) { + } }, - DEBUGGER$build: function(t) { - return new Node(t, DEBUGGER); - }, + CONST: { + build: function(t) { + return new Node(t, VAR); + }, - SEMICOLON$build: function(t) { - return new Node(t, SEMICOLON); - }, + addDecl: function(n, n2, x) { + n.push(n2); + }, - SEMICOLON$setExpression: function(n, e) { - n.expression = e; + finish: function(n) { + } }, - SEMICOLON$finish: function(n) { - }, + LET: { + build: function(t) { + return new Node(t, LET); + }, - LABEL$build: function(t) { - return new Node(t, LABEL); - }, + addDecl: function(n, n2, x) { + n.push(n2); + }, - LABEL$setLabel: function(n, e) { - n.label = e; + finish: function(n) { + } }, - LABEL$setStatement: function(n, s) { - n.statement = s; - }, + DECL: { + build: function(t) { + return new Node(t, IDENTIFIER); + }, - LABEL$finish: function(n) { - }, + setName: function(n, v) { + n.name = v; + }, - FUNCTION$build: function(t) { - var n = new Node(t); - if (n.type !== FUNCTION) - n.type = (n.value === "get") ? GETTER : SETTER; - n.params = []; - return n; - }, + setInitializer: function(n, e) { + n.initializer = e; + }, - FUNCTION$setName: function(n, v) { - n.name = v; - }, + setReadOnly: function(n, b) { + n.readOnly = b; + }, - FUNCTION$addParam: function(n, v) { - n.params.push(v); + finish: function(n) { + } }, - FUNCTION$setBody: function(n, s) { - n.body = s; - }, + LET_BLOCK: { + build: function(t) { + var n = Node(t, LET_BLOCK); + n.varDecls = []; + return n; + }, - FUNCTION$hoistVars: function(x) { - }, + setVariables: function(n, n2) { + n.variables = n2; + }, - FUNCTION$finish: function(n, x) { - }, + setExpression: function(n, e) { + n.expression = e; + }, - VAR$build: function(t) { - return new Node(t, VAR); - }, + setBlock: function(n, s) { + n.block = s; + }, - VAR$addDecl: function(n, n2, x) { - n.push(n2); + finish: function(n) { + } }, - VAR$finish: function(n) { - }, + BLOCK: { + build: function(t, id) { + var n = new Node(t, BLOCK); + n.varDecls = []; + n.id = id; + return n; + }, - CONST$build: function(t) { - return new Node(t, VAR); - }, + hoistLets: function(n) { + }, - CONST$addDecl: function(n, n2, x) { - n.push(n2); - }, + addStatement: function(n, n2) { + n.push(n2); + }, - CONST$finish: function(n) { + finish: function(n) { + } }, - LET$build: function(t) { - return new Node(t, LET); - }, + EXPRESSION: { + build: function(t, tt) { + return new Node(t, tt); + }, - LET$addDecl: function(n, n2, x) { - n.push(n2); - }, + addOperand: function(n, n2) { + n.push(n2); + }, - LET$finish: function(n) { + finish: function(n) { + } }, - DECL$build: function(t) { - return new Node(t, IDENTIFIER); - }, + ASSIGN: { + build: function(t) { + return new Node(t, ASSIGN); + }, - DECL$setName: function(n, v) { - n.name = v; - }, + addOperand: function(n, n2) { + n.push(n2); + }, - DECL$setInitializer: function(n, e) { - n.initializer = e; - }, + setAssignOp: function(n, o) { + n.assignOp = o; + }, - DECL$setReadOnly: function(n, b) { - n.readOnly = b; + finish: function(n) { + } }, - DECL$finish: function(n) { - }, + HOOK: { + build: function(t) { + return new Node(t, HOOK); + }, - LET_BLOCK$build: function(t) { - var n = Node(t, LET_BLOCK); - n.varDecls = []; - return n; - }, + setCondition: function(n, e) { + n[0] = e; + }, - LET_BLOCK$setVariables: function(n, n2) { - n.variables = n2; - }, + setThenPart: function(n, n2) { + n[1] = n2; + }, - LET_BLOCK$setExpression: function(n, e) { - n.expression = e; - }, + setElsePart: function(n, n2) { + n[2] = n2; + }, - LET_BLOCK$setBlock: function(n, s) { - n.block = s; + finish: function(n) { + } }, - LET_BLOCK$finish: function(n) { - }, + OR: mkBinopBuilder(OR), + AND: mkBinopBuilder(AND), + BITWISE_OR: mkBinopBuilder(BITWISE_OR), + BITWISE_XOR: mkBinopBuilder(BITWISE_XOR), + BITWISE_AND: mkBinopBuilder(BITWISE_AND), + EQUALITY: mkBinopBuilder(), // EQ | NE | STRICT_EQ | STRICT_NE + RELATIONAL: mkBinopBuilder(), // LT | LE | GE | GT + SHIFT: mkBinopBuilder(), // LSH | RSH | URSH + ADD: mkBinopBuilder(), // PLUS | MINUS + MULTIPLY: mkBinopBuilder(), // MUL | DIV | MOD - BLOCK$build: function(t, id) { - var n = new Node(t, BLOCK); - n.varDecls = []; - n.id = id; - return n; - }, + UNARY: { + // DELETE | VOID | TYPEOF | NOT | BITWISE_NOT + // UNARY_PLUS | UNARY_MINUS | INCREMENT | DECREMENT + build: function(t) { + if (t.token.type === PLUS) + t.token.type = UNARY_PLUS; + else if (t.token.type === MINUS) + t.token.type = UNARY_MINUS; + return new Node(t); + }, - BLOCK$hoistLets: function(n) { - }, + addOperand: function(n, n2) { + n.push(n2); + }, - BLOCK$addStatement: function(n, n2) { - n.push(n2); - }, + setPostfix: function(n) { + n.postfix = true; + }, - BLOCK$finish: function(n) { + finish: function(n) { + } }, - EXPRESSION$build: function(t, tt) { - return new Node(t, tt); - }, + MEMBER: { + // NEW | DOT | INDEX + build: function(t, tt) { + return new Node(t, tt); + }, - EXPRESSION$addOperand: function(n, n2) { - n.push(n2); - }, + rebuildNewWithArgs: function(n) { + n.type = NEW_WITH_ARGS; + }, - EXPRESSION$finish: function(n) { - }, + addOperand: function(n, n2) { + n.push(n2); + }, - ASSIGN$build: function(t) { - return new Node(t, ASSIGN); + finish: function(n) { + } }, - ASSIGN$addOperand: function(n, n2) { - n.push(n2); - }, + PRIMARY: { + build: function(t, tt) { + // NB t.token.type must be NULL, THIS, TRUIE, FALSE, IDENTIFIER, + // NUMBER, STRING, or REGEXP. + return new Node(t, tt); + }, - ASSIGN$setAssignOp: function(n, o) { - n.assignOp = o; + finish: function(n) { + } }, - ASSIGN$finish: function(n) { - }, + ARRAY_INIT: { + build: function(t) { + return new Node(t, ARRAY_INIT); + }, - HOOK$build: function(t) { - return new Node(t, HOOK); - }, + addElement: function(n, n2) { + n.push(n2); + }, - HOOK$setCondition: function(n, e) { - n[0] = e; + finish: function(n) { + } }, - HOOK$setThenPart: function(n, n2) { - n[1] = n2; - }, + ARRAY_COMP: { + build: function(t) { + return new Node(t, ARRAY_COMP); + }, - HOOK$setElsePart: function(n, n2) { - n[2] = n2; - }, + setExpression: function(n, e) { + n.expression = e + }, - HOOK$finish: function(n) { - }, + setTail: function(n, n2) { + n.tail = n2; + }, - OR$build: function(t) { - return new Node(t, OR); + finish: function(n) { + } }, - OR$addOperand: function(n, n2) { - n.push(n2); - }, + COMP_TAIL: { + build: function(t) { + return new Node(t, COMP_TAIL); + }, - OR$finish: function(n) { - }, + setGuard: function(n, e) { + n.guard = e; + }, - AND$build: function(t) { - return new Node(t, AND); - }, + addFor: function(n, n2) { + n.push(n2); + }, - AND$addOperand: function(n, n2) { - n.push(n2); + finish: function(n) { + } }, - AND$finish: function(n) { - }, + OBJECT_INIT: { + build: function(t) { + return new Node(t, OBJECT_INIT); + }, - BITWISE_OR$build: function(t) { - return new Node(t, BITWISE_OR); - }, + addProperty: function(n, n2) { + n.push(n2); + }, - BITWISE_OR$addOperand: function(n, n2) { - n.push(n2); + finish: function(n) { + } }, - BITWISE_OR$finish: function(n) { - }, + PROPERTY_INIT: { + build: function(t) { + return new Node(t, PROPERTY_INIT); + }, - BITWISE_XOR$build: function(t) { - return new Node(t, BITWISE_XOR); - }, + addOperand: function(n, n2) { + n.push(n2); + }, - BITWISE_XOR$addOperand: function(n, n2) { - n.push(n2); + finish: function(n) { + } }, - BITWISE_XOR$finish: function(n) { - }, + COMMA: { + build: function(t) { + return new Node(t, COMMA); + }, - BITWISE_AND$build: function(t) { - return new Node(t, BITWISE_AND); - }, + addOperand: function(n, n2) { + n.push(n2); + }, - BITWISE_AND$addOperand: function(n, n2) { - n.push(n2); + finish: function(n) { + } }, - BITWISE_AND$finish: function(n) { - }, + LIST: { + build: function(t) { + return new Node(t, LIST); + }, - EQUALITY$build: function(t) { - // NB t.token.type must be EQ, NE, STRICT_EQ, or STRICT_NE. - return new Node(t); - }, - - EQUALITY$addOperand: function(n, n2) { - n.push(n2); - }, - - EQUALITY$finish: function(n) { - }, - - RELATIONAL$build: function(t) { - // NB t.token.type must be LT, LE, GE, or GT. - return new Node(t); - }, - - RELATIONAL$addOperand: function(n, n2) { - n.push(n2); - }, - - RELATIONAL$finish: function(n) { - }, - - SHIFT$build: function(t) { - // NB t.token.type must be LSH, RSH, or URSH. - return new Node(t); - }, - - SHIFT$addOperand: function(n, n2) { - n.push(n2); - }, - - SHIFT$finish: function(n) { - }, - - ADD$build: function(t) { - // NB t.token.type must be PLUS or MINUS. - return new Node(t); - }, - - ADD$addOperand: function(n, n2) { - n.push(n2); - }, - - ADD$finish: function(n) { - }, - - MULTIPLY$build: function(t) { - // NB t.token.type must be MUL, DIV, or MOD. - return new Node(t); - }, - - MULTIPLY$addOperand: function(n, n2) { - n.push(n2); - }, - - MULTIPLY$finish: function(n) { - }, - - UNARY$build: function(t) { - // NB t.token.type must be DELETE, VOID, TYPEOF, NOT, BITWISE_NOT, - // UNARY_PLUS, UNARY_MINUS, INCREMENT, or DECREMENT. - if (t.token.type === PLUS) - t.token.type = UNARY_PLUS; - else if (t.token.type === MINUS) - t.token.type = UNARY_MINUS; - return new Node(t); - }, - - UNARY$addOperand: function(n, n2) { - n.push(n2); - }, - - UNARY$setPostfix: function(n) { - n.postfix = true; - }, - - UNARY$finish: function(n) { - }, - - MEMBER$build: function(t, tt) { - // NB t.token.type must be NEW, DOT, or INDEX. - return new Node(t, tt); - }, - - MEMBER$rebuildNewWithArgs: function(n) { - n.type = NEW_WITH_ARGS; - }, - - MEMBER$addOperand: function(n, n2) { - n.push(n2); - }, - - MEMBER$finish: function(n) { - }, - - PRIMARY$build: function(t, tt) { - // NB t.token.type must be NULL, THIS, TRUIE, FALSE, IDENTIFIER, - // NUMBER, STRING, or REGEXP. - return new Node(t, tt); - }, - - PRIMARY$finish: function(n) { - }, - - ARRAY_INIT$build: function(t) { - return new Node(t, ARRAY_INIT); - }, - - ARRAY_INIT$addElement: function(n, n2) { - n.push(n2); - }, - - ARRAY_INIT$finish: function(n) { - }, - - ARRAY_COMP$build: function(t) { - return new Node(t, ARRAY_COMP); - }, - - ARRAY_COMP$setExpression: function(n, e) { - n.expression = e - }, - - ARRAY_COMP$setTail: function(n, n2) { - n.tail = n2; - }, - - ARRAY_COMP$finish: function(n) { - }, - - COMP_TAIL$build: function(t) { - return new Node(t, COMP_TAIL); - }, - - COMP_TAIL$setGuard: function(n, e) { - n.guard = e; - }, - - COMP_TAIL$addFor: function(n, n2) { - n.push(n2); - }, - - COMP_TAIL$finish: function(n) { - }, - - OBJECT_INIT$build: function(t) { - return new Node(t, OBJECT_INIT); - }, - - OBJECT_INIT$addProperty: function(n, n2) { - n.push(n2); - }, - - OBJECT_INIT$finish: function(n) { - }, - - PROPERTY_INIT$build: function(t) { - return new Node(t, PROPERTY_INIT); - }, - - PROPERTY_INIT$addOperand: function(n, n2) { - n.push(n2); - }, - - PROPERTY_INIT$finish: function(n) { - }, - - COMMA$build: function(t) { - return new Node(t, COMMA); - }, - - COMMA$addOperand: function(n, n2) { - n.push(n2); - }, - - COMMA$finish: function(n) { - }, - - LIST$build: function(t) { - return new Node(t, LIST); - }, - - LIST$addOperand: function(n, n2) { - n.push(n2); - }, + addOperand: function(n, n2) { + n.push(n2); + }, - LIST$finish: function(n) { + finish: function(n) { + } }, setHoists: function(id, vds) { @@ -936,16 +967,17 @@ Narcissus.parser = (function() { * For more details in its interaction with hoisting, see comments in * FunctionDefinition. */ - var b = x.builder; - var n = b.BLOCK$build(t, x.blockId++); - b.BLOCK$hoistLets(n); + var builder = x.builder; + var b = builder.BLOCK; + var n = b.build(t, x.blockId++); + b.hoistLets(n); x.stmtStack.push(n); while (!t.done && t.peek(true) !== RIGHT_CURLY) - b.BLOCK$addStatement(n, Statement(t, x)); + b.addStatement(n, Statement(t, x)); x.stmtStack.pop(); - b.BLOCK$finish(n); + b.finish(n); if (n.needsHoisting) { - b.setHoists(n.id, n.varDecls); + builder.setHoists(n.id, n.varDecls); /* * If a block needs hoisting, we need to propagate this flag up to * the CompilerContext. @@ -971,7 +1003,7 @@ Narcissus.parser = (function() { */ function Statement(t, x) { var i, label, n, n2, ss, tt = t.get(true); - var b = x.builder; + var builder = x.builder, b, b2, b3; // Cases for statements ending in a right curly return early, avoiding the // common semicolon insertion magic after this switch. @@ -989,20 +1021,24 @@ Narcissus.parser = (function() { return n; case IF: - n = b.IF$build(t); - b.IF$setCondition(n, ParenExpression(t, x)); + b = builder.IF; + n = b.build(t); + b.setCondition(n, ParenExpression(t, x)); x.stmtStack.push(n); - b.IF$setThenPart(n, Statement(t, x)); + b.setThenPart(n, Statement(t, x)); if (t.match(ELSE)) - b.IF$setElsePart(n, Statement(t, x)); + b.setElsePart(n, Statement(t, x)); x.stmtStack.pop(); - b.IF$finish(n); + b.finish(n); return n; case SWITCH: // This allows CASEs after a DEFAULT, which is in the standard. - n = b.SWITCH$build(t); - b.SWITCH$setDiscriminant(n, ParenExpression(t, x)); + b = builder.SWITCH; + b2 = builder.DEFAULT; + b3 = builder.CASE; + n = b.build(t); + b.setDiscriminant(n, ParenExpression(t, x)); x.stmtStack.push(n); t.mustMatch(LEFT_CURLY); while ((tt = t.get()) !== RIGHT_CURLY) { @@ -1010,40 +1046,41 @@ Narcissus.parser = (function() { case DEFAULT: if (n.defaultIndex >= 0) throw t.newSyntaxError("More than one switch default"); - n2 = b.DEFAULT$build(t); - b.SWITCH$setDefaultIndex(n, n.cases.length); + n2 = b2.build(t); + b.setDefaultIndex(n, n.cases.length); t.mustMatch(COLON); - b.DEFAULT$initializeStatements(n2, t); + b2.initializeStatements(n2, t); while ((tt=t.peek(true)) !== CASE && tt !== DEFAULT && tt !== RIGHT_CURLY) - b.DEFAULT$addStatement(n2, Statement(t, x)); - b.DEFAULT$finish(n2); + b2.addStatement(n2, Statement(t, x)); + b2.finish(n2); break; case CASE: - n2 = b.CASE$build(t); - b.CASE$setLabel(n2, Expression(t, x, COLON)); + n2 = b3.build(t); + b3.setLabel(n2, Expression(t, x, COLON)); t.mustMatch(COLON); - b.CASE$initializeStatements(n2, t); + b3.initializeStatements(n2, t); while ((tt=t.peek(true)) !== CASE && tt !== DEFAULT && tt !== RIGHT_CURLY) - b.CASE$addStatement(n2, Statement(t, x)); - b.CASE$finish(n2); + b3.addStatement(n2, Statement(t, x)); + b3.finish(n2); break; default: throw t.newSyntaxError("Invalid switch case"); } - b.SWITCH$addCase(n, n2); + b.addCase(n, n2); } x.stmtStack.pop(); - b.SWITCH$finish(n); + b.finish(n); return n; case FOR: - n = b.FOR$build(t); + b = builder.FOR; + n = b.build(t); if (t.match(IDENTIFIER) && t.token.value === "each") - b.FOR$rebuildForEach(n); + b.rebuildForEach(n); t.mustMatch(LEFT_PAREN); if ((tt = t.peek()) !== SEMICOLON) { x.inForLoopInit = true; @@ -1059,7 +1096,7 @@ Narcissus.parser = (function() { * Let in for head, we need to add an implicit block * around the rest of the for. */ - var forBlock = b.BLOCK$build(t, x.blockId++); + var forBlock = builder.BLOCK.build(t, x.blockId++); x.stmtStack.push(forBlock); n2 = Variables(t, x, forBlock); } @@ -1069,51 +1106,53 @@ Narcissus.parser = (function() { x.inForLoopInit = false; } if (n2 && t.match(IN)) { - b.FOR$rebuildForIn(n); - b.FOR$setObject(n, Expression(t, x), forBlock); + b.rebuildForIn(n); + b.setObject(n, Expression(t, x), forBlock); if (n2.type === VAR || n2.type === LET) { if (n2.length !== 1) { throw new SyntaxError("Invalid for..in left-hand side", t.filename, n2.lineno); } - b.FOR$setIterator(n, n2[0], n2, forBlock); + b.setIterator(n, n2[0], n2, forBlock); } else { - b.FOR$setIterator(n, n2, null, forBlock); + b.setIterator(n, n2, null, forBlock); } } else { - b.FOR$setSetup(n, n2); + b.setSetup(n, n2); t.mustMatch(SEMICOLON); if (n.isEach) throw t.newSyntaxError("Invalid for each..in loop"); - b.FOR$setCondition(n, (t.peek() === SEMICOLON) + b.setCondition(n, (t.peek() === SEMICOLON) ? null : Expression(t, x)); t.mustMatch(SEMICOLON); - b.FOR$setUpdate(n, (t.peek() === RIGHT_PAREN) + b.setUpdate(n, (t.peek() === RIGHT_PAREN) ? null : Expression(t, x)); } t.mustMatch(RIGHT_PAREN); - b.FOR$setBody(n, nest(t, x, n, Statement)); + b.setBody(n, nest(t, x, n, Statement)); if (forBlock) { - b.BLOCK$finish(forBlock); + builder.BLOCK.finish(forBlock); x.stmtStack.pop(); } - b.FOR$finish(n); + b.finish(n); return n; case WHILE: - n = b.WHILE$build(t); - b.WHILE$setCondition(n, ParenExpression(t, x)); - b.WHILE$setBody(n, nest(t, x, n, Statement)); - b.WHILE$finish(n); + b = builder.WHILE; + n = b.build(t); + b.setCondition(n, ParenExpression(t, x)); + b.setBody(n, nest(t, x, n, Statement)); + b.finish(n); return n; case DO: - n = b.DO$build(t); - b.DO$setBody(n, nest(t, x, n, Statement, WHILE)); - b.DO$setCondition(n, ParenExpression(t, x)); - b.DO$finish(n); + b = builder.DO; + n = b.build(t); + b.setBody(n, nest(t, x, n, Statement, WHILE)); + b.setCondition(n, ParenExpression(t, x)); + b.finish(n); if (!x.ecmaStrictMode) { //