This is an automated email from the ASF dual-hosted git repository. nickva pushed a commit to branch update-quickjs-jun-2-2026 in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 72694d78e27e1d81a6ff6724300604ee57a83bd3 Author: Nick Vatamaniuc <[email protected]> AuthorDate: Tue Jun 2 00:23:35 2026 -0400 QuickJS Update: 10-40% speedup, compilation fixes * Add custom malloc for small blocks /w an 11% speedup in v8 bench (as claimed upstream). My own local `make microbench` showed a 13458 nsec -> 7385 nsec improvement which is closer to 40%(!) https://github.com/bellard/quickjs/commit/99e9181d117a0cb65d37cb11a55a34bf29853309 * Add Uint8Array base64/hex methods (initial patch by saghul) https://github.com/bellard/quickjs/commit/e182e3df5c51017a7faa97a654b3f2b9c6330448 * Fix `memcpy()` undefined behavior if a pointer is NULL with zero size https://github.com/bellard/quickjs/commit/1f50b39e99d12042fc2e2ce3b754719fa42af753 * Fixed error handling in `os.exec()` https://github.com/bellard/quickjs/commit/b1b4733bd9f75509ffbf1243b754a8e61278b790 * Add missing NULL pointer check https://github.com/bellard/quickjs/issues/504 * Use __EMSCRIPTEN__ define instead of EMSCRIPTEN https://github.com/bellard/quickjs/commit/9b90125510d8a57bb1b687a798237d8b7133fb49 * Heap-Use-After-Free in Atomics Operations via ResizableArrayBuffer https://github.com/bellard/quickjs/issues/508 * Expose Unicode version in C API https://github.com/bellard/quickjs/issues/509 * Fix member access on non-decimal numeric literals https://github.com/bellard/quickjs/commit/66afb7234cf53957094509014cc5e97fa152b0f4 * Avoid initializer-string warning for the digits array https://github.com/bellard/quickjs/commit/d38eea93365879b5bff8b649d8dbb4dc1b849e56 WARNING: `couchjs.c` inlines refcount helpers `(JS_FreeValue/JS_DupValue/...)` from `quickjs.h`. Rebar port compiler rebuilds couchjs.o when couchjs.c changes not when included header changes. This update alters the header (refcount layout) so a stale couchjs.o would mismatch the new libquickjs.a and corrupt refcounts. TL;DR do clean -xffd build with this change. --- .../patches/01-spidermonkey-185-mode.patch | 6 +- src/couch_quickjs/patches/02-test262-errors.patch | 4 +- src/couch_quickjs/quickjs/Changelog | 1 + src/couch_quickjs/quickjs/cutils.c | 2 +- src/couch_quickjs/quickjs/libunicode.c | 3 +- src/couch_quickjs/quickjs/libunicode.h | 5 + src/couch_quickjs/quickjs/quickjs-atom.h | 4 + src/couch_quickjs/quickjs/quickjs-libc.c | 4 +- src/couch_quickjs/quickjs/quickjs.c | 1914 ++++++++++++++++---- src/couch_quickjs/quickjs/quickjs.h | 15 +- src/couch_quickjs/quickjs/test262.conf | 2 +- src/couch_quickjs/quickjs/test262_errors.txt | 2 - 12 files changed, 1586 insertions(+), 376 deletions(-) diff --git a/src/couch_quickjs/patches/01-spidermonkey-185-mode.patch b/src/couch_quickjs/patches/01-spidermonkey-185-mode.patch index 65f473856..db9c45430 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 2026-03-23 13:45:52 -+++ quickjs/quickjs.c 2026-03-23 17:19:56 -@@ -31462,10 +31462,24 @@ +--- quickjs-master/quickjs.c 2026-05-21 13:02:15 ++++ quickjs/quickjs.c 2026-06-02 00:18:00 +@@ -31848,10 +31848,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 db454f201..10a692f12 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 2026-03-23 13:45:52 -+++ quickjs/test262_errors.txt 2026-03-23 17:19:56 +--- quickjs-master/test262_errors.txt 2026-05-21 13:02:15 ++++ quickjs/test262_errors.txt 2026-06-02 00:18:00 @@ -23,6 +23,8 @@ test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-export-star-as-from-and-import-star-as-and-export.js:74: SyntaxError: export 'foo' in module 'test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-import-star-as-and-export.js' is ambiguous test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-export-star-as-from.js:75: SyntaxError: export 'foo' in module 'test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-export-star-as-from.js' is ambiguous diff --git a/src/couch_quickjs/quickjs/Changelog b/src/couch_quickjs/quickjs/Changelog index 3c08f0c58..697193f3e 100644 --- a/src/couch_quickjs/quickjs/Changelog +++ b/src/couch_quickjs/quickjs/Changelog @@ -1,3 +1,4 @@ +- added custom malloc for small blocks (11% faster on bench-v8) - micro optimizations (30% faster on bench-v8) - added resizable array buffers - added ArrayBuffer.prototype.transfer diff --git a/src/couch_quickjs/quickjs/cutils.c b/src/couch_quickjs/quickjs/cutils.c index 52ff1649b..6a3aeca45 100644 --- a/src/couch_quickjs/quickjs/cutils.c +++ b/src/couch_quickjs/quickjs/cutils.c @@ -315,7 +315,7 @@ int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp) #if 0 -#if defined(EMSCRIPTEN) || defined(__ANDROID__) +#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) static void *rqsort_arg; static int (*rqsort_cmp)(const void *, const void *, void *); diff --git a/src/couch_quickjs/quickjs/libunicode.c b/src/couch_quickjs/quickjs/libunicode.c index 0c510ccb1..0b7b6d0b1 100644 --- a/src/couch_quickjs/quickjs/libunicode.c +++ b/src/couch_quickjs/quickjs/libunicode.c @@ -1189,7 +1189,8 @@ int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, goto not_latin1; } buf = (int *)dbuf->buf; - memcpy(buf, src, src_len * sizeof(int)); + if (src_len != 0) + memcpy(buf, src, src_len * sizeof(int)); *pdst = (uint32_t *)buf; return src_len; not_latin1: ; diff --git a/src/couch_quickjs/quickjs/libunicode.h b/src/couch_quickjs/quickjs/libunicode.h index 5b02c82b4..c2fdeac89 100644 --- a/src/couch_quickjs/quickjs/libunicode.h +++ b/src/couch_quickjs/quickjs/libunicode.h @@ -26,6 +26,11 @@ #include <stdint.h> +/* unicode standard version */ +#define LIBUNICODE_UNICODE_VERSION_MAJOR 17 +#define LIBUNICODE_UNICODE_VERSION_MINOR 0 +#define LIBUNICODE_UNICODE_VERSION_PATCH 0 + /* define it to include all the unicode tables (40KB larger) */ #define CONFIG_ALL_UNICODE diff --git a/src/couch_quickjs/quickjs/quickjs-atom.h b/src/couch_quickjs/quickjs/quickjs-atom.h index dc64f2fb0..13c1ccdb9 100644 --- a/src/couch_quickjs/quickjs/quickjs-atom.h +++ b/src/couch_quickjs/quickjs/quickjs-atom.h @@ -190,6 +190,10 @@ DEF(unicodeSets, "unicodeSets") DEF(not_equal, "not-equal") DEF(timed_out, "timed-out") DEF(ok, "ok") +DEF(toISOString, "toISOString") +DEF(alphabet, "alphabet") +DEF(lastChunkHandling, "lastChunkHandling") +DEF(omitPadding, "omitPadding") /* */ DEF(toJSON, "toJSON") DEF(maxByteLength, "maxByteLength") diff --git a/src/couch_quickjs/quickjs/quickjs-libc.c b/src/couch_quickjs/quickjs/quickjs-libc.c index c24b6d53e..84f990d94 100644 --- a/src/couch_quickjs/quickjs/quickjs-libc.c +++ b/src/couch_quickjs/quickjs/quickjs-libc.c @@ -3309,7 +3309,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, for(i = 0; i < exec_argc; i++) JS_FreeCString(ctx, exec_argv[i]); js_free(ctx, exec_argv); - if (envp != environ) { + if (envp && envp != environ) { char **p; p = envp; while (*p != NULL) { @@ -3879,7 +3879,7 @@ void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)) #define OS_PLATFORM "win32" #elif defined(__APPLE__) #define OS_PLATFORM "darwin" -#elif defined(EMSCRIPTEN) +#elif defined(__EMSCRIPTEN__) #define OS_PLATFORM "js" #else #define OS_PLATFORM "linux" diff --git a/src/couch_quickjs/quickjs/quickjs.c b/src/couch_quickjs/quickjs/quickjs.c index 972f9787b..67b574917 100644 --- a/src/couch_quickjs/quickjs/quickjs.c +++ b/src/couch_quickjs/quickjs/quickjs.c @@ -49,7 +49,7 @@ #define OPTIMIZE 1 #define SHORT_OPCODES 1 -#if defined(EMSCRIPTEN) +#if defined(__EMSCRIPTEN__) #define DIRECT_DISPATCH 0 #else #define DIRECT_DISPATCH 1 @@ -68,11 +68,11 @@ /* define to include Atomics.* operations which depend on the OS threads */ -#if !defined(EMSCRIPTEN) +#if !defined(__EMSCRIPTEN__) #define CONFIG_ATOMICS #endif -#if !defined(EMSCRIPTEN) +#if !defined(__EMSCRIPTEN__) /* enable stack limitation */ #define CONFIG_STACK_CHECK #endif @@ -236,9 +236,80 @@ typedef enum { typedef enum OPCodeEnum OPCodeEnum; -struct JSRuntime { +/* JS malloc */ + +#define JS_MALLOC_ALIGN 8 +#define JS_MALLOC_ARENA_SIZE 4096 +#define JS_MALLOC_BLOCK_SIZE_COUNT 31 +#define JS_MALLOC_MIN_SMALL_SIZE 16 +#define JS_MALLOC_MAX_SMALL_SIZE 512 + +/* allow iteration among the allocated blocks. Currently not used. May + be used to suppress the memory overhead of JSGCObjectHeader */ +//#define JS_MALLOC_USE_ITER + +#define FREE_NIL 0xffff + +/* 8 byte header */ +/* Notes: + - the header is necessary at least to recover a pointer to + JSMallocArena because we don't want to enforce a page + alignment on the system malloc(). + - could store the block offset instead of (block_idx, + block_size_idx), but it would require a division to recover the block + index. +*/ +typedef struct JSMallocBlockHeader { + union { + uint16_t block_idx; /* FREE_NIL if large block */ + uint16_t free_next; /* FREE_NIL if none */ + } u; + uint8_t block_size_idx; + uint8_t gc_obj_type : 7; + uint8_t mark : 1; + int ref_count; + __attribute__((aligned(JS_MALLOC_ALIGN))) uint8_t user_data[]; +} JSMallocBlockHeader; + +typedef struct JSMallocLargeBlockHeader { +#ifdef JS_MALLOC_USE_ITER + struct list_head link; +#endif + JSMallocBlockHeader header; +} JSMallocLargeBlockHeader; + +typedef struct { + struct list_head free_link; + struct list_head link; + uint8_t block_size_idx; + uint16_t n_used_blocks; /* number of allocated blocks */ + uint16_t n_blocks; /* total number of blocks */ + uint16_t first_free_block; /* FREE_NIL if none */ +#ifdef JS_MALLOC_USE_ITER + /* bit set to 1 for allocated block */ + uint32_t bitmap[((JS_MALLOC_ARENA_SIZE / JS_MALLOC_MIN_SMALL_SIZE) + 31) / 32]; +#endif + /* n_blocks memory blocks of identical size */ + __attribute__((aligned(JS_MALLOC_ALIGN))) uint8_t blocks[]; +} JSMallocArena; + +typedef struct { + struct list_head arena_list[JS_MALLOC_BLOCK_SIZE_COUNT]; /* list of JSMallocArena.link (all arenas) */ + struct list_head free_arena_list[JS_MALLOC_BLOCK_SIZE_COUNT]; /* list of JSMallocArena.free_link (arenas where n_used_blocks < n_blocks) */ +#ifdef JS_MALLOC_USE_ITER + struct list_head large_block_list; /* list of JSMallocLargeBlockHeader.link */ +#endif + __attribute__((aligned(JS_MALLOC_ALIGN))) uint8_t zero_size_block[sizeof(JSMallocBlockHeader)]; + + /* callbacks to the host malloc */ JSMallocFunctions mf; JSMallocState malloc_state; +} JSMallocContext; + +/* end JS Malloc */ + +struct JSRuntime { + JSMallocContext malloc_ctx; const char *rt_info; int atom_hash_size; /* power of two */ @@ -354,12 +425,6 @@ typedef enum { reference count that can reference other GC objects. JS Objects are a particular type of GC object. */ struct JSGCObjectHeader { - int ref_count; /* must come first, 32-bit */ - JSGCObjectTypeEnum gc_obj_type : 4; - 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; }; @@ -375,16 +440,10 @@ typedef struct { } JSWeakRefHeader; typedef struct JSVarRef { - union { - JSGCObjectHeader header; /* must come first */ - struct { - int __gc_ref_count; /* corresponds to header.ref_count */ - uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ - uint8_t is_detached; - uint8_t is_lexical; /* only used with global variables */ - uint8_t is_const; /* only used with global variables */ - }; - }; + JSGCObjectHeader header; /* must come first */ + uint8_t is_detached; + uint8_t is_lexical; /* only used with global variables */ + uint8_t is_const; /* only used with global variables */ JSValue *pvalue; /* pointer to the value, either on the stack or to 'value' */ union { @@ -421,7 +480,6 @@ typedef uint128_t js_dlimb_t; #endif typedef struct JSBigInt { - JSRefCountHeader header; /* must come first, 32-bit */ uint32_t len; /* number of limbs, >= 1 */ js_limb_t tab[]; /* two's complement representation, always normalized so that 'len' is the minimum @@ -515,7 +573,6 @@ typedef enum { #define JS_ATOM_HASH_PRIVATE JS_ATOM_HASH_MASK struct JSString { - JSRefCountHeader header; /* must come first, 32-bit */ uint32_t len : 31; uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ /* for JS_ATOM_TYPE_SYMBOL: hash = weakref_count, atom_type = 3, @@ -534,7 +591,6 @@ struct JSString { }; typedef struct JSStringRope { - JSRefCountHeader header; /* must come first, 32-bit */ uint32_t len; uint8_t is_wide_char; /* 0 = 8 bits, 1 = 16 bits characters */ uint8_t depth; /* max depth of the rope tree */ @@ -908,47 +964,40 @@ typedef struct JSShapeProperty { } JSShapeProperty; struct JSShape { - /* hash table of size hash_mask + 1 before the start of the - structure (see prop_hash_end()). */ JSGCObjectHeader header; /* true if the shape is inserted in the shape hash table. If not, JSShape.hash is not valid */ uint8_t is_hashed; uint32_t hash; /* current hash value */ - uint32_t prop_hash_mask; + uint32_t prop_hash_mask; /* >= 2 */ int prop_size; /* allocated properties */ int prop_count; /* include deleted properties */ int deleted_prop_count; JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */ JSObject *proto; - JSShapeProperty prop[0]; /* prop_size elements */ + uint32_t hash_table[]; /* prop_hash_mask + 1 elements */ + /* followed by JSShapeProperty prop[prop_size]; */ }; struct JSObject { - union { - JSGCObjectHeader header; - struct { - int __gc_ref_count; /* corresponds to header.ref_count */ - uint8_t __gc_mark : 7; /* corresponds to header.mark/gc_obj_type */ - /* 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 is_std_array_prototype : 1; - - uint8_t extensible : 1; - uint8_t free_mark : 1; /* only used when freeing objects with cycles */ - uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ - uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_MAPPED_ARGUMENTS and typed arrays) */ - uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ - uint8_t has_immutable_prototype : 1; /* cannot modify the prototype */ - uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ - uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ - uint16_t class_id; /* see JS_CLASS_x */ - }; - }; + JSGCObjectHeader header; + /* 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 is_std_array_prototype : 1; + + uint8_t extensible : 1; + uint8_t free_mark : 1; /* only used when freeing objects with cycles */ + uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ + uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_MAPPED_ARGUMENTS and typed arrays) */ + uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ + uint8_t has_immutable_prototype : 1; /* cannot modify the prototype */ + uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ + uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ + uint16_t class_id; /* see JS_CLASS_x */ /* count the number of weak references to this object. The object structure is freed only if header.ref_count = 0 and weakref_count = 0 */ @@ -1345,6 +1394,10 @@ static JSValue js_error_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); static JSVarRef *js_global_object_find_uninitialized_var(JSContext *ctx, JSObject *p, JSAtom atom, BOOL is_lexical); +static int typed_array_init(JSContext *ctx, JSValueConst obj, + JSValue buffer, uint64_t offset, uint64_t len, + BOOL track_rab); + static const JSClassExoticMethods js_arguments_exotic_methods; static const JSClassExoticMethods js_string_exotic_methods; @@ -1352,62 +1405,420 @@ static const JSClassExoticMethods js_proxy_exotic_methods; static const JSClassExoticMethods js_module_ns_exotic_methods; static JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT; +/* JS malloc */ + +/* max overhead for size >= 64: 12.5% */ +static const uint16_t js_malloc_block_sizes[JS_MALLOC_BLOCK_SIZE_COUNT] = { + 16, + 24, + 32, + 40, + 48, + 56, + 64, + 72, + 80, + 88, + 96, + 104, + 112, + 120, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 288, + 320, + 352, + 384, + 416, + 448, + 480, + 512, +}; + +static int get_block_size_index(size_t size) +{ + if (size <= 16) { + return 0; + } else if (size <= 128) { + return (size + 7) / 8 - 2; + } else if (size <= 256) { + return (size + 15) / 16 + 6; + } else if (size <= 512) { + return (size + 31) / 32 + 14; + } else { + return JS_MALLOC_BLOCK_SIZE_COUNT; + } +} + +static JSMallocBlockHeader *get_zero_size_block(JSMallocContext *s) +{ + return (JSMallocBlockHeader *)s->zero_size_block; +} + +static void js_malloc_init(JSMallocContext *s) +{ + int i; + memset(s, 0, sizeof(*s)); + get_zero_size_block(s)->u.block_idx = FREE_NIL; + for(i = 0; i < JS_MALLOC_BLOCK_SIZE_COUNT; i++) { + init_list_head(&s->arena_list[i]); + init_list_head(&s->free_arena_list[i]); + } +#ifdef JS_MALLOC_USE_ITER + init_list_head(&s->large_block_list); +#endif +} + +static void *get_arena_block(JSMallocArena *ar, unsigned int idx, unsigned int block_size) +{ + return ar->blocks + idx * block_size; +} + +static inline JSMallocBlockHeader *js_rc(void *ptr) +{ + return container_of(ptr, JSMallocBlockHeader, user_data); +} + +static no_inline JSMallocArena *js_malloc_new_arena(JSMallocContext *s, int block_size_idx) +{ + JSMallocBlockHeader *b; + JSMallocArena *ar; + int n_blocks, block_size, i; + + block_size = js_malloc_block_sizes[block_size_idx]; + n_blocks = (JS_MALLOC_ARENA_SIZE - sizeof(JSMallocArena)) / block_size; + ar = s->mf.js_malloc(&s->malloc_state, sizeof(JSMallocArena) + n_blocks * block_size); + if (!ar) + return NULL; + + ar->block_size_idx = block_size_idx; + ar->n_blocks = n_blocks; + ar->n_used_blocks = 0; + ar->first_free_block = 0; +#ifdef JS_MALLOC_USE_ITER + { + int n_bitmap_words = (n_blocks + 31) / 32; + for(i = 0; i < n_bitmap_words; i++) + ar->bitmap[i] = 0; + } +#endif + for(i = 0; i < n_blocks - 1; i++) { + b = get_arena_block(ar, i, block_size); + b->u.free_next = i + 1; + b->block_size_idx = block_size_idx; + } + b = get_arena_block(ar, n_blocks - 1, block_size); + b->u.free_next = FREE_NIL; + b->block_size_idx = block_size_idx; + + /* add to the head */ + list_add(&ar->link, &s->arena_list[block_size_idx]); + list_add(&ar->free_link, &s->free_arena_list[block_size_idx]); + return ar; +} + +static no_inline void *js_malloc_large(JSMallocContext *s, size_t size) +{ + JSMallocLargeBlockHeader *b; + b = s->mf.js_malloc(&s->malloc_state, sizeof(JSMallocLargeBlockHeader) + size); + if (!b) + return NULL; + b->header.u.block_idx = FREE_NIL; + b->header.block_size_idx = 0xff; /* fail safe */ +#ifdef JS_MALLOC_USE_ITER + list_add_tail(&b->link, &s->large_block_list); +#endif + return b->header.user_data; +} + +static void *__js_malloc(JSMallocContext *s, size_t size) +{ + size_t total_size; + if (unlikely(size == 0)) { + JSMallocBlockHeader *b = get_zero_size_block(s); + return b->user_data; + } else { + total_size = ((size + JS_MALLOC_ALIGN - 1) & ~(JS_MALLOC_ALIGN - 1)) + + sizeof(JSMallocBlockHeader); + if (total_size <= JS_MALLOC_MAX_SMALL_SIZE) { /* TEST */ + int block_size_idx; + unsigned int block_idx, block_size; + JSMallocBlockHeader *b; + JSMallocArena *ar; + struct list_head *el, *head; + + block_size_idx = get_block_size_index(total_size); + block_size = js_malloc_block_sizes[block_size_idx]; + head = &s->free_arena_list[block_size_idx]; + el = head->next; + if (unlikely(el == head)) { + ar = js_malloc_new_arena(s, block_size_idx); + if (!ar) + return NULL; + } else { + ar = list_entry(el, JSMallocArena, free_link); + } + block_idx = ar->first_free_block; + b = get_arena_block(ar, ar->first_free_block, block_size); + ar->first_free_block = b->u.free_next; + b->u.block_idx = block_idx; + ar->n_used_blocks++; + if (unlikely(ar->n_used_blocks == ar->n_blocks)) { + list_del(&ar->free_link); + } +#ifdef JS_MALLOC_USE_ITER + ar->bitmap[block_idx / 32] |= 1 << (block_idx % 32); +#endif + return b->user_data; + } else { + return js_malloc_large(s, size); + } + } +} + +static void __js_free(JSMallocContext *s, void *ptr) +{ + JSMallocBlockHeader *b; + + if (!ptr) + return; + b = container_of(ptr, JSMallocBlockHeader, user_data); + if (unlikely(b->u.block_idx == FREE_NIL)) { + /* large or zero size block */ + if (b == get_zero_size_block(s)) { + /* nothing to do */ + } else { + JSMallocLargeBlockHeader *lb = container_of(ptr, JSMallocLargeBlockHeader, header.user_data); +#ifdef JS_MALLOC_USE_ITER + list_del(&lb->link); +#endif + s->mf.js_free(&s->malloc_state, lb); + } + } else { + unsigned int block_idx = b->u.block_idx; + unsigned int block_size_idx = b->block_size_idx; + unsigned int block_size = js_malloc_block_sizes[block_size_idx]; + JSMallocArena *ar = (JSMallocArena *)((uint8_t *)b - block_size * block_idx - sizeof(JSMallocArena)); + b->u.free_next = ar->first_free_block; + ar->first_free_block = block_idx; +#ifdef JS_MALLOC_USE_ITER + ar->bitmap[block_idx / 32] &= ~(1 << (block_idx % 32)); +#endif + /* add back to the free list if needed */ + if (unlikely(ar->n_used_blocks == ar->n_blocks)) { + list_add(&ar->free_link, &s->free_arena_list[block_size_idx]); + } + ar->n_used_blocks--; + if (unlikely(ar->n_used_blocks == 0)) { + list_del(&ar->link); + list_del(&ar->free_link); + s->mf.js_free(&s->malloc_state, ar); + } + } +} + +static void *__js_realloc(JSMallocContext *s, void *ptr, size_t size) +{ + JSMallocBlockHeader *b; + if (ptr == NULL) { + return __js_malloc(s, size); + } else if (size == 0) { + __js_free(s, ptr); + return NULL; + } + b = container_of(ptr, JSMallocBlockHeader, user_data); + if (b->u.block_idx == FREE_NIL) { + if (b == get_zero_size_block(s)) { + return __js_malloc(s, size); + } else { + JSMallocLargeBlockHeader *lb, *new_lb; + lb = container_of(ptr, JSMallocLargeBlockHeader, header.user_data); +#ifdef JS_MALLOC_USE_ITER + list_del(&lb->link); +#endif + new_lb = s->mf.js_realloc(&s->malloc_state, lb, sizeof(JSMallocLargeBlockHeader) + size); + if (!new_lb) { +#ifdef JS_MALLOC_USE_ITER + /* add again in the list */ + list_add_tail(&lb->link, &s->large_block_list); +#endif + return NULL; + } + new_lb->header.u.block_idx = FREE_NIL; + new_lb->header.block_size_idx = 0xff; /* fail safe */ +#ifdef JS_MALLOC_USE_ITER + list_add_tail(&new_lb->link, &s->large_block_list); +#endif + return new_lb->header.user_data; + } + } else { + unsigned int block_size_idx = b->block_size_idx; + size_t block_size = js_malloc_block_sizes[block_size_idx]; + size_t total_size, old_size; + void *new_ptr; + JSMallocBlockHeader *new_b; + + total_size = ((size + JS_MALLOC_ALIGN - 1) & ~(JS_MALLOC_ALIGN - 1)) + + sizeof(JSMallocBlockHeader); + if (total_size <= block_size) + return ptr; + new_ptr = __js_malloc(s, size); + if (!new_ptr) + return NULL; + new_b = container_of(new_ptr, JSMallocBlockHeader, user_data); + /* copy the GC data */ + new_b->gc_obj_type = b->gc_obj_type; + new_b->mark = b->mark; + new_b->ref_count = b->ref_count; + /* copy the data */ + old_size = block_size - sizeof(JSMallocBlockHeader); + if (size > old_size) + size = old_size; + memcpy(new_ptr, ptr, size); + __js_free(s, ptr); + return new_ptr; + } +} + +static size_t __js_malloc_usable_size(JSMallocContext *s, const char *ptr) +{ + JSMallocBlockHeader *b; + if (!ptr) + return 0; + b = container_of(ptr, JSMallocBlockHeader, user_data); + if (b->u.block_idx == FREE_NIL) { + if (b == get_zero_size_block(s)) { + return 0; + } else { + JSMallocLargeBlockHeader *lb; + size_t size; + lb = container_of(ptr, JSMallocLargeBlockHeader, header.user_data); + if (s->mf.js_malloc_usable_size) { + size = s->mf.js_malloc_usable_size(lb); + if (size != 0) + size -= sizeof(JSMallocLargeBlockHeader); + return size; + } else { + return 0; + } + } + } else { + size_t block_size = js_malloc_block_sizes[b->block_size_idx]; + return block_size - sizeof(*b); + } +} + +static __maybe_unused void js_malloc_dump_arenas(JSMallocContext *s) +{ + struct list_head *el; + int block_size_idx; + + printf("%20s %10s %10s\n", "PTR", "BLK_SIZE", "ALLOC"); + for(block_size_idx = 0; block_size_idx < JS_MALLOC_BLOCK_SIZE_COUNT; block_size_idx++) { + int block_size = js_malloc_block_sizes[block_size_idx]; + list_for_each(el, &s->arena_list[block_size_idx]) { + JSMallocArena *ar = list_entry(el, JSMallocArena, link); + printf("%20p %10u %9.1f%%\n", + ar, block_size, + (double)ar->n_used_blocks / ar->n_blocks * 100); + } + } +} + +#ifdef JS_MALLOC_USE_ITER +typedef void JSMallocIterFunc(void *opaque, void *ptr); + +/* iterate thru allocated blocks. The allocated block list should not + be modified while iterating. */ +static __maybe_unused void js_malloc_iter(JSMallocContext *s, JSMallocIterFunc *iter_func, void *iter_opaque) +{ + struct list_head *el; + int block_size_idx; + int i, j, n_words; + uint32_t bmp; + + for(block_size_idx = 0; block_size_idx < JS_MALLOC_BLOCK_SIZE_COUNT; block_size_idx++) { + unsigned int block_size = js_malloc_block_sizes[block_size_idx]; + list_for_each(el, &s->arena_list[block_size_idx]) { + JSMallocArena *ar = list_entry(el, JSMallocArena, link); + n_words = (ar->n_blocks + 31) / 32; + for(i = 0; i < n_words; i++) { + bmp = ar->bitmap[i]; + while (bmp != 0) { + j = ctz32(bmp); + bmp &= ~(1 << j); + iter_func(iter_opaque, get_arena_block(ar, i * 32+ j, block_size)); + } + } + } + } + list_for_each(el, &s->large_block_list) { + JSMallocLargeBlockHeader *lb = list_entry(el, JSMallocLargeBlockHeader, link); + iter_func(iter_opaque, lb->header.user_data); + } +} +#endif + +/* end JS malloc */ + static void js_trigger_gc(JSRuntime *rt, size_t size) { BOOL force_gc; #ifdef FORCE_GC_AT_MALLOC force_gc = TRUE; #else - force_gc = ((rt->malloc_state.malloc_size + size) > + force_gc = ((rt->malloc_ctx.malloc_state.malloc_size + size) > rt->malloc_gc_threshold); #endif if (force_gc) { #ifdef DUMP_GC printf("GC: size=%" PRIu64 "\n", - (uint64_t)rt->malloc_state.malloc_size); + (uint64_t)rt->malloc_ctx.malloc_state.malloc_size); #endif JS_RunGC(rt); - rt->malloc_gc_threshold = rt->malloc_state.malloc_size + - (rt->malloc_state.malloc_size >> 1); + rt->malloc_gc_threshold = rt->malloc_ctx.malloc_state.malloc_size + + (rt->malloc_ctx.malloc_state.malloc_size >> 1); } } -static size_t js_malloc_usable_size_unknown(const void *ptr) -{ - return 0; -} - -void *js_malloc_rt(JSRuntime *rt, size_t size) +extern force_inline void *js_malloc_rt(JSRuntime *rt, size_t size) { - return rt->mf.js_malloc(&rt->malloc_state, size); + return __js_malloc(&rt->malloc_ctx, size); } -void js_free_rt(JSRuntime *rt, void *ptr) +extern force_inline void js_free_rt(JSRuntime *rt, void *ptr) { - rt->mf.js_free(&rt->malloc_state, ptr); + __js_free(&rt->malloc_ctx, ptr); } -void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size) +extern force_inline void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size) { - return rt->mf.js_realloc(&rt->malloc_state, ptr, size); + return __js_realloc(&rt->malloc_ctx, ptr, size); } -size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr) +extern force_inline size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr) { - return rt->mf.js_malloc_usable_size(ptr); + return __js_malloc_usable_size(&rt->malloc_ctx, ptr); } void *js_mallocz_rt(JSRuntime *rt, size_t size) { void *ptr; ptr = js_malloc_rt(rt, size); - if (!ptr) + if (unlikely(!ptr)) return NULL; return memset(ptr, 0, size); } /* Throw out of memory in case of error */ -void *js_malloc(JSContext *ctx, size_t size) +extern force_inline void *js_malloc(JSContext *ctx, size_t size) { void *ptr; ptr = js_malloc_rt(ctx->rt, size); @@ -1419,7 +1830,7 @@ void *js_malloc(JSContext *ctx, size_t size) } /* Throw out of memory in case of error */ -void *js_mallocz(JSContext *ctx, size_t size) +extern force_inline void *js_mallocz(JSContext *ctx, size_t size) { void *ptr; ptr = js_mallocz_rt(ctx->rt, size); @@ -1430,7 +1841,7 @@ void *js_mallocz(JSContext *ctx, size_t size) return ptr; } -void js_free(JSContext *ctx, void *ptr) +extern force_inline void js_free(JSContext *ctx, void *ptr) { js_free_rt(ctx->rt, ptr); } @@ -1525,7 +1936,7 @@ static void *js_realloc_bytecode_rt(void *opaque, void *ptr, size_t size) avoid some overflows. */ return NULL; } else { - return rt->mf.js_realloc(&rt->malloc_state, ptr, size); + return js_realloc_rt(rt, ptr, size); } } @@ -1657,12 +2068,9 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) if (!rt) return NULL; memset(rt, 0, sizeof(*rt)); - rt->mf = *mf; - if (!rt->mf.js_malloc_usable_size) { - /* use dummy function if none provided */ - rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown; - } - rt->malloc_state = ms; + js_malloc_init(&rt->malloc_ctx); + rt->malloc_ctx.mf = *mf; + rt->malloc_ctx.malloc_state = ms; rt->malloc_gc_threshold = 256 * 1024; init_list_head(&rt->context_list); @@ -1723,7 +2131,7 @@ static size_t js_def_malloc_usable_size(const void *ptr) return malloc_size(ptr); #elif defined(_WIN32) return _msize((void *)ptr); -#elif defined(EMSCRIPTEN) +#elif defined(__EMSCRIPTEN__) return 0; #elif defined(__linux__) || defined(__GLIBC__) return malloc_usable_size((void *)ptr); @@ -1803,7 +2211,7 @@ JSRuntime *JS_NewRuntime(void) void JS_SetMemoryLimit(JSRuntime *rt, size_t limit) { - rt->malloc_state.malloc_limit = limit; + rt->malloc_ctx.malloc_state.malloc_limit = limit; } /* use -1 to disable automatic GC */ @@ -1910,7 +2318,7 @@ int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx) JS_FreeValue(ctx, res); js_free(ctx, e); if (pctx) { - if (ctx->header.ref_count > 1) + if (js_rc(ctx)->ref_count > 1) *pctx = ctx; else *pctx = NULL; @@ -1941,7 +2349,7 @@ static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char str = js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char); if (unlikely(!str)) return NULL; - str->header.ref_count = 1; + js_rc(str)->ref_count = 1; str->is_wide_char = is_wide_char; str->len = max_len; str->atom_type = 0; @@ -1967,7 +2375,7 @@ static JSString *js_alloc_string(JSContext *ctx, int max_len, int is_wide_char) /* same as JS_FreeValueRT() but faster */ static inline void js_free_string(JSRuntime *rt, JSString *str) { - if (--str->header.ref_count <= 0) { + if (--js_rc(str)->ref_count <= 0) { if (str->atom_type) { JS_FreeAtomStruct(rt, str); } else { @@ -2016,14 +2424,14 @@ void JS_FreeRuntime(JSRuntime *rt) referenced externally */ list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); - p->mark = 0; + js_rc(p)->mark = 0; } gc_decref(rt); header_done = FALSE; list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); - if (p->ref_count != 0) { + if (js_rc(p)->ref_count != 0) { if (!header_done) { printf("Object leaks:\n"); JS_DumpObjectHeader(rt); @@ -2036,7 +2444,7 @@ void JS_FreeRuntime(JSRuntime *rt) count = 0; list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); - if (p->ref_count == 0) { + if (js_rc(p)->ref_count == 0) { count++; } } @@ -2064,7 +2472,7 @@ void JS_FreeRuntime(JSRuntime *rt) for(i = 0; i < rt->atom_size; i++) { JSAtomStruct *p = rt->atom_array[i]; if (!atom_is_free(p) /* && p->str*/) { - if (i >= JS_ATOM_END || p->header.ref_count != 1) { + if (i >= JS_ATOM_END || js_rc(p)->ref_count != 1) { if (!header_done) { header_done = TRUE; if (rt->rt_info) { @@ -2078,7 +2486,7 @@ void JS_FreeRuntime(JSRuntime *rt) if (rt->rt_info) { printf(" "); } else { - printf(" %6u %6u ", i, p->header.ref_count); + printf(" %6u %6u ", i, js_rc(p)->ref_count); } switch (p->atom_type) { case JS_ATOM_TYPE_STRING: @@ -2102,7 +2510,7 @@ void JS_FreeRuntime(JSRuntime *rt) break; } if (rt->rt_info) { - printf(":%u", p->header.ref_count); + printf(":%u", js_rc(p)->ref_count); } else { printf("\n"); } @@ -2141,11 +2549,11 @@ void JS_FreeRuntime(JSRuntime *rt) if (rt->rt_info) { printf(" "); } else { - printf(" %6u ", str->header.ref_count); + printf(" %6u ", js_rc(str)->ref_count); } JS_DumpString(rt, str); if (rt->rt_info) { - printf(":%u", str->header.ref_count); + printf(":%u", js_rc(str)->ref_count); } else { printf("\n"); } @@ -2156,7 +2564,7 @@ void JS_FreeRuntime(JSRuntime *rt) printf("\n"); } { - JSMallocState *s = &rt->malloc_state; + JSMallocState *s = &rt->malloc_ctx.malloc_state; if (s->malloc_count > 1) { if (rt->rt_info) printf("%s:1: ", rt->rt_info); @@ -2168,8 +2576,8 @@ void JS_FreeRuntime(JSRuntime *rt) #endif { - JSMallocState ms = rt->malloc_state; - rt->mf.js_free(&ms, rt); + JSMallocState ms = rt->malloc_ctx.malloc_state; + rt->malloc_ctx.mf.js_free(&ms, rt); } } @@ -2181,7 +2589,7 @@ JSContext *JS_NewContextRaw(JSRuntime *rt) ctx = js_mallocz_rt(rt, sizeof(JSContext)); if (!ctx) return NULL; - ctx->header.ref_count = 1; + js_rc(ctx)->ref_count = 1; add_gc_object(rt, &ctx->header, JS_GC_OBJ_TYPE_JS_CONTEXT); ctx->class_proto = js_malloc_rt(rt, sizeof(ctx->class_proto[0]) * @@ -2292,7 +2700,7 @@ static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag) JSContext *JS_DupContext(JSContext *ctx) { - ctx->header.ref_count++; + js_rc(ctx)->ref_count++; return ctx; } @@ -2350,9 +2758,9 @@ void JS_FreeContext(JSContext *ctx) JSRuntime *rt = ctx->rt; int i; - if (--ctx->header.ref_count > 0) + if (--js_rc(ctx)->ref_count > 0) return; - assert(ctx->header.ref_count == 0); + assert(js_rc(ctx)->ref_count == 0); #ifdef DUMP_ATOMS JS_DumpAtoms(ctx->rt); @@ -2584,8 +2992,8 @@ static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p) printf("<null>"); return; } - printf("%d", p->header.ref_count); - sep = (p->header.ref_count == 1) ? '\"' : '\''; + printf("%d", js_rc((void *)p)->ref_count); + sep = (js_rc((void *)p)->ref_count == 1) ? '\"' : '\''; putchar(sep); for(i = 0; i < p->len; i++) { JS_DumpChar(stdout, string_get(p, i), sep); @@ -2693,7 +3101,7 @@ static JSAtom JS_DupAtomRT(JSRuntime *rt, JSAtom v) if (!__JS_AtomIsConst(v)) { p = rt->atom_array[v]; - p->header.ref_count++; + js_rc(p)->ref_count++; } return v; } @@ -2706,7 +3114,7 @@ JSAtom JS_DupAtom(JSContext *ctx, JSAtom v) if (!__JS_AtomIsConst(v)) { rt = ctx->rt; p = rt->atom_array[v]; - p->header.ref_count++; + js_rc(p)->ref_count++; } return v; } @@ -2775,7 +3183,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) i = js_get_atom_index(rt, str); /* reduce string refcount and increase atom's unless constant */ if (__JS_AtomIsConst(i)) - str->header.ref_count--; + js_rc(str)->ref_count--; return i; } /* try and locate an already registered atom */ @@ -2791,7 +3199,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) p->len == len && js_string_memcmp(p, 0, str, 0, len) == 0) { if (!__JS_AtomIsConst(i)) - p->header.ref_count++; + js_rc(p)->ref_count++; goto done; } i = p->hash_next; @@ -2831,7 +3239,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) js_free_rt(rt, new_array); goto fail; } - p->header.ref_count = 1; /* not refcounted */ + js_rc(p)->ref_count = 1; /* not refcounted */ p->atom_type = JS_ATOM_TYPE_SYMBOL; #ifdef DUMP_LEAKS list_add_tail(&p->link, &rt->string_list); @@ -2863,7 +3271,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) 1 - str->is_wide_char); if (unlikely(!p)) goto fail; - p->header.ref_count = 1; + js_rc(p)->ref_count = 1; p->is_wide_char = str->is_wide_char; p->len = str->len; #ifdef DUMP_LEAKS @@ -2877,7 +3285,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) p = js_malloc_rt(rt, sizeof(JSAtomStruct)); /* empty wide string */ if (!p) return JS_ATOM_NULL; - p->header.ref_count = 1; + js_rc(p)->ref_count = 1; p->is_wide_char = 1; /* Hack to represent NULL as a JSString */ p->len = 0; #ifdef DUMP_LEAKS @@ -2946,7 +3354,7 @@ static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len, p->is_wide_char == 0 && memcmp(p->u.str8, str, len) == 0) { if (!__JS_AtomIsConst(i)) - p->header.ref_count++; + js_rc(p)->ref_count++; return i; } i = p->hash_next; @@ -2958,7 +3366,7 @@ static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p) { #if 0 /* JS_ATOM_NULL is not refcounted: __JS_AtomIsConst() includes 0 */ if (unlikely(i == JS_ATOM_NULL)) { - p->header.ref_count = INT32_MAX / 2; + js_rc(p)->ref_count = INT32_MAX / 2; return; } #endif @@ -3008,7 +3416,7 @@ static void __JS_FreeAtom(JSRuntime *rt, uint32_t i) JSAtomStruct *p; p = rt->atom_array[i]; - if (--p->header.ref_count > 0) + if (--js_rc(p)->ref_count > 0) return; JS_FreeAtomStruct(rt, p); } @@ -4258,7 +4666,7 @@ static BOOL JS_ConcatStringInPlace(JSContext *ctx, JSString *p1, JSValueConst op if (p2->len == 0) return TRUE; - if (p1->header.ref_count != 1) + if (js_rc(p1)->ref_count != 1) return FALSE; size1 = js_malloc_usable_size(ctx, p1); if (p1->is_wide_char) { @@ -4431,7 +4839,7 @@ static JSValue js_linearize_string_rope(JSContext *ctx, JSValue rope) if (string_buffer_concat_value(b, rope)) goto fail; ret = string_buffer_end(b); - if (r->header.ref_count > 1) { + if (js_rc(r)->ref_count > 1) { /* update the rope so that it won't need to be linearized again */ JS_FreeValue(ctx, r->left); JS_FreeValue(ctx, r->right); @@ -4484,7 +4892,7 @@ static JSValue js_new_string_rope(JSContext *ctx, JSValue op1, JSValue op2) r = js_malloc(ctx, sizeof(*r)); if (!r) goto fail; - r->header.ref_count = 1; + js_rc(r)->ref_count = 1; r->len = len; r->is_wide_char = is_wide_char; r->depth = depth + 1; @@ -4703,28 +5111,13 @@ static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2) static inline size_t get_shape_size(size_t hash_size, size_t prop_size) { - return hash_size * sizeof(uint32_t) + sizeof(JSShape) + + return sizeof(JSShape) + hash_size * sizeof(uint32_t) + prop_size * sizeof(JSShapeProperty); } -static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size) -{ - return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size); -} - -static inline uint32_t *prop_hash_end(JSShape *sh) -{ - return (uint32_t *)sh; -} - -static inline void *get_alloc_from_shape(JSShape *sh) -{ - return prop_hash_end(sh) - ((intptr_t)sh->prop_hash_mask + 1); -} - static inline JSShapeProperty *get_shape_prop(JSShape *sh) { - return sh->prop; + return (JSShapeProperty *)((uint32_t *)(sh + 1) + sh->prop_hash_mask + 1); } static int init_shape_hash(JSRuntime *rt) @@ -4813,20 +5206,17 @@ static inline JSShape *js_new_shape_nohash(JSContext *ctx, JSObject *proto, int hash_size, int prop_size) { JSRuntime *rt = ctx->rt; - void *sh_alloc; JSShape *sh; - sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size)); - if (!sh_alloc) + sh = js_malloc(ctx, get_shape_size(hash_size, prop_size)); + if (!sh) return NULL; - sh = get_shape_from_alloc(sh_alloc, hash_size); - sh->header.ref_count = 1; + js_rc(sh)->ref_count = 1; add_gc_object(rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); if (proto) JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, proto)); sh->proto = proto; - memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) * - hash_size); + memset(sh->hash_table, 0, sizeof(sh->hash_table[0]) * hash_size); sh->prop_hash_mask = hash_size - 1; sh->prop_size = prop_size; sh->prop_count = 0; @@ -4869,20 +5259,18 @@ static JSShape *js_new_shape(JSContext *ctx, JSObject *proto) static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1) { JSShape *sh; - void *sh_alloc, *sh_alloc1; size_t size; JSShapeProperty *pr; uint32_t i, hash_size; hash_size = sh1->prop_hash_mask + 1; size = get_shape_size(hash_size, sh1->prop_size); - sh_alloc = js_malloc(ctx, size); - if (!sh_alloc) + sh = js_malloc(ctx, size); + if (!sh) return NULL; - sh_alloc1 = get_alloc_from_shape(sh1); - memcpy(sh_alloc, sh_alloc1, size); - sh = get_shape_from_alloc(sh_alloc, hash_size); - sh->header.ref_count = 1; + memcpy(&sh->header + 1, &sh1->header + 1, + size - sizeof(JSGCObjectHeader)); + js_rc(sh)->ref_count = 1; add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); sh->is_hashed = FALSE; if (sh->proto) { @@ -4896,7 +5284,7 @@ static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1) static JSShape *js_dup_shape(JSShape *sh) { - sh->header.ref_count++; + js_rc(sh)->ref_count++; return sh; } @@ -4905,7 +5293,7 @@ static void js_free_shape0(JSRuntime *rt, JSShape *sh) uint32_t i; JSShapeProperty *pr; - assert(sh->header.ref_count == 0); + assert(js_rc(sh)->ref_count == 0); if (sh->is_hashed) js_shape_hash_unlink(rt, sh); if (sh->proto != NULL) { @@ -4917,12 +5305,12 @@ static void js_free_shape0(JSRuntime *rt, JSShape *sh) pr++; } remove_gc_object(&sh->header); - js_free_rt(rt, get_alloc_from_shape(sh)); + js_free_rt(rt, sh); } static void js_free_shape(JSRuntime *rt, JSShape *sh) { - if (unlikely(--sh->header.ref_count <= 0)) { + if (unlikely(--js_rc(sh)->ref_count <= 0)) { js_free_shape0(rt, sh); } } @@ -4940,7 +5328,6 @@ static no_inline int resize_properties(JSContext *ctx, JSShape **psh, JSShape *sh; uint32_t new_size, new_hash_size, new_hash_mask, i; JSShapeProperty *pr; - void *sh_alloc; intptr_t h; JSShape *old_sh; @@ -4961,35 +5348,41 @@ static no_inline int resize_properties(JSContext *ctx, JSShape **psh, /* resize the property shapes. Using js_realloc() is not possible in case the GC runs during the allocation */ old_sh = sh; - sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); - if (!sh_alloc) + sh = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh) return -1; - sh = get_shape_from_alloc(sh_alloc, new_hash_size); - list_del(&old_sh->header.link); - /* copy all the shape properties */ - memcpy(sh, old_sh, - sizeof(JSShape) + sizeof(sh->prop[0]) * old_sh->prop_count); - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + remove_gc_object(&old_sh->header); + js_rc(sh)->ref_count = 1; + add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); + + memcpy(&sh->header + 1, &old_sh->header + 1, + sizeof(JSShape) - sizeof(JSGCObjectHeader)); + if (new_hash_size != (sh->prop_hash_mask + 1)) { /* resize the hash table and the properties */ new_hash_mask = new_hash_size - 1; sh->prop_hash_mask = new_hash_mask; - memset(prop_hash_end(sh) - new_hash_size, 0, - sizeof(prop_hash_end(sh)[0]) * new_hash_size); - for(i = 0, pr = sh->prop; i < sh->prop_count; i++, pr++) { + memset(sh->hash_table, 0, + sizeof(sh->hash_table[0]) * new_hash_size); + memcpy(get_shape_prop(sh), get_shape_prop(old_sh), + sizeof(JSShapeProperty) * old_sh->prop_count); + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { if (pr->atom != JS_ATOM_NULL) { h = ((uintptr_t)pr->atom & new_hash_mask); - pr->hash_next = prop_hash_end(sh)[-h - 1]; - prop_hash_end(sh)[-h - 1] = i + 1; + pr->hash_next = sh->hash_table[h]; + sh->hash_table[h] = i + 1; } } } else { - /* just copy the previous hash table */ - memcpy(prop_hash_end(sh) - new_hash_size, prop_hash_end(old_sh) - new_hash_size, - sizeof(prop_hash_end(sh)[0]) * new_hash_size); + /* just copy the previous hash table and the properties */ + memcpy(sh->hash_table, old_sh->hash_table, + sizeof(sh->hash_table[0]) * new_hash_size); + + memcpy(get_shape_prop(sh), get_shape_prop(old_sh), + sizeof(JSShapeProperty) * old_sh->prop_count); } - js_free(ctx, get_alloc_from_shape(old_sh)); + js_free(ctx, old_sh); *psh = sh; sh->prop_size = new_size; return 0; @@ -4999,7 +5392,6 @@ static no_inline int resize_properties(JSContext *ctx, JSShape **psh, static int compact_properties(JSContext *ctx, JSObject *p) { JSShape *sh, *old_sh; - void *sh_alloc; intptr_t h; uint32_t new_hash_size, i, j, new_hash_mask, new_size; JSShapeProperty *old_pr, *pr; @@ -5019,28 +5411,31 @@ static int compact_properties(JSContext *ctx, JSObject *p) /* resize the hash table and the properties */ old_sh = sh; - sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); - if (!sh_alloc) + sh = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh) return -1; - sh = get_shape_from_alloc(sh_alloc, new_hash_size); - list_del(&old_sh->header.link); - memcpy(sh, old_sh, sizeof(JSShape)); - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + remove_gc_object(&old_sh->header); + + js_rc(sh)->ref_count = 1; + add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); - memset(prop_hash_end(sh) - new_hash_size, 0, - sizeof(prop_hash_end(sh)[0]) * new_hash_size); + memcpy(&sh->header + 1, &old_sh->header + 1, + sizeof(JSShape) - sizeof(JSGCObjectHeader)); + + memset(sh->hash_table, 0, sizeof(sh->hash_table[0]) * new_hash_size); + sh->prop_hash_mask = new_hash_mask; j = 0; - old_pr = old_sh->prop; - pr = sh->prop; + old_pr = get_shape_prop(old_sh); + pr = get_shape_prop(sh); prop = p->prop; for(i = 0; i < sh->prop_count; i++) { if (old_pr->atom != JS_ATOM_NULL) { pr->atom = old_pr->atom; pr->flags = old_pr->flags; h = ((uintptr_t)old_pr->atom & new_hash_mask); - pr->hash_next = prop_hash_end(sh)[-h - 1]; - prop_hash_end(sh)[-h - 1] = j + 1; + pr->hash_next = sh->hash_table[h]; + sh->hash_table[h] = j + 1; prop[j] = prop[i]; j++; pr++; @@ -5048,13 +5443,12 @@ static int compact_properties(JSContext *ctx, JSObject *p) old_pr++; } assert(j == (sh->prop_count - sh->deleted_prop_count)); - sh->prop_hash_mask = new_hash_mask; sh->prop_size = new_size; sh->deleted_prop_count = 0; sh->prop_count = j; p->shape = sh; - js_free(ctx, get_alloc_from_shape(old_sh)); + js_free(ctx, old_sh); /* reduce the size of the object properties */ new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); @@ -5101,8 +5495,8 @@ static int add_shape_property(JSContext *ctx, JSShape **psh, /* add in hash table */ hash_mask = sh->prop_hash_mask; h = atom & hash_mask; - pr->hash_next = prop_hash_end(sh)[-h - 1]; - prop_hash_end(sh)[-h - 1] = sh->prop_count; + pr->hash_next = sh->hash_table[h]; + sh->hash_table[h] = sh->prop_count; return 0; } @@ -5143,13 +5537,15 @@ static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh, if (sh1->hash == h && sh1->proto == sh->proto && sh1->prop_count == ((n = sh->prop_count) + 1)) { + JSShapeProperty *prop = get_shape_prop(sh); + JSShapeProperty *prop1 = get_shape_prop(sh1); for(i = 0; i < n; i++) { - if (unlikely(sh1->prop[i].atom != sh->prop[i].atom) || - unlikely(sh1->prop[i].flags != sh->prop[i].flags)) + if (unlikely(prop1[i].atom != prop[i].atom) || + unlikely(prop1[i].flags != prop[i].flags)) goto next; } - if (unlikely(sh1->prop[n].atom != atom) || - unlikely(sh1->prop[n].flags != prop_flags)) + if (unlikely(prop1[n].atom != atom) || + unlikely(prop1[n].flags != prop_flags)) goto next; return sh1; } @@ -5165,11 +5561,11 @@ static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh) /* XXX: should output readable class prototype */ printf("%5d %3d%c %14p %5d %5d", i, - sh->header.ref_count, " *"[sh->is_hashed], + js_rc(sh)->ref_count, " *"[sh->is_hashed], (void *)sh->proto, sh->prop_size, sh->prop_count); for(j = 0; j < sh->prop_count; j++) { printf(" %s", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), - sh->prop[j].atom)); + get_shape_prop(sh)[j].atom)); } printf("\n"); } @@ -5193,7 +5589,7 @@ static __maybe_unused void JS_DumpShapes(JSRuntime *rt) /* dump non-hashed shapes */ list_for_each(el, &rt->gc_obj_list) { gp = list_entry(el, JSGCObjectHeader, link); - if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + if (js_rc(gp)->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { p = (JSObject *)gp; if (!p->shape->is_hashed) { JS_DumpShape(rt, -1, p->shape); @@ -5317,7 +5713,7 @@ static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID clas } break; } - p->header.ref_count = 1; + js_rc(p)->ref_count = 1; add_gc_object(ctx->rt, &p->header, JS_GC_OBJ_TYPE_JS_OBJECT); if (props) { for(i = 0; i < sh->prop_count; i++) @@ -5715,7 +6111,7 @@ static force_inline JSShapeProperty *find_own_property1(JSObject *p, intptr_t h; sh = p->shape; h = (uintptr_t)atom & sh->prop_hash_mask; - h = prop_hash_end(sh)[-h - 1]; + h = sh->hash_table[h]; prop = get_shape_prop(sh); while (h) { pr = &prop[h - 1]; @@ -5736,7 +6132,7 @@ static force_inline JSShapeProperty *find_own_property(JSProperty **ppr, intptr_t h; sh = p->shape; h = (uintptr_t)atom & sh->prop_hash_mask; - h = prop_hash_end(sh)[-h - 1]; + h = sh->hash_table[h]; prop = get_shape_prop(sh); while (h) { pr = &prop[h - 1]; @@ -5759,8 +6155,8 @@ static void set_cycle_flag(JSContext *ctx, JSValueConst obj) static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref) { if (var_ref) { - assert(var_ref->header.ref_count > 0); - if (--var_ref->header.ref_count == 0) { + assert(js_rc(var_ref)->ref_count > 0); + if (--js_rc(var_ref)->ref_count == 0) { if (var_ref->is_detached) { JS_FreeValueRT(rt, var_ref->value); } else { @@ -5969,7 +6365,7 @@ static void free_object(JSRuntime *rt, JSObject *p) remove_gc_object(&p->header); if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES) { - if (p->header.ref_count == 0 && p->weakref_count == 0) { + if (js_rc(p)->ref_count == 0 && p->weakref_count == 0) { js_free_rt(rt, p); } else { /* keep the object structure because there are may be @@ -5981,14 +6377,14 @@ static void free_object(JSRuntime *rt, JSObject *p) if (p->weakref_count == 0) { js_free_rt(rt, p); } else { - p->header.mark = 0; /* reset the mark so that the weakref can be freed */ + js_rc(p)->mark = 0; /* reset the mark so that the weakref can be freed */ } } } static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp) { - switch(gp->gc_obj_type) { + switch(js_rc(gp)->gc_obj_type) { case JS_GC_OBJ_TYPE_JS_OBJECT: free_object(rt, (JSObject *)gp); break; @@ -6017,7 +6413,7 @@ static void free_zero_refcount(JSRuntime *rt) if (el == &rt->gc_zero_ref_count_list) break; p = list_entry(el, JSGCObjectHeader, link); - assert(p->ref_count == 0); + assert(js_rc(p)->ref_count == 0); free_gc_object(rt, p); } rt->gc_phase = JS_GC_PHASE_NONE; @@ -6071,7 +6467,7 @@ void __JS_FreeValueRT(JSRuntime *rt, JSValue v) if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { list_del(&p->link); list_add(&p->link, &rt->gc_zero_ref_count_list); - p->mark = 1; /* indicate that the object is about to be freed */ + js_rc(p)->mark = 1; /* indicate that the object is about to be freed */ if (rt->gc_phase == JS_GC_PHASE_NONE) { free_zero_refcount(rt); } @@ -6135,8 +6531,8 @@ static void gc_remove_weak_objects(JSRuntime *rt) static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, JSGCObjectTypeEnum type) { - h->mark = 0; - h->gc_obj_type = type; + js_rc(h)->mark = 0; + js_rc(h)->gc_obj_type = type; list_add_tail(&h->link, &rt->gc_obj_list); } @@ -6163,7 +6559,7 @@ void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, JS_MarkFunc *mark_func) { - switch(gp->gc_obj_type) { + switch(js_rc(gp)->gc_obj_type) { case JS_GC_OBJ_TYPE_JS_OBJECT: { JSObject *p = (JSObject *)gp; @@ -6281,9 +6677,9 @@ static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, static void gc_decref_child(JSRuntime *rt, JSGCObjectHeader *p) { - assert(p->ref_count > 0); - p->ref_count--; - if (p->ref_count == 0 && p->mark == 1) { + assert(js_rc(p)->ref_count > 0); + js_rc(p)->ref_count--; + if (js_rc(p)->ref_count == 0 && js_rc(p)->mark == 1) { list_del(&p->link); list_add_tail(&p->link, &rt->tmp_obj_list); } @@ -6301,10 +6697,10 @@ static void gc_decref(JSRuntime *rt) tmp_obj_list */ list_for_each_safe(el, el1, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); - assert(p->mark == 0); + assert(js_rc(p)->mark == 0); mark_children(rt, p, gc_decref_child); - p->mark = 1; - if (p->ref_count == 0) { + js_rc(p)->mark = 1; + if (js_rc(p)->ref_count == 0) { list_del(&p->link); list_add_tail(&p->link, &rt->tmp_obj_list); } @@ -6313,19 +6709,19 @@ static void gc_decref(JSRuntime *rt) static void gc_scan_incref_child(JSRuntime *rt, JSGCObjectHeader *p) { - p->ref_count++; - if (p->ref_count == 1) { + js_rc(p)->ref_count++; + if (js_rc(p)->ref_count == 1) { /* ref_count was 0: remove from tmp_obj_list and add at the end of gc_obj_list */ list_del(&p->link); list_add_tail(&p->link, &rt->gc_obj_list); - p->mark = 0; /* reset the mark for the next GC call */ + js_rc(p)->mark = 0; /* reset the mark for the next GC call */ } } static void gc_scan_incref_child2(JSRuntime *rt, JSGCObjectHeader *p) { - p->ref_count++; + js_rc(p)->ref_count++; } static void gc_scan(JSRuntime *rt) @@ -6336,8 +6732,8 @@ static void gc_scan(JSRuntime *rt) /* keep the objects with a refcount > 0 and their children. */ list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); - assert(p->ref_count > 0); - p->mark = 0; /* reset the mark for the next GC call */ + assert(js_rc(p)->ref_count > 0); + js_rc(p)->mark = 0; /* reset the mark for the next GC call */ mark_children(rt, p, gc_scan_incref_child); } @@ -6366,7 +6762,7 @@ static void gc_free_cycles(JSRuntime *rt) /* Only need to free the GC object associated with JS values or async functions. The rest will be automatically removed because they must be referenced by them. */ - switch(p->gc_obj_type) { + switch(js_rc(p)->gc_obj_type) { case JS_GC_OBJ_TYPE_JS_OBJECT: case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: @@ -6391,14 +6787,14 @@ static void gc_free_cycles(JSRuntime *rt) list_for_each_safe(el, el1, &rt->gc_zero_ref_count_list) { p = list_entry(el, JSGCObjectHeader, link); - assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT || - p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE || - p->gc_obj_type == JS_GC_OBJ_TYPE_ASYNC_FUNCTION || - p->gc_obj_type == JS_GC_OBJ_TYPE_MODULE); - if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT && + assert(js_rc(p)->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT || + js_rc(p)->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE || + js_rc(p)->gc_obj_type == JS_GC_OBJ_TYPE_ASYNC_FUNCTION || + js_rc(p)->gc_obj_type == JS_GC_OBJ_TYPE_MODULE); + if (js_rc(p)->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT && ((JSObject *)p)->weakref_count != 0) { /* keep the object because there are weak references to it */ - p->mark = 0; + js_rc(p)->mark = 0; } else { js_free_rt(rt, p); } @@ -6462,7 +6858,7 @@ static void compute_value_size(JSValueConst val, JSMemoryUsage_helper *hp); static void compute_jsstring_size(JSString *str, JSMemoryUsage_helper *hp) { if (!str->atom_type) { /* atoms are handled separately */ - double s_ref_count = str->header.ref_count; + double s_ref_count = js_rc(str)->ref_count; hp->str_count += 1 / s_ref_count; hp->str_size += ((sizeof(*str) + (str->len << str->is_wide_char) + 1 - str->is_wide_char) / s_ref_count); @@ -6527,9 +6923,9 @@ void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) JSMemoryUsage_helper mem = { 0 }, *hp = &mem; memset(s, 0, sizeof(*s)); - s->malloc_count = rt->malloc_state.malloc_count; - s->malloc_size = rt->malloc_state.malloc_size; - s->malloc_limit = rt->malloc_state.malloc_limit; + s->malloc_count = rt->malloc_ctx.malloc_state.malloc_count; + s->malloc_size = rt->malloc_ctx.malloc_state.malloc_size; + s->malloc_limit = rt->malloc_ctx.malloc_state.malloc_limit; s->memory_used_count = 2; /* rt + rt->class_array */ s->memory_used_size = sizeof(JSRuntime) + sizeof(JSValue) * rt->class_count; @@ -6589,10 +6985,10 @@ void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) JSShapeProperty *prs; /* XXX: could count the other GC object types too */ - if (gp->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { + if (js_rc(gp)->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { compute_bytecode_size((JSFunctionBytecode *)gp, hp); continue; - } else if (gp->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) { + } else if (js_rc(gp)->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) { continue; } p = (JSObject *)gp; @@ -6670,7 +7066,7 @@ void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) s->js_func_size += b->closure_var_count * sizeof(*var_refs); for (i = 0; i < b->closure_var_count; i++) { if (var_refs[i]) { - double ref_count = var_refs[i]->header.ref_count; + double ref_count = js_rc(var_refs[i])->ref_count; s->memory_used_count += 1 / ref_count; s->js_func_size += sizeof(*var_refs[i]) / ref_count; /* handle non object closed values */ @@ -6856,7 +7252,7 @@ void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) list_for_each(el, &rt->gc_obj_list) { JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); JSObject *p; - if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + if (js_rc(gp)->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { p = (JSObject *)gp; obj_classes[min_uint32(p->class_id, JS_CLASS_INIT_COUNT)]++; } @@ -7785,7 +8181,7 @@ static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, /* WARNING: a varref is returned as a string ! */ prs->flags |= JS_PROP_VARREF; pr->u.var_ref = JS_VALUE_GET_PTR(val); - pr->u.var_ref->header.ref_count++; + js_rc(pr->u.var_ref)->ref_count++; } else if (p->class_id == JS_CLASS_GLOBAL_OBJECT) { JSVarRef *var_ref; /* in the global object we use references */ @@ -8815,7 +9211,7 @@ static JSProperty *add_property(JSContext *ctx, p->shape = js_dup_shape(new_sh); js_free_shape(ctx->rt, sh); return &p->prop[new_sh->prop_count - 1]; - } else if (sh->header.ref_count != 1) { + } else if (js_rc(sh)->ref_count != 1) { /* if the shape is shared, clone it */ new_sh = js_clone_shape(ctx, sh); if (!new_sh) @@ -8827,7 +9223,7 @@ static JSProperty *add_property(JSContext *ctx, p->shape = new_sh; } } - assert(p->shape->header.ref_count == 1); + assert(js_rc(p->shape)->ref_count == 1); if (add_shape_property(ctx, &p->shape, p, prop, prop_flags)) return NULL; return &p->prop[p->shape->prop_count - 1]; @@ -8888,14 +9284,14 @@ static int remove_global_object_property(JSContext *ctx, JSObject *p, JSProperty *pr1; var_ref = pr->u.var_ref; - if (var_ref->header.ref_count == 1) + if (js_rc(var_ref)->ref_count == 1) return 0; p1 = JS_VALUE_GET_OBJ(p->u.global_object.uninitialized_vars); pr1 = add_property(ctx, p1, prs->atom, JS_PROP_C_W_E | JS_PROP_VARREF); if (!pr1) return -1; pr1->u.var_ref = var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; JS_FreeValue(ctx, var_ref->value); var_ref->is_lexical = FALSE; var_ref->is_const = FALSE; @@ -8914,7 +9310,7 @@ static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) redo: sh = p->shape; h1 = atom & sh->prop_hash_mask; - h = prop_hash_end(sh)[-h1 - 1]; + h = sh->hash_table[h1]; prop = get_shape_prop(sh); lpr = NULL; lpr_idx = 0; /* prevent warning */ @@ -8935,7 +9331,7 @@ static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) lpr = get_shape_prop(sh) + lpr_idx; lpr->hash_next = pr->hash_next; } else { - prop_hash_end(sh)[-h1 - 1] = pr->hash_next; + sh->hash_table[h1] = pr->hash_next; } sh->deleted_prop_count++; /* free the entry */ @@ -9036,7 +9432,7 @@ static int set_array_length(JSContext *ctx, JSObject *p, JSValue val, if (ret) return -1; /* JS_ToArrayLengthFree() must be done before the read-only test */ - if (unlikely(!(p->shape->prop[0].flags & JS_PROP_WRITABLE))) + if (unlikely(!(get_shape_prop(p->shape)[0].flags & JS_PROP_WRITABLE))) return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); if (likely(p->fast_array)) { @@ -9817,7 +10213,7 @@ static int JS_CreateProperty(JSContext *ctx, JSObject *p, if (prs1) { delete_obj = p1; var_ref = pr1->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; } else { var_ref = js_create_var_ref(ctx, FALSE); if (!var_ref) @@ -9902,7 +10298,7 @@ static int js_shape_prepare_update(JSContext *ctx, JSObject *p, sh = p->shape; if (sh->is_hashed) { - if (sh->header.ref_count != 1) { + if (js_rc(sh)->ref_count != 1) { if (pprs) idx = *pprs - get_shape_prop(sh); /* clone the shape (the resulting one is no longer hashed) */ @@ -11193,7 +11589,7 @@ static JSBigInt *js_bigint_new(JSContext *ctx, int len) r = js_malloc(ctx, sizeof(JSBigInt) + len * sizeof(js_limb_t)); if (!r) return NULL; - r->header.ref_count = 1; + js_rc(r)->ref_count = 1; r->len = len; return r; } @@ -11201,7 +11597,6 @@ static JSBigInt *js_bigint_new(JSContext *ctx, int len) static JSBigInt *js_bigint_set_si(JSBigIntBuf *buf, js_slimb_t a) { JSBigInt *r = (JSBigInt *)buf->big_int_buf; - r->header.ref_count = 0; /* fail safe */ r->len = 1; r->tab[0] = a; return r; @@ -11213,7 +11608,6 @@ static JSBigInt *js_bigint_set_si64(JSBigIntBuf *buf, int64_t a) return js_bigint_set_si(buf, a); #else JSBigInt *r = (JSBigInt *)buf->big_int_buf; - r->header.ref_count = 0; /* fail safe */ if (a >= INT32_MIN && a <= INT32_MAX) { r->len = 1; r->tab[0] = a; @@ -11328,7 +11722,7 @@ static JSBigInt *js_bigint_normalize1(JSContext *ctx, JSBigInt *a, int l) { js_limb_t v; - assert(a->header.ref_count == 1); + assert(js_rc(a)->ref_count == 1); while (l > 1) { v = a->tab[l - 1]; if ((v != 0 && v != -1) || @@ -12143,7 +12537,11 @@ static JSBigInt *js_bigint_from_string(JSContext *ctx, } /* 2 <= base <= 36 */ -static char const digits[36] = "0123456789abcdefghijklmnopqrstuvwxyz"; +static char const digits[36] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' +}; /* special version going backwards */ /* XXX: use dtoa.c */ @@ -12438,7 +12836,7 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, to_digit((uint8_t)p[1]) < radix)) { p++; } - if (!(flags & ATOD_INT_ONLY)) { + if (!(flags & ATOD_INT_ONLY) && radix == 10) { if (*p == '.' && (p > p_start || to_digit((uint8_t)p[1]) < radix)) { is_float = TRUE; p++; @@ -12448,9 +12846,7 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, (*p == sep && to_digit((uint8_t)p[1]) < radix)) p++; } - if (p > p_start && - (((*p == 'e' || *p == 'E') && radix == 10) || - ((*p == 'p' || *p == 'P') && (radix == 2 || radix == 8 || radix == 16)))) { + if (p > p_start && (*p == 'e' || *p == 'E')) { const char *p1 = p + 1; is_float = TRUE; if (*p1 == '+') { @@ -12487,19 +12883,9 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, } buf[j] = '\0'; - if (flags & ATOD_ACCEPT_SUFFIX) { - if (*p == 'n') { - p++; - atod_type = ATOD_TYPE_BIG_INT; - } else { - if (is_float && radix != 10) - goto fail; - } - } else { - if (atod_type == ATOD_TYPE_FLOAT64) { - if (is_float && radix != 10) - goto fail; - } + if ((flags & ATOD_ACCEPT_SUFFIX) && *p == 'n') { + p++; + atod_type = ATOD_TYPE_BIG_INT; } switch(atod_type) { @@ -13419,8 +13805,8 @@ static void js_print_string(JSPrintValueState *s, JSValueConst val) int sep; if (s->options.raw_dump && JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { JSString *p = JS_VALUE_GET_STRING(val); - js_printf(s, "%d", p->header.ref_count); - sep = (p->header.ref_count == 1) ? '\"' : '\''; + js_printf(s, "%d", js_rc(p)->ref_count); + sep = (js_rc(p)->ref_count == 1) ? '\"' : '\''; } else { sep = '\"'; } @@ -14102,10 +14488,10 @@ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) sh = p->shape; /* the shape can be NULL while freeing an object */ printf("%14p %4d ", (void *)p, - p->header.ref_count); + js_rc(p)->ref_count); if (sh) { printf("%3d%c %14p ", - sh->header.ref_count, + js_rc(sh)->ref_count, " *"[sh->is_hashed], (void *)sh->proto); } else { @@ -14123,13 +14509,13 @@ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) { - if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + if (js_rc(p)->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { JS_DumpObject(rt, (JSObject *)p); } else { printf("%14p %4d ", (void *)p, - p->ref_count); - switch(p->gc_obj_type) { + js_rc(p)->ref_count); + switch(js_rc(p)->gc_obj_type) { case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: printf("[function bytecode]"); break; @@ -14149,7 +14535,7 @@ static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) printf("[module]"); break; default: - printf("[unknown %d]", p->gc_obj_type); + printf("[unknown %d]", js_rc(p)->gc_obj_type); break; } printf("\n"); @@ -16612,7 +16998,7 @@ static JSVarRef *js_create_var_ref(JSContext *ctx, BOOL is_lexical) var_ref = js_malloc(ctx, sizeof(JSVarRef)); if (!var_ref) return NULL; - var_ref->header.ref_count = 1; + js_rc(var_ref)->ref_count = 1; if (is_lexical) var_ref->value = JS_UNINITIALIZED; else @@ -16652,7 +17038,7 @@ static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, if (var_ref) { /* reference to the already created local variable */ assert(var_ref->pvalue == pvalue); - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } @@ -16660,7 +17046,7 @@ static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, var_ref = js_malloc(ctx, sizeof(JSVarRef)); if (!var_ref) return NULL; - var_ref->header.ref_count = 1; + js_rc(var_ref)->ref_count = 1; add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); var_ref->is_detached = FALSE; var_ref->is_lexical = FALSE; @@ -16678,7 +17064,7 @@ static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, the JSVarRef of async functions during the GC. It would have the advantage of allowing the release of unused stack frames in a cycle. */ - async_func->header.ref_count++; + js_rc(async_func)->ref_count++; } var_ref->pvalue = pvalue; return var_ref; @@ -16709,7 +17095,7 @@ static JSVarRef *js_global_object_get_uninitialized_var(JSContext *ctx, JSObject if (prs) { assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); var_ref = pr->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } @@ -16722,7 +17108,7 @@ static JSVarRef *js_global_object_get_uninitialized_var(JSContext *ctx, JSObject return NULL; } pr->u.var_ref = var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } @@ -16741,7 +17127,7 @@ static JSVarRef *js_global_object_find_uninitialized_var(JSContext *ctx, JSObjec if (prs) { assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); var_ref = pr->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; delete_property(ctx, p1, atom); if (!is_lexical) var_ref->value = JS_UNDEFINED; @@ -16772,7 +17158,7 @@ static JSVarRef *js_closure_define_global_var(JSContext *ctx, JSClosureVar *cv, if (prs) { assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); var_ref = pr->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } @@ -16810,7 +17196,7 @@ static JSVarRef *js_closure_define_global_var(JSContext *ctx, JSClosureVar *cv, return NULL; } else { var_ref = pr->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; } if (cv->var_kind == JS_VAR_GLOBAL_FUNCTION_DECL && (prs->flags & JS_PROP_CONFIGURABLE)) { @@ -16820,7 +17206,7 @@ static JSVarRef *js_closure_define_global_var(JSContext *ctx, JSClosureVar *cv, free_property(ctx->rt, pr, prs->flags); prs->flags = flags | JS_PROP_VARREF; pr->u.var_ref = var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; } else { assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); prs->flags = (prs->flags & ~JS_PROP_C_W_E) | flags; @@ -16852,7 +17238,7 @@ static JSVarRef *js_closure_define_global_var(JSContext *ctx, JSClosureVar *cv, return NULL; } pr->u.var_ref = var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } @@ -16868,7 +17254,7 @@ static JSVarRef *js_closure_global_var(JSContext *ctx, JSClosureVar *cv) if (prs) { assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); var_ref = pr->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } p = JS_VALUE_GET_OBJ(ctx->global_obj); @@ -16883,7 +17269,7 @@ static JSVarRef *js_closure_global_var(JSContext *ctx, JSClosureVar *cv) } if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { var_ref = pr->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } } @@ -16952,7 +17338,7 @@ static JSValue js_closure2(JSContext *ctx, JSValue func_obj, case JS_CLOSURE_REF: case JS_CLOSURE_GLOBAL_REF: var_ref = cur_var_refs[cv->var_idx]; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; break; default: abort(); @@ -18412,7 +18798,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, goto exception; if (opcode == OP_make_var_ref_ref) { var_ref = var_refs[idx]; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; } else { var_ref = get_var_ref(ctx, sf, idx, opcode == OP_make_arg_ref); if (!var_ref) @@ -20335,7 +20721,7 @@ static JSAsyncFunctionState *async_func_init(JSContext *ctx, if (!s) return NULL; memset(s, 0, sizeof(*s)); - s->header.ref_count = 1; + js_rc(s)->ref_count = 1; add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION); sf = &s->frame; @@ -20425,7 +20811,7 @@ static void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) JS_FreeValueRT(rt, s->resolving_funcs[1]); remove_gc_object(&s->header); - if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && s->header.ref_count != 0) { + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && js_rc(s)->ref_count != 0) { list_add_tail(&s->header.link, &rt->gc_zero_ref_count_list); } else { js_free_rt(rt, s); @@ -20434,7 +20820,7 @@ static void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) { - if (--s->header.ref_count == 0) { + if (--js_rc(s)->ref_count == 0) { if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { list_del(&s->header.link); list_add(&s->header.link, &rt->gc_zero_ref_count_list); @@ -20657,7 +21043,7 @@ static int js_async_function_resolve_create(JSContext *ctx, return -1; } p = JS_VALUE_GET_OBJ(resolving_funcs[i]); - s->header.ref_count++; + js_rc(s)->ref_count++; p->u.async_function_data = s; } return 0; @@ -29092,7 +29478,7 @@ static JSModuleDef *js_new_module_def(JSContext *ctx, JSAtom name) JS_FreeAtom(ctx, name); return NULL; } - m->header.ref_count = 1; + js_rc(m)->ref_count = 1; add_gc_object(ctx->rt, &m->header, JS_GC_OBJ_TYPE_MODULE); m->module_name = name; m->module_ns = JS_UNDEFINED; @@ -29180,7 +29566,7 @@ static void js_free_module_def(JSRuntime *rt, JSModuleDef *m) list_del(&m->link); } remove_gc_object(&m->header); - if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && m->header.ref_count != 0) { + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && js_rc(m)->ref_count != 0) { list_add_tail(&m->header.link, &rt->gc_zero_ref_count_list); } else { js_free_rt(rt, m); @@ -29900,7 +30286,7 @@ static JSValue js_build_module_ns(JSContext *ctx, JSModuleDef *m) JS_PROP_VARREF); if (!pr) goto fail; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; pr->u.var_ref = var_ref; } break; @@ -30191,7 +30577,7 @@ static int js_inner_module_linking(JSContext *ctx, JSModuleDef *m, p1 = JS_VALUE_GET_OBJ(res_m->func_obj); var_ref = p1->u.func.var_refs[res_me->u.local.var_idx]; } - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; var_refs[mi->var_idx] = var_ref; #ifdef DUMP_MODULE_RESOLVE printf("local export (var_ref=%p)\n", var_ref); @@ -30207,7 +30593,7 @@ static int js_inner_module_linking(JSContext *ctx, JSModuleDef *m, JSExportEntry *me = &m->export_entries[i]; if (me->export_type == JS_EXPORT_TYPE_LOCAL) { var_ref = var_refs[me->u.local.var_idx]; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; me->u.local.var_ref = var_ref; } } @@ -35567,7 +35953,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) b = js_mallocz(ctx, function_size); if (!b) goto fail; - b->header.ref_count = 1; + js_rc(b)->ref_count = 1; b->byte_code_buf = (void *)((uint8_t*)b + byte_code_offset); b->byte_code_len = fd->byte_code.size; @@ -35749,7 +36135,7 @@ static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b) } remove_gc_object(&b->header); - if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && b->header.ref_count != 0) { + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && js_rc(b)->ref_count != 0) { list_add_tail(&b->header.link, &rt->gc_zero_ref_count_list); } else { js_free_rt(rt, b); @@ -36770,7 +37156,6 @@ static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj, ret = JS_EvalInternal(ctx, this_obj, str, len, "<input>", flags, scope_idx); JS_FreeCString(ctx, str); return ret; - } JSValue JS_EvalThis(JSContext *ctx, JSValueConst this_obj, @@ -38184,8 +38569,6 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) uint64_t function_size; memset(&bc, 0, sizeof(bc)); - bc.header.ref_count = 1; - //bc.gc_header.mark = 0; if (bc_get_u16(s, &v16)) goto fail; @@ -38250,7 +38633,6 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) return JS_EXCEPTION; memcpy(b, &bc, offsetof(JSFunctionBytecode, debug)); - b->header.ref_count = 1; if (local_count != 0) { b->vardefs = (void *)((uint8_t*)b + vardefs_offset); } @@ -38261,6 +38643,7 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) b->cpool = (void *)((uint8_t*)b + cpool_offset); } + js_rc(b)->ref_count = 1; add_gc_object(ctx->rt, &b->header, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); obj = JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b); @@ -48122,6 +48505,8 @@ static BOOL check_regexp_getter(JSContext *ctx, return FALSE; if ((prs->flags & JS_PROP_TMASK) != JS_PROP_GETSET) return FALSE; + if (!pr->u.getset.getter) + return FALSE; return JS_IsCFunction(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter), func, magic); } @@ -51032,11 +51417,11 @@ static BOOL js_weakref_is_target(JSValueConst val) /* XXX: add a specific JSWeakRef value type ? */ static BOOL js_weakref_is_live(JSValueConst val) { - int *pref_count; + void *p; if (JS_IsUndefined(val)) return TRUE; - pref_count = JS_VALUE_GET_PTR(val); - return (*pref_count != 0); + p = JS_VALUE_GET_PTR(val); + return (js_rc(p)->ref_count != 0); } /* 'val' can be JS_UNDEFINED */ @@ -51049,15 +51434,15 @@ static void js_weakref_free(JSRuntime *rt, JSValue val) /* 'mark' is tested to avoid freeing the object structure when it is about to be freed in a cycle or in free_zero_refcount() */ - if (p->weakref_count == 0 && p->header.ref_count == 0 && - p->header.mark == 0) { + if (p->weakref_count == 0 && js_rc(p)->ref_count == 0 && + js_rc(p)->mark == 0) { js_free_rt(rt, p); } } else if (JS_VALUE_GET_TAG(val) == JS_TAG_SYMBOL) { JSString *p = JS_VALUE_GET_STRING(val); assert(p->hash >= 1); p->hash--; - if (p->hash == 0 && p->header.ref_count == 0) { + if (p->hash == 0 && js_rc(p)->ref_count == 0) { /* can remove the dummy structure */ js_free_rt(rt, p); } @@ -55391,7 +55776,7 @@ static JSValue js_date_toJSON(JSContext *ctx, JSValueConst this_val, goto done; } } - method = JS_GetPropertyStr(ctx, obj, "toISOString"); + method = JS_GetProperty(ctx, obj, JS_ATOM_toISOString); if (JS_IsException(method)) goto exception; if (!JS_IsFunction(ctx, method)) { @@ -58267,6 +58652,797 @@ static JSValue js_typed_array_toSorted(JSContext *ctx, JSValueConst this_val, return ret; } +/* Uint8Array base64/hex (tc39 proposal-arraybuffer-base64) */ + +enum { + B64_ALPHABET_BASE64 = 0, + B64_ALPHABET_BASE64URL = 1, +}; + +enum { + B64_LAST_LOOSE = 0, + B64_LAST_STRICT = 1, + B64_LAST_STOP_BEFORE_PARTIAL = 2, +}; + +static const unsigned char b64_enc[64] = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p', + 'q','r','s','t','u','v','w','x','y','z', + '0','1','2','3','4','5','6','7','8','9', + '+','/' +}; + +static const unsigned char b64url_enc[64] = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p', + 'q','r','s','t','u','v','w','x','y','z', + '0','1','2','3','4','5','6','7','8','9', + '-','_' +}; + +#define K_WS 64 +#define K_ER 65 + +static const uint8_t b64_dec[256] = { + [ 0]=K_ER, [ 1]=K_ER, [ 2]=K_ER, [ 3]=K_ER, [ 4]=K_ER, [ 5]=K_ER, [ 6]=K_ER, [ 7]=K_ER, + [ 8]=K_ER, [ 9]=K_WS, [ 10]=K_WS, [ 11]=K_ER, [ 12]=K_WS, [ 13]=K_WS, [ 14]=K_ER, [ 15]=K_ER, + [ 16]=K_ER, [ 17]=K_ER, [ 18]=K_ER, [ 19]=K_ER, [ 20]=K_ER, [ 21]=K_ER, [ 22]=K_ER, [ 23]=K_ER, + [ 24]=K_ER, [ 25]=K_ER, [ 26]=K_ER, [ 27]=K_ER, [ 28]=K_ER, [ 29]=K_ER, [ 30]=K_ER, [ 31]=K_ER, + [' ']=K_WS, ['!']=K_ER, ['"']=K_ER, ['#']=K_ER, ['$']=K_ER, ['%']=K_ER, ['&']=K_ER, [ 39]=K_ER, + ['(']=K_ER, [')']=K_ER, ['*']=K_ER, ['+']= 62, [',']=K_ER, ['-']=K_ER, ['.']=K_ER, ['/']= 63, + ['0']= 52, ['1']= 53, ['2']= 54, ['3']= 55, ['4']= 56, ['5']= 57, ['6']= 58, ['7']= 59, + ['8']= 60, ['9']= 61, [':']=K_ER, [';']=K_ER, ['<']=K_ER, ['=']=K_ER, ['>']=K_ER, ['?']=K_ER, + ['@']=K_ER, ['A']= 0, ['B']= 1, ['C']= 2, ['D']= 3, ['E']= 4, ['F']= 5, ['G']= 6, + ['H']= 7, ['I']= 8, ['J']= 9, ['K']= 10, ['L']= 11, ['M']= 12, ['N']= 13, ['O']= 14, + ['P']= 15, ['Q']= 16, ['R']= 17, ['S']= 18, ['T']= 19, ['U']= 20, ['V']= 21, ['W']= 22, + ['X']= 23, ['Y']= 24, ['Z']= 25, ['[']=K_ER, [ 92]=K_ER, [']']=K_ER, ['^']=K_ER, ['_']=K_ER, + ['`']=K_ER, ['a']= 26, ['b']= 27, ['c']= 28, ['d']= 29, ['e']= 30, ['f']= 31, ['g']= 32, + ['h']= 33, ['i']= 34, ['j']= 35, ['k']= 36, ['l']= 37, ['m']= 38, ['n']= 39, ['o']= 40, + ['p']= 41, ['q']= 42, ['r']= 43, ['s']= 44, ['t']= 45, ['u']= 46, ['v']= 47, ['w']= 48, + ['x']= 49, ['y']= 50, ['z']= 51, ['{']=K_ER, ['|']=K_ER, ['}']=K_ER, ['~']=K_ER, [127]=K_ER, + [128]=K_ER, [129]=K_ER, [130]=K_ER, [131]=K_ER, [132]=K_ER, [133]=K_ER, [134]=K_ER, [135]=K_ER, + [136]=K_ER, [137]=K_ER, [138]=K_ER, [139]=K_ER, [140]=K_ER, [141]=K_ER, [142]=K_ER, [143]=K_ER, + [144]=K_ER, [145]=K_ER, [146]=K_ER, [147]=K_ER, [148]=K_ER, [149]=K_ER, [150]=K_ER, [151]=K_ER, + [152]=K_ER, [153]=K_ER, [154]=K_ER, [155]=K_ER, [156]=K_ER, [157]=K_ER, [158]=K_ER, [159]=K_ER, + [160]=K_ER, [161]=K_ER, [162]=K_ER, [163]=K_ER, [164]=K_ER, [165]=K_ER, [166]=K_ER, [167]=K_ER, + [168]=K_ER, [169]=K_ER, [170]=K_ER, [171]=K_ER, [172]=K_ER, [173]=K_ER, [174]=K_ER, [175]=K_ER, + [176]=K_ER, [177]=K_ER, [178]=K_ER, [179]=K_ER, [180]=K_ER, [181]=K_ER, [182]=K_ER, [183]=K_ER, + [184]=K_ER, [185]=K_ER, [186]=K_ER, [187]=K_ER, [188]=K_ER, [189]=K_ER, [190]=K_ER, [191]=K_ER, + [192]=K_ER, [193]=K_ER, [194]=K_ER, [195]=K_ER, [196]=K_ER, [197]=K_ER, [198]=K_ER, [199]=K_ER, + [200]=K_ER, [201]=K_ER, [202]=K_ER, [203]=K_ER, [204]=K_ER, [205]=K_ER, [206]=K_ER, [207]=K_ER, + [208]=K_ER, [209]=K_ER, [210]=K_ER, [211]=K_ER, [212]=K_ER, [213]=K_ER, [214]=K_ER, [215]=K_ER, + [216]=K_ER, [217]=K_ER, [218]=K_ER, [219]=K_ER, [220]=K_ER, [221]=K_ER, [222]=K_ER, [223]=K_ER, + [224]=K_ER, [225]=K_ER, [226]=K_ER, [227]=K_ER, [228]=K_ER, [229]=K_ER, [230]=K_ER, [231]=K_ER, + [232]=K_ER, [233]=K_ER, [234]=K_ER, [235]=K_ER, [236]=K_ER, [237]=K_ER, [238]=K_ER, [239]=K_ER, + [240]=K_ER, [241]=K_ER, [242]=K_ER, [243]=K_ER, [244]=K_ER, [245]=K_ER, [246]=K_ER, [247]=K_ER, + [248]=K_ER, [249]=K_ER, [250]=K_ER, [251]=K_ER, [252]=K_ER, [253]=K_ER, [254]=K_ER, [255]=K_ER, +}; + +static const uint8_t b64url_dec[256] = { + [ 0]=K_ER, [ 1]=K_ER, [ 2]=K_ER, [ 3]=K_ER, [ 4]=K_ER, [ 5]=K_ER, [ 6]=K_ER, [ 7]=K_ER, + [ 8]=K_ER, [ 9]=K_WS, [ 10]=K_WS, [ 11]=K_ER, [ 12]=K_WS, [ 13]=K_WS, [ 14]=K_ER, [ 15]=K_ER, + [ 16]=K_ER, [ 17]=K_ER, [ 18]=K_ER, [ 19]=K_ER, [ 20]=K_ER, [ 21]=K_ER, [ 22]=K_ER, [ 23]=K_ER, + [ 24]=K_ER, [ 25]=K_ER, [ 26]=K_ER, [ 27]=K_ER, [ 28]=K_ER, [ 29]=K_ER, [ 30]=K_ER, [ 31]=K_ER, + [' ']=K_WS, ['!']=K_ER, ['"']=K_ER, ['#']=K_ER, ['$']=K_ER, ['%']=K_ER, ['&']=K_ER, [ 39]=K_ER, + ['(']=K_ER, [')']=K_ER, ['*']=K_ER, ['+']=K_ER, [',']=K_ER, ['-']= 62, ['.']=K_ER, ['/']=K_ER, + ['0']= 52, ['1']= 53, ['2']= 54, ['3']= 55, ['4']= 56, ['5']= 57, ['6']= 58, ['7']= 59, + ['8']= 60, ['9']= 61, [':']=K_ER, [';']=K_ER, ['<']=K_ER, ['=']=K_ER, ['>']=K_ER, ['?']=K_ER, + ['@']=K_ER, ['A']= 0, ['B']= 1, ['C']= 2, ['D']= 3, ['E']= 4, ['F']= 5, ['G']= 6, + ['H']= 7, ['I']= 8, ['J']= 9, ['K']= 10, ['L']= 11, ['M']= 12, ['N']= 13, ['O']= 14, + ['P']= 15, ['Q']= 16, ['R']= 17, ['S']= 18, ['T']= 19, ['U']= 20, ['V']= 21, ['W']= 22, + ['X']= 23, ['Y']= 24, ['Z']= 25, ['[']=K_ER, [ 92]=K_ER, [']']=K_ER, ['^']=K_ER, ['_']= 63, + ['`']=K_ER, ['a']= 26, ['b']= 27, ['c']= 28, ['d']= 29, ['e']= 30, ['f']= 31, ['g']= 32, + ['h']= 33, ['i']= 34, ['j']= 35, ['k']= 36, ['l']= 37, ['m']= 38, ['n']= 39, ['o']= 40, + ['p']= 41, ['q']= 42, ['r']= 43, ['s']= 44, ['t']= 45, ['u']= 46, ['v']= 47, ['w']= 48, + ['x']= 49, ['y']= 50, ['z']= 51, ['{']=K_ER, ['|']=K_ER, ['}']=K_ER, ['~']=K_ER, [127]=K_ER, + [128]=K_ER, [129]=K_ER, [130]=K_ER, [131]=K_ER, [132]=K_ER, [133]=K_ER, [134]=K_ER, [135]=K_ER, + [136]=K_ER, [137]=K_ER, [138]=K_ER, [139]=K_ER, [140]=K_ER, [141]=K_ER, [142]=K_ER, [143]=K_ER, + [144]=K_ER, [145]=K_ER, [146]=K_ER, [147]=K_ER, [148]=K_ER, [149]=K_ER, [150]=K_ER, [151]=K_ER, + [152]=K_ER, [153]=K_ER, [154]=K_ER, [155]=K_ER, [156]=K_ER, [157]=K_ER, [158]=K_ER, [159]=K_ER, + [160]=K_ER, [161]=K_ER, [162]=K_ER, [163]=K_ER, [164]=K_ER, [165]=K_ER, [166]=K_ER, [167]=K_ER, + [168]=K_ER, [169]=K_ER, [170]=K_ER, [171]=K_ER, [172]=K_ER, [173]=K_ER, [174]=K_ER, [175]=K_ER, + [176]=K_ER, [177]=K_ER, [178]=K_ER, [179]=K_ER, [180]=K_ER, [181]=K_ER, [182]=K_ER, [183]=K_ER, + [184]=K_ER, [185]=K_ER, [186]=K_ER, [187]=K_ER, [188]=K_ER, [189]=K_ER, [190]=K_ER, [191]=K_ER, + [192]=K_ER, [193]=K_ER, [194]=K_ER, [195]=K_ER, [196]=K_ER, [197]=K_ER, [198]=K_ER, [199]=K_ER, + [200]=K_ER, [201]=K_ER, [202]=K_ER, [203]=K_ER, [204]=K_ER, [205]=K_ER, [206]=K_ER, [207]=K_ER, + [208]=K_ER, [209]=K_ER, [210]=K_ER, [211]=K_ER, [212]=K_ER, [213]=K_ER, [214]=K_ER, [215]=K_ER, + [216]=K_ER, [217]=K_ER, [218]=K_ER, [219]=K_ER, [220]=K_ER, [221]=K_ER, [222]=K_ER, [223]=K_ER, + [224]=K_ER, [225]=K_ER, [226]=K_ER, [227]=K_ER, [228]=K_ER, [229]=K_ER, [230]=K_ER, [231]=K_ER, + [232]=K_ER, [233]=K_ER, [234]=K_ER, [235]=K_ER, [236]=K_ER, [237]=K_ER, [238]=K_ER, [239]=K_ER, + [240]=K_ER, [241]=K_ER, [242]=K_ER, [243]=K_ER, [244]=K_ER, [245]=K_ER, [246]=K_ER, [247]=K_ER, + [248]=K_ER, [249]=K_ER, [250]=K_ER, [251]=K_ER, [252]=K_ER, [253]=K_ER, [254]=K_ER, [255]=K_ER, +}; + +static size_t b64_encode(const uint8_t *src, size_t len, char *dst, + const unsigned char *alpha) +{ + size_t i, j; + + for (i = 0, j = 0; i + 3 <= len; i += 3, j += 4) { + uint32_t v = 65536*src[i] + 256*src[i + 1] + src[i + 2]; + dst[j + 0] = alpha[(v >> 18) & 63]; + dst[j + 1] = alpha[(v >> 12) & 63]; + dst[j + 2] = alpha[(v >> 6) & 63]; + dst[j + 3] = alpha[v & 63]; + } + + size_t rem = len - i; + if (rem == 1) { + uint32_t v = 65536*src[i]; + dst[j++] = alpha[(v >> 18) & 63]; + dst[j++] = alpha[(v >> 12) & 63]; + dst[j++] = '='; + dst[j++] = '='; + } else if (rem == 2) { + uint32_t v = 65536*src[i] + 256*src[i + 1]; + dst[j++] = alpha[(v >> 18) & 63]; + dst[j++] = alpha[(v >> 12) & 63]; + dst[j++] = alpha[(v >> 6) & 63]; + dst[j++] = '='; + } + return j; +} + +static size_t b64_skip_ws(const char *src, size_t len, size_t index, + const uint8_t *dec_table) +{ + while (index < len && dec_table[(unsigned char)src[index]] == K_WS) + index++; + return index; +} + +/* Implements the FromBase64 abstract operation. + src/src_len: the input string (must be ASCII/latin1) + dst/max_len: output buffer + flags: b64_flags or b64_flags_url (selects valid characters) + last_chunk: B64_LAST_LOOSE, B64_LAST_STRICT, or B64_LAST_STOP_BEFORE_PARTIAL + *p_read: set to number of input characters consumed + *p_err: set to 1 on error, 0 on success + Returns: number of bytes written to dst */ +static size_t from_base64(const char *src, size_t src_len, + uint8_t *dst, size_t max_len, + const uint8_t *dec_table, int last_chunk, + size_t *p_read, int *p_err) +{ + size_t read = 0, written = 0; + uint32_t v, acc = 0; + int seen = 0; + size_t index = 0; + uint8_t ch; + + *p_err = 0; + + if (max_len == 0) { + *p_read = 0; + return 0; + } + + for (;;) { + if (seen == 0) { + /* Fast path: decode complete groups of 4 valid characters. + Breaks out on whitespace, padding, invalid chars, or capacity. */ + while (index + 4 <= src_len && written + 3 <= max_len) { + uint32_t v0, v1, v2, v3; + v0 = dec_table[(unsigned char)src[index]]; + v1 = dec_table[(unsigned char)src[index + 1]]; + v2 = dec_table[(unsigned char)src[index + 2]]; + v3 = dec_table[(unsigned char)src[index + 3]]; + if ((v0 | v1 | v2 | v3) >= 64) + break; + v = (v0 << 18) | (v1 << 12) | (v2 << 6) | v3; + dst[written] = (uint8_t)(v >> 16); + dst[written + 1] = (uint8_t)(v >> 8); + dst[written + 2] = (uint8_t)(v); + written += 3; + index += 4; + } + read = index; + + if (written >= max_len) { + *p_read = read; + return written; + } + } + + /* Slow path: handle whitespace, padding, partial groups, capacity. */ + index = b64_skip_ws(src, src_len, index, dec_table); + + if (index == src_len) { + if (seen > 0) { + if (last_chunk == B64_LAST_STOP_BEFORE_PARTIAL) { + *p_read = read; + return written; + } + if (last_chunk == B64_LAST_STRICT) { + *p_err = 1; + return 0; + } + /* loose */ + if (seen == 1) { + *p_err = 1; + return 0; + } + break; + } + *p_read = src_len; + return written; + } + + ch = src[index++]; + + if (ch == '=') { + if (seen < 2) { + *p_err = 1; + return 0; + } + index = b64_skip_ws(src, src_len, index, dec_table); + if (seen == 2) { + if (index == src_len) { + if (last_chunk == B64_LAST_STOP_BEFORE_PARTIAL) { + *p_read = read; + return written; + } + *p_err = 1; + return 0; + } + if (src[index] == '=') { + index++; + index = b64_skip_ws(src, src_len, index, dec_table); + } else { + *p_err = 1; + return 0; + } + } + /* After padding, only whitespace is allowed */ + if (index != src_len) { + *p_err = 1; + return 0; + } + if (last_chunk == B64_LAST_STRICT) { + uint32_t mask = (seen == 2) ? 0xF : 0x3; + if (acc & mask) { + *p_err = 1; + return 0; + } + } + break; + } + + v = dec_table[ch]; + if (v >= 64) { + *p_err = 1; + return 0; + } + + /* Check remaining capacity before committing to this group */ + { + size_t remaining = max_len - written; + if ((remaining == 1 && seen == 2) || + (remaining == 2 && seen == 3)) { + *p_read = read; + return written; + } + } + + acc = (acc << 6) | v; + seen++; + + if (seen == 4) { + dst[written] = (uint8_t)(acc >> 16); + dst[written + 1] = (uint8_t)(acc >> 8); + dst[written + 2] = (uint8_t)(acc); + written += 3; + acc = 0; + seen = 0; + read = index; + if (written >= max_len) { + *p_read = read; + return written; + } + } + } + + if (seen == 2) { + dst[written++] = (uint8_t)(acc >> 4); + } else if (seen == 3) { + dst[written] = (uint8_t)(acc >> 10); + dst[written + 1] = (uint8_t)(acc >> 2); + written += 2; + } + *p_read = src_len; + return written; +} + +/* Hex helpers */ +static const char u8a_hex_digits[] = "0123456789abcdef"; + +static size_t u8a_hex_encode(const uint8_t *src, size_t len, char *dst) +{ + for (size_t i = 0; i < len; i++) { + dst[i * 2] = u8a_hex_digits[src[i] >> 4]; + dst[i * 2 + 1] = u8a_hex_digits[src[i] & 0xF]; + } + return len * 2; +} + +/* Decode hex string to bytes. + Returns bytes written. Sets *p_read to chars consumed, *p_err on error. */ +static size_t u8a_hex_decode(const char *src, size_t src_len, + uint8_t *dst, size_t max_len, + size_t *p_read, int *p_err) +{ + size_t written = 0, i = 0; + *p_err = 0; + + if (src_len & 1) { + *p_err = 1; + return 0; + } + + while (i < src_len && written < max_len) { + int hi = from_hex(src[i]); + int lo = from_hex(src[i + 1]); + if (hi < 0 || lo < 0) { + *p_err = 1; + return 0; + } + dst[written++] = (uint8_t)((hi << 4) | lo); + i += 2; + } + + *p_read = i; + return written; +} + +static JSValue JS_NewUint8ArrayCopy(JSContext *ctx, const uint8_t *buf, size_t len) +{ + JSValue buffer, obj; + JSArrayBuffer *abuf; + + buffer = js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, NULL, + JS_CLASS_ARRAY_BUFFER, + (uint8_t *)buf, + js_array_buffer_free, NULL, + TRUE); + if (JS_IsException(buffer)) + return JS_EXCEPTION; + obj = js_create_from_ctor(ctx, JS_UNDEFINED, JS_CLASS_UINT8_ARRAY); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, buffer); + return JS_EXCEPTION; + } + abuf = js_get_array_buffer(ctx, buffer); + assert(abuf != NULL); + if (typed_array_init(ctx, obj, buffer, 0, abuf->byte_length, /*track_rab*/FALSE)) { + // 'buffer' is freed on error above. + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +/* Validate that this_val is a Uint8Array (type check only, no detach check). + Returns the JSObject pointer or NULL on error (throws). */ +static JSObject *check_uint8array(JSContext *ctx, JSValueConst this_val) +{ + JSObject *p; + + if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) + goto fail; + p = JS_VALUE_GET_OBJ(this_val); + if (p->class_id != JS_CLASS_UINT8_ARRAY) + goto fail; + return p; +fail: + JS_ThrowTypeError(ctx, "not a Uint8Array"); + return NULL; +} + +/* Get the data pointer and length of a Uint8Array, checking for detached + buffers. Must be called after options are read (per spec ordering). + Returns 0 on success, -1 on error (throws). */ +static int get_uint8array_bytes(JSContext *ctx, JSObject *p, + uint8_t **pdata, size_t *plen) +{ + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); + *pdata = NULL; /* fail safe */ + *plen = 0; + return -1; + } + *pdata = p->u.array.u.uint8_ptr; + *plen = p->u.array.count; + return 0; +} + +/* Validate options is undefined or an object (GetOptionsObject). + Returns 0 on success, -1 on error (throws). */ +static int check_options_object(JSContext *ctx, JSValueConst options) +{ + if (JS_IsUndefined(options)) + return 0; + if (!JS_IsObject(options)) { + JS_ThrowTypeError(ctx, "options must be an object"); + return -1; + } + return 0; +} + +/* Parse the 'alphabet' option from an options object. + Returns B64_ALPHABET_BASE64 or B64_ALPHABET_BASE64URL, or -1 on error. */ +static int parse_alphabet_option(JSContext *ctx, JSValueConst options) +{ + JSValue val; + const char *str; + int ret; + + if (JS_IsUndefined(options)) + return B64_ALPHABET_BASE64; + + val = JS_GetProperty(ctx, options, JS_ATOM_alphabet); + if (JS_IsException(val)) + return -1; + if (JS_IsUndefined(val)) + return B64_ALPHABET_BASE64; + if (!JS_IsString(val)) { + JS_FreeValue(ctx, val); + JS_ThrowTypeError(ctx, "expected string for alphabet"); + return -1; + } + + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) + return -1; + + if (!strcmp(str, "base64")) + ret = B64_ALPHABET_BASE64; + else if (!strcmp(str, "base64url")) + ret = B64_ALPHABET_BASE64URL; + else { + JS_ThrowTypeError(ctx, "invalid alphabet"); + ret = -1; + } + JS_FreeCString(ctx, str); + return ret; +} + +/* Parse the 'lastChunkHandling' option. Returns mode or -1 on error. */ +static int parse_last_chunk_option(JSContext *ctx, JSValueConst options) +{ + JSValue val; + const char *str; + int ret; + + if (JS_IsUndefined(options)) + return B64_LAST_LOOSE; + + val = JS_GetProperty(ctx, options, JS_ATOM_lastChunkHandling); + if (JS_IsException(val)) + return -1; + if (JS_IsUndefined(val)) + return B64_LAST_LOOSE; + if (!JS_IsString(val)) { + JS_FreeValue(ctx, val); + JS_ThrowTypeError(ctx, "expected string for lastChunkHandling"); + return -1; + } + + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) + return -1; + + if (!strcmp(str, "loose")) + ret = B64_LAST_LOOSE; + else if (!strcmp(str, "strict")) + ret = B64_LAST_STRICT; + else if (!strcmp(str, "stop-before-partial")) + ret = B64_LAST_STOP_BEFORE_PARTIAL; + else { + JS_ThrowTypeError(ctx, "invalid lastChunkHandling option"); + ret = -1; + } + JS_FreeCString(ctx, str); + return ret; +} + +/* Uint8Array.prototype.toBase64([options]) */ +static JSValue js_uint8array_to_base64(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *data; + size_t len; + JSValueConst options; + JSObject *p; + int alphabet, omit_padding; + size_t out_len, written; + JSString *ostr; + char *dst; + + p = check_uint8array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + + options = argc > 0 ? argv[0] : JS_UNDEFINED; + if (check_options_object(ctx, options)) + return JS_EXCEPTION; + alphabet = parse_alphabet_option(ctx, options); + if (alphabet < 0) + return JS_EXCEPTION; + + omit_padding = 0; + if (!JS_IsUndefined(options)) { + JSValue op_val = JS_GetProperty(ctx, options, JS_ATOM_omitPadding); + if (JS_IsException(op_val)) + return JS_EXCEPTION; + omit_padding = JS_ToBool(ctx, op_val); + JS_FreeValue(ctx, op_val); + } + + if (get_uint8array_bytes(ctx, p, &data, &len)) + return JS_EXCEPTION; + + out_len = 4 * ((len + 2) / 3); + + if (unlikely(out_len > JS_STRING_LEN_MAX)) + return JS_ThrowRangeError(ctx, "output too large"); + + ostr = js_alloc_string(ctx, out_len, 0); + if (!ostr) + return JS_EXCEPTION; + + dst = (char *)ostr->u.str8; + written = b64_encode(data, len, dst, + alphabet == B64_ALPHABET_BASE64URL ? b64url_enc : b64_enc); + if (omit_padding) { + while (written > 0 && dst[written - 1] == '=') + written--; + } + dst[written] = '\0'; + + ostr->len = written; + return JS_MKPTR(JS_TAG_STRING, ostr); +} + +/* Uint8Array.prototype.toHex() */ +static JSValue js_uint8array_to_hex(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *data; + size_t len, out_len; + JSObject *p; + JSString *ostr; + + p = check_uint8array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (get_uint8array_bytes(ctx, p, &data, &len)) + return JS_EXCEPTION; + + out_len = len * 2; + if (unlikely(out_len > JS_STRING_LEN_MAX)) + return JS_ThrowRangeError(ctx, "output too large"); + + ostr = js_alloc_string(ctx, out_len, 0); + if (!ostr) + return JS_EXCEPTION; + + u8a_hex_encode(data, len, (char *)ostr->u.str8); + ostr->u.str8[out_len] = '\0'; + return JS_MKPTR(JS_TAG_STRING, ostr); +} + +/* Uint8Array.fromBase64(string[, options]) */ +static JSValue js_uint8array_from_base64(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *str; + size_t str_len, read_pos, decoded_len, out_cap; + int alphabet, last_chunk, err; + uint8_t *buf; + JSValue result; + JSValueConst options; + + if (!JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "expected string"); + + str = JS_ToCStringLen(ctx, &str_len, argv[0]); + if (!str) + return JS_EXCEPTION; + + options = argc > 1 ? argv[1] : JS_UNDEFINED; + if (check_options_object(ctx, options)) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + alphabet = parse_alphabet_option(ctx, options); + if (alphabet < 0) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + last_chunk = parse_last_chunk_option(ctx, options); + if (last_chunk < 0) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + out_cap = (str_len / 4) * 3 + 3; + buf = js_malloc(ctx, out_cap); + if (!buf) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + decoded_len = from_base64(str, str_len, buf, out_cap, + alphabet == B64_ALPHABET_BASE64URL + ? b64url_dec : b64_dec, + last_chunk, &read_pos, &err); + JS_FreeCString(ctx, str); + + if (err) { + js_free(ctx, buf); + return JS_ThrowSyntaxError(ctx, "invalid base64 string"); + } + + result = JS_NewUint8ArrayCopy(ctx, buf, decoded_len); + js_free(ctx, buf); + return result; +} + +/* Uint8Array.fromHex(string) */ +static JSValue js_uint8array_from_hex(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *str; + size_t str_len, read_pos, decoded_len, out_cap; + int err; + uint8_t *buf; + JSValue result; + + if (!JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "expected string"); + + str = JS_ToCStringLen(ctx, &str_len, argv[0]); + if (!str) + return JS_EXCEPTION; + + out_cap = str_len / 2 + 1; + buf = js_malloc(ctx, out_cap); + if (!buf) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + decoded_len = u8a_hex_decode(str, str_len, buf, out_cap, &read_pos, &err); + JS_FreeCString(ctx, str); + + if (err) { + js_free(ctx, buf); + return JS_ThrowSyntaxError(ctx, "invalid hex string"); + } + + /* XXX: could avoid the copy */ + result = JS_NewUint8ArrayCopy(ctx, buf, decoded_len); + js_free(ctx, buf); + return result; +} + +/* Return a { read, written } result object */ +static JSValue js_make_read_written(JSContext *ctx, size_t read, size_t written) +{ + JSValue obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + if (JS_DefinePropertyValueStr(ctx, obj, "read", + JS_NewUint32(ctx, read), JS_PROP_C_W_E) < 0) + goto fail; + if (JS_DefinePropertyValueStr(ctx, obj, "written", + JS_NewUint32(ctx, written), JS_PROP_C_W_E) < 0) + goto fail; + return obj; +fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +/* Uint8Array.prototype.setFromBase64(string[, options]) */ +static JSValue js_uint8array_set_from_base64(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *data; + size_t len; + const char *str; + size_t str_len, read_pos, decoded_len; + JSObject *p; + int alphabet, last_chunk, err; + JSValueConst options; + + p = check_uint8array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + + if (!JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "expected string"); + + str = JS_ToCStringLen(ctx, &str_len, argv[0]); + if (!str) + return JS_EXCEPTION; + + options = argc > 1 ? argv[1] : JS_UNDEFINED; + if (check_options_object(ctx, options)) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + alphabet = parse_alphabet_option(ctx, options); + if (alphabet < 0) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + last_chunk = parse_last_chunk_option(ctx, options); + if (last_chunk < 0) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + if (get_uint8array_bytes(ctx, p, &data, &len)) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + decoded_len = from_base64(str, str_len, data, len, + alphabet == B64_ALPHABET_BASE64URL + ? b64url_dec : b64_dec, + last_chunk, &read_pos, &err); + JS_FreeCString(ctx, str); + + if (err) + return JS_ThrowSyntaxError(ctx, "invalid base64 string"); + + return js_make_read_written(ctx, read_pos, decoded_len); +} + +/* Uint8Array.prototype.setFromHex(string) */ +static JSValue js_uint8array_set_from_hex(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *data; + size_t len; + const char *str; + size_t str_len, read_pos, decoded_len; + JSObject *p; + int err; + + p = check_uint8array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + + if (!JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "expected string"); + + str = JS_ToCStringLen(ctx, &str_len, argv[0]); + if (!str) + return JS_EXCEPTION; + + if (get_uint8array_bytes(ctx, p, &data, &len)) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + decoded_len = u8a_hex_decode(str, str_len, data, len, &read_pos, &err); + JS_FreeCString(ctx, str); + + if (err) + return JS_ThrowSyntaxError(ctx, "invalid hex string"); + + return js_make_read_written(ctx, read_pos, decoded_len); +} + static const JSCFunctionListEntry js_typed_array_base_funcs[] = { JS_CFUNC_DEF("from", 1, js_typed_array_from ), JS_CFUNC_DEF("of", 0, js_typed_array_of ), @@ -58320,6 +59496,20 @@ static const JSCFunctionListEntry js_typed_array_funcs[] = { JS_PROP_INT32_DEF("BYTES_PER_ELEMENT", 8, 0), }; +static const JSCFunctionListEntry js_uint8array_proto_funcs[] = { + JS_PROP_INT32_DEF("BYTES_PER_ELEMENT", 1, 0), + JS_CFUNC_DEF("toBase64", 0, js_uint8array_to_base64), + JS_CFUNC_DEF("toHex", 0, js_uint8array_to_hex), + JS_CFUNC_DEF("setFromBase64", 1, js_uint8array_set_from_base64), + JS_CFUNC_DEF("setFromHex", 1, js_uint8array_set_from_hex), +}; + +static const JSCFunctionListEntry js_uint8array_funcs[] = { + JS_PROP_INT32_DEF("BYTES_PER_ELEMENT", 1, 0), + JS_CFUNC_DEF("fromBase64", 1, js_uint8array_from_base64), + JS_CFUNC_DEF("fromHex", 1, js_uint8array_from_hex), +}; + static JSValue js_typed_array_base_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -59020,19 +60210,16 @@ typedef enum AtomicsOpEnum { ATOMICS_OP_LOAD, } AtomicsOpEnum; -static int js_atomics_get_ptr(JSContext *ctx, void **pptr, - JSObject **pobj, uint64_t *pidx, - int *psize_log2, JSClassID *pclass_id, - JSValueConst obj, JSValueConst idx_val, - int is_waitable) +static JSObject *js_atomics_get_buf(JSContext *ctx, + JSValueConst obj, JSValueConst idx_val, + uint64_t *pidx, int is_waitable) { JSObject *p; JSTypedArray *ta; JSArrayBuffer *abuf; - void *ptr; uint64_t idx; BOOL err; - int size_log2, old_len; + int old_len; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) goto fail; @@ -59046,59 +60233,44 @@ static int js_atomics_get_ptr(JSContext *ctx, void **pptr, if (err) { fail: JS_ThrowTypeError(ctx, "integer TypedArray expected"); - return -1; + return NULL; } ta = p->u.typed_array; abuf = ta->buffer->u.array_buffer; if (!abuf->shared) { if (is_waitable == 2) { JS_ThrowTypeError(ctx, "not a SharedArrayBuffer TypedArray"); - return -1; + return NULL; } if (abuf->detached) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - return -1; + return NULL; } } old_len = p->u.array.count; if (JS_ToIndex(ctx, &idx, idx_val)) { - return -1; + return NULL; } if (idx >= old_len) goto oob; - if (is_waitable == 1) { - /* notify(): just avoid having an invalid pointer if overflow */ - if (idx >= p->u.array.count) - ptr = NULL; - } else { + if (is_waitable != 1) { /* RevalidateAtomicAccess() */ if (typed_array_is_oob(p)) { JS_ThrowTypeErrorArrayBufferOOB(ctx); - return -1; + return NULL; } if (idx >= p->u.array.count) { oob: JS_ThrowRangeError(ctx, "out-of-bound access"); - return -1; + return NULL; } } - size_log2 = typed_array_size_log2(p->class_id); - ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); - - *pptr = ptr; - if (pobj) - *pobj = p; - if (pidx) - *pidx = idx; - if (psize_log2) - *psize_log2 = size_log2; - if (pclass_id) - *pclass_id = p->class_id; - return 0; + *pidx = idx; + return p; } static JSValue js_atomics_op(JSContext *ctx, @@ -59109,12 +60281,12 @@ static JSValue js_atomics_op(JSContext *ctx, uint64_t v, a, rep_val, idx; void *ptr; JSValue ret; - JSClassID class_id; JSObject *p; - if (js_atomics_get_ptr(ctx, &ptr, &p, &idx, &size_log2, &class_id, - argv[0], argv[1], 0)) + p = js_atomics_get_buf(ctx, argv[0], argv[1], &idx, 0); + if (!p) return JS_EXCEPTION; + size_log2 = typed_array_size_log2(p->class_id); rep_val = 0; if (op == ATOMICS_OP_LOAD) { v = 0; @@ -59144,9 +60316,10 @@ static JSValue js_atomics_op(JSContext *ctx, return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); if (idx >= p->u.array.count) return JS_ThrowRangeError(ctx, "out-of-bound access"); - } - - switch(op | (size_log2 << 3)) { + } + ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); + + switch(op | (size_log2 << 3)) { #define OP(op_name, func_name) \ case ATOMICS_OP_ ## op_name | (0 << 3): \ @@ -59215,7 +60388,7 @@ static JSValue js_atomics_op(JSContext *ctx, abort(); } - switch(class_id) { + switch(p->class_id) { case JS_CLASS_INT8_ARRAY: a = (int8_t)a; goto done; @@ -59257,10 +60430,11 @@ static JSValue js_atomics_store(JSContext *ctx, JSObject *p; uint64_t idx; int64_t v; - - if (js_atomics_get_ptr(ctx, &ptr, &p, &idx, &size_log2, NULL, - argv[0], argv[1], 0)) + + p = js_atomics_get_buf(ctx, argv[0], argv[1], &idx, 0); + if (!p) return JS_EXCEPTION; + size_log2 = typed_array_size_log2(p->class_id); if (size_log2 == 3) { ret = JS_ToBigIntFree(ctx, JS_DupValue(ctx, argv[2])); if (JS_IsException(ret)) @@ -59286,6 +60460,8 @@ static JSValue js_atomics_store(JSContext *ctx, if (idx >= p->u.array.count) return JS_ThrowRangeError(ctx, "out-of-bound access"); + ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); + switch(size_log2) { case 0: atomic_store((_Atomic(uint8_t) *)ptr, v); @@ -59374,8 +60550,10 @@ static JSValue js_atomics_wait(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv) { + JSObject *p; int64_t v; int32_t v32; + uint64_t idx; void *ptr; int64_t timeout; struct timespec ts; @@ -59383,9 +60561,12 @@ static JSValue js_atomics_wait(JSContext *ctx, int ret, size_log2, res; double d; - if (js_atomics_get_ptr(ctx, &ptr, NULL, NULL, &size_log2, NULL, - argv[0], argv[1], 2)) + p = js_atomics_get_buf(ctx, argv[0], argv[1], &idx, 2); + if (!p) return JS_EXCEPTION; + size_log2 = typed_array_size_log2(p->class_id); + ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); + /* 'argv[0]' is a SharedArrayBuffer so it cannot be detached nor reduced */ if (size_log2 == 3) { if (JS_ToBigInt64(ctx, &v, argv[2])) @@ -59459,13 +60640,17 @@ static JSValue js_atomics_notify(JSContext *ctx, { struct list_head *el, *el1, waiter_list; int32_t count, n; + uint64_t idx; + int size_log2; void *ptr; JSAtomicsWaiter *waiter; JSArrayBuffer *abuf; JSObject *p; - if (js_atomics_get_ptr(ctx, &ptr, &p, NULL, NULL, NULL, argv[0], argv[1], 1)) + p = js_atomics_get_buf(ctx, argv[0], argv[1], &idx, 1); + if (!p) return JS_EXCEPTION; + size_log2 = typed_array_size_log2(p->class_id); if (JS_IsUndefined(argv[2])) { count = INT32_MAX; @@ -59478,6 +60663,7 @@ static JSValue js_atomics_notify(JSContext *ctx, abuf = p->u.typed_array->buffer->u.array_buffer; if (abuf->shared && count > 0) { /* 'argv[0]' is a SharedArrayBuffer so it cannot be detached nor reduced */ + ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); pthread_mutex_lock(&js_atomics_mutex); init_list_head(&waiter_list); list_for_each_safe(el, el1, &js_atomics_waiter_list) { @@ -59584,17 +60770,25 @@ int JS_AddIntrinsicTypedArrays(JSContext *ctx) for(i = JS_CLASS_UINT8C_ARRAY; i < JS_CLASS_UINT8C_ARRAY + JS_TYPED_ARRAY_COUNT; i++) { char buf[ATOM_GET_STR_BUF_SIZE]; const char *name; - const JSCFunctionListEntry *bpe; name = JS_AtomGetStr(ctx, buf, sizeof(buf), JS_ATOM_Uint8ClampedArray + i - JS_CLASS_UINT8C_ARRAY); - bpe = js_typed_array_funcs + typed_array_size_log2(i); - obj = JS_NewCConstructor(ctx, i, name, - ft.generic, 3, JS_CFUNC_constructor_magic, i, - typed_array_base_func, - bpe, 1, - bpe, 1, - 0); + if (i == JS_CLASS_UINT8_ARRAY) { + obj = JS_NewCConstructor(ctx, i, name, + ft.generic, 3, JS_CFUNC_constructor_magic, i, + typed_array_base_func, + js_uint8array_funcs, countof(js_uint8array_funcs), + js_uint8array_proto_funcs, countof(js_uint8array_proto_funcs), + 0); + } else { + const JSCFunctionListEntry *bpe = js_typed_array_funcs + typed_array_size_log2(i); + obj = JS_NewCConstructor(ctx, i, name, + ft.generic, 3, JS_CFUNC_constructor_magic, i, + typed_array_base_func, + bpe, 1, + bpe, 1, + 0); + } if (JS_IsException(obj)) { fail: JS_FreeValue(ctx, typed_array_base_func); diff --git a/src/couch_quickjs/quickjs/quickjs.h b/src/couch_quickjs/quickjs/quickjs.h index 92cc000d0..57574e6b8 100644 --- a/src/couch_quickjs/quickjs/quickjs.h +++ b/src/couch_quickjs/quickjs/quickjs.h @@ -95,6 +95,7 @@ enum { /* any larger tag is FLOAT64 if JS_NAN_BOXING */ }; +/* must match the layout of 'JSMallocBlockHeader' */ typedef struct JSRefCountHeader { int ref_count; } JSRefCountHeader; @@ -675,10 +676,16 @@ JSValue __js_printf_like(2, 3) JS_ThrowInternalError(JSContext *ctx, const char JSValue JS_ThrowOutOfMemory(JSContext *ctx); void __JS_FreeValue(JSContext *ctx, JSValue v); + +static inline JSRefCountHeader *__js_rc(void *ptr) +{ + return (JSRefCountHeader *)((uint32_t *)ptr - 1); +} + static inline void JS_FreeValue(JSContext *ctx, JSValue v) { if (JS_VALUE_HAS_REF_COUNT(v)) { - JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); + JSRefCountHeader *p = __js_rc(JS_VALUE_GET_PTR(v)); if (--p->ref_count <= 0) { __JS_FreeValue(ctx, v); } @@ -688,7 +695,7 @@ void __JS_FreeValueRT(JSRuntime *rt, JSValue v); static inline void JS_FreeValueRT(JSRuntime *rt, JSValue v) { if (JS_VALUE_HAS_REF_COUNT(v)) { - JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); + JSRefCountHeader *p = __js_rc(JS_VALUE_GET_PTR(v)); if (--p->ref_count <= 0) { __JS_FreeValueRT(rt, v); } @@ -698,7 +705,7 @@ static inline void JS_FreeValueRT(JSRuntime *rt, JSValue v) static inline JSValue JS_DupValue(JSContext *ctx, JSValueConst v) { if (JS_VALUE_HAS_REF_COUNT(v)) { - JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); + JSRefCountHeader *p = __js_rc(JS_VALUE_GET_PTR(v)); p->ref_count++; } return (JSValue)v; @@ -707,7 +714,7 @@ static inline JSValue JS_DupValue(JSContext *ctx, JSValueConst v) static inline JSValue JS_DupValueRT(JSRuntime *rt, JSValueConst v) { if (JS_VALUE_HAS_REF_COUNT(v)) { - JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); + JSRefCountHeader *p = __js_rc(JS_VALUE_GET_PTR(v)); p->ref_count++; } return (JSValue)v; diff --git a/src/couch_quickjs/quickjs/test262.conf b/src/couch_quickjs/quickjs/test262.conf index ab8b0a5bd..8fe86cc6f 100644 --- a/src/couch_quickjs/quickjs/test262.conf +++ b/src/couch_quickjs/quickjs/test262.conf @@ -236,7 +236,7 @@ u180e Uint16Array Uint32Array Uint8Array -uint8array-base64=skip +uint8array-base64 Uint8ClampedArray upsert WeakMap diff --git a/src/couch_quickjs/quickjs/test262_errors.txt b/src/couch_quickjs/quickjs/test262_errors.txt index 4d6d0bd3e..dd1cceea6 100644 --- a/src/couch_quickjs/quickjs/test262_errors.txt +++ b/src/couch_quickjs/quickjs/test262_errors.txt @@ -33,8 +33,6 @@ test262/test/staging/sm/Function/function-name-for.js:13: Test262Error: Expected test262/test/staging/sm/Function/implicit-this-in-parameter-expression.js:12: Test262Error: Expected SameValue(«[object Object]», «undefined») to be true test262/test/staging/sm/Function/invalid-parameter-list.js:13: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/staging/sm/Function/invalid-parameter-list.js:13: strict mode: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all -test262/test/staging/sm/TypedArray/prototype-constructor-identity.js:17: Test262Error: Expected SameValue(«2», «6») to be true -test262/test/staging/sm/TypedArray/prototype-constructor-identity.js:17: strict mode: Test262Error: Expected SameValue(«2», «6») to be true test262/test/staging/sm/async-functions/async-contains-unicode-escape.js:11: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/staging/sm/async-functions/async-contains-unicode-escape.js:11: strict mode: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/staging/sm/async-functions/await-in-arrow-parameters.js:10: Test262Error: AsyncFunction:(a = (b = await/r/g) => {}) => {} Expected a SyntaxError to be thrown but no exception was thrown at all
