Improve string concatenation hack
- add more cases of in place string concatenation this temporary hack improves the microbench timing by 30% but has little impact on the test262 timings.
This commit is contained in:
parent
8e21b96738
commit
c06af876f6
3 changed files with 99 additions and 80 deletions
3
Makefile
3
Makefile
|
@ -48,9 +48,10 @@ PREFIX?=/usr/local
|
||||||
#CONFIG_ASAN=y
|
#CONFIG_ASAN=y
|
||||||
# use memory sanitizer
|
# use memory sanitizer
|
||||||
#CONFIG_MSAN=y
|
#CONFIG_MSAN=y
|
||||||
# include the code for BigFloat/BigDecimal, math mode and faster large integers
|
|
||||||
# use UB sanitizer
|
# use UB sanitizer
|
||||||
#CONFIG_UBSAN=y
|
#CONFIG_UBSAN=y
|
||||||
|
|
||||||
|
# include the code for BigFloat/BigDecimal and math mode
|
||||||
CONFIG_BIGNUM=y
|
CONFIG_BIGNUM=y
|
||||||
|
|
||||||
OBJDIR=.obj
|
OBJDIR=.obj
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
2024-01-13
|
2024-02-14
|
||||||
|
|
168
quickjs.c
168
quickjs.c
|
@ -1454,6 +1454,10 @@ static inline int is_digit(int c) {
|
||||||
return c >= '0' && c <= '9';
|
return c >= '0' && c <= '9';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int string_get(const JSString *p, int idx) {
|
||||||
|
return p->is_wide_char ? p->u.str16[idx] : p->u.str8[idx];
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct JSClassShortDef {
|
typedef struct JSClassShortDef {
|
||||||
JSAtom class_name;
|
JSAtom class_name;
|
||||||
JSClassFinalizer *finalizer;
|
JSClassFinalizer *finalizer;
|
||||||
|
@ -2427,10 +2431,7 @@ static inline BOOL is_num_string(uint32_t *pval, const JSString *p)
|
||||||
len = p->len;
|
len = p->len;
|
||||||
if (len == 0 || len > 10)
|
if (len == 0 || len > 10)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
if (p->is_wide_char)
|
c = string_get(p, 0);
|
||||||
c = p->u.str16[0];
|
|
||||||
else
|
|
||||||
c = p->u.str8[0];
|
|
||||||
if (is_num(c)) {
|
if (is_num(c)) {
|
||||||
if (c == '0') {
|
if (c == '0') {
|
||||||
if (len != 1)
|
if (len != 1)
|
||||||
|
@ -2439,10 +2440,7 @@ static inline BOOL is_num_string(uint32_t *pval, const JSString *p)
|
||||||
} else {
|
} else {
|
||||||
n = c - '0';
|
n = c - '0';
|
||||||
for(i = 1; i < len; i++) {
|
for(i = 1; i < len; i++) {
|
||||||
if (p->is_wide_char)
|
c = string_get(p, i);
|
||||||
c = p->u.str16[i];
|
|
||||||
else
|
|
||||||
c = p->u.str8[i];
|
|
||||||
if (!is_num(c))
|
if (!is_num(c))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
n64 = (uint64_t)n * 10 + (c - '0');
|
n64 = (uint64_t)n * 10 + (c - '0');
|
||||||
|
@ -2487,23 +2485,8 @@ static uint32_t hash_string(const JSString *str, uint32_t h)
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
static __maybe_unused void JS_DumpString(JSRuntime *rt,
|
static __maybe_unused void JS_DumpChar(JSRuntime *rt, int c, int sep)
|
||||||
const JSString *p)
|
|
||||||
{
|
{
|
||||||
int i, c, sep;
|
|
||||||
|
|
||||||
if (p == NULL) {
|
|
||||||
printf("<null>");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
printf("%d", p->header.ref_count);
|
|
||||||
sep = (p->header.ref_count == 1) ? '\"' : '\'';
|
|
||||||
putchar(sep);
|
|
||||||
for(i = 0; i < p->len; i++) {
|
|
||||||
if (p->is_wide_char)
|
|
||||||
c = p->u.str16[i];
|
|
||||||
else
|
|
||||||
c = p->u.str8[i];
|
|
||||||
if (c == sep || c == '\\') {
|
if (c == sep || c == '\\') {
|
||||||
putchar('\\');
|
putchar('\\');
|
||||||
putchar(c);
|
putchar(c);
|
||||||
|
@ -2515,6 +2498,21 @@ static __maybe_unused void JS_DumpString(JSRuntime *rt,
|
||||||
} else {
|
} else {
|
||||||
printf("\\u%04x", c);
|
printf("\\u%04x", c);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p)
|
||||||
|
{
|
||||||
|
int i, sep;
|
||||||
|
|
||||||
|
if (p == NULL) {
|
||||||
|
printf("<null>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
printf("%d", p->header.ref_count);
|
||||||
|
sep = (p->header.ref_count == 1) ? '\"' : '\'';
|
||||||
|
putchar(sep);
|
||||||
|
for(i = 0; i < p->len; i++) {
|
||||||
|
JS_DumpChar(rt, string_get(p, i), sep);
|
||||||
}
|
}
|
||||||
putchar(sep);
|
putchar(sep);
|
||||||
}
|
}
|
||||||
|
@ -2857,6 +2855,7 @@ static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len,
|
||||||
return __JS_NewAtom(rt, p, atom_type);
|
return __JS_NewAtom(rt, p, atom_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Warning: str must be ASCII only */
|
||||||
static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len,
|
static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len,
|
||||||
int atom_type)
|
int atom_type)
|
||||||
{
|
{
|
||||||
|
@ -2951,11 +2950,13 @@ static JSAtom JS_NewAtomStr(JSContext *ctx, JSString *p)
|
||||||
return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING);
|
return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* str is UTF-8 encoded */
|
||||||
JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len)
|
JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len)
|
||||||
{
|
{
|
||||||
JSValue val;
|
JSValue val;
|
||||||
|
|
||||||
if (len == 0 || !is_digit(*str)) {
|
if (len == 0 || !is_digit(*str)) {
|
||||||
|
// XXX: this will not work if UTF-8 encoded str contains non ASCII bytes
|
||||||
JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING);
|
JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING);
|
||||||
if (atom)
|
if (atom)
|
||||||
return atom;
|
return atom;
|
||||||
|
@ -3061,10 +3062,7 @@ static const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size,
|
||||||
return (const char *)str->u.str8;
|
return (const char *)str->u.str8;
|
||||||
}
|
}
|
||||||
for(i = 0; i < str->len; i++) {
|
for(i = 0; i < str->len; i++) {
|
||||||
if (str->is_wide_char)
|
c = string_get(str, i);
|
||||||
c = str->u.str16[i];
|
|
||||||
else
|
|
||||||
c = str->u.str8[i];
|
|
||||||
if ((q - buf) >= buf_size - UTF8_CHAR_LEN_MAX)
|
if ((q - buf) >= buf_size - UTF8_CHAR_LEN_MAX)
|
||||||
break;
|
break;
|
||||||
if (c < 128) {
|
if (c < 128) {
|
||||||
|
@ -3695,10 +3693,6 @@ static int string_buffer_putc(StringBuffer *s, uint32_t c)
|
||||||
return string_buffer_putc16(s, c);
|
return string_buffer_putc16(s, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int string_get(const JSString *p, int idx) {
|
|
||||||
return p->is_wide_char ? p->u.str16[idx] : p->u.str8[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
static int string_getc(const JSString *p, int *pidx)
|
static int string_getc(const JSString *p, int *pidx)
|
||||||
{
|
{
|
||||||
int idx, c, c1;
|
int idx, c, c1;
|
||||||
|
@ -4185,6 +4179,42 @@ static JSValue JS_ConcatString1(JSContext *ctx,
|
||||||
return JS_MKPTR(JS_TAG_STRING, p);
|
return JS_MKPTR(JS_TAG_STRING, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static BOOL JS_ConcatStringInPlace(JSContext *ctx, JSString *p1, JSValueConst op2) {
|
||||||
|
if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) {
|
||||||
|
JSString *p2 = JS_VALUE_GET_STRING(op2);
|
||||||
|
size_t size1;
|
||||||
|
|
||||||
|
if (p2->len == 0)
|
||||||
|
return TRUE;
|
||||||
|
if (p1->header.ref_count != 1)
|
||||||
|
return FALSE;
|
||||||
|
size1 = js_malloc_usable_size(ctx, p1);
|
||||||
|
if (p1->is_wide_char) {
|
||||||
|
if (size1 >= sizeof(*p1) + ((p1->len + p2->len) << 1)) {
|
||||||
|
if (p2->is_wide_char) {
|
||||||
|
memcpy(p1->u.str16 + p1->len, p2->u.str16, p2->len << 1);
|
||||||
|
p1->len += p2->len;
|
||||||
|
return TRUE;
|
||||||
|
} else {
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < p2->len; i++) {
|
||||||
|
p1->u.str16[p1->len++] = p2->u.str8[i];
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!p2->is_wide_char) {
|
||||||
|
if (size1 >= sizeof(*p1) + p1->len + p2->len + 1) {
|
||||||
|
memcpy(p1->u.str8 + p1->len, p2->u.str8, p2->len);
|
||||||
|
p1->len += p2->len;
|
||||||
|
p1->u.str8[p1->len] = '\0';
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
/* op1 and op2 are converted to strings. For convenience, op1 or op2 =
|
/* op1 and op2 are converted to strings. For convenience, op1 or op2 =
|
||||||
JS_EXCEPTION are accepted and return JS_EXCEPTION. */
|
JS_EXCEPTION are accepted and return JS_EXCEPTION. */
|
||||||
static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2)
|
static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2)
|
||||||
|
@ -4207,27 +4237,11 @@ static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p1 = JS_VALUE_GET_STRING(op1);
|
p1 = JS_VALUE_GET_STRING(op1);
|
||||||
p2 = JS_VALUE_GET_STRING(op2);
|
if (JS_ConcatStringInPlace(ctx, p1, op2)) {
|
||||||
|
|
||||||
/* XXX: could also check if p1 is empty */
|
|
||||||
if (p2->len == 0) {
|
|
||||||
goto ret_op1;
|
|
||||||
}
|
|
||||||
if (p1->header.ref_count == 1 && p1->is_wide_char == p2->is_wide_char
|
|
||||||
&& js_malloc_usable_size(ctx, p1) >= sizeof(*p1) + ((p1->len + p2->len) << p2->is_wide_char) + 1 - p1->is_wide_char) {
|
|
||||||
/* Concatenate in place in available space at the end of p1 */
|
|
||||||
if (p1->is_wide_char) {
|
|
||||||
memcpy(p1->u.str16 + p1->len, p2->u.str16, p2->len << 1);
|
|
||||||
p1->len += p2->len;
|
|
||||||
} else {
|
|
||||||
memcpy(p1->u.str8 + p1->len, p2->u.str8, p2->len);
|
|
||||||
p1->len += p2->len;
|
|
||||||
p1->u.str8[p1->len] = '\0';
|
|
||||||
}
|
|
||||||
ret_op1:
|
|
||||||
JS_FreeValue(ctx, op2);
|
JS_FreeValue(ctx, op2);
|
||||||
return op1;
|
return op1;
|
||||||
}
|
}
|
||||||
|
p2 = JS_VALUE_GET_STRING(op2);
|
||||||
ret = JS_ConcatString1(ctx, p1, p2);
|
ret = JS_ConcatString1(ctx, p1, p2);
|
||||||
JS_FreeValue(ctx, op1);
|
JS_FreeValue(ctx, op1);
|
||||||
JS_FreeValue(ctx, op2);
|
JS_FreeValue(ctx, op2);
|
||||||
|
@ -17778,6 +17792,11 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
|
||||||
sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) +
|
sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) +
|
||||||
JS_VALUE_GET_FLOAT64(op2));
|
JS_VALUE_GET_FLOAT64(op2));
|
||||||
sp--;
|
sp--;
|
||||||
|
} else if (JS_IsString(op1) && JS_IsString(op2)) {
|
||||||
|
sp[-2] = JS_ConcatString(ctx, op1, op2);
|
||||||
|
sp--;
|
||||||
|
if (JS_IsException(sp[-1]))
|
||||||
|
goto exception;
|
||||||
} else {
|
} else {
|
||||||
add_slow:
|
add_slow:
|
||||||
if (js_add_slow(ctx, sp))
|
if (js_add_slow(ctx, sp))
|
||||||
|
@ -17788,38 +17807,45 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
|
||||||
BREAK;
|
BREAK;
|
||||||
CASE(OP_add_loc):
|
CASE(OP_add_loc):
|
||||||
{
|
{
|
||||||
|
JSValue op2;
|
||||||
JSValue *pv;
|
JSValue *pv;
|
||||||
int idx;
|
int idx;
|
||||||
idx = *pc;
|
idx = *pc;
|
||||||
pc += 1;
|
pc += 1;
|
||||||
|
|
||||||
|
op2 = sp[-1];
|
||||||
pv = &var_buf[idx];
|
pv = &var_buf[idx];
|
||||||
if (likely(JS_VALUE_IS_BOTH_INT(*pv, sp[-1]))) {
|
if (likely(JS_VALUE_IS_BOTH_INT(*pv, op2))) {
|
||||||
int64_t r;
|
int64_t r;
|
||||||
r = (int64_t)JS_VALUE_GET_INT(*pv) +
|
r = (int64_t)JS_VALUE_GET_INT(*pv) + JS_VALUE_GET_INT(op2);
|
||||||
JS_VALUE_GET_INT(sp[-1]);
|
|
||||||
if (unlikely((int)r != r))
|
if (unlikely((int)r != r))
|
||||||
goto add_loc_slow;
|
goto add_loc_slow;
|
||||||
*pv = JS_NewInt32(ctx, r);
|
*pv = JS_NewInt32(ctx, r);
|
||||||
sp--;
|
sp--;
|
||||||
} else if (JS_VALUE_GET_TAG(*pv) == JS_TAG_STRING) {
|
} else if (JS_VALUE_IS_BOTH_FLOAT(*pv, op2)) {
|
||||||
JSValue op1;
|
*pv = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(*pv) +
|
||||||
op1 = sp[-1];
|
JS_VALUE_GET_FLOAT64(op2));
|
||||||
sp--;
|
sp--;
|
||||||
op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE);
|
} else if (JS_VALUE_GET_TAG(*pv) == JS_TAG_STRING) {
|
||||||
if (JS_IsException(op1))
|
sp--;
|
||||||
|
op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE);
|
||||||
|
if (JS_IsException(op2))
|
||||||
goto exception;
|
goto exception;
|
||||||
op1 = JS_ConcatString(ctx, JS_DupValue(ctx, *pv), op1);
|
if (JS_ConcatStringInPlace(ctx, JS_VALUE_GET_STRING(*pv), op2)) {
|
||||||
if (JS_IsException(op1))
|
JS_FreeValue(ctx, op2);
|
||||||
|
} else {
|
||||||
|
op2 = JS_ConcatString(ctx, JS_DupValue(ctx, *pv), op2);
|
||||||
|
if (JS_IsException(op2))
|
||||||
goto exception;
|
goto exception;
|
||||||
set_value(ctx, pv, op1);
|
set_value(ctx, pv, op2);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
JSValue ops[2];
|
JSValue ops[2];
|
||||||
add_loc_slow:
|
add_loc_slow:
|
||||||
/* In case of exception, js_add_slow frees ops[0]
|
/* In case of exception, js_add_slow frees ops[0]
|
||||||
and ops[1], so we must duplicate *pv */
|
and ops[1], so we must duplicate *pv */
|
||||||
ops[0] = JS_DupValue(ctx, *pv);
|
ops[0] = JS_DupValue(ctx, *pv);
|
||||||
ops[1] = sp[-1];
|
ops[1] = op2;
|
||||||
sp--;
|
sp--;
|
||||||
if (js_add_slow(ctx, ops + 2))
|
if (js_add_slow(ctx, ops + 2))
|
||||||
goto exception;
|
goto exception;
|
||||||
|
@ -35685,6 +35711,7 @@ static JSString *JS_ReadString(BCReaderState *s)
|
||||||
js_free_string(s->ctx->rt, p);
|
js_free_string(s->ctx->rt, p);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
// XXX: potential endianness issue
|
||||||
memcpy(p->u.str8, s->ptr, size);
|
memcpy(p->u.str8, s->ptr, size);
|
||||||
s->ptr += size;
|
s->ptr += size;
|
||||||
if (!is_wide_char) {
|
if (!is_wide_char) {
|
||||||
|
@ -41142,10 +41169,7 @@ static int js_string_get_own_property(JSContext *ctx,
|
||||||
idx = __JS_AtomToUInt32(prop);
|
idx = __JS_AtomToUInt32(prop);
|
||||||
if (idx < p1->len) {
|
if (idx < p1->len) {
|
||||||
if (desc) {
|
if (desc) {
|
||||||
if (p1->is_wide_char)
|
ch = string_get(p1, idx);
|
||||||
ch = p1->u.str16[idx];
|
|
||||||
else
|
|
||||||
ch = p1->u.str8[idx];
|
|
||||||
desc->flags = JS_PROP_ENUMERABLE;
|
desc->flags = JS_PROP_ENUMERABLE;
|
||||||
desc->value = js_new_string_char(ctx, ch);
|
desc->value = js_new_string_char(ctx, ch);
|
||||||
desc->getter = JS_UNDEFINED;
|
desc->getter = JS_UNDEFINED;
|
||||||
|
@ -41411,10 +41435,7 @@ static JSValue js_string_charCodeAt(JSContext *ctx, JSValueConst this_val,
|
||||||
if (idx < 0 || idx >= p->len) {
|
if (idx < 0 || idx >= p->len) {
|
||||||
ret = JS_NAN;
|
ret = JS_NAN;
|
||||||
} else {
|
} else {
|
||||||
if (p->is_wide_char)
|
c = string_get(p, idx);
|
||||||
c = p->u.str16[idx];
|
|
||||||
else
|
|
||||||
c = p->u.str8[idx];
|
|
||||||
ret = JS_NewInt32(ctx, c);
|
ret = JS_NewInt32(ctx, c);
|
||||||
}
|
}
|
||||||
JS_FreeValue(ctx, val);
|
JS_FreeValue(ctx, val);
|
||||||
|
@ -41444,10 +41465,7 @@ static JSValue js_string_charAt(JSContext *ctx, JSValueConst this_val,
|
||||||
else
|
else
|
||||||
ret = js_new_string8(ctx, NULL, 0);
|
ret = js_new_string8(ctx, NULL, 0);
|
||||||
} else {
|
} else {
|
||||||
if (p->is_wide_char)
|
c = string_get(p, idx);
|
||||||
c = p->u.str16[idx];
|
|
||||||
else
|
|
||||||
c = p->u.str8[idx];
|
|
||||||
ret = js_new_string_char(ctx, c);
|
ret = js_new_string_char(ctx, c);
|
||||||
}
|
}
|
||||||
JS_FreeValue(ctx, val);
|
JS_FreeValue(ctx, val);
|
||||||
|
@ -45324,7 +45342,7 @@ static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc,
|
||||||
has_content = TRUE;
|
has_content = TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (has_content && JS_VALUE_GET_STRING(jsc->gap)->len != 0) {
|
if (has_content && !JS_IsEmptyString(jsc->gap)) {
|
||||||
string_buffer_putc8(jsc->b, '\n');
|
string_buffer_putc8(jsc->b, '\n');
|
||||||
string_buffer_concat_value(jsc->b, indent);
|
string_buffer_concat_value(jsc->b, indent);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue