Trace String.prototype.substring for two-arg case.
authorshaver@mozilla.org
Tue, 29 Jul 2008 07:32:18 -0700
changeset 17870 a0232a1e6de84f18888fae3da92160463fb4c3b7
parent 17869 2e79ec0210996f3d9810080ed803cb77bab80b90
child 17871 baa9cc8969fbfedfec31c2643a266fd7c372fc99
push id1452
push usershaver@mozilla.com
push dateFri, 22 Aug 2008 00:08:22 +0000
treeherderautoland@d13bb0868596 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone1.9.1a1pre
Trace String.prototype.substring for two-arg case. * Export str_substring as js_str_substring. * Add basic String_p_substring builtin (only handle end > begin, both in range). * Add String_p_substring_1 builtin for the missing-end case. * INS_CONST for named constants in traces. * Support boxing of strings. * Support CALLPROP with primitive this. * Support traceable natives which require cx and this. * Support fallible traceable natives. * Fix JSOP_LENGTH to use i2f on result (need that everything-is-doubles T-shirt). * Add strings test.
js/src/builtins.tbl
js/src/jsbuiltins.cpp
js/src/jsstr.cpp
js/src/jstracer.cpp
js/src/trace-test.js
--- a/js/src/builtins.tbl
+++ b/js/src/builtins.tbl
@@ -43,8 +43,10 @@ BUILTIN1(UnboxInt32,            LO,     
 BUILTIN2(dmod,                  F,  F,  F,      jsdouble,  jsdouble, jsdouble,                 1, 1)
 BUILTIN1(doubleToInt32,         F,      LO,     int32,     jsdouble,                           1, 1)
 BUILTIN1(doubleToUint32,        F,      LO,     int32,     jsdouble,                           1, 1)
 BUILTIN1(Math_sin,              F,      F,      jsdouble,  jsdouble,                           1, 1)
 BUILTIN1(Math_cos,              F,      F,      jsdouble,  jsdouble,                           1, 1)
 BUILTIN2(Math_pow,              F,  F,  F,      jsdouble,  jsdouble, jsdouble,                 1, 1)
 BUILTIN1(Math_sqrt,             F,      F,      jsdouble,  jsdouble,                           1, 1)
 BUILTIN4(Array_dense_setelem,   LO, LO, LO, LO, LO, bool, JSContext*, JSObject*, jsint, jsval, 1, 1)
+BUILTIN4(String_p_substring,    LO, LO, LO, LO, LO, JSString*, JSContext*, JSString*, jsint, jsint, 1, 1)
+BUILTIN3(String_p_substring_1,  LO, LO, LO, LO, JSString*, JSContext*, JSString*, jsint, 1, 1)
--- a/js/src/jsbuiltins.cpp
+++ b/js/src/jsbuiltins.cpp
@@ -44,16 +44,17 @@
 #include "jsapi.h"
 #include "jsarray.h"
 #include "jsbool.h"
 #include "jsnum.h"
 #include "jsgc.h"
 #include "jscntxt.h"
 #include "nanojit/avmplus.h"
 #include "nanojit/nanojit.h"
+#include "jsstr.h"
 #include "jstracer.h"
 
 using namespace nanojit;
 
 jsdouble FASTCALL builtin_dmod(jsdouble a, jsdouble b)
 {
     if (b == 0.0) {
         jsdpun u;
@@ -165,16 +166,31 @@ bool FASTCALL builtin_Array_dense_setele
             obj->fslots[JSSLOT_ARRAY_COUNT]++;
         }
         obj->dslots[i] = v;
         return true;
     }
     return OBJ_SET_PROPERTY(cx, obj, INT_TO_JSID(i), &v);
 }
 
+JSString* FASTCALL
+builtin_String_p_substring(JSContext *cx, JSString *str, jsint begin, jsint end)
+{
+    JS_ASSERT(end >= begin);
+    return js_NewDependentString(cx, str, (size_t)begin, (size_t)(end - begin));
+}
+
+JSString* FASTCALL
+builtin_String_p_substring_1(JSContext *cx, JSString *str, jsint begin)
+{
+    jsint end = JSSTRING_LENGTH(str);
+    JS_ASSERT(end >= begin);
+    return js_NewDependentString(cx, str, (size_t)begin, (size_t)(end - begin));
+}
+
 #define LO ARGSIZE_LO
 #define F  ARGSIZE_F
 #define Q  ARGSIZE_Q
 
 #ifdef DEBUG
 #define NAME(op) ,#op
 #else
 #define NAME(op)
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -694,18 +694,18 @@ static JSBool
 str_toString(JSContext *cx, uintN argc, jsval *vp)
 {
     return js_GetPrimitiveThis(cx, vp, &js_StringClass, vp);
 }
 
 /*
  * Java-like string native methods.
  */
