This is an automated email from the ASF dual-hosted git repository. vatamane pushed a commit to branch more-quickjs-optimizations in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 6d5005a68ebe0c17f387ac49ee24100819dd5345 Author: Nick Vatamaniuc <[email protected]> AuthorDate: Wed Oct 8 17:44:16 2025 -0400 More QuickJS Optimization Along with the previous updates Fabrice added a 15% speed improvement as measured by bench-v8! * Optimized `post_inc` and `post_dec` https://github.com/bellard/quickjs/commit/8e8eefb922b205bb56de14e0279b4e42b5f3f460 * Optimized `string_buffer_putc()` https://github.com/bellard/quickjs/commit/79f3ae295998246674097c9e20b712bbe50f39fd * Faster appending of elements in arrays https://github.com/bellard/quickjs/commit/c8a8cf57c66cd7b89fe6f3283beecc7de67b4648 * Changelog updates https://github.com/bellard/quickjs/commit/7a488f3e0c1c03699c069bc04f5ce5f1c7f3414a --- .../patches/01-spidermonkey-185-mode.patch | 6 +- src/couch_quickjs/patches/02-test262-errors.patch | 4 +- src/couch_quickjs/quickjs/Changelog | 10 ++ src/couch_quickjs/quickjs/quickjs.c | 170 ++++++++++++++++----- 4 files changed, 143 insertions(+), 47 deletions(-) diff --git a/src/couch_quickjs/patches/01-spidermonkey-185-mode.patch b/src/couch_quickjs/patches/01-spidermonkey-185-mode.patch index 681eedbdc..cbb77897c 100644 --- a/src/couch_quickjs/patches/01-spidermonkey-185-mode.patch +++ b/src/couch_quickjs/patches/01-spidermonkey-185-mode.patch @@ -1,6 +1,6 @@ ---- quickjs-master/quickjs.c 2025-10-04 04:46:29 -+++ quickjs/quickjs.c 2025-10-06 23:16:06 -@@ -30866,10 +30866,24 @@ +--- quickjs-master/quickjs.c 2025-10-08 08:16:51 ++++ quickjs/quickjs.c 2025-10-08 17:43:48 +@@ -30951,10 +30951,24 @@ if (s->token.val == TOK_FUNCTION || (token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, TRUE) == TOK_FUNCTION)) { diff --git a/src/couch_quickjs/patches/02-test262-errors.patch b/src/couch_quickjs/patches/02-test262-errors.patch index 07644016f..41ff848ab 100644 --- a/src/couch_quickjs/patches/02-test262-errors.patch +++ b/src/couch_quickjs/patches/02-test262-errors.patch @@ -1,5 +1,5 @@ ---- quickjs-master/test262_errors.txt 2025-10-04 04:46:29 -+++ quickjs/test262_errors.txt 2025-10-06 23:19:01 +--- quickjs-master/test262_errors.txt 2025-10-08 08:16:51 ++++ quickjs/test262_errors.txt 2025-10-08 17:43:48 @@ -6,6 +6,8 @@ test262/test/annexB/language/expressions/assignmenttargettype/callexpression.js:33: SyntaxError: invalid assignment left-hand side test262/test/annexB/language/expressions/assignmenttargettype/cover-callexpression-and-asyncarrowhead.js:20: SyntaxError: invalid assignment left-hand side diff --git a/src/couch_quickjs/quickjs/Changelog b/src/couch_quickjs/quickjs/Changelog index 8a32a92c1..7d6afd6da 100644 --- a/src/couch_quickjs/quickjs/Changelog +++ b/src/couch_quickjs/quickjs/Changelog @@ -1,3 +1,13 @@ +- micro optimizations (15% faster on bench-v8) +- added resizable array buffers +- added ArrayBuffer.prototype.transfer +- added the Iterator object and methods +- added set methods +- added Atomics.pause +- added added Map and WeakMap upsert methods +- added Math.sumPrecise() +- misc bug fixes + 2025-09-13: - added JSON modules and import attributes diff --git a/src/couch_quickjs/quickjs/quickjs.c b/src/couch_quickjs/quickjs/quickjs.c index 9d795238d..3c2236975 100644 --- a/src/couch_quickjs/quickjs/quickjs.c +++ b/src/couch_quickjs/quickjs/quickjs.c @@ -353,7 +353,8 @@ typedef enum { struct JSGCObjectHeader { int ref_count; /* must come first, 32-bit */ JSGCObjectTypeEnum gc_obj_type : 4; - uint8_t mark : 4; /* used by the GC */ + uint8_t mark : 1; /* used by the GC */ + uint8_t dummy0: 3; uint8_t dummy1; /* not used by the GC */ uint16_t dummy2; /* not used by the GC */ struct list_head link; @@ -446,7 +447,14 @@ struct JSContext { uint16_t binary_object_count; int binary_object_size; - + /* TRUE if the array prototype is "normal": + - no small index properties which are get/set or non writable + - its prototype is Object.prototype + - Object.prototype has no small index properties which are get/set or non writable + - the prototype of Object.prototype is null (always true as it is immutable) + */ + uint8_t std_array_prototype; + JSShape *array_shape; /* initial shape for Array objects */ JSValue *class_proto; @@ -904,10 +912,6 @@ struct JSShape { /* true if the shape is inserted in the shape hash table. If not, JSShape.hash is not valid */ uint8_t is_hashed; - /* If true, the shape may have small array index properties 'n' with 0 - <= n <= 2^31-1. If false, the shape is guaranteed not to have - small array index properties */ - uint8_t has_small_array_index; uint32_t hash; /* current hash value */ uint32_t prop_hash_mask; int prop_size; /* allocated properties */ @@ -923,7 +927,8 @@ struct JSObject { JSGCObjectHeader header; struct { int __gc_ref_count; /* corresponds to header.ref_count */ - uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ + uint8_t __gc_mark : 7; /* corresponds to header.mark/gc_obj_type */ + uint8_t is_prototype : 1; /* object may be used as prototype */ uint8_t extensible : 1; uint8_t free_mark : 1; /* only used when freeing objects with cycles */ @@ -3610,7 +3615,7 @@ static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c) return 0; } -static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c) +static no_inline int string_buffer_putc16_slow(StringBuffer *s, uint32_t c) { if (unlikely(s->len >= s->size)) { if (string_buffer_realloc(s, s->len + 1, c)) @@ -3655,11 +3660,10 @@ static int string_buffer_putc16(StringBuffer *s, uint32_t c) return 0; } } - return string_buffer_putc_slow(s, c); + return string_buffer_putc16_slow(s, c); } -/* 0 <= c <= 0x10ffff */ -static int string_buffer_putc(StringBuffer *s, uint32_t c) +static int string_buffer_putc_slow(StringBuffer *s, uint32_t c) { if (unlikely(c >= 0x10000)) { /* surrogate pair */ @@ -3670,6 +3674,27 @@ static int string_buffer_putc(StringBuffer *s, uint32_t c) return string_buffer_putc16(s, c); } +/* 0 <= c <= 0x10ffff */ +static inline int string_buffer_putc(StringBuffer *s, uint32_t c) +{ + if (likely(s->len < s->size)) { + if (s->is_wide_char) { + if (c < 0x10000) { + s->str->u.str16[s->len++] = c; + return 0; + } else if (likely((s->len + 1) < s->size)) { + s->str->u.str16[s->len++] = get_hi_surrogate(c); + s->str->u.str16[s->len++] = get_lo_surrogate(c); + return 0; + } + } else if (c < 0x100) { + s->str->u.str8[s->len++] = c; + return 0; + } + } + return string_buffer_putc_slow(s, c); +} + static int string_getc(const JSString *p, int *pidx) { int idx, c, c1; @@ -4745,7 +4770,6 @@ static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto, /* insert in the hash table */ sh->hash = shape_initial_hash(proto); sh->is_hashed = TRUE; - sh->has_small_array_index = FALSE; js_shape_hash_link(ctx->rt, sh); return sh; } @@ -4990,7 +5014,6 @@ static int add_shape_property(JSContext *ctx, JSShape **psh, pr = &prop[sh->prop_count++]; pr->atom = JS_DupAtom(ctx, atom); pr->flags = prop_flags; - sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom); /* add in hash table */ hash_mask = sh->prop_hash_mask; h = atom & hash_mask; @@ -5105,6 +5128,7 @@ static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID clas if (unlikely(!p)) goto fail; p->class_id = class_id; + p->is_prototype = 0; p->extensible = TRUE; p->free_mark = 0; p->is_exotic = 0; @@ -7381,6 +7405,14 @@ static int JS_SetPrototypeInternal(JSContext *ctx, JSValueConst obj, if (sh->proto) JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); sh->proto = proto; + if (proto) + proto->is_prototype = TRUE; + if (p->is_prototype) { + /* track modification of Array.prototype */ + if (unlikely(p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]))) { + ctx->std_array_prototype = FALSE; + } + } return TRUE; } @@ -8567,6 +8599,14 @@ static JSProperty *add_property(JSContext *ctx, { JSShape *sh, *new_sh; + if (unlikely(p->is_prototype)) { + /* track addition of small integer properties to Array.prototype and Object.prototype */ + if (unlikely((p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]) || + p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_OBJECT])) && + __JS_AtomIsTaggedInt(prop))) { + ctx->std_array_prototype = FALSE; + } + } sh = p->shape; if (sh->is_hashed) { /* try to find an existing shape */ @@ -8636,6 +8676,11 @@ static no_inline __exception int convert_fast_array_to_array(JSContext *ctx, p->u.array.u.values = NULL; /* fail safe */ p->u.array.u1.size = 0; p->fast_array = 0; + + /* track modification of Array.prototype */ + if (unlikely(p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]))) { + ctx->std_array_prototype = FALSE; + } return 0; } @@ -8860,8 +8905,8 @@ static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len) /* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array = TRUE and p->extensible = TRUE */ -static int add_fast_array_element(JSContext *ctx, JSObject *p, - JSValue val, int flags) +static inline int add_fast_array_element(JSContext *ctx, JSObject *p, + JSValue val, int flags) { uint32_t new_len, array_len; /* extend the array by one */ @@ -9263,27 +9308,13 @@ static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, switch(p->class_id) { case JS_CLASS_ARRAY: if (unlikely(idx >= (uint32_t)p->u.array.count)) { - JSObject *p1; - JSShape *sh1; - /* fast path to add an element to the array */ - if (idx != (uint32_t)p->u.array.count || - !p->fast_array || !p->extensible) + if (unlikely(idx != (uint32_t)p->u.array.count || + !p->fast_array || + !p->extensible || + p->shape->proto != JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]) || + !ctx->std_array_prototype)) { goto slow_path; - /* check if prototype chain has a numeric property */ - p1 = p->shape->proto; - while (p1 != NULL) { - sh1 = p1->shape; - if (p1->class_id == JS_CLASS_ARRAY) { - if (unlikely(!p1->fast_array)) - goto slow_path; - } else if (p1->class_id == JS_CLASS_OBJECT) { - if (unlikely(sh1->has_small_array_index)) - goto slow_path; - } else { - goto slow_path; - } - p1 = sh1->proto; } /* add element */ return add_fast_array_element(ctx, p, val, flags); @@ -18669,9 +18700,32 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, idx = JS_VALUE_GET_INT(sp[-2]); if (unlikely(p->class_id != JS_CLASS_ARRAY)) goto put_array_el_slow_path; - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto put_array_el_slow_path; - set_value(ctx, &p->u.array.u.values[idx], sp[-1]); + if (unlikely(idx >= (uint32_t)p->u.array.count)) { + uint32_t new_len, array_len; + if (unlikely(idx != (uint32_t)p->u.array.count || + !p->fast_array || + !p->extensible || + p->shape->proto != JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]) || + !ctx->std_array_prototype)) { + goto put_array_el_slow_path; + } + if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) != JS_TAG_INT)) + goto put_array_el_slow_path; + /* cannot overflow otherwise the length would not be an integer */ + new_len = idx + 1; + if (unlikely(new_len > p->u.array.u1.size)) + goto put_array_el_slow_path; + array_len = JS_VALUE_GET_INT(p->prop[0].u.value); + if (new_len > array_len) { + if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) + goto put_array_el_slow_path; + p->prop[0].u.value = JS_NewInt32(ctx, new_len); + } + p->u.array.count = new_len; + p->u.array.u.values[idx] = sp[-1]; + } else { + set_value(ctx, &p->u.array.u.values[idx], sp[-1]); + } JS_FreeValue(ctx, sp[-3]); sp -= 3; } else { @@ -19045,11 +19099,42 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, } BREAK; CASE(OP_post_inc): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto post_inc_slow; + sp[0] = JS_NewInt32(ctx, val + 1); + } else { + post_inc_slow: + sf->cur_pc = pc; + if (js_post_inc_slow(ctx, sp, opcode)) + goto exception; + } + sp++; + } + BREAK; CASE(OP_post_dec): - sf->cur_pc = pc; - if (js_post_inc_slow(ctx, sp, opcode)) - goto exception; - sp++; + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto post_dec_slow; + sp[0] = JS_NewInt32(ctx, val - 1); + } else { + post_dec_slow: + sf->cur_pc = pc; + if (js_post_inc_slow(ctx, sp, opcode)) + goto exception; + } + sp++; + } BREAK; CASE(OP_inc_loc): { @@ -54306,7 +54391,8 @@ static void JS_AddIntrinsicBasicObjects(JSContext *ctx) JS_PROP_INITIAL_HASH_SIZE, 1); add_shape_property(ctx, &ctx->array_shape, NULL, JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_LENGTH); - + ctx->std_array_prototype = TRUE; + /* XXX: could test it on first context creation to ensure that no new atoms are created in JS_AddIntrinsicBasicObjects(). It is necessary to avoid useless renumbering of atoms after
