Allocate short strings in the GC heap, avoiding malloc + free (553541, r=igor).
authorAndreas Gal <gal@mozilla.com>
Mon, 22 Mar 2010 20:02:07 -0700
changeset 40334 1dca6e135a1e38d049cb51acb1222cb5b358dd75
parent 40333 114be95eca4c15276e22ac60e7ddd50507eb4271
child 40335 400097fb04e78ea846efa085c29eb11c07c4b4e2
push id12610
push userrsayre@mozilla.com
push dateMon, 05 Apr 2010 17:26:41 +0000
treeherdermozilla-central@1942c0b4e101 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersigor
bugs553541
milestone1.9.3a3pre
Allocate short strings in the GC heap, avoiding malloc + free (553541, r=igor).
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsnum.cpp
js/src/jsstr.cpp
js/src/jsstr.h
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -752,16 +752,17 @@ GetFinalizableThingSize(unsigned thingKi
     JS_STATIC_ASSERT(JS_EXTERNAL_STRING_LIMIT == 8);
 
     static const uint8 map[FINALIZE_LIMIT] = {
         sizeof(JSObject),   /* FINALIZE_OBJECT */
         sizeof(JSFunction), /* FINALIZE_FUNCTION */
 #if JS_HAS_XML_SUPPORT
         sizeof(JSXML),      /* FINALIZE_XML */
 #endif
+        sizeof(JSShortString), /* FINALIZE_SHORT_STRING  */
         sizeof(JSString),   /* FINALIZE_STRING */
         sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING0 */
         sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING1 */
         sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING2 */
         sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING3 */
         sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING4 */
         sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING5 */
         sizeof(JSString),   /* FINALIZE_EXTERNAL_STRING6 */
@@ -775,20 +776,21 @@ GetFinalizableThingSize(unsigned thingKi
 static inline size_t
 GetFinalizableTraceKind(size_t thingKind)
 {
     JS_STATIC_ASSERT(JS_EXTERNAL_STRING_LIMIT == 8);
 
     static const uint8 map[FINALIZE_LIMIT] = {
         JSTRACE_OBJECT,     /* FINALIZE_OBJECT */
         JSTRACE_OBJECT,     /* FINALIZE_FUNCTION */
-#if JS_HAS_XML_SUPPORT      /* FINALIZE_XML */
-        JSTRACE_XML,
-#endif                      /* FINALIZE_STRING */
-        JSTRACE_STRING,
+#if JS_HAS_XML_SUPPORT
+        JSTRACE_XML,        /* FINALIZE_XML */
+#endif
+        JSTRACE_STRING,     /* FINALIZE_SHORT_STRING     */
+        JSTRACE_STRING,     /* FINALIZE_STRING           */
         JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING0 */
         JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING1 */
         JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING2 */
         JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING3 */
         JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING4 */
         JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING5 */
         JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING6 */
         JSTRACE_STRING,     /* FINALIZE_EXTERNAL_STRING7 */
@@ -861,17 +863,17 @@ js_GetGCThingTraceKind(void *thing)
 
 JSRuntime*
 js_GetGCStringRuntime(JSString *str)
 {
     JSGCArenaList *list = JSGCArena::fromGCThing(str)->info.list;
     JS_ASSERT(list->thingSize == sizeof(JSString));
 
     unsigned i = list->thingKind;
-    JS_ASSERT(i == FINALIZE_STRING ||
+    JS_ASSERT(i == FINALIZE_STRING || i == FINALIZE_SHORT_STRING ||
               (FINALIZE_EXTERNAL_STRING0 <= i &&
                i < FINALIZE_EXTERNAL_STRING0 + JS_EXTERNAL_STRING_LIMIT));
     return (JSRuntime *)((uint8 *)(list - i) -
                          offsetof(JSRuntime, gcArenaList));
 }
 
 bool
 js_IsAboutToBeFinalized(void *thing)
@@ -980,16 +982,17 @@ js_DumpGCStats(JSRuntime *rt, FILE *fp)
 {
     static const char *const GC_ARENA_NAMES[] = {
         "double",
         "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",
@@ -2329,16 +2332,17 @@ JSWeakRoots::mark(JSTracer *trc)
 {
 #ifdef DEBUG
     const char * const newbornNames[] = {
         "newborn_object",             /* FINALIZE_OBJECT */
         "newborn_function",           /* FINALIZE_FUNCTION */
 #if JS_HAS_XML_SUPPORT
         "newborn_xml",                /* FINALIZE_XML */
 #endif
+        "newborn_short_string",       /* FINALIZE_SHORT_STRING */
         "newborn_string",             /* FINALIZE_STRING */
         "newborn_external_string0",   /* FINALIZE_EXTERNAL_STRING0 */
         "newborn_external_string1",   /* FINALIZE_EXTERNAL_STRING1 */
         "newborn_external_string2",   /* FINALIZE_EXTERNAL_STRING2 */
         "newborn_external_string3",   /* FINALIZE_EXTERNAL_STRING3 */
         "newborn_external_string4",   /* FINALIZE_EXTERNAL_STRING4 */
         "newborn_external_string5",   /* FINALIZE_EXTERNAL_STRING5 */
         "newborn_external_string6",   /* FINALIZE_EXTERNAL_STRING6 */
@@ -2602,16 +2606,28 @@ js_ChangeExternalStringFinalizer(JSStrin
             str_finalizers[i] = newop;
             return intN(i);
         }
     }
     return -1;
 }
 
 inline void
+FinalizeShortString(JSContext *cx, JSShortString *str, unsigned thingKind)
+{
+    JS_ASSERT(FINALIZE_SHORT_STRING == thingKind);
+    JS_ASSERT(!JSString::isStatic(&str->header));
+    JS_RUNTIME_UNMETER(cx->runtime, liveStrings);
+    if (str->header.isDependent()) {
+        JS_ASSERT(str->header.dependentBase());
+        JS_RUNTIME_UNMETER(cx->runtime, liveDependentStrings);
+    }
+}
+
+inline void
 FinalizeString(JSContext *cx, JSString *str, unsigned thingKind)
 {
     JS_ASSERT(FINALIZE_STRING == thingKind);
     JS_ASSERT(!JSString::isStatic(str));
     JS_RUNTIME_UNMETER(cx->runtime, liveStrings);
     if (str->isDependent()) {
         JS_ASSERT(str->dependentBase());
         JS_RUNTIME_UNMETER(cx->runtime, liveDependentStrings);
@@ -2657,16 +2673,17 @@ js_FinalizeStringRT(JSRuntime *rt, JSStr
         /* A dependent string can not be external and must be valid. */
         JS_ASSERT(JSGCArena::fromGCThing(str)->info.list->thingKind ==
                   FINALIZE_STRING);
         JS_ASSERT(str->dependentBase());
         JS_RUNTIME_UNMETER(rt, liveDependentStrings);
     } else {
         unsigned thingKind = JSGCArena::fromGCThing(str)->info.list->thingKind;
         JS_ASSERT(IsFinalizableStringKind(thingKind));
+        JS_ASSERT(thingKind != FINALIZE_SHORT_STRING);
 
         /* A stillborn string has null chars, so is not valid. */
         jschar *chars = str->flatChars();
         if (!chars)
             return;
         if (thingKind == FINALIZE_STRING) {
             rt->free(chars);
         } else {
@@ -3191,16 +3208,18 @@ js_GC(JSContext *cx, JSGCInvocationKind 
 #endif
 
     /*
      * We sweep the deflated cache before we finalize the strings so the
      * cache can safely use js_IsAboutToBeFinalized..
      */
     rt->deflatedStringCache->sweep(cx);
 
+    FinalizeArenaList<JSShortString, FinalizeShortString>
+        (cx, FINALIZE_SHORT_STRING, &emptyArenas);
     FinalizeArenaList<JSString, FinalizeString>
         (cx, FINALIZE_STRING, &emptyArenas);
     for (unsigned i = FINALIZE_EXTERNAL_STRING0;
          i <= FINALIZE_EXTERNAL_STRING_LAST;
          ++i) {
         FinalizeArenaList<JSString, FinalizeExternalString>
             (cx, i, &emptyArenas);
     }
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -239,16 +239,17 @@ js_CallGCMarker(JSTracer *trc, void *thi
  * ordinary string to simplify js_GetExternalStringGCType.
  */
 enum JSFinalizeGCThingKind {
     FINALIZE_OBJECT,
     FINALIZE_FUNCTION,
 #if JS_HAS_XML_SUPPORT
     FINALIZE_XML,
 #endif
+    FINALIZE_SHORT_STRING,
     FINALIZE_STRING,
     FINALIZE_EXTERNAL_STRING0,
     FINALIZE_EXTERNAL_STRING1,
     FINALIZE_EXTERNAL_STRING2,
     FINALIZE_EXTERNAL_STRING3,
     FINALIZE_EXTERNAL_STRING4,
     FINALIZE_EXTERNAL_STRING5,
     FINALIZE_EXTERNAL_STRING6,
@@ -280,16 +281,24 @@ js_NewGCObject(JSContext *cx)
 }
 
 static inline JSString *
 js_NewGCString(JSContext *cx)
 {
     return (JSString *) js_NewFinalizableGCThing(cx, FINALIZE_STRING);
 }
 
+struct JSShortString;
+
+static inline JSShortString *
+js_NewGCShortString(JSContext *cx)
+{
+    return (JSShortString *) js_NewFinalizableGCThing(cx, FINALIZE_SHORT_STRING);
+}
+
 static inline JSString *
 js_NewGCExternalString(JSContext *cx, uintN type)
 {
     JS_ASSERT(type < JS_EXTERNAL_STRING_LIMIT);
     type += FINALIZE_EXTERNAL_STRING0;
     return (JSString *) js_NewFinalizableGCThing(cx, type);
 }
 
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -322,17 +322,17 @@ num_toSource(JSContext *cx, uintN argc, 
     JS_ASSERT(JSVAL_IS_NUMBER(v));
     d = JSVAL_IS_INT(v) ? (jsdouble)JSVAL_TO_INT(v) : *JSVAL_TO_DOUBLE(v);
     numStr = JS_dtostr(numBuf, sizeof numBuf, DTOSTR_STANDARD, 0, d);
     if (!numStr) {
         JS_ReportOutOfMemory(cx);
         return JS_FALSE;
     }
     JS_snprintf(buf, sizeof buf, "(new %s(%s))", js_NumberClass.name, numStr);
-    str = JS_NewStringCopyZ(cx, buf);
+    str = js_NewStringCopyZ(cx, buf);
     if (!str)
         return JS_FALSE;
     *vp = STRING_TO_JSVAL(str);
     return JS_TRUE;
 }
 #endif
 
 /* The buf must be big enough for MIN_INT to fit including '-' and '\0'. */
@@ -584,17 +584,17 @@ num_to(JSContext *cx, JSDToStrMode zeroA
         }
     }
 
     numStr = JS_dtostr(buf, sizeof buf, oneArgMode, (jsint)precision + precisionOffset, d);
     if (!numStr) {
         JS_ReportOutOfMemory(cx);
         return JS_FALSE;
     }
-    str = JS_NewStringCopyZ(cx, numStr);
+    str = js_NewStringCopyZ(cx, numStr);
     if (!str)
         return JS_FALSE;
     *vp = STRING_TO_JSVAL(str);
     return JS_TRUE;
 }
 
 /*
  * In the following three implementations, we allow a larger range of precision
@@ -887,17 +887,17 @@ js_NumberToStringWithBase(JSContext *cx,
             if (i < 10)
                 return JSString::intString(i);
             return JSString::unitString(jschar('a' + i - 10));
         }
     }
     numStr = NumberToCString(cx, d, base, buf, sizeof buf);
     if (!numStr)
         return NULL;
-    s = JS_NewStringCopyZ(cx, numStr);
+    s = js_NewStringCopyZ(cx, numStr);
     if (!(numStr >= buf && numStr < buf + sizeof buf))
         js_free(numStr);
     return s;
 }
 
 JSString * JS_FASTCALL
 js_NumberToString(JSContext *cx, jsdouble d)
 {
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -3105,16 +3105,59 @@ js_NewString(JSContext *cx, jschar *char
     JS_LOCK_RUNTIME_VOID(rt,
         (rt->lengthSum += (double)length,
          rt->lengthSquaredSum += (double)length * (double)length));
   }
 #endif
     return str;
 }
 
+static bool
+FitsIntoShortString(size_t length)
+{
+    return length < sizeof JSString / sizeof jschar;
+}
+
+JSString *
+NewShortString(JSContext *cx, const jschar *chars, size_t length)
+{
+    JS_ASSERT(FitsIntoShortString(length));
+    JSShortString *str = js_NewGCShortString(cx);
+    if (!str)
+        return NULL;
+    jschar *storage = &str->data[0];
+    memcpy(storage, chars, sizeof(jschar) * length);
+    storage[length] = 0;
+    str->header.initFlat(storage, length);
+    return &str->header;
+}
+
+JSString *
+NewShortString(JSContext *cx, const char *chars, size_t length)
+{
+    JS_ASSERT(FitsIntoShortString(length));
+    JSShortString *str = js_NewGCShortString(cx);
+    if (!str)
+        return NULL;
+    jschar *storage = &str->data[0];
+    if (js_CStringsAreUTF8) {
+        if (!js_InflateStringToBuffer(cx, chars, length, NULL, &length))
+            return NULL;
+        storage[length] = 0;
+    } else {
+        size_t n = length;
+        jschar *p = storage;
+        while (n-- > 0)
+            *p++ = jschar(*chars++);
+        *p = 0;
+    }
+    str->header.initFlat(storage, length);
+    return &str->header;
+}
+
 static const size_t sMinWasteSize = 16;
 
 JSString *
 js_NewStringFromCharBuffer(JSContext *cx, JSCharBuffer &cb)
 {
     if (cb.empty())
         return ATOM_TO_STRING(cx->runtime->atomState.emptyAtom);
 
@@ -3199,49 +3242,75 @@ void printJSStringStats(JSRuntime *rt)
     fprintf(stderr, "%lu total dependent strings, mean length %g (sigma %g)\n",
             (unsigned long)rt->totalDependentStrings, mean, sigma);
 }
 #endif
 
 JSString *
 js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n)
 {
+    if (FitsIntoShortString(n))
+        return NewShortString(cx, s, n);
+
     jschar *news;
     JSString *str;
 
     news = (jschar *) cx->malloc((n + 1) * sizeof(jschar));
     if (!news)
         return NULL;
     js_strncpy(news, s, n);
     news[n] = 0;
     str = js_NewString(cx, news, n);
     if (!str)
         cx->free(news);
     return str;
 }
 
 JSString *
+js_NewStringCopyN(JSContext *cx, const char *s, size_t n)
+{
+    /*
+     * For simplicity, if UTF8 encoding is enabled, we assume conservatively
+     * that the length of the decided string is less or equal to the number
+     * of bytes.
+     */
+    if (FitsIntoShortString(n))
+        return NewShortString(cx, s, n);
+    return JS_NewStringCopyN(cx, s, n);
+}
+
+JSString *
 js_NewStringCopyZ(JSContext *cx, const jschar *s)
 {
     size_t n, m;
     jschar *news;
     JSString *str;
 
     n = js_strlen(s);
+
+    if (FitsIntoShortString(n))
+        return NewShortString(cx, s, n);
+
     m = (n + 1) * sizeof(jschar);
     news = (jschar *) cx->malloc(m);
     if (!news)
         return NULL;
     memcpy(news, s, m);
     str = js_NewString(cx, news, n);
     if (!str)
         cx->free(news);
     return str;
 }
 
+JSString *
+js_NewStringCopyZ(JSContext *cx, const char *s)
+{
+    return js_NewStringCopyN(cx, s, strlen(s));
+}
+
 JS_FRIEND_API(const char *)
 js_ValueToPrintable(JSContext *cx, jsval v, JSValueToStringFun v2sfun)
 {
     JSString *str;
 
     str = v2sfun(cx, v);
     if (!str)
         return NULL;
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -49,18 +49,16 @@
  * allocated from the malloc heap.
  */
 #include <ctype.h>
 #include "jspubtd.h"
 #include "jsprvtd.h"
 #include "jshashtable.h"
 #include "jslock.h"
 
-JS_BEGIN_EXTERN_C
-
 #define JSSTRING_BIT(n)             ((size_t)1 << (n))
 #define JSSTRING_BITMASK(n)         (JSSTRING_BIT(n) - 1)
 
 enum {
     UNIT_STRING_LIMIT        = 256U,
     INT_STRING_LIMIT         = 256U
 };
 
@@ -295,16 +293,24 @@ struct JSString {
     static const char *deflatedIntStringTable[];
     static const char deflatedUnitStringTable[];
 
     static JSString *unitString(jschar c);
     static JSString *getUnitString(JSContext *cx, JSString *str, size_t index);
     static JSString *intString(jsint i);
 };
 
+struct JSShortString {
+    JSString header;
+    union {
+        JSString dummy;
+        jschar data[1];
+    };
+};
+
 extern const jschar *
 js_GetStringChars(JSContext *cx, JSString *str);
 
 extern JSString * JS_FASTCALL
 js_ConcatStrings(JSContext *cx, JSString *left, JSString *right);
 
 extern const jschar *
 js_UndependString(JSContext *cx, JSString *str);
@@ -468,19 +474,23 @@ JS_ISSPACE(jschar c)
 #define JS7_UNDEC(c)    ((c) - '0')
 #define JS7_ISHEX(c)    ((c) < 128 && isxdigit(c))
 #define JS7_UNHEX(c)    (uintN)(JS7_ISDEC(c) ? (c) - '0' : 10 + tolower(c) - 'a')
 #define JS7_ISLET(c)    ((c) < 128 && isalpha(c))
 
 /* Initialize the String class, returning its prototype object. */
 extern JSClass js_StringClass;
 
+JS_BEGIN_EXTERN_C
+
 extern JSObject *
 js_InitStringClass(JSContext *cx, JSObject *obj);
 
+JS_END_EXTERN_C
+
 extern const char js_escape_str[];
 extern const char js_unescape_str[];
 extern const char js_uneval_str[];
 extern const char js_decodeURI_str[];
 extern const char js_encodeURI_str[];
 extern const char js_decodeURIComponent_str[];
 extern const char js_encodeURIComponent_str[];
 
@@ -499,20 +509,26 @@ js_NewStringFromCharBuffer(JSContext *cx
 extern JSString *
 js_NewDependentString(JSContext *cx, JSString *base, size_t start,
                       size_t length);
 
 /* Copy a counted string and GC-allocate a descriptor for it. */
 extern JSString *
 js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n);
 
+extern JSString *
+js_NewStringCopyN(JSContext *cx, const char *s, size_t n);
+
 /* Copy a C string and GC-allocate a descriptor for it. */
 extern JSString *
 js_NewStringCopyZ(JSContext *cx, const jschar *s);
 
+extern JSString *
+js_NewStringCopyZ(JSContext *cx, const char *s);
+
 /*
  * Convert a value to a printable C string.
  */
 typedef JSString *(*JSValueToStringFun)(JSContext *cx, jsval v);
 
 extern JS_FRIEND_API(const char *)
 js_ValueToPrintable(JSContext *cx, jsval v, JSValueToStringFun v2sfun);
 
@@ -686,18 +702,16 @@ js_OneUcs4ToUtf8Char(uint8 *utf8Buffer, 
 
 extern JS_FRIEND_API(size_t)
 js_PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp,
                         JSString *str, uint32 quote);
 
 extern JSBool
 js_String(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
 
-JS_END_EXTERN_C
-
 namespace js {
 
 class DeflatedStringCache {
   public:
     DeflatedStringCache();
     bool init();
     ~DeflatedStringCache();