diff --git a/examples/results.txt b/examples/results.txt --- a/examples/results.txt +++ b/examples/results.txt @@ -523,13 +523,13 @@ compat-table/es6/Proxy.handler.construct.invariants.js: OK compat-table/es6/Proxy.handler.construct.js: OK compat-table/es6/Proxy.handler.defineProperty.invariants.js: failed -compat-table/es6/Proxy.handler.defineProperty.js: failed +compat-table/es6/Proxy.handler.defineProperty.js: OK compat-table/es6/Proxy.handler.deleteProperty.invariants.js: OK compat-table/es6/Proxy.handler.deleteProperty.js: OK compat-table/es6/Proxy.handler.getOwnPropertyDescriptor.invariants.js: failed compat-table/es6/Proxy.handler.getOwnPropertyDescriptor.js: failed compat-table/es6/Proxy.handler.getPrototypeOf.invariants.js: failed -compat-table/es6/Proxy.handler.getPrototypeOf.js: failed +compat-table/es6/Proxy.handler.getPrototypeOf.js: OK compat-table/es6/Proxy.handler.get.instances.js: failed compat-table/es6/Proxy.handler.get.invariants.js: OK compat-table/es6/Proxy.handler.get.js: OK @@ -896,7 +896,7 @@ compat-table/es6/misc.Proxy.get.Array.reverse.js: failed compat-table/es6/misc.Proxy.get.Array.shift.js: failed compat-table/es6/misc.Proxy.get.Array.splice.js: failed -compat-table/es6/misc.Proxy.get.Array.toString.js: failed +compat-table/es6/misc.Proxy.get.Array.toString.js: OK compat-table/es6/misc.Proxy.get.ClassDefinitionEvaluation.js: OK compat-table/es6/misc.Proxy.get.CreateDynamicFunction.js: failed compat-table/es6/misc.Proxy.get.CreateListFromArrayLike.js: failed @@ -922,7 +922,7 @@ compat-table/es6/misc.Proxy.get.String.replace.js: failed compat-table/es6/misc.Proxy.get.String.search.js: failed compat-table/es6/misc.Proxy.get.String.split.js: failed -compat-table/es6/misc.Proxy.get.ToPrimitive.js: TypeError: Cannot convert object to primitive value +compat-table/es6/misc.Proxy.get.ToPrimitive.js: OK compat-table/es6/misc.Proxy.get.ToPropertyDescriptor.js: failed compat-table/es6/misc.Proxy.get.instanceof.js: OK compat-table/es6/misc.Proxy.ownKeys.SerializeJSONObject.js: failed @@ -1153,26 +1153,26 @@ compat-table/es2017/SharedArrayBuffer.prototype.slice.js: OK compat-table/es2017/String.prototype.padEnd.js: OK compat-table/es2017/String.prototype.padStart.js: OK -compat-table/es2017/annex-b.Object.prototype.__defineGetter__.ToObject.js: ReferenceError: '__defineGetter__' is not defined -compat-table/es2017/annex-b.Object.prototype.__defineGetter__.js: TypeError: Cannot read properties of undefined (reading 'call') -compat-table/es2017/annex-b.Object.prototype.__defineGetter__.symbols.js: TypeError: Cannot read properties of undefined (reading 'call') -compat-table/es2017/annex-b.Object.prototype.__defineSetter__.ToObject.js: ReferenceError: '__defineSetter__' is not defined -compat-table/es2017/annex-b.Object.prototype.__defineSetter__.js: TypeError: Cannot read properties of undefined (reading 'call') -compat-table/es2017/annex-b.Object.prototype.__defineSetter__.symbols.js: TypeError: Cannot read properties of undefined (reading 'call') -compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.ToObject.js: ReferenceError: '__lookupGetter__' is not defined -compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.js: TypeError: Cannot read properties of undefined (reading 'call') -compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.prototype-chain.js: TypeError: Cannot read properties of undefined (reading 'call') -compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.shadow-accessors.js: TypeError: undefined is not a function -compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.symbols.js: TypeError: Cannot read properties of undefined (reading 'call') -compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.ToObject.js: ReferenceError: '__lookupSetter__' is not defined -compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.js: TypeError: Cannot read properties of undefined (reading 'call') -compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.prototype-chain.js: TypeError: Cannot read properties of undefined (reading 'call') -compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.shadow-accessors.js: TypeError: undefined is not a function -compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.symbols.js: TypeError: Cannot read properties of undefined (reading 'call') -compat-table/es2017/annex-b.Proxy.__defineGetter__.js: TypeError: Cannot read properties of undefined (reading 'call') -compat-table/es2017/annex-b.Proxy.__defineSetter__.js: TypeError: Cannot read properties of undefined (reading 'call') -compat-table/es2017/annex-b.Proxy.__lookupGetter__.js: TypeError: Cannot read properties of undefined (reading 'call') -compat-table/es2017/annex-b.Proxy.__lookupSetter__.js: TypeError: Cannot read properties of undefined (reading 'call') +compat-table/es2017/annex-b.Object.prototype.__defineGetter__.ToObject.js: OK +compat-table/es2017/annex-b.Object.prototype.__defineGetter__.js: OK +compat-table/es2017/annex-b.Object.prototype.__defineGetter__.symbols.js: OK +compat-table/es2017/annex-b.Object.prototype.__defineSetter__.ToObject.js: OK +compat-table/es2017/annex-b.Object.prototype.__defineSetter__.js: OK +compat-table/es2017/annex-b.Object.prototype.__defineSetter__.symbols.js: OK +compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.ToObject.js: OK +compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.js: OK +compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.prototype-chain.js: OK +compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.shadow-accessors.js: OK +compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.symbols.js: OK +compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.ToObject.js: OK +compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.js: OK +compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.prototype-chain.js: OK +compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.shadow-accessors.js: OK +compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.symbols.js: OK +compat-table/es2017/annex-b.Proxy.__defineGetter__.js: OK +compat-table/es2017/annex-b.Proxy.__defineSetter__.js: OK +compat-table/es2017/annex-b.Proxy.__lookupGetter__.js: OK +compat-table/es2017/annex-b.Proxy.__lookupSetter__.js: OK compat-table/es2017/annex-b.for-in-assignment-non-strict.js: OK compat-table/es2017/async.Symbol.toStringTag.js: failed compat-table/es2017/async.arrow-in-class.js: OK diff --git a/include/internal.h b/include/internal.h --- a/include/internal.h +++ b/include/internal.h @@ -70,8 +70,8 @@ #define PROTO_WALK_F_OBJECT_ONLY (1u << 0) #define PROTO_WALK_F_LOOKUP (1u << 1) -#define ROPE_MAX_DEPTH 64 -#define ROPE_FLATTEN_THRESHOLD (32 * 1024) +#define ROPE_MAX_DEPTH 255 +#define ROPE_FLATTEN_THRESHOLD (512 * 1024) #define T_EMPTY (NANBOX_PREFIX | ((ant_value_t)T_SENTINEL << NANBOX_TYPE_SHIFT) | 0xDEADULL) #define T_SPECIAL_OBJECT_MASK (JS_TPFLG(T_OBJ) | JS_TPFLG(T_ARR)) @@ -207,7 +207,7 @@ ant_pool_t symbol; ant_pool_t permanent; ant_class_pool_t bigint; - ant_class_pool_t string; + ant_string_pool_t string; } pool; struct { @@ -253,12 +253,6 @@ bool thrown_exists; }; -typedef struct { - ant_offset_t len; - uint8_t is_ascii; - char bytes[]; -} ant_flat_string_t; - enum { STR_ASCII_UNKNOWN = 0, STR_ASCII_YES = 1, @@ -267,11 +261,9 @@ typedef struct { ant_offset_t len; - uint8_t depth; - ant_value_t left; - ant_value_t right; - ant_value_t cached; -} ant_rope_heap_t; + uint8_t is_ascii; + char bytes[]; +} ant_flat_string_t; typedef struct { const char *ptr; @@ -483,6 +475,14 @@ static inline ant_flat_string_t *str_flat_from_bytes(const char *str) { return (ant_flat_string_t *)((char *)str - offsetof(ant_flat_string_t, bytes)); +} + +static inline ant_flat_string_t *large_string_flat_ptr(ant_large_string_alloc_t *alloc) { + return alloc ? (ant_flat_string_t *)&alloc->len : NULL; +} + +static inline ant_large_string_alloc_t *large_string_alloc_from_flat(ant_flat_string_t *flat) { + return flat ? (ant_large_string_alloc_t *)((char *)flat - offsetof(ant_large_string_alloc_t, len)) : NULL; } static inline uint8_t str_detect_ascii_bytes(const char *str, size_t len) { diff --git a/include/pool.h b/include/pool.h --- a/include/pool.h +++ b/include/pool.h @@ -44,6 +44,39 @@ ant_pool_bucket_t classes[ANT_POOL_SIZE_CLASS_COUNT]; } ant_class_pool_t; +typedef struct ant_large_string_alloc { + struct ant_large_string_alloc *next; + struct ant_large_string_alloc *prev; + size_t capacity; + size_t alloc_size; + uint32_t quarantine_epoch; + uint8_t marked; + ant_offset_t len; + uint8_t is_ascii; + char bytes[]; +} ant_large_string_alloc_t; + +typedef struct { + ant_offset_t len; + uint8_t depth; + ant_value_t left; + ant_value_t right; + ant_value_t cached; +} ant_rope_heap_t; + +typedef struct { + ant_large_string_alloc_t *live; + ant_large_string_alloc_t *reusable; + ant_large_string_alloc_t *quarantine; + uint32_t gc_epoch; +} ant_large_string_space_t; + +typedef struct ant_string_pool { + size_t block_size; + ant_pool_bucket_t classes[ANT_POOL_SIZE_CLASS_COUNT]; + ant_large_string_space_t large; +} ant_string_pool_t; + typedef enum { ANT_ALLOC_ROPE = 0, ANT_ALLOC_SYMBOL = 1, @@ -56,6 +89,14 @@ size_t capacity; size_t blocks; } ant_pool_stats_t; + +typedef struct { + ant_pool_stats_t pooled; + ant_pool_stats_t large_live; + ant_pool_stats_t large_reusable; + ant_pool_stats_t large_quarantine; + ant_pool_stats_t total; +} ant_string_pool_stats_t; static inline ant_pool_block_t *pool_free_next(ant_pool_block_t *b) { ant_pool_block_t *p; memcpy(&p, b->data, sizeof(ant_pool_block_t *)); return p; @@ -101,6 +142,7 @@ void js_pool_destroy(ant_pool_t *pool); void js_class_pool_destroy(ant_class_pool_t *pool); +void js_string_pool_destroy(ant_string_pool_t *pool); void *js_type_alloc( ant_t *js, ant_alloc_kind_t kind, @@ -114,5 +156,6 @@ ant_pool_stats_t js_pool_stats(ant_pool_t *pool); ant_pool_stats_t js_class_pool_stats(ant_class_pool_t *pool); +ant_string_pool_stats_t js_string_pool_stats(ant_string_pool_t *pool); #endif diff --git a/include/utils.h b/include/utils.h --- a/include/utils.h +++ b/include/utils.h @@ -54,7 +54,7 @@ size_t len; } repl_capture_t; -void repl_template( +bool repl_template( const char *repl, size_t repl_len, const char *matched, size_t matched_len, const char *str, size_t str_len, size_t position, diff --git a/src/ant.c b/src/ant.c --- a/src/ant.c +++ b/src/ant.c @@ -53,6 +53,7 @@ #include #endif +#include "modules/atomics.h" #include "modules/bigint.h" #include "modules/timer.h" #include "modules/symbol.h" @@ -592,7 +593,8 @@ static ant_value_t js_call_valueOf(ant_t *js, ant_value_t value); static ant_value_t js_call_toString(ant_t *js, ant_value_t value); -static ant_value_t js_call_method(ant_t *js, ant_value_t obj, const char *method, size_t method_len, ant_value_t *args, int nargs); +static ant_value_t js_call_method(ant_t *js, ant_value_t obj, const char *method, ant_value_t *args, int nargs); +static ant_value_t builtin_object_defineProperty(ant_t *js, ant_value_t *args, int nargs); static inline bool is_slot_prop(ant_offset_t header); static inline ant_offset_t next_prop(ant_offset_t header); @@ -600,6 +602,7 @@ static ant_value_t builtin_promise_then(ant_t *js, ant_value_t *args, int nargs); static ant_value_t proxy_get(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); static ant_value_t proxy_get_val(ant_t *js, ant_value_t proxy, ant_value_t key_val); +static ant_value_t proxy_get_prototype_of(ant_t *js, ant_value_t proxy); static ant_value_t proxy_set(ant_t *js, ant_value_t proxy, const char *key, size_t key_len, ant_value_t value); static ant_value_t proxy_has(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); static ant_value_t proxy_has_val(ant_t *js, ant_value_t proxy, ant_value_t key_val); @@ -607,6 +610,7 @@ static ant_value_t proxy_has_own(ant_t *js, ant_value_t proxy, ant_value_t key_val); static ant_value_t proxy_delete(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); static ant_value_t proxy_delete_val(ant_t *js, ant_value_t proxy, ant_value_t key_val); +static ant_value_t proxy_define_property(ant_t *js, ant_value_t proxy, ant_value_t key_val, ant_value_t descriptor); static ant_value_t get_ctor_proto(ant_t *js, const char *name, size_t len); static inline void array_len_set(ant_t *js, ant_value_t obj, ant_offset_t new_len); @@ -1729,26 +1733,24 @@ } ant_offset_t total_len = rope_len(rope); - char *buf = (char *)ant_calloc(total_len + 1); - - if (!buf) { - GC_ROOT_RESTORE(js, root_mark); - return js_mkerr(js, "oom"); - } - - ant_offset_t pos = 0; - rope_flatten_into(js, rope, buf, &pos); - buf[pos] = '\0'; - - ant_value_t flat = js_mkstr(js, buf, pos); + ant_value_t flat = js_mkstr(js, NULL, total_len); GC_ROOT_PIN(js, flat); - free(buf); - - if (!is_err(flat)) { - rope_set_cached_flat(rope, flat); + + if (is_err(flat)) { + GC_ROOT_RESTORE(js, root_mark); + return flat; } + + ant_flat_string_t *flat_ptr = (ant_flat_string_t *)(uintptr_t)vdata(flat); + ant_offset_t pos = 0; + rope_flatten_into(js, rope, flat_ptr->bytes, &pos); + flat_ptr->bytes[pos] = '\0'; + flat_ptr->is_ascii = str_detect_ascii_bytes(flat_ptr->bytes, (size_t)pos); + + rope_set_cached_flat(rope, flat); GC_ROOT_RESTORE(js, root_mark); + return flat; } @@ -4069,7 +4071,7 @@ uint8_t left_depth = (vtype(l) == T_STR && str_is_heap_rope(l)) ? rope_depth(l) : 0; uint8_t right_depth = (vtype(r) == T_STR && str_is_heap_rope(r)) ? rope_depth(r) : 0; - uint8_t new_depth = (left_depth > right_depth ? left_depth : right_depth) + 1; + unsigned int new_depth = (unsigned int)(left_depth > right_depth ? left_depth : right_depth) + 1u; if (new_depth >= ROPE_MAX_DEPTH || total_len >= ROPE_FLATTEN_THRESHOLD) { ant_value_t flat_l = l, flat_r = r; @@ -4094,7 +4096,7 @@ return string_builder_finalize(js, &sb); } - return js_mkrope(js, l, r, total_len, new_depth); + return js_mkrope(js, l, r, total_len, (uint8_t)new_depth); } ant_offset_t n1, off1 = vstr(js, l, &n1); @@ -4132,17 +4134,12 @@ typedef enum { ITER_CONTINUE, ITER_BREAK, ITER_ERROR } iter_action_t; typedef iter_action_t (*iter_callback_t)(ant_t *js, ant_value_t value, void *ctx, ant_value_t *out); -static bool js_try_call_method(ant_t *js, ant_value_t obj, const char *method, size_t method_len, ant_value_t *args, int nargs, ant_value_t *out_result) { - ant_value_t getter = js_mkundef(); bool has_getter = false; - uintptr_t off = lkp_with_getter(js, obj, method, method_len, &getter, &has_getter); - - ant_value_t fn; - if (has_getter) { - fn = call_proto_accessor(js, obj, getter, true, NULL, 0, false); - if (is_err(fn)) { *out_result = fn; return true; } - } else if (off != 0) { - fn = propref_load(js, (ant_offset_t)off); - } else return false; +static bool js_try_call_method(ant_t *js, ant_value_t obj, const char *method, ant_value_t *args, int nargs, ant_value_t *out_result) { + ant_value_t fn = js_getprop_fallback(js, obj, method); + if (is_err(fn)) { + *out_result = fn; + return true; + } uint8_t ft = vtype(fn); if (ft != T_FUNC && ft != T_CFUNC) return false; @@ -4167,14 +4164,14 @@ return true; } -static ant_value_t js_call_method(ant_t *js, ant_value_t obj, const char *method, size_t method_len, ant_value_t *args, int nargs) { +static ant_value_t js_call_method(ant_t *js, ant_value_t obj, const char *method, ant_value_t *args, int nargs) { ant_value_t result; - if (!js_try_call_method(js, obj, method, method_len, args, nargs, &result)) return js_mkundef(); + if (!js_try_call_method(js, obj, method, args, nargs, &result)) return js_mkundef(); return result; } static ant_value_t js_call_toString(ant_t *js, ant_value_t value) { - ant_value_t result = js_call_method(js, value, "toString", 8, NULL, 0); + ant_value_t result = js_call_method(js, value, "toString", NULL, 0); if (is_err(result)) return result; if (vtype(result) == T_STR) return result; @@ -4197,7 +4194,7 @@ } static ant_value_t js_call_valueOf(ant_t *js, ant_value_t value) { - ant_value_t result = js_call_method(js, value, "valueOf", 7, NULL, 0); + ant_value_t result = js_call_method(js, value, "valueOf", NULL, 0); if (vtype(result) == T_UNDEF) return value; return result; } @@ -4210,10 +4207,7 @@ static ant_value_t try_exotic_to_primitive(ant_t *js, ant_value_t value, int hint) { ant_value_t tp_sym = get_toPrimitive_sym(); if (vtype(tp_sym) != T_SYMBOL) return mkval(T_UNDEF, 0); - ant_offset_t tp_off = lkp_sym_proto(js, value, (ant_offset_t)vdata(tp_sym)); - if (tp_off == 0) return mkval(T_UNDEF, 0); - - ant_value_t tp_fn = propref_load(js, tp_off); + ant_value_t tp_fn = js_get_sym(js, value, tp_sym); uint8_t ft = vtype(tp_fn); if (ft == T_UNDEF) return mkval(T_UNDEF, 0); @@ -4231,14 +4225,13 @@ static ant_value_t try_ordinary_to_primitive(ant_t *js, ant_value_t value, int hint) { static const char *names[] = {"valueOf", "toString"}; - static const size_t lens[] = {7, 8}; int first = (hint == 1); ant_value_t result; for (int i = 0; i < 2; i++) { int idx = first ^ i; - if (js_try_call_method(js, value, names[idx], lens[idx], NULL, 0, &result)) + if (js_try_call_method(js, value, names[idx], NULL, 0, &result)) if (is_err(result) || is_primitive(result)) return result; } @@ -5603,9 +5596,14 @@ ant_value_t obj = args[0]; uint8_t t = vtype(obj); - if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) return get_prototype_for_type(js, t); + if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) + return get_prototype_for_type(js, t); if (t == T_CFUNC) return get_prototype_for_type(js, t); - if (is_object_type(obj)) return get_proto(js, obj); + + if (is_object_type(obj)) { + if (is_proxy(obj)) return proxy_get_prototype_of(js, obj); + return get_proto(js, obj); + } return js_mknull(); } @@ -5688,11 +5686,20 @@ static ant_value_t legacy_accessor_this_obj(ant_t *js, ant_value_t this_val) { uint8_t t = vtype(this_val); - if (t == T_UNDEF || t == T_NULL) { - return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert undefined or null to object"); - } + + if (t == T_UNDEF || t == T_NULL) return js_mkerr_typed( + js, JS_ERR_TYPE, "Cannot convert undefined or null to object" + ); + if (t == T_CFUNC) return js_cfunc_promote(js, this_val); if (t == T_OBJ || t == T_ARR || t == T_FUNC) return js_as_obj(this_val); + + if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) { + ant_value_t boxed = builtin_Object(js, &this_val, 1); + if (is_err(boxed)) return boxed; + if (is_object_type(boxed)) return js_as_obj(boxed); + } + return js_mkerr_typed(js, JS_ERR_TYPE, "Legacy accessor methods require an object receiver"); } @@ -5747,6 +5754,32 @@ return key_val; } + if (is_proxy(js_as_obj(obj))) { + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, obj); + GC_ROOT_PIN(js, key_val); + GC_ROOT_PIN(js, getter); + + ant_value_t desc = js_mkobj(js); + if (is_err(desc)) { + GC_ROOT_RESTORE(js, root_mark); + return desc; + } + + GC_ROOT_PIN(js, desc); + js_setprop(js, desc, js_mkstr(js, "get", 3), getter); + js_setprop(js, desc, js_mkstr(js, "enumerable", 10), js_true); + js_setprop(js, desc, js_mkstr(js, "configurable", 12), js_true); + + ant_value_t define_args[3] = { obj, key_val, desc }; + ant_value_t result = builtin_object_defineProperty(js, define_args, 3); + + GC_ROOT_RESTORE(js, root_mark); + if (is_err(result)) return result; + + return js_mkundef(); + } + if (vtype(key_val) == T_SYMBOL) { js_set_sym_getter_desc(js, js_as_obj(obj), key_val, getter, JS_DESC_E | JS_DESC_C); } else js_set_getter_desc(js, js_as_obj(obj), key_str, (size_t)key_len, getter, JS_DESC_E | JS_DESC_C); @@ -5775,11 +5808,44 @@ return key_val; } + if (is_proxy(js_as_obj(obj))) { + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, obj); + GC_ROOT_PIN(js, key_val); + GC_ROOT_PIN(js, setter); + + ant_value_t desc = js_mkobj(js); + if (is_err(desc)) { + GC_ROOT_RESTORE(js, root_mark); + return desc; + } + + GC_ROOT_PIN(js, desc); + js_setprop(js, desc, js_mkstr(js, "set", 3), setter); + js_setprop(js, desc, js_mkstr(js, "enumerable", 10), js_true); + js_setprop(js, desc, js_mkstr(js, "configurable", 12), js_true); + + ant_value_t define_args[3] = { obj, key_val, desc }; + ant_value_t result = builtin_object_defineProperty(js, define_args, 3); + + GC_ROOT_RESTORE(js, root_mark); + if (is_err(result)) return result; + + return js_mkundef(); + } + if (vtype(key_val) == T_SYMBOL) { js_set_sym_setter_desc(js, js_as_obj(obj), key_val, setter, JS_DESC_E | JS_DESC_C); } else js_set_setter_desc(js, js_as_obj(obj), key_str, (size_t)key_len, setter, JS_DESC_E | JS_DESC_C); return js_mkundef(); +} + +static ant_value_t legacy_lookup_accessor_from_descriptor(ant_t *js, ant_value_t desc, bool want_getter) { + if (vtype(desc) != T_OBJ) return js_mkundef(); + ant_offset_t off = lkp_interned(js, desc, want_getter ? js->intern.get : js->intern.set, 3); + if (off == 0) return js_mkundef(); + return propref_load(js, off); } static ant_value_t legacy_lookup_accessor(ant_t *js, ant_value_t this_val, ant_value_t key, bool want_getter) { @@ -5795,7 +5861,16 @@ return key_val; } - for (ant_value_t cur = obj; is_object_type(cur); cur = get_proto(js, cur)) { + for (ant_value_t cur = obj; is_object_type(cur); ) { + if (is_proxy(cur)) { + ant_value_t desc = proxy_get_own_property_descriptor(js, cur, key_val); + if (is_err(desc)) return desc; + if (vtype(desc) == T_OBJ) return legacy_lookup_accessor_from_descriptor(js, desc, want_getter); + cur = proxy_get_prototype_of(js, cur); + if (is_err(cur)) return cur; + continue; + } + prop_meta_t meta; bool has_meta = (vtype(key_val) == T_SYMBOL) ? lookup_symbol_prop_meta(cur, sym_off, &meta) @@ -5818,6 +5893,7 @@ } if (lkp(js, cur, key_str, key_len) != 0) return js_mkundef(); } + cur = get_proto(js, cur); } return js_mkundef(); @@ -5969,6 +6045,13 @@ } ant_value_t as_obj = js_as_obj(obj); + if (is_proxy(as_obj)) { + ant_value_t proxy_result = proxy_define_property(js, as_obj, prop, descriptor); + if (is_err(proxy_result)) return proxy_result; + if (!js_truthy(js, proxy_result)) + return js_mkerr_typed(js, JS_ERR_TYPE, "'defineProperty' on proxy: trap returned falsy"); + return obj; + } ant_offset_t prop_len = 0; const char *prop_str = NULL; @@ -5982,9 +6065,8 @@ } else { ant_offset_t prop_off = vstr(js, prop, &prop_len); prop_str = (char *)(uintptr_t)(prop_off); - if (streq(prop_str, prop_len, STR_PROTO, STR_PROTO_LEN)) { + if (streq(prop_str, prop_len, STR_PROTO, STR_PROTO_LEN)) return js_mkerr(js, "Cannot define " STR_PROTO " property"); - } } bool has_value = false, has_get = false, has_set = false; @@ -7945,8 +8027,13 @@ static ant_value_t builtin_array_sort(ant_t *js, ant_value_t *args, int nargs) { ant_value_t arr = js->this_val; ant_value_t compareFn = js_mkundef(); + + ant_value_t result = arr; ant_value_t *vals = NULL, *keys = NULL, *temp_vals = NULL, *temp_keys = NULL; ant_offset_t count = 0, undef_count = 0, len = 0; + + gc_temp_root_scope_t temp_scope = {0}; + bool temp_scope_active = false; if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) return js_mkerr(js, "sort called on non-array"); @@ -7956,9 +8043,15 @@ if (t == T_FUNC || t == T_CFUNC) compareFn = args[0]; else if (t != T_UNDEF) return js_mkerr_typed(js, JS_ERR_TYPE, "compareFn must be a function or undefined"); } + + gc_temp_root_scope_begin(js, &temp_scope); + temp_scope_active = true; + + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_scope, arr))) goto oom; + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_scope, compareFn))) goto oom; len = get_array_length(js, arr); - if (len == 0) return arr; + if (len == 0) goto done; ant_offset_t doff = get_dense_buf(arr); if (doff) { @@ -7981,6 +8074,9 @@ } } if (count <= 1) goto writeback; + for (ant_offset_t i = 0; i < count; i++) { + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_scope, vals[i]))) goto oom; + } bool use_keys = (vtype(compareFn) == T_UNDEF); if (use_keys) { @@ -7988,7 +8084,13 @@ if (!keys) goto oom; for (ant_offset_t i = 0; i < count; i++) { const char *s = js_tostring(js, vals[i]); - keys[i] = js_mkstr(js, s, strlen(s)); + ant_value_t key = js_mkstr(js, s, strlen(s)); + if (is_err(key)) { + result = key; + goto done; + } + keys[i] = key; + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_scope, key))) goto oom; } } @@ -8050,19 +8152,20 @@ for (ant_offset_t i = 0; i < undef_count; i++, out++) arr_set(js, arr, out, js_mkundef()); for (; out < len; out++) arr_del(js, arr, out); } - - free(temp_keys); - free(temp_vals); - free(keys); - free(vals); - return arr; + result = arr; + goto done; oom: + result = js_mkerr(js, "out of memory"); + +done: + if (temp_scope_active) gc_temp_root_scope_end(&temp_scope); free(temp_keys); free(temp_vals); free(keys); free(vals); - return js_mkerr(js, "out of memory"); + + return result; } static ant_value_t builtin_array_splice(ant_t *js, ant_value_t *args, int nargs) { @@ -8184,6 +8287,10 @@ static ant_value_t builtin_array_copyWithin(ant_t *js, ant_value_t *args, int nargs) { ant_value_t arr = js->this_val; + ant_value_t result = arr; + gc_temp_root_scope_t temp_roots = {0}; + + bool temp_roots_active = false; if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) { return js_mkerr(js, "copyWithin called on non-array"); } @@ -8230,22 +8337,45 @@ } ant_value_t *temp = (ant_value_t *)malloc(count * sizeof(ant_value_t)); + if (!temp) return js_mkerr(js, "out of memory"); + gc_temp_root_scope_begin(js, &temp_roots); + temp_roots_active = true; + + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, arr))) goto oom; + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, read_from))) goto oom; + for (int i = 0; i < count; i++) { char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)(start + i)); ant_offset_t elem_off = lkp(js, read_from, idxstr, idxlen); temp[i] = elem_off ? propref_load(js, elem_off) : js_mkundef(); + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, temp[i]))) goto oom; } for (int i = 0; i < count; i++) { char idxstr[16]; size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)(target + i)); ant_value_t key = js_mkstr(js, idxstr, idxlen); - js_setprop(js, arr, key, temp[i]); + if (is_err(key)) { + result = key; + goto done; + } + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, key))) goto oom; + ant_value_t set_result = js_setprop(js, arr, key, temp[i]); + if (is_err(set_result)) { + result = set_result; + goto done; + } } - + +done: + if (temp_roots_active) gc_temp_root_scope_end(&temp_roots); free(temp); - return arr; + return result; + +oom: + result = js_mkerr(js, "out of memory"); + goto done; } static ant_value_t builtin_array_toSorted(ant_t *js, ant_value_t *args, int nargs) { @@ -8344,9 +8474,9 @@ static ant_value_t builtin_array_toString(ant_t *js, ant_value_t *args, int nargs) { ant_value_t arr = js->this_val; - ant_value_t join_result; - if (js_try_call_method(js, arr, "join", 4, NULL, 0, &join_result)) { + + if (js_try_call_method(js, arr, "join", NULL, 0, &join_result)) { if (is_err(join_result)) return join_result; return join_result; } @@ -12437,23 +12567,63 @@ ant_value_t target = data->target; ant_value_t handler = data->handler; + ant_value_t prop_key = key_val; + + if (vtype(prop_key) != T_SYMBOL) { + if (is_object_type(prop_key)) { + prop_key = js_to_primitive(js, prop_key, 1); + if (is_err(prop_key)) return prop_key; + } + + if (vtype(prop_key) != T_SYMBOL) { + prop_key = js_tostring_val(js, prop_key); + if (is_err(prop_key)) return prop_key; + }} ant_offset_t get_trap_off = vtype(handler) == T_OBJ ? lkp_interned(js, handler, js->intern.get, 3) : 0; + if (get_trap_off != 0) { - ant_value_t get_trap = propref_load(js, get_trap_off); - if (vtype(get_trap) == T_FUNC || vtype(get_trap) == T_CFUNC) { - ant_value_t args[3] = { target, key_val, proxy }; - return sv_vm_call(js->vm, js, get_trap, js_mkundef(), args, 3, NULL, false); - } - } + ant_value_t get_trap = propref_load(js, get_trap_off); + if (vtype(get_trap) == T_FUNC || vtype(get_trap) == T_CFUNC) { + ant_value_t args[3] = { target, prop_key, proxy }; + return sv_vm_call(js->vm, js, get_trap, js_mkundef(), args, 3, NULL, false); + }} - if (vtype(key_val) == T_SYMBOL) { - ant_offset_t off = lkp_sym_proto(js, target, (ant_offset_t)vdata(key_val)); + if (vtype(prop_key) == T_SYMBOL) { + ant_offset_t off = lkp_sym_proto(js, target, (ant_offset_t)vdata(prop_key)); return off != 0 ? propref_load(js, off) : js_mkundef(); } - return proxy_get(js, proxy, "", 0); + ant_offset_t key_len = 0; + ant_offset_t key_off = vstr(js, prop_key, &key_len); + const char *key_ptr = (const char *)(uintptr_t)key_off; + + return proxy_get(js, proxy, key_ptr, (size_t)key_len); +} + +static ant_value_t proxy_get_prototype_of(ant_t *js, ant_value_t proxy) { + ant_proxy_state_t *data = get_proxy_data(proxy); + if (!data) return js_mknull(); + + if (data->revoked) + return throw_proxy_error(js, "Cannot perform 'getPrototypeOf' on a proxy that has been revoked"); + + ant_value_t target = data->target; + ant_value_t handler = data->handler; + ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "getPrototypeOf", 14) : 0; + + if (trap_off != 0) { + ant_value_t trap = propref_load(js, trap_off); + if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { + ant_value_t args[1] = { target }; + ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), args, 1, NULL, false); + if (is_err(result)) return result; + if (is_object_type(result) || vtype(result) == T_NULL) return result; + return js_mkerr_typed(js, JS_ERR_TYPE, "'getPrototypeOf' on proxy: trap returned neither object nor null"); + }} + + return get_proto(js, target); } static ant_value_t proxy_has_val(ant_t *js, ant_value_t proxy, ant_value_t key_val) { @@ -12463,21 +12633,37 @@ ant_value_t target = data->target; ant_value_t handler = data->handler; + ant_value_t prop_key = key_val; + + if (vtype(prop_key) != T_SYMBOL) { + if (is_object_type(prop_key)) { + prop_key = js_to_primitive(js, prop_key, 1); + if (is_err(prop_key)) return prop_key; + } + + if (vtype(prop_key) != T_SYMBOL) { + prop_key = js_tostring_val(js, prop_key); + if (is_err(prop_key)) return prop_key; + }} ant_offset_t has_trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "has", 3) : 0; if (has_trap_off != 0) { - ant_value_t has_trap = propref_load(js, has_trap_off); - if (vtype(has_trap) == T_FUNC || vtype(has_trap) == T_CFUNC) { - ant_value_t args[2] = { target, key_val }; - return sv_vm_call(js->vm, js, has_trap, js_mkundef(), args, 2, NULL, false); - } - } + ant_value_t has_trap = propref_load(js, has_trap_off); + if (vtype(has_trap) == T_FUNC || vtype(has_trap) == T_CFUNC) { + ant_value_t args[2] = { target, prop_key }; + return sv_vm_call(js->vm, js, has_trap, js_mkundef(), args, 2, NULL, false); + }} - if (vtype(key_val) == T_SYMBOL) { - ant_offset_t off = lkp_sym_proto(js, target, (ant_offset_t)vdata(key_val)); + if (vtype(prop_key) == T_SYMBOL) { + ant_offset_t off = lkp_sym_proto(js, target, (ant_offset_t)vdata(prop_key)); return js_bool(off != 0); } - return js_false; + + ant_offset_t key_len = 0; + ant_offset_t key_off = vstr(js, prop_key, &key_len); + const char *key_ptr = (const char *)(uintptr_t)key_off; + + return proxy_has(js, proxy, key_ptr, (size_t)key_len); } static ant_value_t proxy_get_own_property_descriptor(ant_t *js, ant_value_t proxy, ant_value_t key_val) { @@ -12492,15 +12678,14 @@ ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "getOwnPropertyDescriptor", 24) : 0; if (trap_off != 0) { - ant_value_t trap = propref_load(js, trap_off); - if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { - ant_value_t args[2] = { target, key_val }; - ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), args, 2, NULL, false); - if (is_err(result)) return result; - if (vtype(result) == T_UNDEF || vtype(result) == T_OBJ) return result; - return js_mkerr_typed(js, JS_ERR_TYPE, "'getOwnPropertyDescriptor' on proxy: trap returned neither object nor undefined"); - } - } + ant_value_t trap = propref_load(js, trap_off); + if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { + ant_value_t args[2] = { target, key_val }; + ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), args, 2, NULL, false); + if (is_err(result)) return result; + if (vtype(result) == T_UNDEF || vtype(result) == T_OBJ) return result; + return js_mkerr_typed(js, JS_ERR_TYPE, "'getOwnPropertyDescriptor' on proxy: trap returned neither object nor undefined"); + }} ant_value_t args[2] = { target, key_val }; return builtin_object_getOwnPropertyDescriptor(js, args, 2); @@ -12510,6 +12695,29 @@ ant_value_t desc = proxy_get_own_property_descriptor(js, proxy, key_val); if (is_err(desc)) return desc; return js_bool(vtype(desc) != T_UNDEF); +} + +static ant_value_t proxy_define_property(ant_t *js, ant_value_t proxy, ant_value_t key_val, ant_value_t descriptor) { + ant_proxy_state_t *data = get_proxy_data(proxy); + if (!data) return js_false; + if (data->revoked) + return throw_proxy_error(js, "Cannot perform 'defineProperty' on a proxy that has been revoked"); + + ant_value_t target = data->target; + ant_value_t handler = data->handler; + ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "defineProperty", 14) : 0; + + if (trap_off != 0) { + ant_value_t trap = propref_load(js, trap_off); + if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { + ant_value_t args[3] = { target, key_val, descriptor }; + return sv_vm_call(js->vm, js, trap, js_mkundef(), args, 3, NULL, false); + }} + + ant_value_t args[3] = { target, key_val, descriptor }; + ant_value_t result = builtin_object_defineProperty(js, args, 3); + if (is_err(result)) return result; + return js_true; } static ant_value_t proxy_delete_val(ant_t *js, ant_value_t proxy, ant_value_t key_val) { @@ -12531,14 +12739,10 @@ ant_offset_t prop_off = lkp_sym(js, target, (ant_offset_t)vdata(key_val)); if (prop_off != 0 && is_nonconfig_prop(js, prop_off)) return js_mkerr_typed(js, JS_ERR_TYPE, "'deleteProperty' on proxy: trap returned truthy for non-configurable property"); - } - return result; + } return result; } } - - if (vtype(key_val) == T_SYMBOL) { - return js_delete_sym_prop(js, target, key_val); - } + if (vtype(key_val) == T_SYMBOL) return js_delete_sym_prop(js, target, key_val); return js_true; } @@ -13136,8 +13340,7 @@ } void js_destroy(ant_t *js) { - if (js == NULL) return; - + if (js == NULL) return; if (js->vm) { sv_vm_destroy(js->vm); js->vm = NULL; @@ -13159,6 +13362,8 @@ js->permanent_objects = NULL; cleanup_buffer_module(); + cleanup_atomics_module(js); + fixed_arena_destroy(&js->obj_arena); fixed_arena_destroy(&js->closure_arena); fixed_arena_destroy(&js->upvalue_arena); @@ -13186,7 +13391,7 @@ js_pool_destroy(&js->pool.permanent); js_class_pool_destroy(&js->pool.bigint); - js_class_pool_destroy(&js->pool.string); + js_string_pool_destroy(&js->pool.string); destroy_runtime(js); if (js->owns_mem) free(js); diff --git a/src/pool.c b/src/pool.c --- a/src/pool.c +++ b/src/pool.c @@ -3,6 +3,7 @@ #include #include +#include static const size_t g_size_classes[ANT_POOL_SIZE_CLASS_COUNT] = { 16u, 24u, 32u, 40u, 48u, 56u, 64u, 80u, @@ -10,35 +11,6 @@ 384u, 448u, 512u, 640u, 768u, 896u, 1024u, 1280u, 1536u, 1792u, 2048u, 2560u, 3072u, 4096u, 8192u, 16384u }; - -static inline bool pool_kind_uses_size_classes(ant_alloc_kind_t kind) { - return kind == ANT_ALLOC_STRING || kind == ANT_ALLOC_BIGINT; -} - -static inline size_t pool_default_block_size(ant_alloc_kind_t kind) { -switch (kind) { - case ANT_ALLOC_SYMBOL: return ANT_POOL_SYMBOL_BLOCK_SIZE; - case ANT_ALLOC_BIGINT: return ANT_POOL_BIGINT_BLOCK_SIZE; - case ANT_ALLOC_STRING: return ANT_POOL_STRING_BLOCK_SIZE; - case ANT_ALLOC_ROPE: - default: return ANT_POOL_ROPE_BLOCK_SIZE; -}} - -static inline ant_pool_t *pool_for_kind(ant_t *js, ant_alloc_kind_t kind) { -switch (kind) { - case ANT_ALLOC_SYMBOL: return &js->pool.symbol; - case ANT_ALLOC_BIGINT: return &js->pool.bigint.base; - case ANT_ALLOC_STRING: return &js->pool.string.base; - case ANT_ALLOC_ROPE: - default: return &js->pool.rope; -}} - -static inline ant_class_pool_t *class_pool_for_kind(ant_t *js, ant_alloc_kind_t kind) { -switch (kind) { - case ANT_ALLOC_BIGINT: return &js->pool.bigint; - case ANT_ALLOC_STRING: return &js->pool.string; - default: return NULL; -}} static inline size_t align_up_size(size_t value, size_t align) { if (align == 0) return value; @@ -60,27 +32,165 @@ return 32u; } -static void *pool_bucket_alloc_fast( - ant_pool_bucket_t *bucket, - size_t class_size, - size_t align -) { - if (!bucket) return NULL; +static inline size_t pool_chain_block_cap(size_t block_size, size_t need) { + if (block_size == 0 || block_size >= need) return block_size ? block_size : need; + size_t blocks = (need + block_size - 1) / block_size; + return blocks * block_size; +} - const size_t cache_align = _Alignof(max_align_t); - if (align > cache_align) return NULL; +static inline size_t pool_default_block_size(ant_alloc_kind_t kind) { +switch (kind) { + case ANT_ALLOC_SYMBOL: return ANT_POOL_SYMBOL_BLOCK_SIZE; + case ANT_ALLOC_BIGINT: return ANT_POOL_BIGINT_BLOCK_SIZE; + case ANT_ALLOC_STRING: return ANT_POOL_STRING_BLOCK_SIZE; + case ANT_ALLOC_ROPE: + default: return ANT_POOL_ROPE_BLOCK_SIZE; +}} - if (bucket->slot_stride == 0) { - bucket->slot_stride = align_up_size(class_size, cache_align); +static inline ant_pool_t *pool_for_kind(ant_t *js, ant_alloc_kind_t kind) { +switch (kind) { + case ANT_ALLOC_SYMBOL: return &js->pool.symbol; + case ANT_ALLOC_BIGINT: return &js->pool.bigint.base; + case ANT_ALLOC_ROPE: + default: return &js->pool.rope; +}} + +static inline ant_class_pool_t *class_pool_for_kind(ant_t *js, ant_alloc_kind_t kind) { +switch (kind) { + case ANT_ALLOC_BIGINT: return &js->pool.bigint; + default: return NULL; +}} + +static void pool_block_list_destroy(ant_pool_block_t *head) { +while (head) { + ant_pool_block_t *next = head->next; + pool_block_free(head); + head = next; +}} + +static void pool_free_block_list_destroy(ant_pool_block_t *head) { +while (head) { + ant_pool_block_t *next = pool_free_next(head); + pool_block_free(head); + head = next; +}} + +static ant_pool_stats_t pool_block_list_stats(ant_pool_block_t *head) { + ant_pool_stats_t s = {0}; + for (ant_pool_block_t *b = head; b; b = b->next) { + s.used += b->used; + s.capacity += b->cap; + s.blocks++; } + return s; +} + +static inline void pool_stats_add(ant_pool_stats_t *dst, ant_pool_stats_t src) { + dst->used += src.used; + dst->capacity += src.capacity; + dst->blocks += src.blocks; +} + +static inline size_t pool_page_size(void) { + static size_t cached = 0; + if (cached) return cached; +#ifdef _WIN32 + SYSTEM_INFO info; + GetSystemInfo(&info); + cached = info.dwPageSize ? (size_t)info.dwPageSize : 4096u; +#else + long page = sysconf(_SC_PAGESIZE); + cached = page > 0 ? (size_t)page : 4096u; +#endif + return cached; +} + +static inline void large_string_unlink(ant_large_string_alloc_t **head, ant_large_string_alloc_t *alloc) { + if (!head || !alloc) return; + if (alloc->prev) alloc->prev->next = alloc->next; + else *head = alloc->next; + if (alloc->next) alloc->next->prev = alloc->prev; + alloc->next = NULL; + alloc->prev = NULL; +} + +static inline void large_string_push_front(ant_large_string_alloc_t **head, ant_large_string_alloc_t *alloc) { + if (!head || !alloc) return; + alloc->prev = NULL; + alloc->next = *head; + if (*head) (*head)->prev = alloc; + *head = alloc; +} + +static inline size_t flat_payload_capacity_from_size(size_t flat_size) { + if (flat_size <= offsetof(ant_flat_string_t, bytes)) return 0; + return flat_size - offsetof(ant_flat_string_t, bytes) - 1u; +} + +static ant_large_string_alloc_t *large_string_alloc_fresh(size_t flat_size) { + size_t payload_capacity = flat_payload_capacity_from_size(flat_size); + size_t needed = offsetof(ant_large_string_alloc_t, bytes) + payload_capacity + 1u; + size_t alloc_size = align_up_size(needed, pool_page_size()); + + ant_large_string_alloc_t *alloc = (ant_large_string_alloc_t *)ant_os_alloc(alloc_size); + if (!alloc) return NULL; + + alloc->next = NULL; + alloc->prev = NULL; + alloc->capacity = alloc_size - offsetof(ant_large_string_alloc_t, bytes) - 1u; + alloc->alloc_size = alloc_size; + alloc->quarantine_epoch = 0; + alloc->marked = 0; + alloc->len = 0; + alloc->is_ascii = STR_ASCII_UNKNOWN; + alloc->bytes[0] = '\0'; + + return alloc; +} + +static ant_large_string_alloc_t *large_string_reuse_candidate(ant_large_string_space_t *space, size_t flat_size) { + if (!space) return NULL; + + size_t needed_capacity = flat_payload_capacity_from_size(flat_size); + size_t max_capacity = needed_capacity > (SIZE_MAX / 2u) ? SIZE_MAX : needed_capacity * 2u; + + for (ant_large_string_alloc_t *cur = space->reusable; cur; cur = cur->next) + if (cur->capacity >= needed_capacity && cur->capacity <= max_capacity) return cur; + + return NULL; +} + +static ant_flat_string_t *string_large_space_alloc(ant_t *js, size_t flat_size) { + ant_large_string_space_t *space = &js->pool.string.large; + ant_large_string_alloc_t *alloc = large_string_reuse_candidate(space, flat_size); + + if (alloc) large_string_unlink(&space->reusable, alloc); else { + alloc = large_string_alloc_fresh(flat_size); + if (!alloc) return NULL; + } + + alloc->quarantine_epoch = 0; + alloc->marked = 0; + alloc->len = 0; + alloc->is_ascii = STR_ASCII_UNKNOWN; + alloc->bytes[0] = '\0'; + large_string_push_front(&space->live, alloc); + + return large_string_flat_ptr(alloc); +} + +static void *pool_bucket_alloc_fast(ant_pool_bucket_t *bucket, size_t class_size, size_t align) { + if (!bucket) return NULL; + const size_t cache_align = _Alignof(max_align_t); + + if (align > cache_align) return NULL; + if (bucket->slot_stride == 0) bucket->slot_stride = align_up_size(class_size, cache_align); if (bucket->slot_free) { void *ptr = bucket->slot_free; void *next; - memcpy(&next, ptr, sizeof(void *)); bucket->slot_free = next; - return ptr; } @@ -111,9 +221,37 @@ uint8_t *ptr = bucket->cursor; bucket->cursor += bucket->slot_stride; - if (bucket->current) bucket->current->used = (size_t)(bucket->cursor - bucket->current->data); + if (bucket->current) + bucket->current->used = (size_t)(bucket->cursor - bucket->current->data); + return ptr; +} + +static void *string_pool_alloc(ant_t *js, size_t size, size_t align) { + ant_string_pool_t *pool = &js->pool.string; + if (pool->block_size == 0) pool->block_size = pool_default_block_size(ANT_ALLOC_STRING); + + size_t needed = size + (align - 1u); + int class_idx = pool_size_class_index(needed); + + if (class_idx >= 0) { + size_t class_size = g_size_classes[class_idx]; + ant_pool_bucket_t *bucket = &pool->classes[class_idx]; + + if (bucket->block_size == 0) { + size_t bucket_block_size = pool->block_size; + size_t min_block = class_size * pool_class_block_items(class_size); + if (bucket_block_size < min_block) bucket_block_size = min_block; + bucket->block_size = bucket_block_size; + } + + void *fast = pool_bucket_alloc_fast(bucket, class_size, align); + if (fast) return fast; + return pool_alloc_chain(&bucket->head, &bucket->free_head, bucket->block_size, size, align); + } + + return string_large_space_alloc(js, size); } void *pool_alloc_chain( @@ -128,8 +266,7 @@ ant_pool_block_t *block = *head; if (!block) { - size_t cap = block_size; - if (cap < size + align) cap = size + align; + size_t cap = pool_chain_block_cap(block_size, size + align); block = pool_block_alloc(cap); if (!block) return NULL; *head = block; @@ -147,8 +284,7 @@ if (*head) (*head)->prev = block; *head = block; } else { - size_t cap = block_size; - if (cap < size + align) cap = size + align; + size_t cap = pool_chain_block_cap(block_size, size + align); ant_pool_block_t *next = pool_block_alloc(cap); if (!next) return NULL; next->next = block; @@ -162,6 +298,7 @@ void *ptr = (void *)(base + start); block->used = start + size; + return ptr; } @@ -174,72 +311,85 @@ if (pool_threshold < (4u * 1024u * 1024u)) pool_threshold = 4u * 1024u * 1024u; if (js->gc_pool_alloc >= pool_threshold) gc_run(js); + if (kind == ANT_ALLOC_STRING) return string_pool_alloc(js, size, align); ant_pool_t *pool = pool_for_kind(js, kind); if (pool->block_size == 0) pool->block_size = pool_default_block_size(kind); - - if (pool_kind_uses_size_classes(kind)) { - ant_class_pool_t *class_pool = class_pool_for_kind(js, kind); - if (!class_pool) return pool_alloc_chain(&pool->head, &pool->free_head, pool->block_size, size, align); - - size_t needed = size + (align - 1u); - int class_idx = pool_size_class_index(needed); - if (class_idx >= 0) { - size_t class_size = g_size_classes[class_idx]; - ant_pool_bucket_t *bucket = &class_pool->classes[class_idx]; - if (bucket->block_size == 0) { - size_t bucket_block_size = pool->block_size; - size_t min_block = class_size * pool_class_block_items(class_size); - if (bucket_block_size < min_block) bucket_block_size = min_block; - bucket->block_size = bucket_block_size; - } - void *fast = pool_bucket_alloc_fast(bucket, class_size, align); - if (fast) return fast; - return pool_alloc_chain(&bucket->head, &bucket->free_head, bucket->block_size, size, align); + ant_class_pool_t *class_pool = class_pool_for_kind(js, kind); + + if (class_pool) { + size_t needed = size + (align - 1u); + int class_idx = pool_size_class_index(needed); + + if (class_idx >= 0) { + size_t class_size = g_size_classes[class_idx]; + ant_pool_bucket_t *bucket = &class_pool->classes[class_idx]; + if (bucket->block_size == 0) { + size_t bucket_block_size = pool->block_size; + size_t min_block = class_size * pool_class_block_items(class_size); + if (bucket_block_size < min_block) bucket_block_size = min_block; + bucket->block_size = bucket_block_size; } - } + void *fast = pool_bucket_alloc_fast(bucket, class_size, align); + if (fast) return fast; + return pool_alloc_chain(&bucket->head, &bucket->free_head, bucket->block_size, size, align); + }} return pool_alloc_chain(&pool->head, &pool->free_head, pool->block_size, size, align); } void js_pool_destroy(ant_pool_t *pool) { if (!pool) return; - - ant_pool_block_t *block = pool->head; - while (block) { - ant_pool_block_t *next = block->next; - pool_block_free(block); - block = next; - } + pool_block_list_destroy(pool->head); + pool_free_block_list_destroy(pool->free_head); pool->head = NULL; + pool->free_head = NULL; pool->block_size = 0; } ant_pool_stats_t js_pool_stats(ant_pool_t *pool) { - ant_pool_stats_t s = {0}; - if (!pool) return s; - for (ant_pool_block_t *b = pool->head; b; b = b->next) { - s.used += b->used; - s.capacity += b->cap; - s.blocks++; - } - return s; -} - -static inline void pool_stats_add(ant_pool_stats_t *dst, ant_pool_stats_t src) { - dst->used += src.used; - dst->capacity += src.capacity; - dst->blocks += src.blocks; + if (!pool) return (ant_pool_stats_t){0}; + return pool_block_list_stats(pool->head); } ant_pool_stats_t js_class_pool_stats(ant_class_pool_t *pool) { ant_pool_stats_t s = {0}; if (!pool) return s; - for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) { - ant_pool_t bucket = { .head = pool->classes[i].head }; - pool_stats_add(&s, js_pool_stats(&bucket)); - } + + for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) + pool_stats_add(&s, pool_block_list_stats(pool->classes[i].head)); pool_stats_add(&s, js_pool_stats(&pool->base)); + + return s; +} + +static ant_pool_stats_t string_large_stats(ant_large_string_alloc_t *head, bool include_used) { + ant_pool_stats_t s = {0}; + for (ant_large_string_alloc_t *cur = head; cur; cur = cur->next) { + if (include_used) + s.used += offsetof(ant_flat_string_t, bytes) + (size_t)cur->len + 1u; + s.capacity += cur->alloc_size; + s.blocks++; + } + return s; +} + +ant_string_pool_stats_t js_string_pool_stats(ant_string_pool_t *pool) { + ant_string_pool_stats_t s = {0}; + if (!pool) return s; + + for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) + pool_stats_add(&s.pooled, pool_block_list_stats(pool->classes[i].head)); + + s.large_live = string_large_stats(pool->large.live, true); + s.large_reusable = string_large_stats(pool->large.reusable, false); + s.large_quarantine = string_large_stats(pool->large.quarantine, false); + + s.total = s.pooled; + pool_stats_add(&s.total, s.large_live); + s.total.capacity += s.large_reusable.capacity + s.large_quarantine.capacity; + s.total.blocks += s.large_reusable.blocks + s.large_quarantine.blocks; + return s; } @@ -247,13 +397,12 @@ if (!pool) return; for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) { - ant_pool_block_t *block = pool->classes[i].head; - while (block) { - ant_pool_block_t *next = block->next; - pool_block_free(block); - block = next; - } + pool_block_list_destroy(pool->classes[i].head); + pool_free_block_list_destroy(pool->classes[i].free_head); pool->classes[i].head = NULL; + pool->classes[i].current = NULL; + pool->classes[i].free_head = NULL; + pool->classes[i].slot_free = NULL; pool->classes[i].block_size = 0; pool->classes[i].cursor = NULL; pool->classes[i].end = NULL; @@ -261,4 +410,41 @@ } js_pool_destroy(&pool->base); +} + +void js_string_pool_destroy(ant_string_pool_t *pool) { + if (!pool) return; + + for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) { + pool_block_list_destroy(pool->classes[i].head); + pool_free_block_list_destroy(pool->classes[i].free_head); + pool->classes[i].head = NULL; + pool->classes[i].current = NULL; + pool->classes[i].free_head = NULL; + pool->classes[i].slot_free = NULL; + pool->classes[i].block_size = 0; + pool->classes[i].cursor = NULL; + pool->classes[i].end = NULL; + pool->classes[i].slot_stride = 0; + } + + ant_large_string_alloc_t *lists[] = { + pool->large.live, + pool->large.reusable, + pool->large.quarantine, + }; + + for (size_t i = 0; i < sizeof(lists) / sizeof(lists[0]); i++) { + ant_large_string_alloc_t *cur = lists[i]; + while (cur) { + ant_large_string_alloc_t *next = cur->next; + ant_arena_free(cur, cur->alloc_size); + cur = next; + }} + + pool->large.live = NULL; + pool->large.reusable = NULL; + pool->large.quarantine = NULL; + pool->large.gc_epoch = 0; + pool->block_size = 0; } diff --git a/src/utils.c b/src/utils.c --- a/src/utils.c +++ b/src/utils.c @@ -256,40 +256,70 @@ char **buf; size_t *buf_len; size_t *buf_cap; } rt_ctx_t; -#define RT_APPEND(c, data, dlen) do { \ - if (*(c)->buf_len + (dlen) >= *(c)->buf_cap) { \ - *(c)->buf_cap = (*(c)->buf_len + (dlen) + 1) * 2; \ - *(c)->buf = realloc(*(c)->buf, *(c)->buf_cap); \ - } \ - memcpy(*(c)->buf + *(c)->buf_len, data, dlen); *(c)->buf_len += (dlen); \ -} while(0) +static bool rt_append(rt_ctx_t *c, const char *data, size_t dlen) { + if (dlen == 0) return true; + if (*c->buf_len > SIZE_MAX - dlen - 1) return false; -static void rt_dollar(rt_ctx_t *c) { RT_APPEND(c, "$", 1); *c->ri += 2; } -static void rt_match(rt_ctx_t *c) { RT_APPEND(c, c->matched, c->matched_len); *c->ri += 2; } -static void rt_prefix(rt_ctx_t *c) { RT_APPEND(c, c->str, c->position); *c->ri += 2; } + if (*c->buf_len + dlen >= *c->buf_cap) { + size_t needed = *c->buf_len + dlen + 1; + size_t new_cap = needed * 2; + if (new_cap < needed) new_cap = needed; + + char *next = realloc(*c->buf, new_cap); + if (!next) return false; + *c->buf = next; + *c->buf_cap = new_cap; + } -static void rt_suffix(rt_ctx_t *c) { - size_t after = c->position + c->matched_len; - if (after < c->str_len) RT_APPEND(c, c->str + after, c->str_len - after); - *c->ri += 2; + memcpy(*c->buf + *c->buf_len, data, dlen); + *c->buf_len += dlen; + return true; } -static void rt_capture(rt_ctx_t *c) { +static bool rt_dollar(rt_ctx_t *c) { + *c->ri += 2; + return rt_append(c, "$", 1); +} + +static bool rt_match(rt_ctx_t *c) { + *c->ri += 2; + return rt_append(c, c->matched, c->matched_len); +} + +static bool rt_prefix(rt_ctx_t *c) { + *c->ri += 2; + return rt_append(c, c->str, c->position); +} + +static bool rt_suffix(rt_ctx_t *c) { + size_t after = c->position + c->matched_len; + bool ok = true; + if (after < c->str_len) + ok = rt_append(c, c->str + after, c->str_len - after); + *c->ri += 2; + return ok; +} + +static bool rt_capture(rt_ctx_t *c) { char nc = c->repl[*c->ri + 1]; int gn = nc - '0'; *c->ri += 2; + if (*c->ri < c->repl_len && c->repl[*c->ri] >= '0' && c->repl[*c->ri] <= '9') { int two = gn * 10 + (c->repl[*c->ri] - '0'); if (two <= c->ncaptures) { gn = two; (*c->ri)++; } } - if (gn > 0 && gn <= c->ncaptures && c->caps[gn - 1].ptr) { - RT_APPEND(c, c->caps[gn - 1].ptr, c->caps[gn - 1].len); - } else if (gn == 0 || gn > c->ncaptures) { - RT_APPEND(c, "$", 1); RT_APPEND(c, &nc, 1); - } + + if (gn > 0 && gn <= c->ncaptures && c->caps[gn - 1].ptr) + return rt_append(c, c->caps[gn - 1].ptr, c->caps[gn - 1].len); + + if (gn == 0 || gn > c->ncaptures) + return rt_append(c, "$", 1) && rt_append(c, &nc, 1); + + return true; } -typedef void (*rt_handler_t)(rt_ctx_t *); +typedef bool (*rt_handler_t)(rt_ctx_t *); static rt_handler_t rt_dispatch[128]; static bool rt_dispatch_init = false; @@ -303,7 +333,7 @@ rt_dispatch_init = true; } -void repl_template( +bool repl_template( const char *repl, size_t repl_len, const char *matched, size_t matched_len, const char *str, size_t str_len, size_t position, @@ -324,13 +354,14 @@ unsigned char nc = (unsigned char)repl[ri + 1]; c.ri = &ri; rt_handler_t h = nc < 128 ? rt_dispatch[nc] : NULL; - if (h) { h(&c); continue; } + if (h) { if (!h(&c)) return false; continue; } } - RT_APPEND(&c, &repl[ri], 1); ri++; + if (!rt_append(&c, &repl[ri], 1)) return false; + ri++; } -} -#undef RT_APPEND + return true; +} void *try_oom(size_t size) { void *p = malloc(size); diff --git a/tests/test_array_sort_gc_roots.cjs b/tests/test_array_sort_gc_roots.cjs new file mode 100644 --- /dev/null +++ b/tests/test_array_sort_gc_roots.cjs @@ -0,0 +1,22 @@ +const assert = require("assert"); + +const data = {}; +for (let i = 0; i < 12000; i++) { + const key = "entry-" + String(i).padStart(5, "0") + "-" + "x".repeat(32); + data[key] = i % 2 === 0 ? "pass" : "fail"; +} + +const entries = Object.entries(data).sort(); + +assert.equal(entries.length, 12000); +assert.equal(entries[0][0], "entry-00000-" + "x".repeat(32)); +assert.equal(entries[entries.length - 1][0], "entry-11999-" + "x".repeat(32)); + +const nums = []; +for (let i = 2000; i >= 0; i--) nums.push(i); +nums.sort((a, b) => a - b); + +assert.equal(nums[0], 0); +assert.equal(nums[nums.length - 1], 2000); + +console.log("ok"); diff --git a/tests/test_atomics_wait_async.cjs b/tests/test_atomics_wait_async.cjs new file mode 100644 --- /dev/null +++ b/tests/test_atomics_wait_async.cjs @@ -0,0 +1,22 @@ +const assert = require("assert"); + +(async () => { + const sab = new SharedArrayBuffer(4); + const view = new Int32Array(sab); + + Atomics.store(view, 0, 7); + + const timedOut = Atomics.waitAsync(view, 0, 7, 1); + assert.equal(timedOut.async, true); + assert.equal(await timedOut.value, "timed-out"); + + const pending = Atomics.waitAsync(view, 0, 7, 1000); + assert.equal(pending.async, true); + assert.equal(Atomics.notify(view, 0, 1), 1); + assert.equal(await pending.value, "ok"); + + console.log("ok"); +})().catch((err) => { + console.error(err && err.stack ? err.stack : err); + process.exit(1); +}); diff --git a/tests/test_legacy_accessors.js b/tests/test_legacy_accessors.js --- a/tests/test_legacy_accessors.js +++ b/tests/test_legacy_accessors.js @@ -36,4 +36,73 @@ pass = false; } +let boxedHits = 0; +String.prototype.__defineGetter__('boxedLegacyAccessorProbe', function () { + boxedHits++; + return 'ok'; +}); + +try { + const boxedGetter = Object.prototype.__lookupGetter__.call('abc', 'boxedLegacyAccessorProbe'); + if (typeof boxedGetter !== 'function') { + console.log('FAIL: primitive receivers should be boxed for __lookupGetter__'); + pass = false; + } + + const defineResult = Object.prototype.__defineGetter__.call('abc', 'ephemeral', function () { + return 1; + }); + if (defineResult !== undefined) { + console.log('FAIL: __defineGetter__ on boxed primitive should return undefined'); + pass = false; + } + + if ('abc'.boxedLegacyAccessorProbe !== 'ok' || boxedHits !== 1) { + console.log('FAIL: boxed primitive getter should resolve through String.prototype'); + pass = false; + } +} finally { + delete String.prototype.boxedLegacyAccessorProbe; +} + +let proxyDefined = false; +let proxyLookedUp = false; +const proxyTarget = {}; +const proxy = new Proxy(proxyTarget, { + defineProperty(target, key, desc) { + proxyDefined = key === 'legacyProxy' && typeof desc.get === 'function'; + Object.defineProperty(target, key, desc); + return true; + }, + getOwnPropertyDescriptor(target, key) { + if (key === 'legacyProxy') proxyLookedUp = true; + return Object.getOwnPropertyDescriptor(target, key); + } +}); + +proxy.__defineGetter__('legacyProxy', function () { + return 7; +}); + +const proxyGetter = proxy.__lookupGetter__('legacyProxy'); +if (!proxyDefined) { + console.log('FAIL: proxy __defineGetter__ should go through defineProperty semantics'); + pass = false; +} + +if (!proxyLookedUp) { + console.log('FAIL: proxy __lookupGetter__ should consult getOwnPropertyDescriptor'); + pass = false; +} + +if (typeof proxyGetter !== 'function') { + console.log('FAIL: proxy __lookupGetter__ should return the getter function'); + pass = false; +} + +if (proxy.legacyProxy !== 7) { + console.log('FAIL: proxy getter installed with __defineGetter__ should be used'); + pass = false; +} + if (pass) console.log('PASS'); diff --git a/tests/test_proxy_get_topropertykey.cjs b/tests/test_proxy_get_topropertykey.cjs new file mode 100644 --- /dev/null +++ b/tests/test_proxy_get_topropertykey.cjs @@ -0,0 +1,40 @@ +let pass = true; + +const key = Symbol("k"); +let coercions = 0; +let trappedKey = null; + +const propKey = { + [Symbol.toPrimitive](hint) { + if (hint !== "string") { + console.log("FAIL: property key @@toPrimitive should use string hint"); + pass = false; + } + coercions++; + return key; + } +}; + +const proxy = new Proxy({ [key]: 123 }, { + get(target, actualKey, receiver) { + trappedKey = actualKey; + return Reflect.get(target, actualKey, receiver); + } +}); + +if (proxy[propKey] !== 123) { + console.log("FAIL: proxy get should use symbol returned from @@toPrimitive"); + pass = false; +} + +if (trappedKey !== key) { + console.log("FAIL: proxy get trap should receive symbol property key"); + pass = false; +} + +if (coercions !== 1) { + console.log("FAIL: property key @@toPrimitive should run exactly once"); + pass = false; +} + +if (pass) console.log("PASS"); diff --git a/tests/test_proxy_toprimitive_get_trap.cjs b/tests/test_proxy_toprimitive_get_trap.cjs new file mode 100644 --- /dev/null +++ b/tests/test_proxy_toprimitive_get_trap.cjs @@ -0,0 +1,23 @@ +let pass = true; + +const seen = []; +const proxy = new Proxy({ toString: Function() }, { + get(target, key, receiver) { + seen.push(key); + return Reflect.get(target, key, receiver); + } +}); + +void (proxy + 3); + +if (seen[0] !== Symbol.toPrimitive) { + console.log("FAIL: proxy get trap should observe Symbol.toPrimitive first"); + pass = false; +} + +if (String(seen.slice(1)) !== "valueOf,toString") { + console.log("FAIL: proxy get trap should observe valueOf then toString during ToPrimitive"); + pass = false; +} + +if (pass) console.log("PASS"); diff --git a/tests/test_reflect_proxy_get_toprimitive.cjs b/tests/test_reflect_proxy_get_toprimitive.cjs new file mode 100644 --- /dev/null +++ b/tests/test_reflect_proxy_get_toprimitive.cjs @@ -0,0 +1,37 @@ +let pass = true; + +const key = Symbol("reflect-key"); +let seenHint = null; +let trappedKey = null; + +const propertyKeyObject = { + [Symbol.toPrimitive](hint) { + seenHint = hint; + return key; + } +}; + +const proxy = new Proxy({ [key]: 42 }, { + get(target, actualKey, receiver) { + trappedKey = actualKey; + return Reflect.get(target, actualKey, receiver); + } +}); + +const value = Reflect.get(proxy, propertyKeyObject); +if (value !== 42) { + console.log("FAIL: Reflect.get should read using the property key from @@toPrimitive"); + pass = false; +} + +if (seenHint !== "string") { + console.log("FAIL: Reflect.get property key coercion should use string hint"); + pass = false; +} + +if (trappedKey !== key) { + console.log("FAIL: proxy get trap should receive the symbol returned by @@toPrimitive"); + pass = false; +} + +if (pass) console.log("PASS"); diff --git a/tests/test_regexp_replace_large_template.cjs b/tests/test_regexp_replace_large_template.cjs new file mode 100644 --- /dev/null +++ b/tests/test_regexp_replace_large_template.cjs @@ -0,0 +1,15 @@ +const assert = require("assert"); + +const chunk = "abc-123-def-456-ghi-789"; +const input = Array(2048).fill(chunk).join("|"); +const re = /([a-z]+)-(\d+)-([a-z]+)-(\d+)-([a-z]+)-(\d+)/g; +const replacement = "$1:$2:$3:$4:$5:$6::" + "X".repeat(2048) + "::$&::$`::$'"; + +const out = input.replace(re, replacement); + +assert.equal(typeof out, "string"); +assert.ok(out.includes("abc:123:def:456:ghi:789")); +assert.ok(out.includes("X".repeat(256))); +assert.ok(out.length > input.length); + +console.log("ok"); diff --git a/tests/test_string_large_space_stats.cjs b/tests/test_string_large_space_stats.cjs new file mode 100644 --- /dev/null +++ b/tests/test_string_large_space_stats.cjs @@ -0,0 +1,30 @@ +const assert = require("assert"); + +const before = Ant.stats().pools.string; + +assert.equal(typeof before.used, "number"); +assert.equal(typeof before.capacity, "number"); +assert.equal(typeof before.blocks, "number"); +assert.equal(typeof before.pooled.used, "number"); +assert.equal(typeof before.largeLive.capacity, "number"); +assert.equal(typeof before.largeReusable.capacity, "number"); +assert.equal(typeof before.largeQuarantine.capacity, "number"); + +const large = "x".repeat(256 * 1024); +assert.equal(large.length, 256 * 1024); + +const after = Ant.stats().pools.string; + +assert.ok(after.capacity >= before.capacity); +assert.ok(after.largeLive.capacity >= before.largeLive.capacity); +assert.ok(after.largeLive.blocks >= before.largeLive.blocks); + +const rope = large + large; +assert.equal(rope.length, large.length * 2); + +const afterRope = Ant.stats().pools.string; +assert.equal(typeof afterRope.largeReusable.blocks, "number"); +assert.equal(typeof afterRope.largeQuarantine.blocks, "number"); +assert.ok(afterRope.capacity >= after.capacity); + +console.log("ok"); diff --git a/include/modules/atomics.h b/include/modules/atomics.h --- a/include/modules/atomics.h +++ b/include/modules/atomics.h @@ -4,7 +4,10 @@ #include #include +#include "types.h" + void init_atomics_module(void); +void cleanup_atomics_module(ant_t *js); typedef struct WaitQueueEntry { pthread_cond_t cond; diff --git a/src/gc/gc.c b/src/gc/gc.c --- a/src/gc/gc.c +++ b/src/gc/gc.c @@ -91,8 +91,8 @@ js->old_live_count = js->obj_arena.live_count; js->minor_gc_count = 0; - ant_pool_stats_t pool_stats = js_class_pool_stats(&js->pool.string); - js->gc_pool_last_live = pool_stats.used; + ant_string_pool_stats_t pool_stats = js_string_pool_stats(&js->pool.string); + js->gc_pool_last_live = pool_stats.total.used; js->gc_pool_alloc = 0; gc_adapt_major_interval(live_before, js->obj_arena.live_count); diff --git a/src/gc/strings.c b/src/gc/strings.c --- a/src/gc/strings.c +++ b/src/gc/strings.c @@ -1,6 +1,5 @@ #include "internal.h" #include "gc/strings.h" -#include "pool.h" #include #include @@ -9,27 +8,6 @@ uint8_t *bits; size_t n_slots; } gc_bitmap_t; - -static gc_bitmap_t bitmap_alloc(size_t n_slots) { - size_t nbytes = (n_slots + 7) / 8; - uint8_t *bits = calloc(1, nbytes); - return (gc_bitmap_t){ .bits = bits, .n_slots = n_slots }; -} - -static inline void bitmap_set(gc_bitmap_t *bm, size_t idx) { - if (idx < bm->n_slots && bm->bits) bm->bits[idx / 8] |= (1u << (idx % 8)); -} - -static inline bool bitmap_get(const gc_bitmap_t *bm, size_t idx) { - if (idx >= bm->n_slots || !bm->bits) return false; - return (bm->bits[idx / 8] >> (idx % 8)) & 1u; -} - -static inline void bitmap_free(gc_bitmap_t *bm) { - free(bm->bits); - bm->bits = NULL; - bm->n_slots = 0; -} typedef struct { uintptr_t base; @@ -40,9 +18,55 @@ gc_bitmap_t bitmap; } gc_block_mark_t; +typedef struct { + uintptr_t ptr; + ant_large_string_alloc_t *alloc; +} gc_large_string_mark_t; + static gc_block_mark_t *g_string_marks = NULL; static int g_string_mark_count = 0; static int g_string_mark_cap = 0; + +static gc_large_string_mark_t *g_large_string_marks = NULL; +static int g_large_string_mark_count = 0; +static int g_large_string_mark_cap = 0; + +static gc_bitmap_t bitmap_alloc(size_t n_slots) { + size_t nbytes = (n_slots + 7u) / 8u; + uint8_t *bits = calloc(1, nbytes); + return (gc_bitmap_t){ .bits = bits, .n_slots = n_slots }; +} + +static inline void bitmap_set(gc_bitmap_t *bm, size_t idx) { + if (idx < bm->n_slots && bm->bits) bm->bits[idx / 8u] |= (uint8_t)(1u << (idx % 8u)); +} + +static inline bool bitmap_get(const gc_bitmap_t *bm, size_t idx) { + if (idx >= bm->n_slots || !bm->bits) return false; + return ((bm->bits[idx / 8u] >> (idx % 8u)) & 1u) != 0; +} + +static inline void bitmap_free(gc_bitmap_t *bm) { + free(bm->bits); + bm->bits = NULL; + bm->n_slots = 0; +} + +static int pooled_mark_cmp(const void *a, const void *b) { + const gc_block_mark_t *ma = (const gc_block_mark_t *)a; + const gc_block_mark_t *mb = (const gc_block_mark_t *)b; + if (ma->base < mb->base) return -1; + if (ma->base > mb->base) return 1; + return 0; +} + +static int large_mark_cmp(const void *a, const void *b) { + const gc_large_string_mark_t *ma = (const gc_large_string_mark_t *)a; + const gc_large_string_mark_t *mb = (const gc_large_string_mark_t *)b; + if (ma->ptr < mb->ptr) return -1; + if (ma->ptr > mb->ptr) return 1; + return 0; +} static void collect_bucket_blocks(ant_pool_bucket_t *bucket) { if (!bucket || bucket->slot_stride == 0) return; @@ -51,13 +75,13 @@ for (ant_pool_block_t *b = bucket->head; b; b = b->next) { size_t n_slots = b->used / stride; if (n_slots == 0) continue; - + if (g_string_mark_count >= g_string_mark_cap) { int cap = g_string_mark_cap ? g_string_mark_cap * 2 : 32; g_string_marks = realloc(g_string_marks, (size_t)cap * sizeof(gc_block_mark_t)); g_string_mark_cap = cap; } - + gc_block_mark_t *m = &g_string_marks[g_string_mark_count++]; m->base = (uintptr_t)b->data; m->end = m->base + b->used; @@ -68,51 +92,155 @@ } } -static int mark_cmp(const void *a, const void *b) { - const gc_block_mark_t *ma = (const gc_block_mark_t *)a; - const gc_block_mark_t *mb = (const gc_block_mark_t *)b; - if (ma->base < mb->base) return -1; - if (ma->base > mb->base) return 1; - return 0; +static void collect_large_strings(ant_large_string_space_t *space) { + if (!space) return; + + for (ant_large_string_alloc_t *cur = space->live; cur; cur = cur->next) { + cur->marked = 0; + + if (g_large_string_mark_count >= g_large_string_mark_cap) { + int cap = g_large_string_mark_cap ? g_large_string_mark_cap * 2 : 32; + g_large_string_marks = realloc(g_large_string_marks, (size_t)cap * sizeof(gc_large_string_mark_t)); + g_large_string_mark_cap = cap; + } + + g_large_string_marks[g_large_string_mark_count++] = (gc_large_string_mark_t){ + .ptr = (uintptr_t)large_string_flat_ptr(cur), + .alloc = cur, + }; + } +} + +static inline void unlink_block(ant_pool_bucket_t *bucket, ant_pool_block_t *block) { + if (block->prev) block->prev->next = block->next; + else bucket->head = block->next; + if (block->next) block->next->prev = block->prev; +} + +static inline void large_string_unlink(ant_large_string_alloc_t **head, ant_large_string_alloc_t *alloc) { + if (!head || !alloc) return; + if (alloc->prev) alloc->prev->next = alloc->next; + else *head = alloc->next; + if (alloc->next) alloc->next->prev = alloc->prev; + alloc->next = NULL; + alloc->prev = NULL; +} + +static inline void large_string_push_front(ant_large_string_alloc_t **head, ant_large_string_alloc_t *alloc) { + if (!head || !alloc) return; + alloc->prev = NULL; + alloc->next = *head; + if (*head) (*head)->prev = alloc; + *head = alloc; +} + +static void large_string_promote_quarantine(ant_large_string_space_t *space) { + if (!space) return; + ant_large_string_alloc_t *cur = space->quarantine; + while (cur) { + ant_large_string_alloc_t *next = cur->next; + if (cur->quarantine_epoch < space->gc_epoch) { + large_string_unlink(&space->quarantine, cur); + large_string_push_front(&space->reusable, cur); + } cur = next; + } +} + +static size_t large_string_reusable_budget(const ant_large_string_space_t *space) { + size_t live_capacity = 0; + for (const ant_large_string_alloc_t *cur = space ? space->live : NULL; cur; cur = cur->next) { + live_capacity += cur->capacity; + } + + size_t budget = live_capacity / 4u; + size_t min_budget = 4u * 1024u * 1024u; + size_t max_budget = 64u * 1024u * 1024u; + + if (budget < min_budget) budget = min_budget; + if (budget > max_budget) budget = max_budget; + + return budget; +} + +static void large_string_trim_reusable(ant_large_string_space_t *space) { + if (!space) return; + size_t reusable_capacity = 0; + + for (ant_large_string_alloc_t *cur = space->reusable; cur; cur = cur->next) { + reusable_capacity += cur->capacity; + } + + size_t budget = large_string_reusable_budget(space); + ant_large_string_alloc_t *cur = space->reusable; + + while (cur && reusable_capacity > budget) { + ant_large_string_alloc_t *next = cur->next; + reusable_capacity -= cur->capacity; + large_string_unlink(&space->reusable, cur); + ant_arena_free(cur, cur->alloc_size); + cur = next; + } } void gc_strings_begin(ant_t *js) { g_string_mark_count = 0; - ant_class_pool_t *pool = &js->pool.string; + g_large_string_mark_count = 0; + ant_string_pool_t *pool = &js->pool.string; for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) { pool->classes[i].slot_free = NULL; collect_bucket_blocks(&pool->classes[i]); } - if (g_string_mark_count > 1) - qsort(g_string_marks, (size_t)g_string_mark_count, sizeof(gc_block_mark_t), mark_cmp); + pool->large.gc_epoch++; + if (pool->large.gc_epoch == 0) pool->large.gc_epoch = 1; + + large_string_promote_quarantine(&pool->large); + collect_large_strings(&pool->large); + + if (g_string_mark_count > 1) qsort(g_string_marks, + (size_t)g_string_mark_count, sizeof(gc_block_mark_t), pooled_mark_cmp + ); + + if (g_large_string_mark_count > 1) qsort(g_large_string_marks, + (size_t)g_large_string_mark_count, sizeof(gc_large_string_mark_t), large_mark_cmp + ); } - void gc_strings_mark(ant_t *js, const void *ptr) { - if (!ptr || g_string_mark_count == 0) return; + if (!ptr) return; uintptr_t p = (uintptr_t)ptr; - int lo = 0, hi = g_string_mark_count - 1; - while (lo <= hi) { + if (g_string_mark_count > 0) { + int lo = 0; + int hi = g_string_mark_count - 1; + + while (lo <= hi) { int mid = lo + (hi - lo) / 2; gc_block_mark_t *m = &g_string_marks[mid]; + if (p < m->base) hi = mid - 1; else if (p >= m->end) lo = mid + 1; else { size_t offset = p - m->base; if (offset % m->stride == 0) bitmap_set(&m->bitmap, offset / m->stride); return; - } + }} } -} - -static void unlink_block(ant_pool_bucket_t *bucket, ant_pool_block_t *block) { - if (block->prev) block->prev->next = block->next; - else bucket->head = block->next; - if (block->next) block->next->prev = block->prev; + if (g_large_string_mark_count == 0) return; + int lo = 0; int hi = g_large_string_mark_count - 1; + + while (lo <= hi) { + int mid = lo + (hi - lo) / 2; + gc_large_string_mark_t *m = &g_large_string_marks[mid]; + + if (p < m->ptr) hi = mid - 1; + else if (p > m->ptr) lo = mid + 1; + else { + m->alloc->marked = 1; + return; + }} } void gc_strings_sweep(ant_t *js) { @@ -122,9 +250,12 @@ bool any_live = false; size_t n_slots = m->bitmap.n_slots; + for (size_t j = 0; j < n_slots; j++) { - if (bitmap_get(&m->bitmap, j)) { any_live = true; break; } - } + if (bitmap_get(&m->bitmap, j)) { + any_live = true; + break; + }} if (!any_live && bucket) { unlink_block(bucket, m->block); @@ -137,20 +268,32 @@ } else if (any_live && bucket && m->stride >= sizeof(void *)) { uintptr_t base = m->base; for (size_t j = 0; j < n_slots; j++) { - if (!bitmap_get(&m->bitmap, j)) { + if (bitmap_get(&m->bitmap, j)) continue; void *slot = (void *)(base + j * m->stride); void *old_head = bucket->slot_free; memcpy(slot, &old_head, sizeof(void *)); bucket->slot_free = slot; - }} + } } bitmap_free(&m->bitmap); } - ant_class_pool_t *pool = &js->pool.string; + ant_large_string_space_t *space = &js->pool.string.large; + ant_large_string_alloc_t *cur = space->live; + while (cur) { + ant_large_string_alloc_t *next = cur->next; + if (cur->marked) cur->marked = 0; else { + large_string_unlink(&space->live, cur); + cur->quarantine_epoch = space->gc_epoch; + large_string_push_front(&space->quarantine, cur); + } cur = next; + } + + large_string_trim_reusable(space); + for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) { - ant_pool_bucket_t *bucket = &pool->classes[i]; + ant_pool_bucket_t *bucket = &js->pool.string.classes[i]; ant_pool_block_t *f = bucket->free_head; int kept = 0; @@ -166,13 +309,12 @@ } f = bucket->free_head; - for (int k = 0; k < kept && f; k++) f = pool_free_next(f); if (kept > 0) { - f = bucket->free_head; for (int k = 1; k < kept && f; k++) f = pool_free_next(f); if (f) pool_free_set_next(f, NULL); } else bucket->free_head = NULL; } g_string_mark_count = 0; + g_large_string_mark_count = 0; } diff --git a/src/modules/atomics.c b/src/modules/atomics.c --- a/src/modules/atomics.c +++ b/src/modules/atomics.c @@ -5,18 +5,51 @@ #include #include #include +#include +#include #include "ant.h" #include "errors.h" #include "internal.h" #include "runtime.h" +#include "gc/modules.h" #include "modules/buffer.h" #include "modules/atomics.h" #include "modules/symbol.h" +#include "modules/timer.h" + +typedef enum { + ASYNC_WAIT_SETTLE_NONE = 0, + ASYNC_WAIT_SETTLE_OK, + ASYNC_WAIT_SETTLE_TIMED_OUT, +} async_wait_settle_t; + +typedef struct AsyncWaitEntry { + ant_t *js; + ant_value_t promise; + ArrayBufferData *buffer; + int32_t *address; + uv_timer_t timer; + uv_async_t async; + bool timer_initialized; + bool async_initialized; + uint8_t pending_handles; + _Atomic int settle_state; + _Atomic bool settle_drain_microtasks; + struct AsyncWaitEntry *next; + struct AsyncWaitEntry *prev; +} AsyncWaitEntry; static WaitQueue global_wait_queue; +static AsyncWaitEntry *async_waiters_head = NULL; + static pthread_once_t wait_queue_init_once = PTHREAD_ONCE_INIT; +static pthread_mutex_t async_waiters_lock = PTHREAD_MUTEX_INITIALIZER; + +static inline bool async_waiter_is_linked_locked(AsyncWaitEntry *entry) { + return entry && (entry == async_waiters_head || entry->next || entry->prev); +} static void init_wait_queue(void) { wait_queue_init(&global_wait_queue); @@ -25,6 +58,125 @@ void wait_queue_init(WaitQueue *queue) { queue->head = NULL; pthread_mutex_init(&queue->lock, NULL); +} + +static void async_waiter_add_locked(AsyncWaitEntry *entry) { + entry->next = async_waiters_head; + entry->prev = NULL; + if (async_waiters_head) async_waiters_head->prev = entry; + async_waiters_head = entry; +} + +static void async_waiter_remove_locked(AsyncWaitEntry *entry) { + if (entry->prev) entry->prev->next = entry->next; + else async_waiters_head = entry->next; + if (entry->next) entry->next->prev = entry->prev; + entry->next = NULL; + entry->prev = NULL; +} + +static void async_waiter_release_buffer(AsyncWaitEntry *entry) { + if (!entry || !entry->buffer) return; + free_array_buffer_data(entry->buffer); + entry->buffer = NULL; +} + +static void async_waiter_release_handle(AsyncWaitEntry *entry) { + if (!entry) return; + if (entry->pending_handles > 0) entry->pending_handles--; + if (entry->pending_handles == 0) { + async_waiter_release_buffer(entry); + free(entry); + } +} + +static void async_waiter_close_cb(uv_handle_t *handle) { + AsyncWaitEntry *entry = handle ? handle->data : NULL; + async_waiter_release_handle(entry); +} + +static void async_waiter_close_handles(AsyncWaitEntry *entry) { + bool closed = false; + + if (entry->timer_initialized && !uv_is_closing((uv_handle_t *)&entry->timer)) { + uv_timer_stop(&entry->timer); + uv_close((uv_handle_t *)&entry->timer, async_waiter_close_cb); + entry->timer_initialized = false; + closed = true; + } + + if (entry->async_initialized && !uv_is_closing((uv_handle_t *)&entry->async)) { + uv_close((uv_handle_t *)&entry->async, async_waiter_close_cb); + entry->async_initialized = false; + closed = true; + } + + if (!closed) { + async_waiter_release_buffer(entry); + free(entry); + } +} + +static void async_waiter_queue_settle(AsyncWaitEntry *entry, async_wait_settle_t state, bool drain_microtasks) { + if (!entry) return; + atomic_store(&entry->settle_drain_microtasks, drain_microtasks); + atomic_store(&entry->settle_state, state); + if (entry->async_initialized) uv_async_send(&entry->async); +} + +static void async_waiter_async_cb(uv_async_t *handle) { + AsyncWaitEntry *entry = handle ? handle->data : NULL; + if (!entry || !entry->js) return; + + async_wait_settle_t state = (async_wait_settle_t)atomic_exchange(&entry->settle_state, ASYNC_WAIT_SETTLE_NONE); + if (state == ASYNC_WAIT_SETTLE_NONE) return; + + const char *result = state == ASYNC_WAIT_SETTLE_OK ? "ok" : "timed-out"; + js_resolve_promise(entry->js, entry->promise, js_mkstr(entry->js, result, strlen(result))); + if (atomic_load(&entry->settle_drain_microtasks)) + js_maybe_drain_microtasks_after_async_settle(entry->js); + + async_waiter_close_handles(entry); +} + +static void async_waiter_timeout_cb(uv_timer_t *timer) { + AsyncWaitEntry *entry = timer ? timer->data : NULL; + if (!entry) return; + + pthread_mutex_lock(&async_waiters_lock); + bool linked = async_waiter_is_linked_locked(entry); + if (linked) async_waiter_remove_locked(entry); + pthread_mutex_unlock(&async_waiters_lock); + + if (linked) async_waiter_queue_settle(entry, ASYNC_WAIT_SETTLE_TIMED_OUT, true); +} + +static int async_waiter_notify(int32_t *address, int count) { + int notified = 0; + AsyncWaitEntry *ready = NULL; + + pthread_mutex_lock(&async_waiters_lock); + AsyncWaitEntry *current = async_waiters_head; + while (current && (count == -1 || notified < count)) { + AsyncWaitEntry *next = current->next; + if (current->address == address) { + async_waiter_remove_locked(current); + current->next = ready; + current->prev = NULL; + ready = current; + notified++; + } current = next; + } + + pthread_mutex_unlock(&async_waiters_lock); + while (ready) { + AsyncWaitEntry *next = ready->next; + ready->next = NULL; + async_waiter_queue_settle(ready, ASYNC_WAIT_SETTLE_OK, false); + ready = next; + } + + return notified; } void wait_queue_cleanup(WaitQueue *queue) { @@ -74,12 +226,44 @@ pthread_cond_signal(¤t->cond); pthread_mutex_unlock(¤t->mutex); notified++; - } - current = current->next; + } current = current->next; } pthread_mutex_unlock(&queue->lock); + if (count == -1 || notified < count) + notified += async_waiter_notify(address, count == -1 ? -1 : count - notified); + return notified; +} + +void cleanup_atomics_module(ant_t *js) { + if (!js) return; + AsyncWaitEntry *removed = NULL; + + pthread_mutex_lock(&async_waiters_lock); + AsyncWaitEntry *current = async_waiters_head; + + while (current) { + AsyncWaitEntry *next = current->next; + if (current->js == js) { + async_waiter_remove_locked(current); + current->next = removed; + current->prev = NULL; + removed = current; + } current = next; + } + + pthread_mutex_unlock(&async_waiters_lock); + while (removed) { + AsyncWaitEntry *next = removed->next; + removed->next = NULL; + removed->prev = NULL; + removed->js = NULL; + removed->promise = js_mkundef(); + async_waiter_release_buffer(removed); + async_waiter_close_handles(removed); + removed = next; + } } static bool get_atomic_array_data(ant_t *js, ant_value_t this_val, TypedArrayData **out_data, uint8_t **out_ptr) { @@ -782,6 +966,13 @@ int32_t expected_value = (int32_t)js_getnum(args[2]); _Atomic int32_t *atomic_ptr = (_Atomic int32_t *)(ptr + index * 4); int32_t current_value = atomic_load(atomic_ptr); + double timeout_ms = HUGE_VAL; + + if (nargs > 3 && vtype(args[3]) == T_NUM) { + timeout_ms = js_getnum(args[3]); + if (isnan(timeout_ms)) timeout_ms = HUGE_VAL; + else if (timeout_ms < 0) timeout_ms = 0; + } ant_value_t result_obj = js_mkobj(js); if (current_value != expected_value) { @@ -790,20 +981,60 @@ return result_obj; } + if (timeout_ms == 0) { + js_set(js, result_obj, "async", js_false); + js_set(js, result_obj, "value", js_mkstr(js, "timed-out", 9)); + return result_obj; + } + ant_value_t promise = js_mkpromise(js); + AsyncWaitEntry *entry = calloc(1, sizeof(*entry)); + if (!entry) return js_mkerr(js, "Out of memory"); + + entry->js = js; + entry->promise = promise; + entry->buffer = ta_data->buffer; + entry->buffer->ref_count++; + entry->address = (int32_t *)atomic_ptr; + atomic_store(&entry->settle_state, ASYNC_WAIT_SETTLE_NONE); + atomic_store(&entry->settle_drain_microtasks, false); + + if (uv_async_init(uv_default_loop(), &entry->async, async_waiter_async_cb) != 0) { + async_waiter_close_handles(entry); + return js_mkerr(js, "Failed to initialize Atomics.waitAsync notifier"); + } + + entry->async_initialized = true; + entry->async.data = entry; + entry->pending_handles++; + + if (isfinite(timeout_ms)) { + uint64_t delay = timeout_ms > (double)UINT64_MAX ? UINT64_MAX : (uint64_t)timeout_ms; + if (uv_timer_init(uv_default_loop(), &entry->timer) != 0) { + async_waiter_close_handles(entry); + return js_mkerr(js, "Failed to initialize Atomics.waitAsync timer"); + } + entry->timer_initialized = true; + entry->timer.data = entry; + entry->pending_handles++; + if (uv_timer_start(&entry->timer, async_waiter_timeout_cb, delay, 0) != 0) { + async_waiter_close_handles(entry); + return js_mkerr(js, "Failed to start Atomics.waitAsync timer"); + } + } + + pthread_mutex_lock(&async_waiters_lock); + async_waiter_add_locked(entry); + pthread_mutex_unlock(&async_waiters_lock); + js_set(js, result_obj, "async", js_true); js_set(js, result_obj, "value", promise); - js_resolve_promise(js, promise, js_mkstr(js, "ok", 2)); - + return result_obj; } // Atomics.pause() static ant_value_t js_atomics_pause(ant_t *js, ant_value_t *args, int nargs) { - (void)js; - (void)args; - (void)nargs; - #if defined(__x86_64__) || defined(__i386__) __builtin_ia32_pause(); #elif defined(__aarch64__) || defined(__arm__) @@ -836,4 +1067,12 @@ js_set_sym(js, atomics, get_toStringTag_sym(), js_mkstr(js, "Atomics", 7)); js_set(js, glob, "Atomics", atomics); +} + +void gc_mark_atomics(ant_t *js, gc_mark_fn mark) { + pthread_mutex_lock(&async_waiters_lock); + for (AsyncWaitEntry *entry = async_waiters_head; entry; entry = entry->next) { + if (entry->js == js) mark(js, entry->promise); + } + pthread_mutex_unlock(&async_waiters_lock); } diff --git a/src/modules/buffer.c b/src/modules/buffer.c --- a/src/modules/buffer.c +++ b/src/modules/buffer.c @@ -4,14 +4,15 @@ #include #include "ant.h" +#include "ptr.h" #include "utf8.h" #include "utils.h" #include "errors.h" #include "base64.h" #include "internal.h" #include "runtime.h" +#include "gc/roots.h" #include "descriptors.h" -#include "ptr.h" #include "silver/engine.h" #include "modules/buffer.h" @@ -1252,19 +1253,32 @@ ant_value_t source = args[0]; bool has_map = nargs >= 2 && vtype(args[1]) != T_UNDEF; + ant_value_t map_fn = js_mkundef(); ant_value_t this_arg = nargs >= 3 ? args[2] : js_mkundef(); + ant_value_t result = js_mkundef(); + ant_value_t *collected = NULL; + + gc_temp_root_scope_t temp_roots = {0}; + bool temp_roots_active = false; if (has_map) { - if (!is_callable(args[1])) - return js_mkerr_typed(js, JS_ERR_TYPE, "%s.from: mapFn is not a function", type_name); + if (!is_callable(args[1])) return js_mkerr_typed( + js, JS_ERR_TYPE, "%s.from: mapFn is not a function", type_name + ); map_fn = args[1]; } - ant_value_t *collected = NULL; + gc_temp_root_scope_begin(js, &temp_roots); + temp_roots_active = true; + + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, source))) goto oom; + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, map_fn))) goto oom; + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, this_arg))) goto oom; + size_t count = 0, cap = 16; collected = malloc(cap * sizeof(ant_value_t)); - if (!collected) return js_mkerr(js, "oom"); + if (!collected) goto oom; js_iter_t it; if (js_iter_open(js, source, &it)) { @@ -1273,15 +1287,19 @@ if (count >= cap) { cap *= 2; ant_value_t *tmp = realloc(collected, cap * sizeof(ant_value_t)); - if (!tmp) { free(collected); return js_mkerr(js, "oom"); } + if (!tmp) goto oom; collected = tmp; } if (has_map) { ant_value_t map_args[2] = { item, js_mknum((double)count) }; item = sv_vm_call(js->vm, js, map_fn, this_arg, map_args, 2, NULL, false); - if (is_err(item)) { free(collected); return item; } + if (is_err(item)) { + result = item; + goto done; + } } collected[count++] = item; + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, item))) goto oom; } js_iter_close(js, &it); } else { @@ -1294,23 +1312,29 @@ if (count >= cap) { cap *= 2; ant_value_t *tmp = realloc(collected, cap * sizeof(ant_value_t)); - if (!tmp) { free(collected); return js_mkerr(js, "oom"); } + if (!tmp) goto oom; collected = tmp; } if (has_map) { ant_value_t map_args[2] = { item, js_mknum((double)i) }; item = sv_vm_call(js->vm, js, map_fn, this_arg, map_args, 2, NULL, false); - if (is_err(item)) { free(collected); return item; } + if (is_err(item)) { + result = item; + goto done; + } } collected[count++] = item; + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, item))) goto oom; } } size_t elem_size = get_element_size(type); ArrayBufferData *buffer = create_array_buffer_data(count * elem_size); - if (!buffer) { free(collected); return js_mkerr(js, "oom"); } + if (!buffer) goto oom; - ant_value_t result = create_typed_array(js, type, buffer, 0, count, type_name); + result = create_typed_array(js, type, buffer, 0, count, type_name); + if (is_err(result)) goto done; + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, result))) goto oom; uint8_t *data = buffer->data; for (size_t i = 0; i < count; i++) { @@ -1329,8 +1353,14 @@ default: break; }} +done: + if (temp_roots_active) gc_temp_root_scope_end(&temp_roots); free(collected); return result; + +oom: + result = js_mkerr(js, "oom"); + goto done; } #define DEFINE_TYPEDARRAY_FROM(name, type) \ diff --git a/src/modules/builtin.c b/src/modules/builtin.c --- a/src/modules/builtin.c +++ b/src/modules/builtin.c @@ -166,13 +166,12 @@ // Ant.stats() static ant_value_t js_stats_fn(ant_t *js, ant_value_t *args, int nargs) { - (void) args; (void) nargs; ant_value_t result = js_newobj(js); ant_pool_stats_t rope_s = js_pool_stats(&js->pool.rope); ant_pool_stats_t sym_s = js_pool_stats(&js->pool.symbol); ant_pool_stats_t bigint_s = js_class_pool_stats(&js->pool.bigint); - ant_pool_stats_t string_s = js_class_pool_stats(&js->pool.string); + ant_string_pool_stats_t string_s = js_string_pool_stats(&js->pool.string); ant_value_t pools = js_newobj(js); ant_value_t rope_obj = js_newobj(js); @@ -194,16 +193,41 @@ js_set(js, pools, "bigint", bigint_obj); ant_value_t string_obj = js_newobj(js); - js_set(js, string_obj, "used", js_mknum((double)string_s.used)); - js_set(js, string_obj, "capacity", js_mknum((double)string_s.capacity)); - js_set(js, string_obj, "blocks", js_mknum((double)string_s.blocks)); - js_set(js, pools, "string", string_obj); + js_set(js, string_obj, "used", js_mknum((double)string_s.total.used)); + js_set(js, string_obj, "capacity", js_mknum((double)string_s.total.capacity)); + js_set(js, string_obj, "blocks", js_mknum((double)string_s.total.blocks)); - size_t pool_used = rope_s.used + sym_s.used + bigint_s.used + string_s.used; - size_t pool_cap = rope_s.capacity + sym_s.capacity + bigint_s.capacity + string_s.capacity; + ant_value_t string_pooled_obj = js_newobj(js); + js_set(js, string_pooled_obj, "used", js_mknum((double)string_s.pooled.used)); + js_set(js, string_pooled_obj, "capacity", js_mknum((double)string_s.pooled.capacity)); + js_set(js, string_pooled_obj, "blocks", js_mknum((double)string_s.pooled.blocks)); + js_set(js, string_obj, "pooled", string_pooled_obj); + + ant_value_t string_large_live_obj = js_newobj(js); + js_set(js, string_large_live_obj, "used", js_mknum((double)string_s.large_live.used)); + js_set(js, string_large_live_obj, "capacity", js_mknum((double)string_s.large_live.capacity)); + js_set(js, string_large_live_obj, "blocks", js_mknum((double)string_s.large_live.blocks)); + js_set(js, string_obj, "largeLive", string_large_live_obj); + + ant_value_t string_large_reusable_obj = js_newobj(js); + js_set(js, string_large_reusable_obj, "used", js_mknum((double)string_s.large_reusable.used)); + js_set(js, string_large_reusable_obj, "capacity", js_mknum((double)string_s.large_reusable.capacity)); + js_set(js, string_large_reusable_obj, "blocks", js_mknum((double)string_s.large_reusable.blocks)); + js_set(js, string_obj, "largeReusable", string_large_reusable_obj); + + ant_value_t string_large_quarantine_obj = js_newobj(js); + js_set(js, string_large_quarantine_obj, "used", js_mknum((double)string_s.large_quarantine.used)); + js_set(js, string_large_quarantine_obj, "capacity", js_mknum((double)string_s.large_quarantine.capacity)); + js_set(js, string_large_quarantine_obj, "blocks", js_mknum((double)string_s.large_quarantine.blocks)); + js_set(js, string_obj, "largeQuarantine", string_large_quarantine_obj); + + size_t pool_used = rope_s.used + sym_s.used + bigint_s.used + string_s.total.used; + size_t pool_cap = rope_s.capacity + sym_s.capacity + bigint_s.capacity + string_s.total.capacity; + js_set(js, pools, "totalUsed", js_mknum((double)pool_used)); js_set(js, pools, "totalCapacity", js_mknum((double)pool_cap)); js_set(js, result, "pools", pools); + js_set(js, pools, "string", string_obj); size_t obj_count = 0; size_t obj_bytes = 0; diff --git a/src/modules/reflect.c b/src/modules/reflect.c --- a/src/modules/reflect.c +++ b/src/modules/reflect.c @@ -16,12 +16,23 @@ ant_value_t target = args[0]; ant_value_t key = args[1]; - int t = vtype(target); - if (t != T_OBJ && t != T_FUNC) return js_mkundef(); - - if (vtype(key) != T_STR) return js_mkundef(); - - char *key_str = js_getstr(js, key, NULL); + if (!is_object_type(target)) return js_mkundef(); + + ant_value_t prop_key = key; + if (is_object_type(prop_key)) { + prop_key = js_to_primitive(js, prop_key, 1); + if (is_err(prop_key)) return prop_key; + } + + if (vtype(prop_key) == T_SYMBOL) + return js_get_sym(js, target, prop_key); + + if (vtype(prop_key) != T_STR) { + prop_key = js_tostring_val(js, prop_key); + if (is_err(prop_key)) return prop_key; + } + + char *key_str = js_getstr(js, prop_key, NULL); if (!key_str) return js_mkundef(); return js_get(js, target, key_str); diff --git a/src/modules/regex.c b/src/modules/regex.c --- a/src/modules/regex.c +++ b/src/modules/regex.c @@ -1266,6 +1266,10 @@ ant_offset_t ncap = js_arr_len(js, result); int num_caps = ncap > 1 ? (int)(ncap - 1) : 0; repl_capture_t caps_buf[16], *caps = num_caps <= 16 ? caps_buf : ant_calloc(sizeof(repl_capture_t) * (size_t)num_caps); + if (num_caps > 16 && !caps) { + free(buf); + return js_mkerr(js, "oom"); + } for (int ci = 0; ci < num_caps; ci++) { ant_value_t cap = js_arr_get(js, result, (ant_offset_t)(ci + 1)); if (vtype(cap) == T_STR) { ant_offset_t cl, co = vstr(js, cap, &cl); caps[ci] = (repl_capture_t){ (const char *)(uintptr_t)(co), cl }; } @@ -1273,9 +1277,17 @@ } ant_offset_t mlen, moff = vstr(js, matched, &mlen); str_off = vstr(js, str, &str_len); - repl_template((const char *)(uintptr_t)(rep_off), rep_len, (const char *)(uintptr_t)(moff), mlen, - (const char *)(uintptr_t)(str_off), str_len, position, caps, num_caps, &buf, &buf_len, &buf_cap); + bool ok = repl_template( + (const char *)(uintptr_t)(rep_off), rep_len, + (const char *)(uintptr_t)(moff), mlen, + (const char *)(uintptr_t)(str_off), str_len, position, + caps, num_caps, &buf, &buf_len, &buf_cap + ); if (caps != caps_buf) free(caps); + if (!ok) { + free(buf); + return js_mkerr(js, "oom"); + } } next_src_pos = position + matched_len; } diff --git a/src/silver/compiler.c b/src/silver/compiler.c --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -4343,7 +4343,6 @@ compile_export_emit(&comp, e->name, e->len); } - free(comp.deferred_exports); emit_close_upvals(&comp); emit_op(&comp, OP_RETURN_UNDEF); diff --git a/src/silver/ops/property.h b/src/silver/ops/property.h --- a/src/silver/ops/property.h +++ b/src/silver/ops/property.h @@ -28,6 +28,16 @@ return out; } +static inline ant_value_t sv_key_to_property_key(ant_t *js, ant_value_t key) { + if (vtype(key) == T_SYMBOL) return key; + + ant_value_t prim = is_object_type(key) ? js_to_primitive(js, key, 1) : key; + if (is_err(prim)) return prim; + if (vtype(prim) == T_SYMBOL) return prim; + + return js_tostring_val(js, prim); +} + static inline ant_value_t sv_key_to_propstr(ant_t *js, ant_value_t key) { return coerce_to_str(js, key); } @@ -251,12 +261,16 @@ } static inline ant_value_t sv_getprop_by_key(ant_t *js, ant_value_t obj, ant_value_t key) { - if (vtype(key) == T_SYMBOL) return js_get_sym(js, obj, key); - ant_value_t key_str = sv_key_to_propstr(js, key); + ant_value_t prop_key = sv_key_to_property_key(js, key); + if (is_err(prop_key)) return prop_key; + if (vtype(prop_key) == T_SYMBOL) return js_get_sym(js, obj, prop_key); + + ant_value_t key_str = prop_key; if (is_err(key_str) || vtype(key_str) != T_STR) return js_mkundef(); ant_offset_t klen = 0; ant_offset_t koff = vstr(js, key_str, &klen); + const char *kptr = (const char *)(uintptr_t)(koff); return sv_getprop_fallback_len(js, obj, kptr, klen); } @@ -705,9 +719,9 @@ ant_value_t val = vm->stack[--vm->sp]; ant_value_t key = vm->stack[--vm->sp]; ant_value_t obj = vm->stack[--vm->sp]; - if (vtype(key) == T_SYMBOL) return js_setprop(js, obj, key, val); - ant_value_t key_jv = sv_key_to_propstr(js, key); - return js_setprop(js, obj, key_jv, val); + ant_value_t prop_key = sv_key_to_property_key(js, key); + if (is_err(prop_key)) return prop_key; + return js_setprop(js, obj, prop_key, val); } static inline bool sv_try_define_field_fast(