Checkpoint arguments tour-de-force (453730).
authorBrendan Eich <brendan@mozilla.org>
Fri, 05 Sep 2008 18:29:08 -0700
changeset 19068 ffb36911e55de7425937c6f03b7e916af1770058
parent 19067 cfb318b0d835b298403f0cec8b6dcf8007ad6c88
child 19069 8bd3a8c5fb5a223ba2ffb5fc661441175ab86273
push id1930
push usermrbkap@mozilla.com
push dateWed, 10 Sep 2008 06:40:47 +0000
treeherderautoland@ee61af1469cd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs453730
milestone1.9.1b1pre
Checkpoint arguments tour-de-force (453730).
js/src/builtins.tbl
js/src/jsbuiltins.cpp
js/src/jstracer.cpp
js/src/jstracer.h
js/src/trace-test.js
--- 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(","));