Allocate short strings in the GC heap, avoiding malloc + free (553541, r=igor).
Allocate short strings in the GC heap, avoiding malloc + free (553541, r=igor).
--- 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();