-static JSBool
-str_substring(JSContext *cx, uintN argc, jsval *vp)
+JSBool
+js_str_substring(JSContext *cx, uintN argc, jsval *vp)
 {
     JSString *str;
     jsdouble d;
     jsdouble length, begin, end;
 
     NORMALIZE_THIS(cx, vp, str);
     if (argc != 0) {
         d = js_ValueToNumber(cx, &vp[2]);
@@ -2207,17 +2207,17 @@ static JSFunctionSpec string_methods[] =
 #if JS_HAS_TOSOURCE
     JS_FN("quote",             str_quote,             0,0,GENERIC_PRIMITIVE),
     JS_FN(js_toSource_str,     str_toSource,          0,0,JSFUN_THISP_STRING),
 #endif
 
     /* Java-like methods. */
     JS_FN(js_toString_str,     str_toString,          0,0,JSFUN_THISP_STRING),
     JS_FN(js_valueOf_str,      str_toString,          0,0,JSFUN_THISP_STRING),
-    JS_FN("substring",         str_substring,         0,2,GENERIC_PRIMITIVE),
+    JS_FN("substring",         js_str_substring,      0,2,GENERIC_PRIMITIVE),
     JS_FN("toLowerCase",       str_toLowerCase,       0,0,GENERIC_PRIMITIVE),
     JS_FN("toUpperCase",       str_toUpperCase,       0,0,GENERIC_PRIMITIVE),
     JS_FN("charAt",            str_charAt,            1,1,GENERIC_PRIMITIVE),
     JS_FN("charCodeAt",        str_charCodeAt,        1,1,GENERIC_PRIMITIVE),
     JS_FN("indexOf",           str_indexOf,           1,1,GENERIC_PRIMITIVE),
     JS_FN("lastIndexOf",       str_lastIndexOf,       1,1,GENERIC_PRIMITIVE),
     JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,0,GENERIC_PRIMITIVE),
     JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,0,GENERIC_PRIMITIVE),
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -89,16 +89,18 @@ static struct {
     globalShapeMismatchAtEntry, typeMapTrashed, gslotsTrashed, slotPromoted,
     unstableLoopVariable;
 } stat = { 0LL, };
 #define AUDIT(x) (stat.x++)
 #else
 #define AUDIT(x) ((void)0)
 #endif DEBUG
 
+#define INS_CONST(c) addName(lir->insImm(c), #c)
+
 using namespace avmplus;
 using namespace nanojit;
 
 static GC gc = GC();
 static avmplus::AvmCore* core = new (&gc) avmplus::AvmCore();
 
 Tracker::Tracker()
 {
@@ -1986,16 +1988,19 @@ TraceRecorder::box_jsval(jsval v, LIns*&
         guard(false, lir->ins2(LIR_eq, v_ins, lir->insImmPtr((void*)JSVAL_ERROR_COOKIE)),
               OOM_EXIT);
         return true;
     }
     switch (JSVAL_TAG(v)) {
       case JSVAL_BOOLEAN:
         v_ins = lir->ins2i(LIR_or, lir->ins2i(LIR_lsh, v_ins, JSVAL_TAGBITS), JSVAL_BOOLEAN);
         return true;
+      case JSVAL_STRING:
+        v_ins = lir->ins2(LIR_or, v_ins, INS_CONST(JSVAL_STRING));
+        return true;
     }
     return false;
 }
 
 bool
 TraceRecorder::unbox_jsval(jsval v, LIns*& v_ins)
 {
     if (isNumber(v)) {
@@ -2019,17 +2024,23 @@ TraceRecorder::unbox_jsval(jsval v, LIns
          v_ins = lir->ins2i(LIR_ush, v_ins, JSVAL_TAGBITS);
          return true;
        case JSVAL_OBJECT:
         guard(true,
               lir->ins2i(LIR_eq,
                          lir->ins2(LIR_and, v_ins, lir->insImmPtr((void*)JSVAL_TAGMASK)),
                          JSVAL_OBJECT));
         return true;
-
+      case JSVAL_STRING:
+        guard(true,
+              lir->ins2i(LIR_eq,
+                        lir->ins2(LIR_and, v_ins, lir->insImmPtr((void*)JSVAL_TAGMASK)),
+                        JSVAL_STRING));
+        v_ins = lir->ins2(LIR_and, v_ins, lir->insImmPtr((void*)~JSVAL_TAGMASK));
+        return true;
     }
     return false;
 }
 
 bool
 TraceRecorder::getThis(LIns*& this_ins)
 {
     if (cx->fp->callee) { /* in a function */
@@ -2460,17 +2471,17 @@ bool TraceRecorder::record_JSOP_SETELEM(
 
     /* we have to check that its really an integer, but this check will to go away
        once we peel the loop type down to integer for this slot */
     guard(true, lir->ins2(LIR_feq, get(&r), lir->ins1(LIR_i2f, idx_ins)));
     /* ok, box the value we are storing, store it and we are done */
     LIns* v_ins = get(&v);
     LIns* boxed_ins = v_ins;
     if (!box_jsval(v, boxed_ins))
-        return false;
+        ABORT_TRACE("boxing failed");
     LIns* args[] = { boxed_ins, idx_ins, obj_ins, cx_ins };
     LIns* res_ins = lir->insCall(F_Array_dense_setelem, args);
     guard(false, lir->ins_eq0(res_ins));
     set(&l, v_ins);
     return true;
 }
 
 bool TraceRecorder::record_JSOP_CALLNAME()
@@ -2501,16 +2512,19 @@ JSBool
 js_math_cos(JSContext *cx, uintN argc, jsval *vp);
 
 JSBool
 js_math_pow(JSContext *cx, uintN argc, jsval *vp);
 
 JSBool
 js_math_sqrt(JSContext *cx, uintN argc, jsval *vp);
 
+JSBool
+js_str_substring(JSContext *cx, uintN argc, jsval *vp);
+
 JSInlineFrame*
 TraceRecorder::synthesizeFrame(JSObject* callee, JSObject *thisp, jsbytecode* pc)
 {
     JS_ASSERT(HAS_FUNCTION_CLASS(callee));
 
     JSFunction* fun = GET_FUNCTION_PRIVATE(cx, callee);
     JS_ASSERT(FUN_INTERPRETED(fun));
 
@@ -2567,16 +2581,17 @@ TraceRecorder::synthesizeFrame(JSObject*
     cx->fp = &newifp->frame;
     return newifp;
 }
 
 bool TraceRecorder::record_JSOP_CALL()
 {
     uintN argc = GET_ARGC(cx->fp->regs->pc);
     jsval& fval = stackval(0 - (argc + 2));
+    LIns* thisval_ins = stack(-(argc+1));
 
     if (!VALUE_IS_FUNCTION(cx, fval))
         ABORT_TRACE("CALL on non-function");
 
     JSFunction* fun = GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(fval));
     if (FUN_INTERPRETED(fun)) {
         // TODO: make sure args are not copied, or track the copying via the tracker
         if (fun->nargs != argc)
@@ -2591,53 +2606,106 @@ bool TraceRecorder::record_JSOP_CALL()
     }
 
     if (FUN_SLOW_NATIVE(fun))
         ABORT_TRACE("slow native");
 
     struct JSTraceableNative {
         JSFastNative native;
         int          builtin;
-        intN         argc;
+        const char  *prefix;
         const char  *argtypes;
+        bool         fallible;
     } knownNatives[] = {
-        { js_math_sin,  F_Math_sin,  1, "d" },
-        { js_math_cos,  F_Math_cos,  1, "d" },
-        { js_math_pow,  F_Math_pow,  2, "dd" },
-        { js_math_sqrt, F_Math_sqrt, 1, "d" }
+        { js_math_sin,      F_Math_sin,           "",   "d",    false, },
+        { js_math_cos,      F_Math_cos,           "",   "d",    false, },
+        { js_math_pow,      F_Math_pow,           "",   "dd",   false, },
+        { js_math_sqrt,     F_Math_sqrt,          "",   "d",    false, },
+        { js_str_substring, F_String_p_substring, "TC", "ii",   true, },
+        { js_str_substring, F_String_p_substring_1, "TC", "i",  true, }
     };
 
     for (uintN i = 0; i < JS_ARRAY_LENGTH(knownNatives); i++) {
         JSTraceableNative* known = &knownNatives[i];
         if ((JSFastNative)fun->u.n.native != known->native)
             continue;
 
+        intN knownargc = strlen(known->argtypes);
+        if (argc != knownargc)
+            continue; // might have another specialization for this argc
+
+        intN prefixc = strlen(known->prefix);
         LIns* args[5];
-        LIns** argp = &args[argc-1];
-        switch (known->argc) {
+        LIns** argp = &args[argc + prefixc - 1];
+        char argtype;
+
+#define HANDLE_PREFIX(i)                                                       \
+        argtype = known->prefix[i];                                            \
+        if (argtype == 'C') {                                                  \
+            *argp = cx_ins;                                                    \
+        } else if (argtype == 'T') {                                           \
+            *argp = thisval_ins;                                               \
+        } else {                                                               \
+            JS_ASSERT(0 && "unknown prefix arg type");                         \
+        }                                                                      \
+        argp--;
+
+        switch (prefixc) {
           case 2:
-            JS_ASSERT(known->argtypes[1] == 'd');
-            if (!isNumber(stackval(-2)))
-                ABORT_TRACE("2nd arg must be numeric");
-            *argp = get(&stackval(-2));
-            argp--;
+            HANDLE_PREFIX(1);
             /* FALL THROUGH */
           case 1:
-            JS_ASSERT(known->argtypes[0] == 'd');
-            if (!isNumber(stackval(-1)))
-                ABORT_TRACE("1st arg must be numeric");
-            *argp = get(&stackval(-1));
+            HANDLE_PREFIX(0);
+            /* FALL THROUGH */
+          case 0:
+            break;
+          default:
+            JS_ASSERT(0 && "illegal number of prefix args");
+        }
+
+#undef HANDLE_PREFIX
+        
+#define HANDLE_ARG(i)                                                          \
+        argtype = known->argtypes[i];                                          \
+        if (argtype == 'd' || argtype == 'i') {                                \
+            if (!isNumber(stackval(-(i + 1))))                                 \
+                ABORT_TRACE("arg in position " #i " must be numeric");         \
+            *argp = get(&stackval(-(i + 1)));                                  \
+            if (argtype == 'i')                                                \
+                *argp = f2i(*argp);                                            \
+        } else {                                                               \
+            JS_ASSERT(0 && "unknown arg type");                                \
+        }                                                                      \
+        argp--;
+
+        switch (strlen(known->argtypes)) {
+          case 4:
+            HANDLE_ARG(3);
+            /* FALL THROUGH */
+          case 3:
+            HANDLE_ARG(2);
+            /* FALL THROUGH */
+          case 2:
+            HANDLE_ARG(1);
+            /* FALL THROUGH */
+          case 1:
+            HANDLE_ARG(0);
             /* FALL THROUGH */
           case 0:
             break;
           default:
             JS_ASSERT(0 && "illegal number of args to traceable native");
         }
 
-        set(&fval, lir->insCall(known->builtin, args));
+#undef HANDLE_ARG
+
+        LIns* res_ins = lir->insCall(known->builtin, args);
+        if (known->fallible)
+            guard(false, lir->ins_eq0(res_ins));
+        set(&fval, res_ins);
         return true;
     }
 
     /* Didn't find it. */
     ABORT_TRACE("unknown native");
 }
 
 bool
@@ -3366,31 +3434,66 @@ bool TraceRecorder::record_JSOP_XMLCOMME
 bool TraceRecorder::record_JSOP_XMLPI()
 {
     return false;
 }
 
 bool TraceRecorder::record_JSOP_CALLPROP()
 {
     jsval& l = stackval(-1);
-    if (JSVAL_IS_PRIMITIVE(l))
-        ABORT_TRACE("CALLPROP on primitive");
-
-    JSObject* obj = JSVAL_TO_OBJECT(l);
-    LIns* obj_ins = get(&l);
+    JSObject* obj;
+    LIns* obj_ins;
+    if (!JSVAL_IS_PRIMITIVE(l)) {
+        obj = JSVAL_TO_OBJECT(l);
+        obj_ins = get(&l);
+        stack(0, obj_ins); // |this| for subsequent call
+    } else {
+        jsint i;
+#ifdef DEBUG
+        const char* protoname = NULL;
+#endif
+        if (JSVAL_IS_STRING(l)) {
+            i = JSProto_String;
+#ifdef DEBUG
+            protoname = "String.prototype";
+#endif
+        } else if (JSVAL_IS_NUMBER(l)) {
+            i = JSProto_Number;
+#ifdef DEBUG
+            protoname = "Number.prototype";
+#endif
+        } else if (JSVAL_IS_BOOLEAN(l)) {
+            i = JSProto_Boolean;
+#ifdef DEBUG
+            protoname = "Boolean.prototype";
+#endif
+        } else {
+            JS_ASSERT(JSVAL_IS_NULL(l) || JSVAL_IS_VOID(l));
+            ABORT_TRACE("callprop on null or void");
+        }
+
+        if (!js_GetClassPrototype(cx, NULL, INT_TO_JSID(i), &obj))
+            ABORT_TRACE("GetClassPrototype failed!");
+        
+        obj_ins = lir->insImmPtr((void*)obj);
+#ifdef DEBUG
+        obj_ins = addName(obj_ins, protoname);
+#endif
+        stack(0, get(&l)); // use primitive as |this|
+    }
+        
     JSObject* obj2;
     jsuword pcval;
     if (!test_property_cache(obj, obj_ins, obj2, pcval))
         ABORT_TRACE("missed prop");
 
     if (!PCVAL_IS_OBJECT(pcval))
         ABORT_TRACE("PCE not object");
 
     stack(-1, lir->insImmPtr(PCVAL_TO_OBJECT(pcval)));
-    stack(0, obj_ins);
     return true;
 }
 
 bool TraceRecorder::record_JSOP_GETFUNNS()
 {
     return false;
 }
 bool TraceRecorder::record_JSOP_UNUSED186()
@@ -3648,20 +3751,20 @@ bool TraceRecorder::record_JSOP_LENGTH()
     jsval& l = stackval(-1);
     if (JSVAL_IS_PRIMITIVE(l)) {
         if (!JSVAL_IS_STRING(l))
             ABORT_TRACE("non-string primitives unsupported");
         LIns* str_ins = get(&l);
         LIns* len_ins = lir->insLoadi(str_ins, offsetof(JSString, length));
         // We only support flat strings
         guard(true, addName(lir->ins_eq0(lir->ins2(LIR_and, len_ins,
-                                                   addName(lir->insImm(JSSTRFLAG_DEPENDENT),
-                                                           "JSSTRFLAG_DEPENDENT"))),
+                                                   INS_CONST(JSSTRFLAG_DEPENDENT))),
                             "guard(flat-string)"));
-        set(&l, lir->ins2i(LIR_and, len_ins, JSSTRING_LENGTH_MASK));
+        set(&l, lir->ins1(LIR_i2f, 
+                          lir->ins2(LIR_and, len_ins, INS_CONST(JSSTRING_LENGTH_MASK))));
         return true;
     }
 
     JSObject *obj = JSVAL_TO_OBJECT(l);
     if (!OBJ_IS_DENSE_ARRAY(cx, obj))
         ABORT_TRACE("only dense arrays supported");
     LIns* dslots_ins;
     if (!guardThatObjectIsDenseArray(obj, get(&l), dslots_ins))
--- a/js/src/trace-test.js
+++ b/js/src/trace-test.js
@@ -280,30 +280,42 @@ function testincops(n) {
   for (a[i] = 0; a[i] < n; ++a[i]);
   while (--a[i] >= 0);
 
   return [++o.p, ++a[i]].toString();
 }
 test("testincops", testincops(100), "0,0");
 
 function trees() {
-  var i = 0, o = [0,0,0];  
+  var i = 0, o = [0,0,0];
   for (i = 0; i < 100; ++i) {
     if ((i & 1) == 0) o[0]++;
     else if ((i & 2) == 0) o[1]++;
     else o[2]++;
   }
   return o;
 }
 test("trees", trees(), "50,25,25");
 
 function unboxint() {
     var q = 0;
     var o = [4];
-    for (var i = 0; i < 100; ++i) 
+    for (var i = 0; i < 100; ++i)
 	q = o[0] << 1;
     return q;
 }
 test("unboxint", unboxint(), "8");
 
+function strings()
+{
+  var a = [], b = -1;
+  var s = "abcdefghij";
+  for (var i = 0; i < 10; i++) {
+    a[i] = s.substring(i, i+1);
+    b = s.length;
+  }
+  return a.toString() + b;
+}
+test("strings", strings(), "a,b,c,d,e,f,g,h,i,j10");
+
 /* Keep these at the end so that we can see the summary after the trace-debug spew. */
 print("pass:", passes.length ? passes.join(",") : "<none>");
 print("FAIL:", fails.length ? fails.join(",") : "<none>");