Checkpoint arguments tour-de-force (453730).
Checkpoint arguments tour-de-force (453730).
--- a/js/src/builtins.tbl
+++ b/js/src/builtins.tbl
@@ -93,25 +93,26 @@ BUILTIN2(TypeOfBoolean, LO,
BUILTIN2(NumberToString, LO, F, P, JSString*, JSContext*, jsdouble, 1, 1)
BUILTIN3(Object_p_hasOwnProperty,
LO, LO, LO, LO, jsint, JSContext*, JSObject*, JSString*, 0, 0)
BUILTIN3(Object_p_propertyIsEnumerable,
LO, LO, LO, LO, jsint, JSContext*, JSObject*, JSString*, 0, 0)
BUILTIN2(BooleanToNumber, LO, LO, F, jsdouble, JSContext*, jsint, 1, 1)
BUILTIN2(ObjectToString, LO, LO, P, JSString*, JSContext*, JSObject*, 0, 0)
BUILTIN3(Array_1int, LO, LO, LO, P, JSObject*, JSContext*, JSObject*, jsint, 0, 0)
+BUILTIN3(Array_1str, LO, LO, LO, P, JSObject*, JSContext*, JSObject*, JSString*, 0, 0)
+BUILTIN4(Array_2obj, LO, LO, LO, LO, P, JSObject*, JSContext*, JSObject*, JSObject*, JSObject**, 0, 0)
+BUILTIN5(Array_3num, LO, LO, F, F, F, P, JSObject*, JSContext*, JSObject*, jsdouble, jsdouble, jsdouble, 0, 0)
+BUILTIN1(Arguments, LO, P, JSObject*, JSContext*, 0, 0)
// soft float
BUILTIN1(fneg, F, F, jsdouble, jsdouble, 1, 1)
BUILTIN1(i2f, LO, F, jsdouble, jsint, 1, 1)
BUILTIN1(u2f, LO, F, jsdouble, jsuint, 1, 1)
BUILTIN2(fcmpeq, F, F, LO, jsint, jsdouble, jsdouble, 1, 1)
BUILTIN2(fcmplt, F, F, LO, jsint, jsdouble, jsdouble, 1, 1)
BUILTIN2(fcmple, F, F, LO, jsint, jsdouble, jsdouble, 1, 1)
BUILTIN2(fcmpgt, F, F, LO, jsint, jsdouble, jsdouble, 1, 1)
BUILTIN2(fcmpge, F, F, LO, jsint, jsdouble, jsdouble, 1, 1)
BUILTIN2(fmul, F, F, F, jsdouble, jsdouble, jsdouble, 1, 1)
BUILTIN2(fadd, F, F, F, jsdouble, jsdouble, jsdouble, 1, 1)
BUILTIN2(fdiv, F, F, F, jsdouble, jsdouble, jsdouble, 1, 1)
BUILTIN2(fsub, F, F, F, jsdouble, jsdouble, jsdouble, 1, 1)
-BUILTIN3(Array_1str, LO, LO, LO, P, JSObject*, JSContext*, JSObject*, JSString*, 0, 0)
-BUILTIN4(Array_2obj, LO, LO, LO, LO, P, JSObject*, JSContext*, JSObject*, JSObject*, JSObject**, 0, 0)
-BUILTIN5(Array_3num, LO, LO, F, F, F, P, JSObject*, JSContext*, JSObject*, jsdouble, jsdouble, jsdouble, 0, 0)
--- a/js/src/jsbuiltins.cpp
+++ b/js/src/jsbuiltins.cpp
@@ -713,16 +713,22 @@ js_Array_3num(JSContext* cx, JSObject* p
if (!js_NewDoubleInRootedValue(cx, n1, ++newslots))
return NULL;
if (!js_NewDoubleInRootedValue(cx, n2, ++newslots))
return NULL;
if (!js_NewDoubleInRootedValue(cx, n3, ++newslots))
return NULL;)
}
+JSObject* FASTCALL
+js_Arguments(JSContext* cx)
+{
+ return NULL;
+}
+
/* soft float */
jsdouble FASTCALL
js_fneg(jsdouble x)
{
return -x;
}
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -342,17 +342,17 @@ static LIns* demote(LirWriter *out, LIns
int32_t ci = cf > 0x7fffffff ? uint32_t(cf) : int32_t(cf);
return out->insImm(ci);
}
static bool isPromoteInt(LIns* i)
{
jsdouble d;
return isi2f(i) || i->isconst() ||
- (i->isconstq() && ((d = i->constvalf()) == (jsdouble)(jsint)d) && !JSDOUBLE_IS_NEGZERO(d));
+ (i->isconstq() && (d = i->constvalf()) == jsdouble(jsint(d)) && !JSDOUBLE_IS_NEGZERO(d));
}
static bool isPromoteUint(LIns* i)
{
jsdouble d;
return isu2f(i) || i->isconst() ||
(i->isconstq() && ((d = i->constvalf()) == (jsdouble)(jsuint)d));
}
@@ -798,18 +798,19 @@ TraceRecorder::TraceRecorder(JSContext*
this->globalObj = JS_GetGlobalForObject(cx, cx->fp->scopeChain);
this->anchor = _anchor;
this->fragment = _fragment;
this->lirbuf = _fragment->lirbuf;
this->treeInfo = ti;
this->callDepth = _fragment->calldepth;
JS_ASSERT(!_anchor || _anchor->calldepth == _fragment->calldepth);
this->atoms = cx->fp->script->atomMap.vector;
+ this->deepAborted = false;
+ this->applyingArguments = false;
this->trashTree = false;
- this->deepAborted = false;
this->whichTreeToTrash = _fragment->root;
debug_only_v(printf("recording starting from %s:%u@%u\n", cx->fp->script->filename,
js_PCToLineNumber(cx, cx->fp->script, cx->fp->regs->pc),
cx->fp->regs->pc - cx->fp->script->code););
lir = lir_buf_writer = new (&gc) LirBufWriter(lirbuf);
#ifdef DEBUG
@@ -1348,18 +1349,18 @@ TraceRecorder::writeBack(LIns* i, LIns*
void
TraceRecorder::set(jsval* p, LIns* i, bool initializing)
{
JS_ASSERT(initializing || tracker.has(p));
tracker.set(p, i);
/* If we are writing to this location for the first time, calculate the offset into the
native frame manually, otherwise just look up the last load or store associated with
the same source address (p) and use the same offset/base. */
- LIns* x;
- if ((x = nativeFrameTracker.get(p)) == NULL) {
+ LIns* x = nativeFrameTracker.get(p);
+ if (!x) {
if (isGlobal(p))
x = writeBack(i, gp_ins, nativeGlobalOffset(p));
else
x = writeBack(i, lirbuf->sp, -treeInfo->nativeStackBase + nativeStackOffset(p));
nativeFrameTracker.set(p, x);
} else {
#define ASSERT_VALID_CACHE_HIT(base, offset) \
JS_ASSERT(base == lirbuf->sp || base == gp_ins); \
@@ -2476,16 +2477,21 @@ js_MonitorLoopEdge(JSContext* cx, jsbyte
return false;
}
}
bool
js_MonitorRecording(JSContext* cx)
{
TraceRecorder *tr = JS_TRACE_MONITOR(cx).recorder;
+
+ // Clear one-shot flag used to communicate between record_JSOP_CALL and record_EnterFrame.
+ tr->applyingArguments = false;
+
+ // Process deepAbort() requests now.
if (tr->wasDeepAborted()) {
js_AbortRecording(cx, NULL, "deep abort requested");
return false;
}
jsbytecode* pc = cx->fp->regs->pc;
/* If we hit a break, end the loop and generate an always taken loop exit guard. For other
downward gotos (like if/else) continue recording. */
@@ -3639,20 +3645,30 @@ TraceRecorder::record_EnterFrame()
debug_only_v(printf("EnterFrame %s, callDepth=%d\n",
js_AtomToPrintableString(cx, cx->fp->fun->atom),
callDepth););
JSStackFrame* fp = cx->fp;
LIns* void_ins = INS_CONST(JSVAL_TO_BOOLEAN(JSVAL_VOID));
jsval* vp = &fp->argv[fp->argc];
jsval* vpstop = vp + (fp->fun->nargs - fp->argc);
- while (vp < vpstop) {
- if (vp >= fp->down->regs->sp)
+ if (applyingArguments) {
+ applyingArguments = false;
+ while (vp < vpstop) {
+ JS_ASSERT(vp >= fp->down->regs->sp);
nativeFrameTracker.set(vp, (LIns*)0);
- set(vp++, void_ins, true);
+ LIns* arg_ins = get(&fp->down->argv[fp->argc + (vp - vpstop)]);
+ set(vp++, arg_ins, true);
+ }
+ } else {
+ while (vp < vpstop) {
+ if (vp >= fp->down->regs->sp)
+ nativeFrameTracker.set(vp, (LIns*)0);
+ set(vp++, void_ins, true);
+ }
}
vp = &fp->slots[0];
vpstop = vp + fp->script->nfixed;
while (vp < vpstop)
set(vp++, void_ins, true);
return true;
}
@@ -3737,17 +3753,21 @@ bool
TraceRecorder::record_JSOP_IFNE()
{
return ifop();
}
bool
TraceRecorder::record_JSOP_ARGUMENTS()
{
- return false;
+ LIns* args[] = { cx_ins };
+ LIns* a_ins = lir->insCall(F_Arguments, args);
+ guard(false, lir->ins_eq0(a_ins), OOM_EXIT);
+ stack(0, a_ins);
+ return true;
}
bool
TraceRecorder::record_JSOP_DUP()
{
stack(0, get(&stackval(-1)));
return true;
}
@@ -4544,20 +4564,21 @@ KNOWN_NATIVE_DECL(js_str_charAt)
KNOWN_NATIVE_DECL(js_str_charCodeAt)
KNOWN_NATIVE_DECL(js_str_concat)
KNOWN_NATIVE_DECL(js_str_fromCharCode)
KNOWN_NATIVE_DECL(js_str_substring)
bool
TraceRecorder::record_JSOP_CALL()
{
- jsbytecode *pc = cx->fp->regs->pc;
+ JSStackFrame* fp = cx->fp;
+ jsbytecode *pc = fp->regs->pc;
uintN argc = GET_ARGC(pc);
jsval& fval = stackval(0 - (argc + 2));
- JS_ASSERT(&fval >= StackBase(cx->fp));
+ JS_ASSERT(&fval >= StackBase(fp));
jsval& tval = stackval(0 - (argc + 1));
LIns* this_ins = get(&tval);
if (this_ins->isconstp() && !this_ins->constvalp() && !guardShapelessCallee(fval))
return false;
/*
* Require that the callee be a function object, to avoid guarding on its
@@ -4613,39 +4634,76 @@ TraceRecorder::record_JSOP_CALL()
uintN i = 0;
LIns* arg1_ins = NULL;
jsval arg1 = JSVAL_VOID;
if ((JSFastNative)fun->u.n.native == js_fun_apply) {
if (argc != 2)
ABORT_TRACE("can't trace Function.prototype.apply with other than 2 args");
+ if (!guardShapelessCallee(tval))
+ return false;
+ JSObject* tfunobj = JSVAL_TO_OBJECT(tval);
+ JSFunction* tfun = GET_FUNCTION_PRIVATE(cx, tfunobj);
+
jsval& oval = stackval(-2);
if (JSVAL_IS_PRIMITIVE(oval))
ABORT_TRACE("can't trace Function.prototype.apply with primitive 1st arg");
jsval& aval = stackval(-1);
if (JSVAL_IS_PRIMITIVE(aval))
ABORT_TRACE("can't trace Function.prototype.apply with primitive 2nd arg");
+ JSObject* aobj = JSVAL_TO_OBJECT(aval);
LIns* aval_ins = get(&aval);
- if (!aval_ins->isCall() || aval_ins->fid() != F_Array_1str)
- ABORT_TRACE("can't yet trace Function.prototype.apply on other than [str] 2nd arg");
-
- JSObject* aobj = JSVAL_TO_OBJECT(aval);
+ if (!aval_ins->isCall())
+ ABORT_TRACE("can't trace Function.prototype.apply on non-builtin-call 2nd arg");
+
+ if (aval_ins->fid() == F_Arguments) {
+ JS_ASSERT(OBJ_GET_CLASS(cx, aobj) == &js_ArgumentsClass);
+ JS_ASSERT(OBJ_GET_PRIVATE(cx, aobj) == fp);
+ if (!FUN_INTERPRETED(tfun))
+ ABORT_TRACE("can't trace Function.prototype.apply(native_function, arguments)");
+
+ argc = fp->argc;
+ if (tfun->nargs != argc)
+ ABORT_TRACE("can't trace Function.prototype.apply(scripted_function, arguments)");
+
+ jsval* sp = fp->regs->sp - 4;
+ set(sp, get(&tval));
+ *sp++ = tval;
+ set(sp, get(&oval));
+ *sp++ = oval;
+ jsval* newsp = sp + argc;
+ if (newsp > fp->slots + fp->script->nslots) {
+ JSArena* a = cx->stackPool.current;
+ if (jsuword(newsp) > a->limit)
+ ABORT_TRACE("can't grow stack for Function.prototype.apply");
+ if (jsuword(newsp) > a->avail)
+ a->avail = jsuword(newsp);
+ }
+
+ jsval* argv = fp->argv;
+ for (uintN i = 0; i < JS_MIN(argc, 2); i++) {
+ set(&sp[i], get(&argv[i]));
+ sp[i] = argv[i];
+ }
+ applyingArguments = true;
+ return interpretedFunctionCall(tval, tfun, argc);
+ }
+
+ if (aval_ins->fid() != F_Array_1str)
+ ABORT_TRACE("can't trace Function.prototype.apply on other than [str] 2nd arg");
+
JS_ASSERT(OBJ_IS_ARRAY(cx, aobj));
JS_ASSERT(aobj->fslots[JSSLOT_ARRAY_LENGTH] == 1);
JS_ASSERT(JSVAL_IS_STRING(aobj->dslots[0]));
- if (!guardShapelessCallee(tval))
- return false;
- JSObject* tfunobj = JSVAL_TO_OBJECT(tval);
- JSFunction* tfun = GET_FUNCTION_PRIVATE(cx, tfunobj);
if (FUN_INTERPRETED(tfun))
- ABORT_TRACE("can't yet trace Function.prototype.apply for scripted functions");
+ ABORT_TRACE("can't trace Function.prototype.apply for scripted functions");
JSTraceableNative* known;
for (;;) {
known = &knownNatives[i];
if (known->native == (JSFastNative)tfun->u.n.native)
break;
if (++i == JS_ARRAY_LENGTH(knownNatives))
ABORT_TRACE("unknown native being Function.prototype.apply'ed");
@@ -4902,17 +4960,17 @@ TraceRecorder::prop(JSObject* obj, LIns*
ABORT_TRACE("unboxing");
return true;
}
bool
TraceRecorder::elem(jsval& l, jsval& r, jsval*& vp, LIns*& v_ins, LIns*& addr_ins)
{
/* no guards for type checks, trace specialized this already */
- if (!JSVAL_IS_INT(r) || JSVAL_IS_PRIMITIVE(l))
+ if (JSVAL_IS_PRIMITIVE(l) || !JSVAL_IS_INT(r))
return false;
/*
* Can't specialize to assert obj != global, must guard to avoid aliasing
* stale homes of stacked global variables.
*/
JSObject* obj = JSVAL_TO_OBJECT(l);
if (obj == globalObj)
@@ -5667,23 +5725,37 @@ bool
TraceRecorder::record_JSOP_NOP()
{
return true;
}
bool
TraceRecorder::record_JSOP_ARGSUB()
{
- return false;
+ JSStackFrame* fp = cx->fp;
+ if (!(fp->fun->flags & JSFUN_HEAVYWEIGHT)) {
+ uintN slot = GET_ARGNO(fp->regs->pc);
+ if (slot < fp->argc && !fp->argsobj) {
+ stack(0, get(&cx->fp->argv[slot]));
+ return true;
+ }
+ }
+ ABORT_TRACE("can't trace JSOP_ARGSUB hard case");
}
bool
TraceRecorder::record_JSOP_ARGCNT()
{
- return false;
+ if (!(cx->fp->fun->flags & JSFUN_HEAVYWEIGHT)) {
+ jsdpun u;
+ u.d = cx->fp->argc;
+ stack(0, lir->insImmq(u.u64));
+ return true;
+ }
+ ABORT_TRACE("can't trace heavyweight JSOP_ARGCNT");
}
bool
TraceRecorder::record_JSOP_DEFLOCALFUN()
{
JSFunction* fun;
JSFrameRegs& regs = *cx->fp->regs;
JSScript* script = cx->fp->script;
--- a/js/src/jstracer.h
+++ b/js/src/jstracer.h
@@ -227,18 +227,19 @@ class TraceRecorder {
#endif
nanojit::LIns* cx_ins;
nanojit::LIns* gp_ins;
nanojit::LIns* eos_ins;
nanojit::LIns* eor_ins;
nanojit::LIns* rval_ins;
nanojit::LIns* inner_sp_ins;
nanojit::SideExit exit;
+ bool deepAborted;
+ bool applyingArguments;
bool trashTree;
- bool deepAborted;
nanojit::Fragment* whichTreeToTrash;
Queue<jsbytecode*> inlinedLoopEdges;
Queue<jsbytecode*> cfgMerges;
bool isGlobal(jsval* p) const;
ptrdiff_t nativeGlobalOffset(jsval* p) const;
ptrdiff_t nativeStackOffset(jsval* p) const;
void import(nanojit::LIns* base, ptrdiff_t offset, jsval* p, uint8& t,
@@ -326,17 +327,20 @@ class TraceRecorder {
nanojit::LIns* dslots_ins, nanojit::LIns* idx_ins);
void clearFrameSlotsFromCache();
bool guardShapelessCallee(jsval& callee);
bool interpretedFunctionCall(jsval& fval, JSFunction* fun, uintN argc);
bool forInLoop(jsval* vp);
void trackCfgMerges(jsbytecode* pc);
void fuseIf(jsbytecode* pc, bool cond, nanojit::LIns* x);
+
public:
+ friend bool js_MonitorRecording(JSContext* cx);
+
TraceRecorder(JSContext* cx, nanojit::GuardRecord*, nanojit::Fragment*, TreeInfo*,
unsigned ngslots, uint8* globalTypeMap, uint8* stackTypeMap,
nanojit::GuardRecord* expectedInnerExit);
~TraceRecorder();
nanojit::SideExit* snapshot(nanojit::ExitType exitType);
nanojit::Fragment* getFragment() const { return fragment; }
bool isLoopHeader(JSContext* cx) const;
@@ -365,18 +369,17 @@ public:
#define TRACING_ENABLED(cx) JS_HAS_OPTION(cx, JSOPTION_JIT)
#define RECORD(x) \
JS_BEGIN_MACRO \
TraceRecorder* r = JS_TRACE_MONITOR(cx).recorder; \
if (!js_MonitorRecording(cx)) { \
ENABLE_TRACER(0); \
- } else \
- if (!r->record_##x()) { \
+ } else if (!r->record_##x()) { \
js_AbortRecording(cx, NULL, #x); \
ENABLE_TRACER(0); \
} \
JS_END_MACRO
extern bool
js_MonitorLoopEdge(JSContext* cx, jsbytecode* oldpc, uintN& inlineCallCount);
--- a/js/src/trace-test.js
+++ b/js/src/trace-test.js
@@ -1206,11 +1206,69 @@ function testTypeofHole() {
a[5] = 3;
for (var i = 0; i < 6; ++i)
a[i] = typeof a[i];
return a.toString();
}
testTypeofHole.expected = "undefined,undefined,undefined,undefined,undefined,number"
test(testTypeofHole);
+function test_JSOP_ARGSUB() {
+ function f0() { return arguments[0]; }
+ function f1() { return arguments[1]; }
+ function f2() { return arguments[2]; }
+ function f3() { return arguments[3]; }
+ function f4() { return arguments[4]; }
+ function f5() { return arguments[5]; }
+ function f6() { return arguments[6]; }
+ function f7() { return arguments[7]; }
+ function f8() { return arguments[8]; }
+ function f9() { return arguments[9]; }
+ var a = [];
+ for (var i = 0; i < 10; i++) {
+ a[0] = f0('a');
+ a[1] = f1('a','b');
+ a[2] = f2('a','b','c');
+ a[3] = f3('a','b','c','d');
+ a[4] = f4('a','b','c','d','e');
+ a[5] = f5('a','b','c','d','e','f');
+ a[6] = f6('a','b','c','d','e','f','g');
+ a[7] = f7('a','b','c','d','e','f','g','h');
+ a[8] = f8('a','b','c','d','e','f','g','h','i');
+ a[9] = f9('a','b','c','d','e','f','g','h','i','j');
+ }
+ return a.join("");
+}
+test_JSOP_ARGSUB.expected = "abcdefghij";
+test(test_JSOP_ARGSUB);
+
+function test_JSOP_ARGCNT() {
+ function f0() { return arguments.length; }
+ function f1() { return arguments.length; }
+ function f2() { return arguments.length; }
+ function f3() { return arguments.length; }
+ function f4() { return arguments.length; }
+ function f5() { return arguments.length; }
+ function f6() { return arguments.length; }
+ function f7() { return arguments.length; }
+ function f8() { return arguments.length; }
+ function f9() { return arguments.length; }
+ var a = [];
+ for (var i = 0; i < 10; i++) {
+ a[0] = f0('a');
+ a[1] = f1('a','b');
+ a[2] = f2('a','b','c');
+ a[3] = f3('a','b','c','d');
+ a[4] = f4('a','b','c','d','e');
+ a[5] = f5('a','b','c','d','e','f');
+ a[6] = f6('a','b','c','d','e','f','g');
+ a[7] = f7('a','b','c','d','e','f','g','h');
+ a[8] = f8('a','b','c','d','e','f','g','h','i');
+ a[9] = f9('a','b','c','d','e','f','g','h','i','j');
+ }
+ return a.join(",");
+}
+test_JSOP_ARGCNT.expected = "1,2,3,4,5,6,7,8,9,10";
+test(test_JSOP_ARGCNT);
+
/* Keep these at the end so that we can see the summary after the trace-debug spew. */
print("\npassed:", passes.length && passes.join(","));
print("\nFAILED:", fails.length && fails.join(","));