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:
Charlie Gordon 2024-02-15 10:30:04 +01:00
parent 8e21b96738
commit c06af876f6
3 changed files with 99 additions and 80 deletions

View file

@ -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

View file

@ -1 +1 @@
2024-01-13 2024-02-14

168
quickjs.c
View file

@ -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);
@ -2516,6 +2499,21 @@ static __maybe_unused void JS_DumpString(JSRuntime *rt,
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);
} }