MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

rewrite string pool for performance

+1529 -332
+24 -24
examples/results.txt
··· 523 523 compat-table/es6/Proxy.handler.construct.invariants.js: OK 524 524 compat-table/es6/Proxy.handler.construct.js: OK 525 525 compat-table/es6/Proxy.handler.defineProperty.invariants.js: failed 526 - compat-table/es6/Proxy.handler.defineProperty.js: failed 526 + compat-table/es6/Proxy.handler.defineProperty.js: OK 527 527 compat-table/es6/Proxy.handler.deleteProperty.invariants.js: OK 528 528 compat-table/es6/Proxy.handler.deleteProperty.js: OK 529 529 compat-table/es6/Proxy.handler.getOwnPropertyDescriptor.invariants.js: failed 530 530 compat-table/es6/Proxy.handler.getOwnPropertyDescriptor.js: failed 531 531 compat-table/es6/Proxy.handler.getPrototypeOf.invariants.js: failed 532 - compat-table/es6/Proxy.handler.getPrototypeOf.js: failed 532 + compat-table/es6/Proxy.handler.getPrototypeOf.js: OK 533 533 compat-table/es6/Proxy.handler.get.instances.js: failed 534 534 compat-table/es6/Proxy.handler.get.invariants.js: OK 535 535 compat-table/es6/Proxy.handler.get.js: OK ··· 896 896 compat-table/es6/misc.Proxy.get.Array.reverse.js: failed 897 897 compat-table/es6/misc.Proxy.get.Array.shift.js: failed 898 898 compat-table/es6/misc.Proxy.get.Array.splice.js: failed 899 - compat-table/es6/misc.Proxy.get.Array.toString.js: failed 899 + compat-table/es6/misc.Proxy.get.Array.toString.js: OK 900 900 compat-table/es6/misc.Proxy.get.ClassDefinitionEvaluation.js: OK 901 901 compat-table/es6/misc.Proxy.get.CreateDynamicFunction.js: failed 902 902 compat-table/es6/misc.Proxy.get.CreateListFromArrayLike.js: failed ··· 922 922 compat-table/es6/misc.Proxy.get.String.replace.js: failed 923 923 compat-table/es6/misc.Proxy.get.String.search.js: failed 924 924 compat-table/es6/misc.Proxy.get.String.split.js: failed 925 - compat-table/es6/misc.Proxy.get.ToPrimitive.js: TypeError: Cannot convert object to primitive value 925 + compat-table/es6/misc.Proxy.get.ToPrimitive.js: OK 926 926 compat-table/es6/misc.Proxy.get.ToPropertyDescriptor.js: failed 927 927 compat-table/es6/misc.Proxy.get.instanceof.js: OK 928 928 compat-table/es6/misc.Proxy.ownKeys.SerializeJSONObject.js: failed ··· 1153 1153 compat-table/es2017/SharedArrayBuffer.prototype.slice.js: OK 1154 1154 compat-table/es2017/String.prototype.padEnd.js: OK 1155 1155 compat-table/es2017/String.prototype.padStart.js: OK 1156 - compat-table/es2017/annex-b.Object.prototype.__defineGetter__.ToObject.js: ReferenceError: '__defineGetter__' is not defined 1157 - compat-table/es2017/annex-b.Object.prototype.__defineGetter__.js: TypeError: Cannot read properties of undefined (reading 'call') 1158 - compat-table/es2017/annex-b.Object.prototype.__defineGetter__.symbols.js: TypeError: Cannot read properties of undefined (reading 'call') 1159 - compat-table/es2017/annex-b.Object.prototype.__defineSetter__.ToObject.js: ReferenceError: '__defineSetter__' is not defined 1160 - compat-table/es2017/annex-b.Object.prototype.__defineSetter__.js: TypeError: Cannot read properties of undefined (reading 'call') 1161 - compat-table/es2017/annex-b.Object.prototype.__defineSetter__.symbols.js: TypeError: Cannot read properties of undefined (reading 'call') 1162 - compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.ToObject.js: ReferenceError: '__lookupGetter__' is not defined 1163 - compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.js: TypeError: Cannot read properties of undefined (reading 'call') 1164 - compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.prototype-chain.js: TypeError: Cannot read properties of undefined (reading 'call') 1165 - compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.shadow-accessors.js: TypeError: undefined is not a function 1166 - compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.symbols.js: TypeError: Cannot read properties of undefined (reading 'call') 1167 - compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.ToObject.js: ReferenceError: '__lookupSetter__' is not defined 1168 - compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.js: TypeError: Cannot read properties of undefined (reading 'call') 1169 - compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.prototype-chain.js: TypeError: Cannot read properties of undefined (reading 'call') 1170 - compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.shadow-accessors.js: TypeError: undefined is not a function 1171 - compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.symbols.js: TypeError: Cannot read properties of undefined (reading 'call') 1172 - compat-table/es2017/annex-b.Proxy.__defineGetter__.js: TypeError: Cannot read properties of undefined (reading 'call') 1173 - compat-table/es2017/annex-b.Proxy.__defineSetter__.js: TypeError: Cannot read properties of undefined (reading 'call') 1174 - compat-table/es2017/annex-b.Proxy.__lookupGetter__.js: TypeError: Cannot read properties of undefined (reading 'call') 1175 - compat-table/es2017/annex-b.Proxy.__lookupSetter__.js: TypeError: Cannot read properties of undefined (reading 'call') 1156 + compat-table/es2017/annex-b.Object.prototype.__defineGetter__.ToObject.js: OK 1157 + compat-table/es2017/annex-b.Object.prototype.__defineGetter__.js: OK 1158 + compat-table/es2017/annex-b.Object.prototype.__defineGetter__.symbols.js: OK 1159 + compat-table/es2017/annex-b.Object.prototype.__defineSetter__.ToObject.js: OK 1160 + compat-table/es2017/annex-b.Object.prototype.__defineSetter__.js: OK 1161 + compat-table/es2017/annex-b.Object.prototype.__defineSetter__.symbols.js: OK 1162 + compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.ToObject.js: OK 1163 + compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.js: OK 1164 + compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.prototype-chain.js: OK 1165 + compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.shadow-accessors.js: OK 1166 + compat-table/es2017/annex-b.Object.prototype.__lookupGetter__.symbols.js: OK 1167 + compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.ToObject.js: OK 1168 + compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.js: OK 1169 + compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.prototype-chain.js: OK 1170 + compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.shadow-accessors.js: OK 1171 + compat-table/es2017/annex-b.Object.prototype.__lookupSetter__.symbols.js: OK 1172 + compat-table/es2017/annex-b.Proxy.__defineGetter__.js: OK 1173 + compat-table/es2017/annex-b.Proxy.__defineSetter__.js: OK 1174 + compat-table/es2017/annex-b.Proxy.__lookupGetter__.js: OK 1175 + compat-table/es2017/annex-b.Proxy.__lookupSetter__.js: OK 1176 1176 compat-table/es2017/annex-b.for-in-assignment-non-strict.js: OK 1177 1177 compat-table/es2017/async.Symbol.toStringTag.js: failed 1178 1178 compat-table/es2017/async.arrow-in-class.js: OK
+14 -14
include/internal.h
··· 70 70 #define PROTO_WALK_F_OBJECT_ONLY (1u << 0) 71 71 #define PROTO_WALK_F_LOOKUP (1u << 1) 72 72 73 - #define ROPE_MAX_DEPTH 64 74 - #define ROPE_FLATTEN_THRESHOLD (32 * 1024) 73 + #define ROPE_MAX_DEPTH 255 74 + #define ROPE_FLATTEN_THRESHOLD (512 * 1024) 75 75 76 76 #define T_EMPTY (NANBOX_PREFIX | ((ant_value_t)T_SENTINEL << NANBOX_TYPE_SHIFT) | 0xDEADULL) 77 77 #define T_SPECIAL_OBJECT_MASK (JS_TPFLG(T_OBJ) | JS_TPFLG(T_ARR)) ··· 207 207 ant_pool_t symbol; 208 208 ant_pool_t permanent; 209 209 ant_class_pool_t bigint; 210 - ant_class_pool_t string; 210 + ant_string_pool_t string; 211 211 } pool; 212 212 213 213 struct { ··· 253 253 bool thrown_exists; 254 254 }; 255 255 256 - typedef struct { 257 - ant_offset_t len; 258 - uint8_t is_ascii; 259 - char bytes[]; 260 - } ant_flat_string_t; 261 - 262 256 enum { 263 257 STR_ASCII_UNKNOWN = 0, 264 258 STR_ASCII_YES = 1, ··· 267 261 268 262 typedef struct { 269 263 ant_offset_t len; 270 - uint8_t depth; 271 - ant_value_t left; 272 - ant_value_t right; 273 - ant_value_t cached; 274 - } ant_rope_heap_t; 264 + uint8_t is_ascii; 265 + char bytes[]; 266 + } ant_flat_string_t; 275 267 276 268 typedef struct { 277 269 const char *ptr; ··· 483 475 484 476 static inline ant_flat_string_t *str_flat_from_bytes(const char *str) { 485 477 return (ant_flat_string_t *)((char *)str - offsetof(ant_flat_string_t, bytes)); 478 + } 479 + 480 + static inline ant_flat_string_t *large_string_flat_ptr(ant_large_string_alloc_t *alloc) { 481 + return alloc ? (ant_flat_string_t *)&alloc->len : NULL; 482 + } 483 + 484 + static inline ant_large_string_alloc_t *large_string_alloc_from_flat(ant_flat_string_t *flat) { 485 + return flat ? (ant_large_string_alloc_t *)((char *)flat - offsetof(ant_large_string_alloc_t, len)) : NULL; 486 486 } 487 487 488 488 static inline uint8_t str_detect_ascii_bytes(const char *str, size_t len) {
+3
include/modules/atomics.h
··· 4 4 #include <stdint.h> 5 5 #include <pthread.h> 6 6 7 + #include "types.h" 8 + 7 9 void init_atomics_module(void); 10 + void cleanup_atomics_module(ant_t *js); 8 11 9 12 typedef struct WaitQueueEntry { 10 13 pthread_cond_t cond;
+43
include/pool.h
··· 44 44 ant_pool_bucket_t classes[ANT_POOL_SIZE_CLASS_COUNT]; 45 45 } ant_class_pool_t; 46 46 47 + typedef struct ant_large_string_alloc { 48 + struct ant_large_string_alloc *next; 49 + struct ant_large_string_alloc *prev; 50 + size_t capacity; 51 + size_t alloc_size; 52 + uint32_t quarantine_epoch; 53 + uint8_t marked; 54 + ant_offset_t len; 55 + uint8_t is_ascii; 56 + char bytes[]; 57 + } ant_large_string_alloc_t; 58 + 59 + typedef struct { 60 + ant_offset_t len; 61 + uint8_t depth; 62 + ant_value_t left; 63 + ant_value_t right; 64 + ant_value_t cached; 65 + } ant_rope_heap_t; 66 + 67 + typedef struct { 68 + ant_large_string_alloc_t *live; 69 + ant_large_string_alloc_t *reusable; 70 + ant_large_string_alloc_t *quarantine; 71 + uint32_t gc_epoch; 72 + } ant_large_string_space_t; 73 + 74 + typedef struct ant_string_pool { 75 + size_t block_size; 76 + ant_pool_bucket_t classes[ANT_POOL_SIZE_CLASS_COUNT]; 77 + ant_large_string_space_t large; 78 + } ant_string_pool_t; 79 + 47 80 typedef enum { 48 81 ANT_ALLOC_ROPE = 0, 49 82 ANT_ALLOC_SYMBOL = 1, ··· 57 90 size_t blocks; 58 91 } ant_pool_stats_t; 59 92 93 + typedef struct { 94 + ant_pool_stats_t pooled; 95 + ant_pool_stats_t large_live; 96 + ant_pool_stats_t large_reusable; 97 + ant_pool_stats_t large_quarantine; 98 + ant_pool_stats_t total; 99 + } ant_string_pool_stats_t; 100 + 60 101 static inline ant_pool_block_t *pool_free_next(ant_pool_block_t *b) { 61 102 ant_pool_block_t *p; memcpy(&p, b->data, sizeof(ant_pool_block_t *)); return p; 62 103 } ··· 101 142 102 143 void js_pool_destroy(ant_pool_t *pool); 103 144 void js_class_pool_destroy(ant_class_pool_t *pool); 145 + void js_string_pool_destroy(ant_string_pool_t *pool); 104 146 105 147 void *js_type_alloc( 106 148 ant_t *js, ant_alloc_kind_t kind, ··· 114 156 115 157 ant_pool_stats_t js_pool_stats(ant_pool_t *pool); 116 158 ant_pool_stats_t js_class_pool_stats(ant_class_pool_t *pool); 159 + ant_string_pool_stats_t js_string_pool_stats(ant_string_pool_t *pool); 117 160 118 161 #endif
+1 -1
include/utils.h
··· 54 54 size_t len; 55 55 } repl_capture_t; 56 56 57 - void repl_template( 57 + bool repl_template( 58 58 const char *repl, size_t repl_len, 59 59 const char *matched, size_t matched_len, 60 60 const char *str, size_t str_len, size_t position,
+302 -97
src/ant.c
··· 53 53 #include <sys/resource.h> 54 54 #endif 55 55 56 + #include "modules/atomics.h" 56 57 #include "modules/bigint.h" 57 58 #include "modules/timer.h" 58 59 #include "modules/symbol.h" ··· 592 593 593 594 static ant_value_t js_call_valueOf(ant_t *js, ant_value_t value); 594 595 static ant_value_t js_call_toString(ant_t *js, ant_value_t value); 595 - 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); 596 + static ant_value_t js_call_method(ant_t *js, ant_value_t obj, const char *method, ant_value_t *args, int nargs); 597 + static ant_value_t builtin_object_defineProperty(ant_t *js, ant_value_t *args, int nargs); 596 598 597 599 static inline bool is_slot_prop(ant_offset_t header); 598 600 static inline ant_offset_t next_prop(ant_offset_t header); ··· 600 602 static ant_value_t builtin_promise_then(ant_t *js, ant_value_t *args, int nargs); 601 603 static ant_value_t proxy_get(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); 602 604 static ant_value_t proxy_get_val(ant_t *js, ant_value_t proxy, ant_value_t key_val); 605 + static ant_value_t proxy_get_prototype_of(ant_t *js, ant_value_t proxy); 603 606 static ant_value_t proxy_set(ant_t *js, ant_value_t proxy, const char *key, size_t key_len, ant_value_t value); 604 607 static ant_value_t proxy_has(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); 605 608 static ant_value_t proxy_has_val(ant_t *js, ant_value_t proxy, ant_value_t key_val); ··· 607 610 static ant_value_t proxy_has_own(ant_t *js, ant_value_t proxy, ant_value_t key_val); 608 611 static ant_value_t proxy_delete(ant_t *js, ant_value_t proxy, const char *key, size_t key_len); 609 612 static ant_value_t proxy_delete_val(ant_t *js, ant_value_t proxy, ant_value_t key_val); 613 + static ant_value_t proxy_define_property(ant_t *js, ant_value_t proxy, ant_value_t key_val, ant_value_t descriptor); 610 614 611 615 static ant_value_t get_ctor_proto(ant_t *js, const char *name, size_t len); 612 616 static inline void array_len_set(ant_t *js, ant_value_t obj, ant_offset_t new_len); ··· 1729 1733 } 1730 1734 1731 1735 ant_offset_t total_len = rope_len(rope); 1732 - char *buf = (char *)ant_calloc(total_len + 1); 1733 - 1734 - if (!buf) { 1736 + ant_value_t flat = js_mkstr(js, NULL, total_len); 1737 + GC_ROOT_PIN(js, flat); 1738 + 1739 + if (is_err(flat)) { 1735 1740 GC_ROOT_RESTORE(js, root_mark); 1736 - return js_mkerr(js, "oom"); 1741 + return flat; 1737 1742 } 1738 - 1743 + 1744 + ant_flat_string_t *flat_ptr = (ant_flat_string_t *)(uintptr_t)vdata(flat); 1739 1745 ant_offset_t pos = 0; 1740 - rope_flatten_into(js, rope, buf, &pos); 1741 - buf[pos] = '\0'; 1742 1746 1743 - ant_value_t flat = js_mkstr(js, buf, pos); 1744 - GC_ROOT_PIN(js, flat); 1745 - free(buf); 1746 - 1747 - if (!is_err(flat)) { 1748 - rope_set_cached_flat(rope, flat); 1749 - } 1747 + rope_flatten_into(js, rope, flat_ptr->bytes, &pos); 1748 + flat_ptr->bytes[pos] = '\0'; 1749 + flat_ptr->is_ascii = str_detect_ascii_bytes(flat_ptr->bytes, (size_t)pos); 1750 + 1751 + rope_set_cached_flat(rope, flat); 1752 + GC_ROOT_RESTORE(js, root_mark); 1750 1753 1751 - GC_ROOT_RESTORE(js, root_mark); 1752 1754 return flat; 1753 1755 } 1754 1756 ··· 4069 4071 4070 4072 uint8_t left_depth = (vtype(l) == T_STR && str_is_heap_rope(l)) ? rope_depth(l) : 0; 4071 4073 uint8_t right_depth = (vtype(r) == T_STR && str_is_heap_rope(r)) ? rope_depth(r) : 0; 4072 - uint8_t new_depth = (left_depth > right_depth ? left_depth : right_depth) + 1; 4074 + unsigned int new_depth = (unsigned int)(left_depth > right_depth ? left_depth : right_depth) + 1u; 4073 4075 4074 4076 if (new_depth >= ROPE_MAX_DEPTH || total_len >= ROPE_FLATTEN_THRESHOLD) { 4075 4077 ant_value_t flat_l = l, flat_r = r; ··· 4094 4096 return string_builder_finalize(js, &sb); 4095 4097 } 4096 4098 4097 - return js_mkrope(js, l, r, total_len, new_depth); 4099 + return js_mkrope(js, l, r, total_len, (uint8_t)new_depth); 4098 4100 } 4099 4101 4100 4102 ant_offset_t n1, off1 = vstr(js, l, &n1); ··· 4132 4134 typedef enum { ITER_CONTINUE, ITER_BREAK, ITER_ERROR } iter_action_t; 4133 4135 typedef iter_action_t (*iter_callback_t)(ant_t *js, ant_value_t value, void *ctx, ant_value_t *out); 4134 4136 4135 - 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) { 4136 - ant_value_t getter = js_mkundef(); bool has_getter = false; 4137 - uintptr_t off = lkp_with_getter(js, obj, method, method_len, &getter, &has_getter); 4138 - 4139 - ant_value_t fn; 4140 - if (has_getter) { 4141 - fn = call_proto_accessor(js, obj, getter, true, NULL, 0, false); 4142 - if (is_err(fn)) { *out_result = fn; return true; } 4143 - } else if (off != 0) { 4144 - fn = propref_load(js, (ant_offset_t)off); 4145 - } else return false; 4137 + 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) { 4138 + ant_value_t fn = js_getprop_fallback(js, obj, method); 4139 + if (is_err(fn)) { 4140 + *out_result = fn; 4141 + return true; 4142 + } 4146 4143 4147 4144 uint8_t ft = vtype(fn); 4148 4145 if (ft != T_FUNC && ft != T_CFUNC) return false; ··· 4167 4164 return true; 4168 4165 } 4169 4166 4170 - 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) { 4167 + static ant_value_t js_call_method(ant_t *js, ant_value_t obj, const char *method, ant_value_t *args, int nargs) { 4171 4168 ant_value_t result; 4172 - if (!js_try_call_method(js, obj, method, method_len, args, nargs, &result)) return js_mkundef(); 4169 + if (!js_try_call_method(js, obj, method, args, nargs, &result)) return js_mkundef(); 4173 4170 return result; 4174 4171 } 4175 4172 4176 4173 static ant_value_t js_call_toString(ant_t *js, ant_value_t value) { 4177 - ant_value_t result = js_call_method(js, value, "toString", 8, NULL, 0); 4174 + ant_value_t result = js_call_method(js, value, "toString", NULL, 0); 4178 4175 4179 4176 if (is_err(result)) return result; 4180 4177 if (vtype(result) == T_STR) return result; ··· 4197 4194 } 4198 4195 4199 4196 static ant_value_t js_call_valueOf(ant_t *js, ant_value_t value) { 4200 - ant_value_t result = js_call_method(js, value, "valueOf", 7, NULL, 0); 4197 + ant_value_t result = js_call_method(js, value, "valueOf", NULL, 0); 4201 4198 if (vtype(result) == T_UNDEF) return value; 4202 4199 return result; 4203 4200 } ··· 4210 4207 static ant_value_t try_exotic_to_primitive(ant_t *js, ant_value_t value, int hint) { 4211 4208 ant_value_t tp_sym = get_toPrimitive_sym(); 4212 4209 if (vtype(tp_sym) != T_SYMBOL) return mkval(T_UNDEF, 0); 4213 - ant_offset_t tp_off = lkp_sym_proto(js, value, (ant_offset_t)vdata(tp_sym)); 4214 - if (tp_off == 0) return mkval(T_UNDEF, 0); 4215 - 4216 - ant_value_t tp_fn = propref_load(js, tp_off); 4210 + ant_value_t tp_fn = js_get_sym(js, value, tp_sym); 4217 4211 uint8_t ft = vtype(tp_fn); 4218 4212 4219 4213 if (ft == T_UNDEF) return mkval(T_UNDEF, 0); ··· 4231 4225 4232 4226 static ant_value_t try_ordinary_to_primitive(ant_t *js, ant_value_t value, int hint) { 4233 4227 static const char *names[] = {"valueOf", "toString"}; 4234 - static const size_t lens[] = {7, 8}; 4235 4228 4236 4229 int first = (hint == 1); 4237 4230 ant_value_t result; 4238 4231 4239 4232 for (int i = 0; i < 2; i++) { 4240 4233 int idx = first ^ i; 4241 - if (js_try_call_method(js, value, names[idx], lens[idx], NULL, 0, &result)) 4234 + if (js_try_call_method(js, value, names[idx], NULL, 0, &result)) 4242 4235 if (is_err(result) || is_primitive(result)) return result; 4243 4236 } 4244 4237 ··· 5603 5596 ant_value_t obj = args[0]; 5604 5597 uint8_t t = vtype(obj); 5605 5598 5606 - if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) return get_prototype_for_type(js, t); 5599 + if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) 5600 + return get_prototype_for_type(js, t); 5607 5601 if (t == T_CFUNC) return get_prototype_for_type(js, t); 5608 - if (is_object_type(obj)) return get_proto(js, obj); 5602 + 5603 + if (is_object_type(obj)) { 5604 + if (is_proxy(obj)) return proxy_get_prototype_of(js, obj); 5605 + return get_proto(js, obj); 5606 + } 5609 5607 5610 5608 return js_mknull(); 5611 5609 } ··· 5688 5686 5689 5687 static ant_value_t legacy_accessor_this_obj(ant_t *js, ant_value_t this_val) { 5690 5688 uint8_t t = vtype(this_val); 5691 - if (t == T_UNDEF || t == T_NULL) { 5692 - return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert undefined or null to object"); 5693 - } 5689 + 5690 + if (t == T_UNDEF || t == T_NULL) return js_mkerr_typed( 5691 + js, JS_ERR_TYPE, "Cannot convert undefined or null to object" 5692 + ); 5693 + 5694 5694 if (t == T_CFUNC) return js_cfunc_promote(js, this_val); 5695 5695 if (t == T_OBJ || t == T_ARR || t == T_FUNC) return js_as_obj(this_val); 5696 + 5697 + if (t == T_STR || t == T_NUM || t == T_BOOL || t == T_BIGINT) { 5698 + ant_value_t boxed = builtin_Object(js, &this_val, 1); 5699 + if (is_err(boxed)) return boxed; 5700 + if (is_object_type(boxed)) return js_as_obj(boxed); 5701 + } 5702 + 5696 5703 return js_mkerr_typed(js, JS_ERR_TYPE, "Legacy accessor methods require an object receiver"); 5697 5704 } 5698 5705 ··· 5747 5754 return key_val; 5748 5755 } 5749 5756 5757 + if (is_proxy(js_as_obj(obj))) { 5758 + GC_ROOT_SAVE(root_mark, js); 5759 + GC_ROOT_PIN(js, obj); 5760 + GC_ROOT_PIN(js, key_val); 5761 + GC_ROOT_PIN(js, getter); 5762 + 5763 + ant_value_t desc = js_mkobj(js); 5764 + if (is_err(desc)) { 5765 + GC_ROOT_RESTORE(js, root_mark); 5766 + return desc; 5767 + } 5768 + 5769 + GC_ROOT_PIN(js, desc); 5770 + js_setprop(js, desc, js_mkstr(js, "get", 3), getter); 5771 + js_setprop(js, desc, js_mkstr(js, "enumerable", 10), js_true); 5772 + js_setprop(js, desc, js_mkstr(js, "configurable", 12), js_true); 5773 + 5774 + ant_value_t define_args[3] = { obj, key_val, desc }; 5775 + ant_value_t result = builtin_object_defineProperty(js, define_args, 3); 5776 + 5777 + GC_ROOT_RESTORE(js, root_mark); 5778 + if (is_err(result)) return result; 5779 + 5780 + return js_mkundef(); 5781 + } 5782 + 5750 5783 if (vtype(key_val) == T_SYMBOL) { 5751 5784 js_set_sym_getter_desc(js, js_as_obj(obj), key_val, getter, JS_DESC_E | JS_DESC_C); 5752 5785 } else js_set_getter_desc(js, js_as_obj(obj), key_str, (size_t)key_len, getter, JS_DESC_E | JS_DESC_C); ··· 5775 5808 return key_val; 5776 5809 } 5777 5810 5811 + if (is_proxy(js_as_obj(obj))) { 5812 + GC_ROOT_SAVE(root_mark, js); 5813 + GC_ROOT_PIN(js, obj); 5814 + GC_ROOT_PIN(js, key_val); 5815 + GC_ROOT_PIN(js, setter); 5816 + 5817 + ant_value_t desc = js_mkobj(js); 5818 + if (is_err(desc)) { 5819 + GC_ROOT_RESTORE(js, root_mark); 5820 + return desc; 5821 + } 5822 + 5823 + GC_ROOT_PIN(js, desc); 5824 + js_setprop(js, desc, js_mkstr(js, "set", 3), setter); 5825 + js_setprop(js, desc, js_mkstr(js, "enumerable", 10), js_true); 5826 + js_setprop(js, desc, js_mkstr(js, "configurable", 12), js_true); 5827 + 5828 + ant_value_t define_args[3] = { obj, key_val, desc }; 5829 + ant_value_t result = builtin_object_defineProperty(js, define_args, 3); 5830 + 5831 + GC_ROOT_RESTORE(js, root_mark); 5832 + if (is_err(result)) return result; 5833 + 5834 + return js_mkundef(); 5835 + } 5836 + 5778 5837 if (vtype(key_val) == T_SYMBOL) { 5779 5838 js_set_sym_setter_desc(js, js_as_obj(obj), key_val, setter, JS_DESC_E | JS_DESC_C); 5780 5839 } else js_set_setter_desc(js, js_as_obj(obj), key_str, (size_t)key_len, setter, JS_DESC_E | JS_DESC_C); ··· 5782 5841 return js_mkundef(); 5783 5842 } 5784 5843 5844 + static ant_value_t legacy_lookup_accessor_from_descriptor(ant_t *js, ant_value_t desc, bool want_getter) { 5845 + if (vtype(desc) != T_OBJ) return js_mkundef(); 5846 + ant_offset_t off = lkp_interned(js, desc, want_getter ? js->intern.get : js->intern.set, 3); 5847 + if (off == 0) return js_mkundef(); 5848 + return propref_load(js, off); 5849 + } 5850 + 5785 5851 static ant_value_t legacy_lookup_accessor(ant_t *js, ant_value_t this_val, ant_value_t key, bool want_getter) { 5786 5852 ant_value_t obj = legacy_accessor_this_obj(js, this_val); 5787 5853 if (is_err(obj)) return obj; ··· 5795 5861 return key_val; 5796 5862 } 5797 5863 5798 - for (ant_value_t cur = obj; is_object_type(cur); cur = get_proto(js, cur)) { 5864 + for (ant_value_t cur = obj; is_object_type(cur); ) { 5865 + if (is_proxy(cur)) { 5866 + ant_value_t desc = proxy_get_own_property_descriptor(js, cur, key_val); 5867 + if (is_err(desc)) return desc; 5868 + if (vtype(desc) == T_OBJ) return legacy_lookup_accessor_from_descriptor(js, desc, want_getter); 5869 + cur = proxy_get_prototype_of(js, cur); 5870 + if (is_err(cur)) return cur; 5871 + continue; 5872 + } 5873 + 5799 5874 prop_meta_t meta; 5800 5875 bool has_meta = (vtype(key_val) == T_SYMBOL) 5801 5876 ? lookup_symbol_prop_meta(cur, sym_off, &meta) ··· 5818 5893 } 5819 5894 if (lkp(js, cur, key_str, key_len) != 0) return js_mkundef(); 5820 5895 } 5896 + cur = get_proto(js, cur); 5821 5897 } 5822 5898 5823 5899 return js_mkundef(); ··· 5969 6045 } 5970 6046 5971 6047 ant_value_t as_obj = js_as_obj(obj); 6048 + if (is_proxy(as_obj)) { 6049 + ant_value_t proxy_result = proxy_define_property(js, as_obj, prop, descriptor); 6050 + if (is_err(proxy_result)) return proxy_result; 6051 + if (!js_truthy(js, proxy_result)) 6052 + return js_mkerr_typed(js, JS_ERR_TYPE, "'defineProperty' on proxy: trap returned falsy"); 6053 + return obj; 6054 + } 5972 6055 5973 6056 ant_offset_t prop_len = 0; 5974 6057 const char *prop_str = NULL; ··· 5982 6065 } else { 5983 6066 ant_offset_t prop_off = vstr(js, prop, &prop_len); 5984 6067 prop_str = (char *)(uintptr_t)(prop_off); 5985 - if (streq(prop_str, prop_len, STR_PROTO, STR_PROTO_LEN)) { 6068 + if (streq(prop_str, prop_len, STR_PROTO, STR_PROTO_LEN)) 5986 6069 return js_mkerr(js, "Cannot define " STR_PROTO " property"); 5987 - } 5988 6070 } 5989 6071 5990 6072 bool has_value = false, has_get = false, has_set = false; ··· 7945 8027 static ant_value_t builtin_array_sort(ant_t *js, ant_value_t *args, int nargs) { 7946 8028 ant_value_t arr = js->this_val; 7947 8029 ant_value_t compareFn = js_mkundef(); 8030 + 8031 + ant_value_t result = arr; 7948 8032 ant_value_t *vals = NULL, *keys = NULL, *temp_vals = NULL, *temp_keys = NULL; 7949 8033 ant_offset_t count = 0, undef_count = 0, len = 0; 8034 + 8035 + gc_temp_root_scope_t temp_scope = {0}; 8036 + bool temp_scope_active = false; 7950 8037 7951 8038 if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) 7952 8039 return js_mkerr(js, "sort called on non-array"); ··· 7956 8043 if (t == T_FUNC || t == T_CFUNC) compareFn = args[0]; 7957 8044 else if (t != T_UNDEF) return js_mkerr_typed(js, JS_ERR_TYPE, "compareFn must be a function or undefined"); 7958 8045 } 8046 + 8047 + gc_temp_root_scope_begin(js, &temp_scope); 8048 + temp_scope_active = true; 8049 + 8050 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_scope, arr))) goto oom; 8051 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_scope, compareFn))) goto oom; 7959 8052 7960 8053 len = get_array_length(js, arr); 7961 - if (len == 0) return arr; 8054 + if (len == 0) goto done; 7962 8055 7963 8056 ant_offset_t doff = get_dense_buf(arr); 7964 8057 if (doff) { ··· 7981 8074 } 7982 8075 } 7983 8076 if (count <= 1) goto writeback; 8077 + for (ant_offset_t i = 0; i < count; i++) { 8078 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_scope, vals[i]))) goto oom; 8079 + } 7984 8080 7985 8081 bool use_keys = (vtype(compareFn) == T_UNDEF); 7986 8082 if (use_keys) { ··· 7988 8084 if (!keys) goto oom; 7989 8085 for (ant_offset_t i = 0; i < count; i++) { 7990 8086 const char *s = js_tostring(js, vals[i]); 7991 - keys[i] = js_mkstr(js, s, strlen(s)); 8087 + ant_value_t key = js_mkstr(js, s, strlen(s)); 8088 + if (is_err(key)) { 8089 + result = key; 8090 + goto done; 8091 + } 8092 + keys[i] = key; 8093 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_scope, key))) goto oom; 7992 8094 } 7993 8095 } 7994 8096 ··· 8050 8152 for (ant_offset_t i = 0; i < undef_count; i++, out++) arr_set(js, arr, out, js_mkundef()); 8051 8153 for (; out < len; out++) arr_del(js, arr, out); 8052 8154 } 8155 + result = arr; 8156 + goto done; 8053 8157 8158 + oom: 8159 + result = js_mkerr(js, "out of memory"); 8160 + 8161 + done: 8162 + if (temp_scope_active) gc_temp_root_scope_end(&temp_scope); 8054 8163 free(temp_keys); 8055 8164 free(temp_vals); 8056 8165 free(keys); 8057 8166 free(vals); 8058 - return arr; 8059 8167 8060 - oom: 8061 - free(temp_keys); 8062 - free(temp_vals); 8063 - free(keys); 8064 - free(vals); 8065 - return js_mkerr(js, "out of memory"); 8168 + return result; 8066 8169 } 8067 8170 8068 8171 static ant_value_t builtin_array_splice(ant_t *js, ant_value_t *args, int nargs) { ··· 8184 8287 8185 8288 static ant_value_t builtin_array_copyWithin(ant_t *js, ant_value_t *args, int nargs) { 8186 8289 ant_value_t arr = js->this_val; 8290 + ant_value_t result = arr; 8291 + gc_temp_root_scope_t temp_roots = {0}; 8292 + 8293 + bool temp_roots_active = false; 8187 8294 if (vtype(arr) != T_ARR && vtype(arr) != T_OBJ) { 8188 8295 return js_mkerr(js, "copyWithin called on non-array"); 8189 8296 } ··· 8230 8337 } 8231 8338 8232 8339 ant_value_t *temp = (ant_value_t *)malloc(count * sizeof(ant_value_t)); 8340 + if (!temp) return js_mkerr(js, "out of memory"); 8341 + gc_temp_root_scope_begin(js, &temp_roots); 8342 + temp_roots_active = true; 8343 + 8344 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, arr))) goto oom; 8345 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, read_from))) goto oom; 8346 + 8233 8347 for (int i = 0; i < count; i++) { 8234 8348 char idxstr[16]; 8235 8349 size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)(start + i)); 8236 8350 ant_offset_t elem_off = lkp(js, read_from, idxstr, idxlen); 8237 8351 temp[i] = elem_off ? propref_load(js, elem_off) : js_mkundef(); 8352 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, temp[i]))) goto oom; 8238 8353 } 8239 8354 8240 8355 for (int i = 0; i < count; i++) { 8241 8356 char idxstr[16]; 8242 8357 size_t idxlen = uint_to_str(idxstr, sizeof(idxstr), (unsigned)(target + i)); 8243 8358 ant_value_t key = js_mkstr(js, idxstr, idxlen); 8244 - js_setprop(js, arr, key, temp[i]); 8359 + if (is_err(key)) { 8360 + result = key; 8361 + goto done; 8362 + } 8363 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, key))) goto oom; 8364 + ant_value_t set_result = js_setprop(js, arr, key, temp[i]); 8365 + if (is_err(set_result)) { 8366 + result = set_result; 8367 + goto done; 8368 + } 8245 8369 } 8246 - 8370 + 8371 + done: 8372 + if (temp_roots_active) gc_temp_root_scope_end(&temp_roots); 8247 8373 free(temp); 8248 - return arr; 8374 + return result; 8375 + 8376 + oom: 8377 + result = js_mkerr(js, "out of memory"); 8378 + goto done; 8249 8379 } 8250 8380 8251 8381 static ant_value_t builtin_array_toSorted(ant_t *js, ant_value_t *args, int nargs) { ··· 8344 8474 8345 8475 static ant_value_t builtin_array_toString(ant_t *js, ant_value_t *args, int nargs) { 8346 8476 ant_value_t arr = js->this_val; 8347 - 8348 8477 ant_value_t join_result; 8349 - if (js_try_call_method(js, arr, "join", 4, NULL, 0, &join_result)) { 8478 + 8479 + if (js_try_call_method(js, arr, "join", NULL, 0, &join_result)) { 8350 8480 if (is_err(join_result)) return join_result; 8351 8481 return join_result; 8352 8482 } ··· 12437 12567 12438 12568 ant_value_t target = data->target; 12439 12569 ant_value_t handler = data->handler; 12570 + ant_value_t prop_key = key_val; 12571 + 12572 + if (vtype(prop_key) != T_SYMBOL) { 12573 + if (is_object_type(prop_key)) { 12574 + prop_key = js_to_primitive(js, prop_key, 1); 12575 + if (is_err(prop_key)) return prop_key; 12576 + } 12577 + 12578 + if (vtype(prop_key) != T_SYMBOL) { 12579 + prop_key = js_tostring_val(js, prop_key); 12580 + if (is_err(prop_key)) return prop_key; 12581 + }} 12440 12582 12441 12583 ant_offset_t get_trap_off = vtype(handler) == T_OBJ 12442 12584 ? lkp_interned(js, handler, js->intern.get, 3) : 0; 12585 + 12443 12586 if (get_trap_off != 0) { 12444 - ant_value_t get_trap = propref_load(js, get_trap_off); 12445 - if (vtype(get_trap) == T_FUNC || vtype(get_trap) == T_CFUNC) { 12446 - ant_value_t args[3] = { target, key_val, proxy }; 12447 - return sv_vm_call(js->vm, js, get_trap, js_mkundef(), args, 3, NULL, false); 12448 - } 12449 - } 12587 + ant_value_t get_trap = propref_load(js, get_trap_off); 12588 + if (vtype(get_trap) == T_FUNC || vtype(get_trap) == T_CFUNC) { 12589 + ant_value_t args[3] = { target, prop_key, proxy }; 12590 + return sv_vm_call(js->vm, js, get_trap, js_mkundef(), args, 3, NULL, false); 12591 + }} 12450 12592 12451 - if (vtype(key_val) == T_SYMBOL) { 12452 - ant_offset_t off = lkp_sym_proto(js, target, (ant_offset_t)vdata(key_val)); 12593 + if (vtype(prop_key) == T_SYMBOL) { 12594 + ant_offset_t off = lkp_sym_proto(js, target, (ant_offset_t)vdata(prop_key)); 12453 12595 return off != 0 ? propref_load(js, off) : js_mkundef(); 12454 12596 } 12455 12597 12456 - return proxy_get(js, proxy, "", 0); 12598 + ant_offset_t key_len = 0; 12599 + ant_offset_t key_off = vstr(js, prop_key, &key_len); 12600 + const char *key_ptr = (const char *)(uintptr_t)key_off; 12601 + 12602 + return proxy_get(js, proxy, key_ptr, (size_t)key_len); 12603 + } 12604 + 12605 + static ant_value_t proxy_get_prototype_of(ant_t *js, ant_value_t proxy) { 12606 + ant_proxy_state_t *data = get_proxy_data(proxy); 12607 + if (!data) return js_mknull(); 12608 + 12609 + if (data->revoked) 12610 + return throw_proxy_error(js, "Cannot perform 'getPrototypeOf' on a proxy that has been revoked"); 12611 + 12612 + ant_value_t target = data->target; 12613 + ant_value_t handler = data->handler; 12614 + ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "getPrototypeOf", 14) : 0; 12615 + 12616 + if (trap_off != 0) { 12617 + ant_value_t trap = propref_load(js, trap_off); 12618 + if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { 12619 + ant_value_t args[1] = { target }; 12620 + ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), args, 1, NULL, false); 12621 + if (is_err(result)) return result; 12622 + if (is_object_type(result) || vtype(result) == T_NULL) return result; 12623 + return js_mkerr_typed(js, JS_ERR_TYPE, "'getPrototypeOf' on proxy: trap returned neither object nor null"); 12624 + }} 12625 + 12626 + return get_proto(js, target); 12457 12627 } 12458 12628 12459 12629 static ant_value_t proxy_has_val(ant_t *js, ant_value_t proxy, ant_value_t key_val) { ··· 12463 12633 12464 12634 ant_value_t target = data->target; 12465 12635 ant_value_t handler = data->handler; 12636 + ant_value_t prop_key = key_val; 12637 + 12638 + if (vtype(prop_key) != T_SYMBOL) { 12639 + if (is_object_type(prop_key)) { 12640 + prop_key = js_to_primitive(js, prop_key, 1); 12641 + if (is_err(prop_key)) return prop_key; 12642 + } 12643 + 12644 + if (vtype(prop_key) != T_SYMBOL) { 12645 + prop_key = js_tostring_val(js, prop_key); 12646 + if (is_err(prop_key)) return prop_key; 12647 + }} 12466 12648 12467 12649 ant_offset_t has_trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "has", 3) : 0; 12468 12650 if (has_trap_off != 0) { 12469 - ant_value_t has_trap = propref_load(js, has_trap_off); 12470 - if (vtype(has_trap) == T_FUNC || vtype(has_trap) == T_CFUNC) { 12471 - ant_value_t args[2] = { target, key_val }; 12472 - return sv_vm_call(js->vm, js, has_trap, js_mkundef(), args, 2, NULL, false); 12473 - } 12474 - } 12651 + ant_value_t has_trap = propref_load(js, has_trap_off); 12652 + if (vtype(has_trap) == T_FUNC || vtype(has_trap) == T_CFUNC) { 12653 + ant_value_t args[2] = { target, prop_key }; 12654 + return sv_vm_call(js->vm, js, has_trap, js_mkundef(), args, 2, NULL, false); 12655 + }} 12475 12656 12476 - if (vtype(key_val) == T_SYMBOL) { 12477 - ant_offset_t off = lkp_sym_proto(js, target, (ant_offset_t)vdata(key_val)); 12657 + if (vtype(prop_key) == T_SYMBOL) { 12658 + ant_offset_t off = lkp_sym_proto(js, target, (ant_offset_t)vdata(prop_key)); 12478 12659 return js_bool(off != 0); 12479 12660 } 12480 - return js_false; 12661 + 12662 + ant_offset_t key_len = 0; 12663 + ant_offset_t key_off = vstr(js, prop_key, &key_len); 12664 + const char *key_ptr = (const char *)(uintptr_t)key_off; 12665 + 12666 + return proxy_has(js, proxy, key_ptr, (size_t)key_len); 12481 12667 } 12482 12668 12483 12669 static ant_value_t proxy_get_own_property_descriptor(ant_t *js, ant_value_t proxy, ant_value_t key_val) { ··· 12492 12678 ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "getOwnPropertyDescriptor", 24) : 0; 12493 12679 12494 12680 if (trap_off != 0) { 12495 - ant_value_t trap = propref_load(js, trap_off); 12496 - if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { 12497 - ant_value_t args[2] = { target, key_val }; 12498 - ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), args, 2, NULL, false); 12499 - if (is_err(result)) return result; 12500 - if (vtype(result) == T_UNDEF || vtype(result) == T_OBJ) return result; 12501 - return js_mkerr_typed(js, JS_ERR_TYPE, "'getOwnPropertyDescriptor' on proxy: trap returned neither object nor undefined"); 12502 - } 12503 - } 12681 + ant_value_t trap = propref_load(js, trap_off); 12682 + if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { 12683 + ant_value_t args[2] = { target, key_val }; 12684 + ant_value_t result = sv_vm_call(js->vm, js, trap, js_mkundef(), args, 2, NULL, false); 12685 + if (is_err(result)) return result; 12686 + if (vtype(result) == T_UNDEF || vtype(result) == T_OBJ) return result; 12687 + return js_mkerr_typed(js, JS_ERR_TYPE, "'getOwnPropertyDescriptor' on proxy: trap returned neither object nor undefined"); 12688 + }} 12504 12689 12505 12690 ant_value_t args[2] = { target, key_val }; 12506 12691 return builtin_object_getOwnPropertyDescriptor(js, args, 2); ··· 12512 12697 return js_bool(vtype(desc) != T_UNDEF); 12513 12698 } 12514 12699 12700 + static ant_value_t proxy_define_property(ant_t *js, ant_value_t proxy, ant_value_t key_val, ant_value_t descriptor) { 12701 + ant_proxy_state_t *data = get_proxy_data(proxy); 12702 + if (!data) return js_false; 12703 + if (data->revoked) 12704 + return throw_proxy_error(js, "Cannot perform 'defineProperty' on a proxy that has been revoked"); 12705 + 12706 + ant_value_t target = data->target; 12707 + ant_value_t handler = data->handler; 12708 + ant_offset_t trap_off = vtype(handler) == T_OBJ ? lkp(js, handler, "defineProperty", 14) : 0; 12709 + 12710 + if (trap_off != 0) { 12711 + ant_value_t trap = propref_load(js, trap_off); 12712 + if (vtype(trap) == T_FUNC || vtype(trap) == T_CFUNC) { 12713 + ant_value_t args[3] = { target, key_val, descriptor }; 12714 + return sv_vm_call(js->vm, js, trap, js_mkundef(), args, 3, NULL, false); 12715 + }} 12716 + 12717 + ant_value_t args[3] = { target, key_val, descriptor }; 12718 + ant_value_t result = builtin_object_defineProperty(js, args, 3); 12719 + if (is_err(result)) return result; 12720 + return js_true; 12721 + } 12722 + 12515 12723 static ant_value_t proxy_delete_val(ant_t *js, ant_value_t proxy, ant_value_t key_val) { 12516 12724 ant_proxy_state_t *data = get_proxy_data(proxy); 12517 12725 if (!data) return js_true; ··· 12531 12739 ant_offset_t prop_off = lkp_sym(js, target, (ant_offset_t)vdata(key_val)); 12532 12740 if (prop_off != 0 && is_nonconfig_prop(js, prop_off)) 12533 12741 return js_mkerr_typed(js, JS_ERR_TYPE, "'deleteProperty' on proxy: trap returned truthy for non-configurable property"); 12534 - } 12535 - return result; 12742 + } return result; 12536 12743 } 12537 12744 } 12538 - 12539 - if (vtype(key_val) == T_SYMBOL) { 12540 - return js_delete_sym_prop(js, target, key_val); 12541 - } 12745 + if (vtype(key_val) == T_SYMBOL) return js_delete_sym_prop(js, target, key_val); 12542 12746 return js_true; 12543 12747 } 12544 12748 ··· 13136 13340 } 13137 13341 13138 13342 void js_destroy(ant_t *js) { 13139 - if (js == NULL) return; 13140 - 13343 + if (js == NULL) return; 13141 13344 if (js->vm) { 13142 13345 sv_vm_destroy(js->vm); 13143 13346 js->vm = NULL; ··· 13159 13362 js->permanent_objects = NULL; 13160 13363 13161 13364 cleanup_buffer_module(); 13365 + cleanup_atomics_module(js); 13366 + 13162 13367 fixed_arena_destroy(&js->obj_arena); 13163 13368 fixed_arena_destroy(&js->closure_arena); 13164 13369 fixed_arena_destroy(&js->upvalue_arena); ··· 13186 13391 js_pool_destroy(&js->pool.permanent); 13187 13392 13188 13393 js_class_pool_destroy(&js->pool.bigint); 13189 - js_class_pool_destroy(&js->pool.string); 13394 + js_string_pool_destroy(&js->pool.string); 13190 13395 13191 13396 destroy_runtime(js); 13192 13397 if (js->owns_mem) free(js);
+2 -2
src/gc/gc.c
··· 91 91 js->old_live_count = js->obj_arena.live_count; 92 92 js->minor_gc_count = 0; 93 93 94 - ant_pool_stats_t pool_stats = js_class_pool_stats(&js->pool.string); 95 - js->gc_pool_last_live = pool_stats.used; 94 + ant_string_pool_stats_t pool_stats = js_string_pool_stats(&js->pool.string); 95 + js->gc_pool_last_live = pool_stats.total.used; 96 96 js->gc_pool_alloc = 0; 97 97 98 98 gc_adapt_major_interval(live_before, js->obj_arena.live_count);
+187 -45
src/gc/strings.c
··· 1 1 #include "internal.h" 2 2 #include "gc/strings.h" 3 - #include "pool.h" 4 3 5 4 #include <stdlib.h> 6 5 #include <string.h> ··· 10 9 size_t n_slots; 11 10 } gc_bitmap_t; 12 11 12 + typedef struct { 13 + uintptr_t base; 14 + uintptr_t end; 15 + ant_pool_block_t *block; 16 + ant_pool_bucket_t *bucket; 17 + size_t stride; 18 + gc_bitmap_t bitmap; 19 + } gc_block_mark_t; 20 + 21 + typedef struct { 22 + uintptr_t ptr; 23 + ant_large_string_alloc_t *alloc; 24 + } gc_large_string_mark_t; 25 + 26 + static gc_block_mark_t *g_string_marks = NULL; 27 + static int g_string_mark_count = 0; 28 + static int g_string_mark_cap = 0; 29 + 30 + static gc_large_string_mark_t *g_large_string_marks = NULL; 31 + static int g_large_string_mark_count = 0; 32 + static int g_large_string_mark_cap = 0; 33 + 13 34 static gc_bitmap_t bitmap_alloc(size_t n_slots) { 14 - size_t nbytes = (n_slots + 7) / 8; 35 + size_t nbytes = (n_slots + 7u) / 8u; 15 36 uint8_t *bits = calloc(1, nbytes); 16 37 return (gc_bitmap_t){ .bits = bits, .n_slots = n_slots }; 17 38 } 18 39 19 40 static inline void bitmap_set(gc_bitmap_t *bm, size_t idx) { 20 - if (idx < bm->n_slots && bm->bits) bm->bits[idx / 8] |= (1u << (idx % 8)); 41 + if (idx < bm->n_slots && bm->bits) bm->bits[idx / 8u] |= (uint8_t)(1u << (idx % 8u)); 21 42 } 22 43 23 44 static inline bool bitmap_get(const gc_bitmap_t *bm, size_t idx) { 24 45 if (idx >= bm->n_slots || !bm->bits) return false; 25 - return (bm->bits[idx / 8] >> (idx % 8)) & 1u; 46 + return ((bm->bits[idx / 8u] >> (idx % 8u)) & 1u) != 0; 26 47 } 27 48 28 49 static inline void bitmap_free(gc_bitmap_t *bm) { ··· 31 52 bm->n_slots = 0; 32 53 } 33 54 34 - typedef struct { 35 - uintptr_t base; 36 - uintptr_t end; 37 - ant_pool_block_t *block; 38 - ant_pool_bucket_t *bucket; 39 - size_t stride; 40 - gc_bitmap_t bitmap; 41 - } gc_block_mark_t; 55 + static int pooled_mark_cmp(const void *a, const void *b) { 56 + const gc_block_mark_t *ma = (const gc_block_mark_t *)a; 57 + const gc_block_mark_t *mb = (const gc_block_mark_t *)b; 58 + if (ma->base < mb->base) return -1; 59 + if (ma->base > mb->base) return 1; 60 + return 0; 61 + } 42 62 43 - static gc_block_mark_t *g_string_marks = NULL; 44 - static int g_string_mark_count = 0; 45 - static int g_string_mark_cap = 0; 63 + static int large_mark_cmp(const void *a, const void *b) { 64 + const gc_large_string_mark_t *ma = (const gc_large_string_mark_t *)a; 65 + const gc_large_string_mark_t *mb = (const gc_large_string_mark_t *)b; 66 + if (ma->ptr < mb->ptr) return -1; 67 + if (ma->ptr > mb->ptr) return 1; 68 + return 0; 69 + } 46 70 47 71 static void collect_bucket_blocks(ant_pool_bucket_t *bucket) { 48 72 if (!bucket || bucket->slot_stride == 0) return; ··· 51 75 for (ant_pool_block_t *b = bucket->head; b; b = b->next) { 52 76 size_t n_slots = b->used / stride; 53 77 if (n_slots == 0) continue; 54 - 78 + 55 79 if (g_string_mark_count >= g_string_mark_cap) { 56 80 int cap = g_string_mark_cap ? g_string_mark_cap * 2 : 32; 57 81 g_string_marks = realloc(g_string_marks, (size_t)cap * sizeof(gc_block_mark_t)); 58 82 g_string_mark_cap = cap; 59 83 } 60 - 84 + 61 85 gc_block_mark_t *m = &g_string_marks[g_string_mark_count++]; 62 86 m->base = (uintptr_t)b->data; 63 87 m->end = m->base + b->used; ··· 68 92 } 69 93 } 70 94 71 - static int mark_cmp(const void *a, const void *b) { 72 - const gc_block_mark_t *ma = (const gc_block_mark_t *)a; 73 - const gc_block_mark_t *mb = (const gc_block_mark_t *)b; 74 - if (ma->base < mb->base) return -1; 75 - if (ma->base > mb->base) return 1; 76 - return 0; 95 + static void collect_large_strings(ant_large_string_space_t *space) { 96 + if (!space) return; 97 + 98 + for (ant_large_string_alloc_t *cur = space->live; cur; cur = cur->next) { 99 + cur->marked = 0; 100 + 101 + if (g_large_string_mark_count >= g_large_string_mark_cap) { 102 + int cap = g_large_string_mark_cap ? g_large_string_mark_cap * 2 : 32; 103 + g_large_string_marks = realloc(g_large_string_marks, (size_t)cap * sizeof(gc_large_string_mark_t)); 104 + g_large_string_mark_cap = cap; 105 + } 106 + 107 + g_large_string_marks[g_large_string_mark_count++] = (gc_large_string_mark_t){ 108 + .ptr = (uintptr_t)large_string_flat_ptr(cur), 109 + .alloc = cur, 110 + }; 111 + } 112 + } 113 + 114 + static inline void unlink_block(ant_pool_bucket_t *bucket, ant_pool_block_t *block) { 115 + if (block->prev) block->prev->next = block->next; 116 + else bucket->head = block->next; 117 + if (block->next) block->next->prev = block->prev; 118 + } 119 + 120 + static inline void large_string_unlink(ant_large_string_alloc_t **head, ant_large_string_alloc_t *alloc) { 121 + if (!head || !alloc) return; 122 + if (alloc->prev) alloc->prev->next = alloc->next; 123 + else *head = alloc->next; 124 + if (alloc->next) alloc->next->prev = alloc->prev; 125 + alloc->next = NULL; 126 + alloc->prev = NULL; 127 + } 128 + 129 + static inline void large_string_push_front(ant_large_string_alloc_t **head, ant_large_string_alloc_t *alloc) { 130 + if (!head || !alloc) return; 131 + alloc->prev = NULL; 132 + alloc->next = *head; 133 + if (*head) (*head)->prev = alloc; 134 + *head = alloc; 135 + } 136 + 137 + static void large_string_promote_quarantine(ant_large_string_space_t *space) { 138 + if (!space) return; 139 + ant_large_string_alloc_t *cur = space->quarantine; 140 + while (cur) { 141 + ant_large_string_alloc_t *next = cur->next; 142 + if (cur->quarantine_epoch < space->gc_epoch) { 143 + large_string_unlink(&space->quarantine, cur); 144 + large_string_push_front(&space->reusable, cur); 145 + } cur = next; 146 + } 147 + } 148 + 149 + static size_t large_string_reusable_budget(const ant_large_string_space_t *space) { 150 + size_t live_capacity = 0; 151 + for (const ant_large_string_alloc_t *cur = space ? space->live : NULL; cur; cur = cur->next) { 152 + live_capacity += cur->capacity; 153 + } 154 + 155 + size_t budget = live_capacity / 4u; 156 + size_t min_budget = 4u * 1024u * 1024u; 157 + size_t max_budget = 64u * 1024u * 1024u; 158 + 159 + if (budget < min_budget) budget = min_budget; 160 + if (budget > max_budget) budget = max_budget; 161 + 162 + return budget; 163 + } 164 + 165 + static void large_string_trim_reusable(ant_large_string_space_t *space) { 166 + if (!space) return; 167 + size_t reusable_capacity = 0; 168 + 169 + for (ant_large_string_alloc_t *cur = space->reusable; cur; cur = cur->next) { 170 + reusable_capacity += cur->capacity; 171 + } 172 + 173 + size_t budget = large_string_reusable_budget(space); 174 + ant_large_string_alloc_t *cur = space->reusable; 175 + 176 + while (cur && reusable_capacity > budget) { 177 + ant_large_string_alloc_t *next = cur->next; 178 + reusable_capacity -= cur->capacity; 179 + large_string_unlink(&space->reusable, cur); 180 + ant_arena_free(cur, cur->alloc_size); 181 + cur = next; 182 + } 77 183 } 78 184 79 185 void gc_strings_begin(ant_t *js) { 80 186 g_string_mark_count = 0; 81 - ant_class_pool_t *pool = &js->pool.string; 187 + g_large_string_mark_count = 0; 82 188 189 + ant_string_pool_t *pool = &js->pool.string; 83 190 for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) { 84 191 pool->classes[i].slot_free = NULL; 85 192 collect_bucket_blocks(&pool->classes[i]); 86 193 } 87 194 88 - if (g_string_mark_count > 1) 89 - qsort(g_string_marks, (size_t)g_string_mark_count, sizeof(gc_block_mark_t), mark_cmp); 90 - } 195 + pool->large.gc_epoch++; 196 + if (pool->large.gc_epoch == 0) pool->large.gc_epoch = 1; 197 + 198 + large_string_promote_quarantine(&pool->large); 199 + collect_large_strings(&pool->large); 91 200 201 + if (g_string_mark_count > 1) qsort(g_string_marks, 202 + (size_t)g_string_mark_count, sizeof(gc_block_mark_t), pooled_mark_cmp 203 + ); 204 + 205 + if (g_large_string_mark_count > 1) qsort(g_large_string_marks, 206 + (size_t)g_large_string_mark_count, sizeof(gc_large_string_mark_t), large_mark_cmp 207 + ); 208 + } 92 209 93 210 void gc_strings_mark(ant_t *js, const void *ptr) { 94 - if (!ptr || g_string_mark_count == 0) return; 211 + if (!ptr) return; 95 212 uintptr_t p = (uintptr_t)ptr; 96 213 97 - int lo = 0, hi = g_string_mark_count - 1; 98 - while (lo <= hi) { 214 + if (g_string_mark_count > 0) { 215 + int lo = 0; 216 + int hi = g_string_mark_count - 1; 217 + 218 + while (lo <= hi) { 99 219 int mid = lo + (hi - lo) / 2; 100 220 gc_block_mark_t *m = &g_string_marks[mid]; 221 + 101 222 if (p < m->base) hi = mid - 1; 102 223 else if (p >= m->end) lo = mid + 1; 103 224 else { 104 225 size_t offset = p - m->base; 105 226 if (offset % m->stride == 0) bitmap_set(&m->bitmap, offset / m->stride); 106 227 return; 107 - } 228 + }} 108 229 } 109 - } 110 230 111 - 112 - static void unlink_block(ant_pool_bucket_t *bucket, ant_pool_block_t *block) { 113 - if (block->prev) block->prev->next = block->next; 114 - else bucket->head = block->next; 115 - if (block->next) block->next->prev = block->prev; 231 + if (g_large_string_mark_count == 0) return; 232 + int lo = 0; int hi = g_large_string_mark_count - 1; 233 + 234 + while (lo <= hi) { 235 + int mid = lo + (hi - lo) / 2; 236 + gc_large_string_mark_t *m = &g_large_string_marks[mid]; 237 + 238 + if (p < m->ptr) hi = mid - 1; 239 + else if (p > m->ptr) lo = mid + 1; 240 + else { 241 + m->alloc->marked = 1; 242 + return; 243 + }} 116 244 } 117 245 118 246 void gc_strings_sweep(ant_t *js) { ··· 122 250 123 251 bool any_live = false; 124 252 size_t n_slots = m->bitmap.n_slots; 253 + 125 254 for (size_t j = 0; j < n_slots; j++) { 126 - if (bitmap_get(&m->bitmap, j)) { any_live = true; break; } 127 - } 255 + if (bitmap_get(&m->bitmap, j)) { 256 + any_live = true; 257 + break; 258 + }} 128 259 129 260 if (!any_live && bucket) { 130 261 unlink_block(bucket, m->block); ··· 137 268 } else if (any_live && bucket && m->stride >= sizeof(void *)) { 138 269 uintptr_t base = m->base; 139 270 for (size_t j = 0; j < n_slots; j++) { 140 - if (!bitmap_get(&m->bitmap, j)) { 271 + if (bitmap_get(&m->bitmap, j)) continue; 141 272 void *slot = (void *)(base + j * m->stride); 142 273 void *old_head = bucket->slot_free; 143 274 memcpy(slot, &old_head, sizeof(void *)); 144 275 bucket->slot_free = slot; 145 - }} 276 + } 146 277 } 147 278 148 279 bitmap_free(&m->bitmap); 149 280 } 150 281 151 - ant_class_pool_t *pool = &js->pool.string; 282 + ant_large_string_space_t *space = &js->pool.string.large; 283 + ant_large_string_alloc_t *cur = space->live; 284 + while (cur) { 285 + ant_large_string_alloc_t *next = cur->next; 286 + if (cur->marked) cur->marked = 0; else { 287 + large_string_unlink(&space->live, cur); 288 + cur->quarantine_epoch = space->gc_epoch; 289 + large_string_push_front(&space->quarantine, cur); 290 + } cur = next; 291 + } 292 + 293 + large_string_trim_reusable(space); 294 + 152 295 for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) { 153 - ant_pool_bucket_t *bucket = &pool->classes[i]; 296 + ant_pool_bucket_t *bucket = &js->pool.string.classes[i]; 154 297 ant_pool_block_t *f = bucket->free_head; 155 298 int kept = 0; 156 299 ··· 166 309 } 167 310 168 311 f = bucket->free_head; 169 - for (int k = 0; k < kept && f; k++) f = pool_free_next(f); 170 312 if (kept > 0) { 171 - f = bucket->free_head; 172 313 for (int k = 1; k < kept && f; k++) f = pool_free_next(f); 173 314 if (f) pool_free_set_next(f, NULL); 174 315 } else bucket->free_head = NULL; 175 316 } 176 317 177 318 g_string_mark_count = 0; 319 + g_large_string_mark_count = 0; 178 320 }
+247 -8
src/modules/atomics.c
··· 5 5 #include <pthread.h> 6 6 #include <time.h> 7 7 #include <errno.h> 8 + #include <math.h> 9 + #include <uv.h> 8 10 9 11 #include "ant.h" 10 12 #include "errors.h" 11 13 #include "internal.h" 12 14 #include "runtime.h" 13 15 16 + #include "gc/modules.h" 14 17 #include "modules/buffer.h" 15 18 #include "modules/atomics.h" 16 19 #include "modules/symbol.h" 20 + #include "modules/timer.h" 21 + 22 + typedef enum { 23 + ASYNC_WAIT_SETTLE_NONE = 0, 24 + ASYNC_WAIT_SETTLE_OK, 25 + ASYNC_WAIT_SETTLE_TIMED_OUT, 26 + } async_wait_settle_t; 27 + 28 + typedef struct AsyncWaitEntry { 29 + ant_t *js; 30 + ant_value_t promise; 31 + ArrayBufferData *buffer; 32 + int32_t *address; 33 + uv_timer_t timer; 34 + uv_async_t async; 35 + bool timer_initialized; 36 + bool async_initialized; 37 + uint8_t pending_handles; 38 + _Atomic int settle_state; 39 + _Atomic bool settle_drain_microtasks; 40 + struct AsyncWaitEntry *next; 41 + struct AsyncWaitEntry *prev; 42 + } AsyncWaitEntry; 17 43 18 44 static WaitQueue global_wait_queue; 45 + static AsyncWaitEntry *async_waiters_head = NULL; 46 + 19 47 static pthread_once_t wait_queue_init_once = PTHREAD_ONCE_INIT; 48 + static pthread_mutex_t async_waiters_lock = PTHREAD_MUTEX_INITIALIZER; 49 + 50 + static inline bool async_waiter_is_linked_locked(AsyncWaitEntry *entry) { 51 + return entry && (entry == async_waiters_head || entry->next || entry->prev); 52 + } 20 53 21 54 static void init_wait_queue(void) { 22 55 wait_queue_init(&global_wait_queue); ··· 27 60 pthread_mutex_init(&queue->lock, NULL); 28 61 } 29 62 63 + static void async_waiter_add_locked(AsyncWaitEntry *entry) { 64 + entry->next = async_waiters_head; 65 + entry->prev = NULL; 66 + if (async_waiters_head) async_waiters_head->prev = entry; 67 + async_waiters_head = entry; 68 + } 69 + 70 + static void async_waiter_remove_locked(AsyncWaitEntry *entry) { 71 + if (entry->prev) entry->prev->next = entry->next; 72 + else async_waiters_head = entry->next; 73 + if (entry->next) entry->next->prev = entry->prev; 74 + entry->next = NULL; 75 + entry->prev = NULL; 76 + } 77 + 78 + static void async_waiter_release_buffer(AsyncWaitEntry *entry) { 79 + if (!entry || !entry->buffer) return; 80 + free_array_buffer_data(entry->buffer); 81 + entry->buffer = NULL; 82 + } 83 + 84 + static void async_waiter_release_handle(AsyncWaitEntry *entry) { 85 + if (!entry) return; 86 + if (entry->pending_handles > 0) entry->pending_handles--; 87 + if (entry->pending_handles == 0) { 88 + async_waiter_release_buffer(entry); 89 + free(entry); 90 + } 91 + } 92 + 93 + static void async_waiter_close_cb(uv_handle_t *handle) { 94 + AsyncWaitEntry *entry = handle ? handle->data : NULL; 95 + async_waiter_release_handle(entry); 96 + } 97 + 98 + static void async_waiter_close_handles(AsyncWaitEntry *entry) { 99 + bool closed = false; 100 + 101 + if (entry->timer_initialized && !uv_is_closing((uv_handle_t *)&entry->timer)) { 102 + uv_timer_stop(&entry->timer); 103 + uv_close((uv_handle_t *)&entry->timer, async_waiter_close_cb); 104 + entry->timer_initialized = false; 105 + closed = true; 106 + } 107 + 108 + if (entry->async_initialized && !uv_is_closing((uv_handle_t *)&entry->async)) { 109 + uv_close((uv_handle_t *)&entry->async, async_waiter_close_cb); 110 + entry->async_initialized = false; 111 + closed = true; 112 + } 113 + 114 + if (!closed) { 115 + async_waiter_release_buffer(entry); 116 + free(entry); 117 + } 118 + } 119 + 120 + static void async_waiter_queue_settle(AsyncWaitEntry *entry, async_wait_settle_t state, bool drain_microtasks) { 121 + if (!entry) return; 122 + atomic_store(&entry->settle_drain_microtasks, drain_microtasks); 123 + atomic_store(&entry->settle_state, state); 124 + if (entry->async_initialized) uv_async_send(&entry->async); 125 + } 126 + 127 + static void async_waiter_async_cb(uv_async_t *handle) { 128 + AsyncWaitEntry *entry = handle ? handle->data : NULL; 129 + if (!entry || !entry->js) return; 130 + 131 + async_wait_settle_t state = (async_wait_settle_t)atomic_exchange(&entry->settle_state, ASYNC_WAIT_SETTLE_NONE); 132 + if (state == ASYNC_WAIT_SETTLE_NONE) return; 133 + 134 + const char *result = state == ASYNC_WAIT_SETTLE_OK ? "ok" : "timed-out"; 135 + js_resolve_promise(entry->js, entry->promise, js_mkstr(entry->js, result, strlen(result))); 136 + if (atomic_load(&entry->settle_drain_microtasks)) 137 + js_maybe_drain_microtasks_after_async_settle(entry->js); 138 + 139 + async_waiter_close_handles(entry); 140 + } 141 + 142 + static void async_waiter_timeout_cb(uv_timer_t *timer) { 143 + AsyncWaitEntry *entry = timer ? timer->data : NULL; 144 + if (!entry) return; 145 + 146 + pthread_mutex_lock(&async_waiters_lock); 147 + bool linked = async_waiter_is_linked_locked(entry); 148 + if (linked) async_waiter_remove_locked(entry); 149 + pthread_mutex_unlock(&async_waiters_lock); 150 + 151 + if (linked) async_waiter_queue_settle(entry, ASYNC_WAIT_SETTLE_TIMED_OUT, true); 152 + } 153 + 154 + static int async_waiter_notify(int32_t *address, int count) { 155 + int notified = 0; 156 + AsyncWaitEntry *ready = NULL; 157 + 158 + pthread_mutex_lock(&async_waiters_lock); 159 + AsyncWaitEntry *current = async_waiters_head; 160 + while (current && (count == -1 || notified < count)) { 161 + AsyncWaitEntry *next = current->next; 162 + if (current->address == address) { 163 + async_waiter_remove_locked(current); 164 + current->next = ready; 165 + current->prev = NULL; 166 + ready = current; 167 + notified++; 168 + } current = next; 169 + } 170 + 171 + pthread_mutex_unlock(&async_waiters_lock); 172 + while (ready) { 173 + AsyncWaitEntry *next = ready->next; 174 + ready->next = NULL; 175 + async_waiter_queue_settle(ready, ASYNC_WAIT_SETTLE_OK, false); 176 + ready = next; 177 + } 178 + 179 + return notified; 180 + } 181 + 30 182 void wait_queue_cleanup(WaitQueue *queue) { 31 183 pthread_mutex_lock(&queue->lock); 32 184 WaitQueueEntry *current = queue->head; ··· 74 226 pthread_cond_signal(&current->cond); 75 227 pthread_mutex_unlock(&current->mutex); 76 228 notified++; 77 - } 78 - current = current->next; 229 + } current = current->next; 79 230 } 80 231 81 232 pthread_mutex_unlock(&queue->lock); 233 + if (count == -1 || notified < count) 234 + notified += async_waiter_notify(address, count == -1 ? -1 : count - notified); 235 + 82 236 return notified; 237 + } 238 + 239 + void cleanup_atomics_module(ant_t *js) { 240 + if (!js) return; 241 + AsyncWaitEntry *removed = NULL; 242 + 243 + pthread_mutex_lock(&async_waiters_lock); 244 + AsyncWaitEntry *current = async_waiters_head; 245 + 246 + while (current) { 247 + AsyncWaitEntry *next = current->next; 248 + if (current->js == js) { 249 + async_waiter_remove_locked(current); 250 + current->next = removed; 251 + current->prev = NULL; 252 + removed = current; 253 + } current = next; 254 + } 255 + 256 + pthread_mutex_unlock(&async_waiters_lock); 257 + while (removed) { 258 + AsyncWaitEntry *next = removed->next; 259 + removed->next = NULL; 260 + removed->prev = NULL; 261 + removed->js = NULL; 262 + removed->promise = js_mkundef(); 263 + async_waiter_release_buffer(removed); 264 + async_waiter_close_handles(removed); 265 + removed = next; 266 + } 83 267 } 84 268 85 269 static bool get_atomic_array_data(ant_t *js, ant_value_t this_val, TypedArrayData **out_data, uint8_t **out_ptr) { ··· 782 966 int32_t expected_value = (int32_t)js_getnum(args[2]); 783 967 _Atomic int32_t *atomic_ptr = (_Atomic int32_t *)(ptr + index * 4); 784 968 int32_t current_value = atomic_load(atomic_ptr); 969 + double timeout_ms = HUGE_VAL; 970 + 971 + if (nargs > 3 && vtype(args[3]) == T_NUM) { 972 + timeout_ms = js_getnum(args[3]); 973 + if (isnan(timeout_ms)) timeout_ms = HUGE_VAL; 974 + else if (timeout_ms < 0) timeout_ms = 0; 975 + } 785 976 786 977 ant_value_t result_obj = js_mkobj(js); 787 978 if (current_value != expected_value) { ··· 790 981 return result_obj; 791 982 } 792 983 984 + if (timeout_ms == 0) { 985 + js_set(js, result_obj, "async", js_false); 986 + js_set(js, result_obj, "value", js_mkstr(js, "timed-out", 9)); 987 + return result_obj; 988 + } 989 + 793 990 ant_value_t promise = js_mkpromise(js); 991 + AsyncWaitEntry *entry = calloc(1, sizeof(*entry)); 992 + if (!entry) return js_mkerr(js, "Out of memory"); 993 + 994 + entry->js = js; 995 + entry->promise = promise; 996 + entry->buffer = ta_data->buffer; 997 + entry->buffer->ref_count++; 998 + entry->address = (int32_t *)atomic_ptr; 999 + atomic_store(&entry->settle_state, ASYNC_WAIT_SETTLE_NONE); 1000 + atomic_store(&entry->settle_drain_microtasks, false); 1001 + 1002 + if (uv_async_init(uv_default_loop(), &entry->async, async_waiter_async_cb) != 0) { 1003 + async_waiter_close_handles(entry); 1004 + return js_mkerr(js, "Failed to initialize Atomics.waitAsync notifier"); 1005 + } 1006 + 1007 + entry->async_initialized = true; 1008 + entry->async.data = entry; 1009 + entry->pending_handles++; 1010 + 1011 + if (isfinite(timeout_ms)) { 1012 + uint64_t delay = timeout_ms > (double)UINT64_MAX ? UINT64_MAX : (uint64_t)timeout_ms; 1013 + if (uv_timer_init(uv_default_loop(), &entry->timer) != 0) { 1014 + async_waiter_close_handles(entry); 1015 + return js_mkerr(js, "Failed to initialize Atomics.waitAsync timer"); 1016 + } 1017 + entry->timer_initialized = true; 1018 + entry->timer.data = entry; 1019 + entry->pending_handles++; 1020 + if (uv_timer_start(&entry->timer, async_waiter_timeout_cb, delay, 0) != 0) { 1021 + async_waiter_close_handles(entry); 1022 + return js_mkerr(js, "Failed to start Atomics.waitAsync timer"); 1023 + } 1024 + } 1025 + 1026 + pthread_mutex_lock(&async_waiters_lock); 1027 + async_waiter_add_locked(entry); 1028 + pthread_mutex_unlock(&async_waiters_lock); 1029 + 794 1030 js_set(js, result_obj, "async", js_true); 795 1031 js_set(js, result_obj, "value", promise); 796 - js_resolve_promise(js, promise, js_mkstr(js, "ok", 2)); 797 - 1032 + 798 1033 return result_obj; 799 1034 } 800 1035 801 1036 // Atomics.pause() 802 1037 static ant_value_t js_atomics_pause(ant_t *js, ant_value_t *args, int nargs) { 803 - (void)js; 804 - (void)args; 805 - (void)nargs; 806 - 807 1038 #if defined(__x86_64__) || defined(__i386__) 808 1039 __builtin_ia32_pause(); 809 1040 #elif defined(__aarch64__) || defined(__arm__) ··· 837 1068 js_set_sym(js, atomics, get_toStringTag_sym(), js_mkstr(js, "Atomics", 7)); 838 1069 js_set(js, glob, "Atomics", atomics); 839 1070 } 1071 + 1072 + void gc_mark_atomics(ant_t *js, gc_mark_fn mark) { 1073 + pthread_mutex_lock(&async_waiters_lock); 1074 + for (AsyncWaitEntry *entry = async_waiters_head; entry; entry = entry->next) { 1075 + if (entry->js == js) mark(js, entry->promise); 1076 + } 1077 + pthread_mutex_unlock(&async_waiters_lock); 1078 + }
+41 -11
src/modules/buffer.c
··· 4 4 #include <ctype.h> 5 5 6 6 #include "ant.h" 7 + #include "ptr.h" 7 8 #include "utf8.h" 8 9 #include "utils.h" 9 10 #include "errors.h" 10 11 #include "base64.h" 11 12 #include "internal.h" 12 13 #include "runtime.h" 14 + #include "gc/roots.h" 13 15 #include "descriptors.h" 14 - #include "ptr.h" 15 16 16 17 #include "silver/engine.h" 17 18 #include "modules/buffer.h" ··· 1252 1253 1253 1254 ant_value_t source = args[0]; 1254 1255 bool has_map = nargs >= 2 && vtype(args[1]) != T_UNDEF; 1256 + 1255 1257 ant_value_t map_fn = js_mkundef(); 1256 1258 ant_value_t this_arg = nargs >= 3 ? args[2] : js_mkundef(); 1259 + ant_value_t result = js_mkundef(); 1260 + ant_value_t *collected = NULL; 1261 + 1262 + gc_temp_root_scope_t temp_roots = {0}; 1263 + bool temp_roots_active = false; 1257 1264 1258 1265 if (has_map) { 1259 - if (!is_callable(args[1])) 1260 - return js_mkerr_typed(js, JS_ERR_TYPE, "%s.from: mapFn is not a function", type_name); 1266 + if (!is_callable(args[1])) return js_mkerr_typed( 1267 + js, JS_ERR_TYPE, "%s.from: mapFn is not a function", type_name 1268 + ); 1261 1269 map_fn = args[1]; 1262 1270 } 1263 1271 1264 - ant_value_t *collected = NULL; 1272 + gc_temp_root_scope_begin(js, &temp_roots); 1273 + temp_roots_active = true; 1274 + 1275 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, source))) goto oom; 1276 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, map_fn))) goto oom; 1277 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, this_arg))) goto oom; 1278 + 1265 1279 size_t count = 0, cap = 16; 1266 1280 collected = malloc(cap * sizeof(ant_value_t)); 1267 - if (!collected) return js_mkerr(js, "oom"); 1281 + if (!collected) goto oom; 1268 1282 1269 1283 js_iter_t it; 1270 1284 if (js_iter_open(js, source, &it)) { ··· 1273 1287 if (count >= cap) { 1274 1288 cap *= 2; 1275 1289 ant_value_t *tmp = realloc(collected, cap * sizeof(ant_value_t)); 1276 - if (!tmp) { free(collected); return js_mkerr(js, "oom"); } 1290 + if (!tmp) goto oom; 1277 1291 collected = tmp; 1278 1292 } 1279 1293 if (has_map) { 1280 1294 ant_value_t map_args[2] = { item, js_mknum((double)count) }; 1281 1295 item = sv_vm_call(js->vm, js, map_fn, this_arg, map_args, 2, NULL, false); 1282 - if (is_err(item)) { free(collected); return item; } 1296 + if (is_err(item)) { 1297 + result = item; 1298 + goto done; 1299 + } 1283 1300 } 1284 1301 collected[count++] = item; 1302 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, item))) goto oom; 1285 1303 } 1286 1304 js_iter_close(js, &it); 1287 1305 } else { ··· 1294 1312 if (count >= cap) { 1295 1313 cap *= 2; 1296 1314 ant_value_t *tmp = realloc(collected, cap * sizeof(ant_value_t)); 1297 - if (!tmp) { free(collected); return js_mkerr(js, "oom"); } 1315 + if (!tmp) goto oom; 1298 1316 collected = tmp; 1299 1317 } 1300 1318 if (has_map) { 1301 1319 ant_value_t map_args[2] = { item, js_mknum((double)i) }; 1302 1320 item = sv_vm_call(js->vm, js, map_fn, this_arg, map_args, 2, NULL, false); 1303 - if (is_err(item)) { free(collected); return item; } 1321 + if (is_err(item)) { 1322 + result = item; 1323 + goto done; 1324 + } 1304 1325 } 1305 1326 collected[count++] = item; 1327 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, item))) goto oom; 1306 1328 } 1307 1329 } 1308 1330 1309 1331 size_t elem_size = get_element_size(type); 1310 1332 ArrayBufferData *buffer = create_array_buffer_data(count * elem_size); 1311 - if (!buffer) { free(collected); return js_mkerr(js, "oom"); } 1333 + if (!buffer) goto oom; 1312 1334 1313 - ant_value_t result = create_typed_array(js, type, buffer, 0, count, type_name); 1335 + result = create_typed_array(js, type, buffer, 0, count, type_name); 1336 + if (is_err(result)) goto done; 1337 + if (!gc_temp_root_handle_valid(gc_temp_root_add(&temp_roots, result))) goto oom; 1314 1338 uint8_t *data = buffer->data; 1315 1339 1316 1340 for (size_t i = 0; i < count; i++) { ··· 1329 1353 default: break; 1330 1354 }} 1331 1355 1356 + done: 1357 + if (temp_roots_active) gc_temp_root_scope_end(&temp_roots); 1332 1358 free(collected); 1333 1359 return result; 1360 + 1361 + oom: 1362 + result = js_mkerr(js, "oom"); 1363 + goto done; 1334 1364 } 1335 1365 1336 1366 #define DEFINE_TYPEDARRAY_FROM(name, type) \
+32 -8
src/modules/builtin.c
··· 166 166 167 167 // Ant.stats() 168 168 static ant_value_t js_stats_fn(ant_t *js, ant_value_t *args, int nargs) { 169 - (void) args; (void) nargs; 170 169 ant_value_t result = js_newobj(js); 171 170 172 171 ant_pool_stats_t rope_s = js_pool_stats(&js->pool.rope); 173 172 ant_pool_stats_t sym_s = js_pool_stats(&js->pool.symbol); 174 173 ant_pool_stats_t bigint_s = js_class_pool_stats(&js->pool.bigint); 175 - ant_pool_stats_t string_s = js_class_pool_stats(&js->pool.string); 174 + ant_string_pool_stats_t string_s = js_string_pool_stats(&js->pool.string); 176 175 177 176 ant_value_t pools = js_newobj(js); 178 177 ant_value_t rope_obj = js_newobj(js); ··· 194 193 js_set(js, pools, "bigint", bigint_obj); 195 194 196 195 ant_value_t string_obj = js_newobj(js); 197 - js_set(js, string_obj, "used", js_mknum((double)string_s.used)); 198 - js_set(js, string_obj, "capacity", js_mknum((double)string_s.capacity)); 199 - js_set(js, string_obj, "blocks", js_mknum((double)string_s.blocks)); 200 - js_set(js, pools, "string", string_obj); 196 + js_set(js, string_obj, "used", js_mknum((double)string_s.total.used)); 197 + js_set(js, string_obj, "capacity", js_mknum((double)string_s.total.capacity)); 198 + js_set(js, string_obj, "blocks", js_mknum((double)string_s.total.blocks)); 199 + 200 + ant_value_t string_pooled_obj = js_newobj(js); 201 + js_set(js, string_pooled_obj, "used", js_mknum((double)string_s.pooled.used)); 202 + js_set(js, string_pooled_obj, "capacity", js_mknum((double)string_s.pooled.capacity)); 203 + js_set(js, string_pooled_obj, "blocks", js_mknum((double)string_s.pooled.blocks)); 204 + js_set(js, string_obj, "pooled", string_pooled_obj); 201 205 202 - size_t pool_used = rope_s.used + sym_s.used + bigint_s.used + string_s.used; 203 - size_t pool_cap = rope_s.capacity + sym_s.capacity + bigint_s.capacity + string_s.capacity; 206 + ant_value_t string_large_live_obj = js_newobj(js); 207 + js_set(js, string_large_live_obj, "used", js_mknum((double)string_s.large_live.used)); 208 + js_set(js, string_large_live_obj, "capacity", js_mknum((double)string_s.large_live.capacity)); 209 + js_set(js, string_large_live_obj, "blocks", js_mknum((double)string_s.large_live.blocks)); 210 + js_set(js, string_obj, "largeLive", string_large_live_obj); 211 + 212 + ant_value_t string_large_reusable_obj = js_newobj(js); 213 + js_set(js, string_large_reusable_obj, "used", js_mknum((double)string_s.large_reusable.used)); 214 + js_set(js, string_large_reusable_obj, "capacity", js_mknum((double)string_s.large_reusable.capacity)); 215 + js_set(js, string_large_reusable_obj, "blocks", js_mknum((double)string_s.large_reusable.blocks)); 216 + js_set(js, string_obj, "largeReusable", string_large_reusable_obj); 217 + 218 + ant_value_t string_large_quarantine_obj = js_newobj(js); 219 + js_set(js, string_large_quarantine_obj, "used", js_mknum((double)string_s.large_quarantine.used)); 220 + js_set(js, string_large_quarantine_obj, "capacity", js_mknum((double)string_s.large_quarantine.capacity)); 221 + js_set(js, string_large_quarantine_obj, "blocks", js_mknum((double)string_s.large_quarantine.blocks)); 222 + js_set(js, string_obj, "largeQuarantine", string_large_quarantine_obj); 223 + 224 + size_t pool_used = rope_s.used + sym_s.used + bigint_s.used + string_s.total.used; 225 + size_t pool_cap = rope_s.capacity + sym_s.capacity + bigint_s.capacity + string_s.total.capacity; 226 + 204 227 js_set(js, pools, "totalUsed", js_mknum((double)pool_used)); 205 228 js_set(js, pools, "totalCapacity", js_mknum((double)pool_cap)); 206 229 js_set(js, result, "pools", pools); 230 + js_set(js, pools, "string", string_obj); 207 231 208 232 size_t obj_count = 0; 209 233 size_t obj_bytes = 0;
+17 -6
src/modules/reflect.c
··· 16 16 ant_value_t target = args[0]; 17 17 ant_value_t key = args[1]; 18 18 19 - int t = vtype(target); 20 - if (t != T_OBJ && t != T_FUNC) return js_mkundef(); 21 - 22 - if (vtype(key) != T_STR) return js_mkundef(); 23 - 24 - char *key_str = js_getstr(js, key, NULL); 19 + if (!is_object_type(target)) return js_mkundef(); 20 + 21 + ant_value_t prop_key = key; 22 + if (is_object_type(prop_key)) { 23 + prop_key = js_to_primitive(js, prop_key, 1); 24 + if (is_err(prop_key)) return prop_key; 25 + } 26 + 27 + if (vtype(prop_key) == T_SYMBOL) 28 + return js_get_sym(js, target, prop_key); 29 + 30 + if (vtype(prop_key) != T_STR) { 31 + prop_key = js_tostring_val(js, prop_key); 32 + if (is_err(prop_key)) return prop_key; 33 + } 34 + 35 + char *key_str = js_getstr(js, prop_key, NULL); 25 36 if (!key_str) return js_mkundef(); 26 37 27 38 return js_get(js, target, key_str);
+14 -2
src/modules/regex.c
··· 1266 1266 ant_offset_t ncap = js_arr_len(js, result); 1267 1267 int num_caps = ncap > 1 ? (int)(ncap - 1) : 0; 1268 1268 repl_capture_t caps_buf[16], *caps = num_caps <= 16 ? caps_buf : ant_calloc(sizeof(repl_capture_t) * (size_t)num_caps); 1269 + if (num_caps > 16 && !caps) { 1270 + free(buf); 1271 + return js_mkerr(js, "oom"); 1272 + } 1269 1273 for (int ci = 0; ci < num_caps; ci++) { 1270 1274 ant_value_t cap = js_arr_get(js, result, (ant_offset_t)(ci + 1)); 1271 1275 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 1277 } 1274 1278 ant_offset_t mlen, moff = vstr(js, matched, &mlen); 1275 1279 str_off = vstr(js, str, &str_len); 1276 - repl_template((const char *)(uintptr_t)(rep_off), rep_len, (const char *)(uintptr_t)(moff), mlen, 1277 - (const char *)(uintptr_t)(str_off), str_len, position, caps, num_caps, &buf, &buf_len, &buf_cap); 1280 + bool ok = repl_template( 1281 + (const char *)(uintptr_t)(rep_off), rep_len, 1282 + (const char *)(uintptr_t)(moff), mlen, 1283 + (const char *)(uintptr_t)(str_off), str_len, position, 1284 + caps, num_caps, &buf, &buf_len, &buf_cap 1285 + ); 1278 1286 if (caps != caps_buf) free(caps); 1287 + if (!ok) { 1288 + free(buf); 1289 + return js_mkerr(js, "oom"); 1290 + } 1279 1291 } 1280 1292 next_src_pos = position + matched_len; 1281 1293 }
+270 -84
src/pool.c
··· 3 3 4 4 #include <stddef.h> 5 5 #include <stdlib.h> 6 + #include <string.h> 6 7 7 8 static const size_t g_size_classes[ANT_POOL_SIZE_CLASS_COUNT] = { 8 9 16u, 24u, 32u, 40u, 48u, 56u, 64u, 80u, ··· 11 12 1536u, 1792u, 2048u, 2560u, 3072u, 4096u, 8192u, 16384u 12 13 }; 13 14 14 - static inline bool pool_kind_uses_size_classes(ant_alloc_kind_t kind) { 15 - return kind == ANT_ALLOC_STRING || kind == ANT_ALLOC_BIGINT; 15 + static inline size_t align_up_size(size_t value, size_t align) { 16 + if (align == 0) return value; 17 + size_t mask = align - 1; 18 + return (value + mask) & ~mask; 19 + } 20 + 21 + static inline int pool_size_class_index(size_t need) { 22 + for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) { 23 + if (need <= g_size_classes[i]) return i; 24 + } 25 + return -1; 26 + } 27 + 28 + static inline size_t pool_class_block_items(size_t class_size) { 29 + if (class_size <= 256u) return 256u; 30 + if (class_size <= 1024u) return 128u; 31 + if (class_size <= 4096u) return 64u; 32 + return 32u; 33 + } 34 + 35 + static inline size_t pool_chain_block_cap(size_t block_size, size_t need) { 36 + if (block_size == 0 || block_size >= need) return block_size ? block_size : need; 37 + size_t blocks = (need + block_size - 1) / block_size; 38 + return blocks * block_size; 16 39 } 17 40 18 41 static inline size_t pool_default_block_size(ant_alloc_kind_t kind) { ··· 28 51 switch (kind) { 29 52 case ANT_ALLOC_SYMBOL: return &js->pool.symbol; 30 53 case ANT_ALLOC_BIGINT: return &js->pool.bigint.base; 31 - case ANT_ALLOC_STRING: return &js->pool.string.base; 32 54 case ANT_ALLOC_ROPE: 33 55 default: return &js->pool.rope; 34 56 }} ··· 36 58 static inline ant_class_pool_t *class_pool_for_kind(ant_t *js, ant_alloc_kind_t kind) { 37 59 switch (kind) { 38 60 case ANT_ALLOC_BIGINT: return &js->pool.bigint; 39 - case ANT_ALLOC_STRING: return &js->pool.string; 40 61 default: return NULL; 41 62 }} 42 63 43 - static inline size_t align_up_size(size_t value, size_t align) { 44 - if (align == 0) return value; 45 - size_t mask = align - 1; 46 - return (value + mask) & ~mask; 64 + static void pool_block_list_destroy(ant_pool_block_t *head) { 65 + while (head) { 66 + ant_pool_block_t *next = head->next; 67 + pool_block_free(head); 68 + head = next; 69 + }} 70 + 71 + static void pool_free_block_list_destroy(ant_pool_block_t *head) { 72 + while (head) { 73 + ant_pool_block_t *next = pool_free_next(head); 74 + pool_block_free(head); 75 + head = next; 76 + }} 77 + 78 + static ant_pool_stats_t pool_block_list_stats(ant_pool_block_t *head) { 79 + ant_pool_stats_t s = {0}; 80 + for (ant_pool_block_t *b = head; b; b = b->next) { 81 + s.used += b->used; 82 + s.capacity += b->cap; 83 + s.blocks++; 84 + } 85 + return s; 86 + } 87 + 88 + static inline void pool_stats_add(ant_pool_stats_t *dst, ant_pool_stats_t src) { 89 + dst->used += src.used; 90 + dst->capacity += src.capacity; 91 + dst->blocks += src.blocks; 92 + } 93 + 94 + static inline size_t pool_page_size(void) { 95 + static size_t cached = 0; 96 + if (cached) return cached; 97 + #ifdef _WIN32 98 + SYSTEM_INFO info; 99 + GetSystemInfo(&info); 100 + cached = info.dwPageSize ? (size_t)info.dwPageSize : 4096u; 101 + #else 102 + long page = sysconf(_SC_PAGESIZE); 103 + cached = page > 0 ? (size_t)page : 4096u; 104 + #endif 105 + return cached; 106 + } 107 + 108 + static inline void large_string_unlink(ant_large_string_alloc_t **head, ant_large_string_alloc_t *alloc) { 109 + if (!head || !alloc) return; 110 + if (alloc->prev) alloc->prev->next = alloc->next; 111 + else *head = alloc->next; 112 + if (alloc->next) alloc->next->prev = alloc->prev; 113 + alloc->next = NULL; 114 + alloc->prev = NULL; 115 + } 116 + 117 + static inline void large_string_push_front(ant_large_string_alloc_t **head, ant_large_string_alloc_t *alloc) { 118 + if (!head || !alloc) return; 119 + alloc->prev = NULL; 120 + alloc->next = *head; 121 + if (*head) (*head)->prev = alloc; 122 + *head = alloc; 123 + } 124 + 125 + static inline size_t flat_payload_capacity_from_size(size_t flat_size) { 126 + if (flat_size <= offsetof(ant_flat_string_t, bytes)) return 0; 127 + return flat_size - offsetof(ant_flat_string_t, bytes) - 1u; 47 128 } 48 129 49 - static inline int pool_size_class_index(size_t need) { 50 - for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) { 51 - if (need <= g_size_classes[i]) return i; 52 - } 53 - return -1; 130 + static ant_large_string_alloc_t *large_string_alloc_fresh(size_t flat_size) { 131 + size_t payload_capacity = flat_payload_capacity_from_size(flat_size); 132 + size_t needed = offsetof(ant_large_string_alloc_t, bytes) + payload_capacity + 1u; 133 + size_t alloc_size = align_up_size(needed, pool_page_size()); 134 + 135 + ant_large_string_alloc_t *alloc = (ant_large_string_alloc_t *)ant_os_alloc(alloc_size); 136 + if (!alloc) return NULL; 137 + 138 + alloc->next = NULL; 139 + alloc->prev = NULL; 140 + alloc->capacity = alloc_size - offsetof(ant_large_string_alloc_t, bytes) - 1u; 141 + alloc->alloc_size = alloc_size; 142 + alloc->quarantine_epoch = 0; 143 + alloc->marked = 0; 144 + alloc->len = 0; 145 + alloc->is_ascii = STR_ASCII_UNKNOWN; 146 + alloc->bytes[0] = '\0'; 147 + 148 + return alloc; 54 149 } 55 150 56 - static inline size_t pool_class_block_items(size_t class_size) { 57 - if (class_size <= 256u) return 256u; 58 - if (class_size <= 1024u) return 128u; 59 - if (class_size <= 4096u) return 64u; 60 - return 32u; 151 + static ant_large_string_alloc_t *large_string_reuse_candidate(ant_large_string_space_t *space, size_t flat_size) { 152 + if (!space) return NULL; 153 + 154 + size_t needed_capacity = flat_payload_capacity_from_size(flat_size); 155 + size_t max_capacity = needed_capacity > (SIZE_MAX / 2u) ? SIZE_MAX : needed_capacity * 2u; 156 + 157 + for (ant_large_string_alloc_t *cur = space->reusable; cur; cur = cur->next) 158 + if (cur->capacity >= needed_capacity && cur->capacity <= max_capacity) return cur; 159 + 160 + return NULL; 61 161 } 62 162 63 - static void *pool_bucket_alloc_fast( 64 - ant_pool_bucket_t *bucket, 65 - size_t class_size, 66 - size_t align 67 - ) { 68 - if (!bucket) return NULL; 163 + static ant_flat_string_t *string_large_space_alloc(ant_t *js, size_t flat_size) { 164 + ant_large_string_space_t *space = &js->pool.string.large; 165 + ant_large_string_alloc_t *alloc = large_string_reuse_candidate(space, flat_size); 69 166 167 + if (alloc) large_string_unlink(&space->reusable, alloc); else { 168 + alloc = large_string_alloc_fresh(flat_size); 169 + if (!alloc) return NULL; 170 + } 171 + 172 + alloc->quarantine_epoch = 0; 173 + alloc->marked = 0; 174 + alloc->len = 0; 175 + alloc->is_ascii = STR_ASCII_UNKNOWN; 176 + alloc->bytes[0] = '\0'; 177 + large_string_push_front(&space->live, alloc); 178 + 179 + return large_string_flat_ptr(alloc); 180 + } 181 + 182 + static void *pool_bucket_alloc_fast(ant_pool_bucket_t *bucket, size_t class_size, size_t align) { 183 + if (!bucket) return NULL; 70 184 const size_t cache_align = _Alignof(max_align_t); 185 + 71 186 if (align > cache_align) return NULL; 72 - 73 - if (bucket->slot_stride == 0) { 74 - bucket->slot_stride = align_up_size(class_size, cache_align); 75 - } 187 + if (bucket->slot_stride == 0) bucket->slot_stride = align_up_size(class_size, cache_align); 76 188 77 189 if (bucket->slot_free) { 78 190 void *ptr = bucket->slot_free; 79 191 void *next; 80 - 81 192 memcpy(&next, ptr, sizeof(void *)); 82 193 bucket->slot_free = next; 83 - 84 194 return ptr; 85 195 } 86 196 ··· 111 221 112 222 uint8_t *ptr = bucket->cursor; 113 223 bucket->cursor += bucket->slot_stride; 114 - if (bucket->current) bucket->current->used = (size_t)(bucket->cursor - bucket->current->data); 115 224 225 + if (bucket->current) 226 + bucket->current->used = (size_t)(bucket->cursor - bucket->current->data); 227 + 116 228 return ptr; 117 229 } 118 230 231 + static void *string_pool_alloc(ant_t *js, size_t size, size_t align) { 232 + ant_string_pool_t *pool = &js->pool.string; 233 + if (pool->block_size == 0) pool->block_size = pool_default_block_size(ANT_ALLOC_STRING); 234 + 235 + size_t needed = size + (align - 1u); 236 + int class_idx = pool_size_class_index(needed); 237 + 238 + if (class_idx >= 0) { 239 + size_t class_size = g_size_classes[class_idx]; 240 + ant_pool_bucket_t *bucket = &pool->classes[class_idx]; 241 + 242 + if (bucket->block_size == 0) { 243 + size_t bucket_block_size = pool->block_size; 244 + size_t min_block = class_size * pool_class_block_items(class_size); 245 + if (bucket_block_size < min_block) bucket_block_size = min_block; 246 + bucket->block_size = bucket_block_size; 247 + } 248 + 249 + void *fast = pool_bucket_alloc_fast(bucket, class_size, align); 250 + if (fast) return fast; 251 + return pool_alloc_chain(&bucket->head, &bucket->free_head, bucket->block_size, size, align); 252 + } 253 + 254 + return string_large_space_alloc(js, size); 255 + } 256 + 119 257 void *pool_alloc_chain( 120 258 ant_pool_block_t **head, 121 259 ant_pool_block_t **free_head, ··· 128 266 129 267 ant_pool_block_t *block = *head; 130 268 if (!block) { 131 - size_t cap = block_size; 132 - if (cap < size + align) cap = size + align; 269 + size_t cap = pool_chain_block_cap(block_size, size + align); 133 270 block = pool_block_alloc(cap); 134 271 if (!block) return NULL; 135 272 *head = block; ··· 147 284 if (*head) (*head)->prev = block; 148 285 *head = block; 149 286 } else { 150 - size_t cap = block_size; 151 - if (cap < size + align) cap = size + align; 287 + size_t cap = pool_chain_block_cap(block_size, size + align); 152 288 ant_pool_block_t *next = pool_block_alloc(cap); 153 289 if (!next) return NULL; 154 290 next->next = block; ··· 162 298 163 299 void *ptr = (void *)(base + start); 164 300 block->used = start + size; 301 + 165 302 return ptr; 166 303 } 167 304 ··· 174 311 175 312 if (pool_threshold < (4u * 1024u * 1024u)) pool_threshold = 4u * 1024u * 1024u; 176 313 if (js->gc_pool_alloc >= pool_threshold) gc_run(js); 314 + if (kind == ANT_ALLOC_STRING) return string_pool_alloc(js, size, align); 177 315 178 316 ant_pool_t *pool = pool_for_kind(js, kind); 179 317 if (pool->block_size == 0) pool->block_size = pool_default_block_size(kind); 180 - 181 - if (pool_kind_uses_size_classes(kind)) { 182 - ant_class_pool_t *class_pool = class_pool_for_kind(js, kind); 183 - if (!class_pool) return pool_alloc_chain(&pool->head, &pool->free_head, pool->block_size, size, align); 184 - 185 - size_t needed = size + (align - 1u); 186 - int class_idx = pool_size_class_index(needed); 187 - if (class_idx >= 0) { 188 - size_t class_size = g_size_classes[class_idx]; 189 - ant_pool_bucket_t *bucket = &class_pool->classes[class_idx]; 190 - if (bucket->block_size == 0) { 191 - size_t bucket_block_size = pool->block_size; 192 - size_t min_block = class_size * pool_class_block_items(class_size); 193 - if (bucket_block_size < min_block) bucket_block_size = min_block; 194 - bucket->block_size = bucket_block_size; 195 - } 196 - void *fast = pool_bucket_alloc_fast(bucket, class_size, align); 197 - if (fast) return fast; 198 - return pool_alloc_chain(&bucket->head, &bucket->free_head, bucket->block_size, size, align); 318 + ant_class_pool_t *class_pool = class_pool_for_kind(js, kind); 319 + 320 + if (class_pool) { 321 + size_t needed = size + (align - 1u); 322 + int class_idx = pool_size_class_index(needed); 323 + 324 + if (class_idx >= 0) { 325 + size_t class_size = g_size_classes[class_idx]; 326 + ant_pool_bucket_t *bucket = &class_pool->classes[class_idx]; 327 + if (bucket->block_size == 0) { 328 + size_t bucket_block_size = pool->block_size; 329 + size_t min_block = class_size * pool_class_block_items(class_size); 330 + if (bucket_block_size < min_block) bucket_block_size = min_block; 331 + bucket->block_size = bucket_block_size; 199 332 } 200 - } 333 + void *fast = pool_bucket_alloc_fast(bucket, class_size, align); 334 + if (fast) return fast; 335 + return pool_alloc_chain(&bucket->head, &bucket->free_head, bucket->block_size, size, align); 336 + }} 201 337 202 338 return pool_alloc_chain(&pool->head, &pool->free_head, pool->block_size, size, align); 203 339 } 204 340 205 341 void js_pool_destroy(ant_pool_t *pool) { 206 342 if (!pool) return; 207 - 208 - ant_pool_block_t *block = pool->head; 209 - while (block) { 210 - ant_pool_block_t *next = block->next; 211 - pool_block_free(block); 212 - block = next; 213 - } 343 + pool_block_list_destroy(pool->head); 344 + pool_free_block_list_destroy(pool->free_head); 214 345 pool->head = NULL; 346 + pool->free_head = NULL; 215 347 pool->block_size = 0; 216 348 } 217 349 218 350 ant_pool_stats_t js_pool_stats(ant_pool_t *pool) { 351 + if (!pool) return (ant_pool_stats_t){0}; 352 + return pool_block_list_stats(pool->head); 353 + } 354 + 355 + ant_pool_stats_t js_class_pool_stats(ant_class_pool_t *pool) { 219 356 ant_pool_stats_t s = {0}; 220 357 if (!pool) return s; 221 - for (ant_pool_block_t *b = pool->head; b; b = b->next) { 222 - s.used += b->used; 223 - s.capacity += b->cap; 224 - s.blocks++; 225 - } 358 + 359 + for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) 360 + pool_stats_add(&s, pool_block_list_stats(pool->classes[i].head)); 361 + pool_stats_add(&s, js_pool_stats(&pool->base)); 362 + 226 363 return s; 227 364 } 228 365 229 - static inline void pool_stats_add(ant_pool_stats_t *dst, ant_pool_stats_t src) { 230 - dst->used += src.used; 231 - dst->capacity += src.capacity; 232 - dst->blocks += src.blocks; 366 + static ant_pool_stats_t string_large_stats(ant_large_string_alloc_t *head, bool include_used) { 367 + ant_pool_stats_t s = {0}; 368 + for (ant_large_string_alloc_t *cur = head; cur; cur = cur->next) { 369 + if (include_used) 370 + s.used += offsetof(ant_flat_string_t, bytes) + (size_t)cur->len + 1u; 371 + s.capacity += cur->alloc_size; 372 + s.blocks++; 373 + } 374 + return s; 233 375 } 234 376 235 - ant_pool_stats_t js_class_pool_stats(ant_class_pool_t *pool) { 236 - ant_pool_stats_t s = {0}; 377 + ant_string_pool_stats_t js_string_pool_stats(ant_string_pool_t *pool) { 378 + ant_string_pool_stats_t s = {0}; 237 379 if (!pool) return s; 238 - for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) { 239 - ant_pool_t bucket = { .head = pool->classes[i].head }; 240 - pool_stats_add(&s, js_pool_stats(&bucket)); 241 - } 242 - pool_stats_add(&s, js_pool_stats(&pool->base)); 380 + 381 + for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) 382 + pool_stats_add(&s.pooled, pool_block_list_stats(pool->classes[i].head)); 383 + 384 + s.large_live = string_large_stats(pool->large.live, true); 385 + s.large_reusable = string_large_stats(pool->large.reusable, false); 386 + s.large_quarantine = string_large_stats(pool->large.quarantine, false); 387 + 388 + s.total = s.pooled; 389 + pool_stats_add(&s.total, s.large_live); 390 + s.total.capacity += s.large_reusable.capacity + s.large_quarantine.capacity; 391 + s.total.blocks += s.large_reusable.blocks + s.large_quarantine.blocks; 392 + 243 393 return s; 244 394 } 245 395 ··· 247 397 if (!pool) return; 248 398 249 399 for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) { 250 - ant_pool_block_t *block = pool->classes[i].head; 251 - while (block) { 252 - ant_pool_block_t *next = block->next; 253 - pool_block_free(block); 254 - block = next; 255 - } 400 + pool_block_list_destroy(pool->classes[i].head); 401 + pool_free_block_list_destroy(pool->classes[i].free_head); 256 402 pool->classes[i].head = NULL; 403 + pool->classes[i].current = NULL; 404 + pool->classes[i].free_head = NULL; 405 + pool->classes[i].slot_free = NULL; 257 406 pool->classes[i].block_size = 0; 258 407 pool->classes[i].cursor = NULL; 259 408 pool->classes[i].end = NULL; ··· 262 411 263 412 js_pool_destroy(&pool->base); 264 413 } 414 + 415 + void js_string_pool_destroy(ant_string_pool_t *pool) { 416 + if (!pool) return; 417 + 418 + for (int i = 0; i < ANT_POOL_SIZE_CLASS_COUNT; i++) { 419 + pool_block_list_destroy(pool->classes[i].head); 420 + pool_free_block_list_destroy(pool->classes[i].free_head); 421 + pool->classes[i].head = NULL; 422 + pool->classes[i].current = NULL; 423 + pool->classes[i].free_head = NULL; 424 + pool->classes[i].slot_free = NULL; 425 + pool->classes[i].block_size = 0; 426 + pool->classes[i].cursor = NULL; 427 + pool->classes[i].end = NULL; 428 + pool->classes[i].slot_stride = 0; 429 + } 430 + 431 + ant_large_string_alloc_t *lists[] = { 432 + pool->large.live, 433 + pool->large.reusable, 434 + pool->large.quarantine, 435 + }; 436 + 437 + for (size_t i = 0; i < sizeof(lists) / sizeof(lists[0]); i++) { 438 + ant_large_string_alloc_t *cur = lists[i]; 439 + while (cur) { 440 + ant_large_string_alloc_t *next = cur->next; 441 + ant_arena_free(cur, cur->alloc_size); 442 + cur = next; 443 + }} 444 + 445 + pool->large.live = NULL; 446 + pool->large.reusable = NULL; 447 + pool->large.quarantine = NULL; 448 + pool->large.gc_epoch = 0; 449 + pool->block_size = 0; 450 + }
-1
src/silver/compiler.c
··· 4343 4343 compile_export_emit(&comp, e->name, e->len); 4344 4344 } 4345 4345 4346 - free(comp.deferred_exports); 4347 4346 emit_close_upvals(&comp); 4348 4347 emit_op(&comp, OP_RETURN_UNDEF); 4349 4348
+19 -5
src/silver/ops/property.h
··· 28 28 return out; 29 29 } 30 30 31 + static inline ant_value_t sv_key_to_property_key(ant_t *js, ant_value_t key) { 32 + if (vtype(key) == T_SYMBOL) return key; 33 + 34 + ant_value_t prim = is_object_type(key) ? js_to_primitive(js, key, 1) : key; 35 + if (is_err(prim)) return prim; 36 + if (vtype(prim) == T_SYMBOL) return prim; 37 + 38 + return js_tostring_val(js, prim); 39 + } 40 + 31 41 static inline ant_value_t sv_key_to_propstr(ant_t *js, ant_value_t key) { 32 42 return coerce_to_str(js, key); 33 43 } ··· 251 261 } 252 262 253 263 static inline ant_value_t sv_getprop_by_key(ant_t *js, ant_value_t obj, ant_value_t key) { 254 - if (vtype(key) == T_SYMBOL) return js_get_sym(js, obj, key); 255 - ant_value_t key_str = sv_key_to_propstr(js, key); 264 + ant_value_t prop_key = sv_key_to_property_key(js, key); 265 + if (is_err(prop_key)) return prop_key; 266 + if (vtype(prop_key) == T_SYMBOL) return js_get_sym(js, obj, prop_key); 267 + 268 + ant_value_t key_str = prop_key; 256 269 if (is_err(key_str) || vtype(key_str) != T_STR) return js_mkundef(); 257 270 258 271 ant_offset_t klen = 0; 259 272 ant_offset_t koff = vstr(js, key_str, &klen); 273 + 260 274 const char *kptr = (const char *)(uintptr_t)(koff); 261 275 return sv_getprop_fallback_len(js, obj, kptr, klen); 262 276 } ··· 705 719 ant_value_t val = vm->stack[--vm->sp]; 706 720 ant_value_t key = vm->stack[--vm->sp]; 707 721 ant_value_t obj = vm->stack[--vm->sp]; 708 - if (vtype(key) == T_SYMBOL) return js_setprop(js, obj, key, val); 709 - ant_value_t key_jv = sv_key_to_propstr(js, key); 710 - return js_setprop(js, obj, key_jv, val); 722 + ant_value_t prop_key = sv_key_to_property_key(js, key); 723 + if (is_err(prop_key)) return prop_key; 724 + return js_setprop(js, obj, prop_key, val); 711 725 } 712 726 713 727 static inline bool sv_try_define_field_fast(
+55 -24
src/utils.c
··· 256 256 char **buf; size_t *buf_len; size_t *buf_cap; 257 257 } rt_ctx_t; 258 258 259 - #define RT_APPEND(c, data, dlen) do { \ 260 - if (*(c)->buf_len + (dlen) >= *(c)->buf_cap) { \ 261 - *(c)->buf_cap = (*(c)->buf_len + (dlen) + 1) * 2; \ 262 - *(c)->buf = realloc(*(c)->buf, *(c)->buf_cap); \ 263 - } \ 264 - memcpy(*(c)->buf + *(c)->buf_len, data, dlen); *(c)->buf_len += (dlen); \ 265 - } while(0) 259 + static bool rt_append(rt_ctx_t *c, const char *data, size_t dlen) { 260 + if (dlen == 0) return true; 261 + if (*c->buf_len > SIZE_MAX - dlen - 1) return false; 266 262 267 - static void rt_dollar(rt_ctx_t *c) { RT_APPEND(c, "$", 1); *c->ri += 2; } 268 - static void rt_match(rt_ctx_t *c) { RT_APPEND(c, c->matched, c->matched_len); *c->ri += 2; } 269 - static void rt_prefix(rt_ctx_t *c) { RT_APPEND(c, c->str, c->position); *c->ri += 2; } 263 + if (*c->buf_len + dlen >= *c->buf_cap) { 264 + size_t needed = *c->buf_len + dlen + 1; 265 + size_t new_cap = needed * 2; 266 + if (new_cap < needed) new_cap = needed; 267 + 268 + char *next = realloc(*c->buf, new_cap); 269 + if (!next) return false; 270 + *c->buf = next; 271 + *c->buf_cap = new_cap; 272 + } 273 + 274 + memcpy(*c->buf + *c->buf_len, data, dlen); 275 + *c->buf_len += dlen; 276 + return true; 277 + } 278 + 279 + static bool rt_dollar(rt_ctx_t *c) { 280 + *c->ri += 2; 281 + return rt_append(c, "$", 1); 282 + } 283 + 284 + static bool rt_match(rt_ctx_t *c) { 285 + *c->ri += 2; 286 + return rt_append(c, c->matched, c->matched_len); 287 + } 288 + 289 + static bool rt_prefix(rt_ctx_t *c) { 290 + *c->ri += 2; 291 + return rt_append(c, c->str, c->position); 292 + } 270 293 271 - static void rt_suffix(rt_ctx_t *c) { 294 + static bool rt_suffix(rt_ctx_t *c) { 272 295 size_t after = c->position + c->matched_len; 273 - if (after < c->str_len) RT_APPEND(c, c->str + after, c->str_len - after); 296 + bool ok = true; 297 + if (after < c->str_len) 298 + ok = rt_append(c, c->str + after, c->str_len - after); 274 299 *c->ri += 2; 300 + return ok; 275 301 } 276 302 277 - static void rt_capture(rt_ctx_t *c) { 303 + static bool rt_capture(rt_ctx_t *c) { 278 304 char nc = c->repl[*c->ri + 1]; 279 305 int gn = nc - '0'; 280 306 *c->ri += 2; 307 + 281 308 if (*c->ri < c->repl_len && c->repl[*c->ri] >= '0' && c->repl[*c->ri] <= '9') { 282 309 int two = gn * 10 + (c->repl[*c->ri] - '0'); 283 310 if (two <= c->ncaptures) { gn = two; (*c->ri)++; } 284 311 } 285 - if (gn > 0 && gn <= c->ncaptures && c->caps[gn - 1].ptr) { 286 - RT_APPEND(c, c->caps[gn - 1].ptr, c->caps[gn - 1].len); 287 - } else if (gn == 0 || gn > c->ncaptures) { 288 - RT_APPEND(c, "$", 1); RT_APPEND(c, &nc, 1); 289 - } 312 + 313 + if (gn > 0 && gn <= c->ncaptures && c->caps[gn - 1].ptr) 314 + return rt_append(c, c->caps[gn - 1].ptr, c->caps[gn - 1].len); 315 + 316 + if (gn == 0 || gn > c->ncaptures) 317 + return rt_append(c, "$", 1) && rt_append(c, &nc, 1); 318 + 319 + return true; 290 320 } 291 321 292 - typedef void (*rt_handler_t)(rt_ctx_t *); 322 + typedef bool (*rt_handler_t)(rt_ctx_t *); 293 323 static rt_handler_t rt_dispatch[128]; 294 324 static bool rt_dispatch_init = false; 295 325 ··· 303 333 rt_dispatch_init = true; 304 334 } 305 335 306 - void repl_template( 336 + bool repl_template( 307 337 const char *repl, size_t repl_len, 308 338 const char *matched, size_t matched_len, 309 339 const char *str, size_t str_len, size_t position, ··· 324 354 unsigned char nc = (unsigned char)repl[ri + 1]; 325 355 c.ri = &ri; 326 356 rt_handler_t h = nc < 128 ? rt_dispatch[nc] : NULL; 327 - if (h) { h(&c); continue; } 357 + if (h) { if (!h(&c)) return false; continue; } 328 358 } 329 - RT_APPEND(&c, &repl[ri], 1); ri++; 359 + if (!rt_append(&c, &repl[ri], 1)) return false; 360 + ri++; 330 361 } 331 - } 332 362 333 - #undef RT_APPEND 363 + return true; 364 + } 334 365 335 366 void *try_oom(size_t size) { 336 367 void *p = malloc(size);
+22
tests/test_array_sort_gc_roots.cjs
··· 1 + const assert = require("assert"); 2 + 3 + const data = {}; 4 + for (let i = 0; i < 12000; i++) { 5 + const key = "entry-" + String(i).padStart(5, "0") + "-" + "x".repeat(32); 6 + data[key] = i % 2 === 0 ? "pass" : "fail"; 7 + } 8 + 9 + const entries = Object.entries(data).sort(); 10 + 11 + assert.equal(entries.length, 12000); 12 + assert.equal(entries[0][0], "entry-00000-" + "x".repeat(32)); 13 + assert.equal(entries[entries.length - 1][0], "entry-11999-" + "x".repeat(32)); 14 + 15 + const nums = []; 16 + for (let i = 2000; i >= 0; i--) nums.push(i); 17 + nums.sort((a, b) => a - b); 18 + 19 + assert.equal(nums[0], 0); 20 + assert.equal(nums[nums.length - 1], 2000); 21 + 22 + console.log("ok");
+22
tests/test_atomics_wait_async.cjs
··· 1 + const assert = require("assert"); 2 + 3 + (async () => { 4 + const sab = new SharedArrayBuffer(4); 5 + const view = new Int32Array(sab); 6 + 7 + Atomics.store(view, 0, 7); 8 + 9 + const timedOut = Atomics.waitAsync(view, 0, 7, 1); 10 + assert.equal(timedOut.async, true); 11 + assert.equal(await timedOut.value, "timed-out"); 12 + 13 + const pending = Atomics.waitAsync(view, 0, 7, 1000); 14 + assert.equal(pending.async, true); 15 + assert.equal(Atomics.notify(view, 0, 1), 1); 16 + assert.equal(await pending.value, "ok"); 17 + 18 + console.log("ok"); 19 + })().catch((err) => { 20 + console.error(err && err.stack ? err.stack : err); 21 + process.exit(1); 22 + });
+69
tests/test_legacy_accessors.js
··· 36 36 pass = false; 37 37 } 38 38 39 + let boxedHits = 0; 40 + String.prototype.__defineGetter__('boxedLegacyAccessorProbe', function () { 41 + boxedHits++; 42 + return 'ok'; 43 + }); 44 + 45 + try { 46 + const boxedGetter = Object.prototype.__lookupGetter__.call('abc', 'boxedLegacyAccessorProbe'); 47 + if (typeof boxedGetter !== 'function') { 48 + console.log('FAIL: primitive receivers should be boxed for __lookupGetter__'); 49 + pass = false; 50 + } 51 + 52 + const defineResult = Object.prototype.__defineGetter__.call('abc', 'ephemeral', function () { 53 + return 1; 54 + }); 55 + if (defineResult !== undefined) { 56 + console.log('FAIL: __defineGetter__ on boxed primitive should return undefined'); 57 + pass = false; 58 + } 59 + 60 + if ('abc'.boxedLegacyAccessorProbe !== 'ok' || boxedHits !== 1) { 61 + console.log('FAIL: boxed primitive getter should resolve through String.prototype'); 62 + pass = false; 63 + } 64 + } finally { 65 + delete String.prototype.boxedLegacyAccessorProbe; 66 + } 67 + 68 + let proxyDefined = false; 69 + let proxyLookedUp = false; 70 + const proxyTarget = {}; 71 + const proxy = new Proxy(proxyTarget, { 72 + defineProperty(target, key, desc) { 73 + proxyDefined = key === 'legacyProxy' && typeof desc.get === 'function'; 74 + Object.defineProperty(target, key, desc); 75 + return true; 76 + }, 77 + getOwnPropertyDescriptor(target, key) { 78 + if (key === 'legacyProxy') proxyLookedUp = true; 79 + return Object.getOwnPropertyDescriptor(target, key); 80 + } 81 + }); 82 + 83 + proxy.__defineGetter__('legacyProxy', function () { 84 + return 7; 85 + }); 86 + 87 + const proxyGetter = proxy.__lookupGetter__('legacyProxy'); 88 + if (!proxyDefined) { 89 + console.log('FAIL: proxy __defineGetter__ should go through defineProperty semantics'); 90 + pass = false; 91 + } 92 + 93 + if (!proxyLookedUp) { 94 + console.log('FAIL: proxy __lookupGetter__ should consult getOwnPropertyDescriptor'); 95 + pass = false; 96 + } 97 + 98 + if (typeof proxyGetter !== 'function') { 99 + console.log('FAIL: proxy __lookupGetter__ should return the getter function'); 100 + pass = false; 101 + } 102 + 103 + if (proxy.legacyProxy !== 7) { 104 + console.log('FAIL: proxy getter installed with __defineGetter__ should be used'); 105 + pass = false; 106 + } 107 + 39 108 if (pass) console.log('PASS');
+40
tests/test_proxy_get_topropertykey.cjs
··· 1 + let pass = true; 2 + 3 + const key = Symbol("k"); 4 + let coercions = 0; 5 + let trappedKey = null; 6 + 7 + const propKey = { 8 + [Symbol.toPrimitive](hint) { 9 + if (hint !== "string") { 10 + console.log("FAIL: property key @@toPrimitive should use string hint"); 11 + pass = false; 12 + } 13 + coercions++; 14 + return key; 15 + } 16 + }; 17 + 18 + const proxy = new Proxy({ [key]: 123 }, { 19 + get(target, actualKey, receiver) { 20 + trappedKey = actualKey; 21 + return Reflect.get(target, actualKey, receiver); 22 + } 23 + }); 24 + 25 + if (proxy[propKey] !== 123) { 26 + console.log("FAIL: proxy get should use symbol returned from @@toPrimitive"); 27 + pass = false; 28 + } 29 + 30 + if (trappedKey !== key) { 31 + console.log("FAIL: proxy get trap should receive symbol property key"); 32 + pass = false; 33 + } 34 + 35 + if (coercions !== 1) { 36 + console.log("FAIL: property key @@toPrimitive should run exactly once"); 37 + pass = false; 38 + } 39 + 40 + if (pass) console.log("PASS");
+23
tests/test_proxy_toprimitive_get_trap.cjs
··· 1 + let pass = true; 2 + 3 + const seen = []; 4 + const proxy = new Proxy({ toString: Function() }, { 5 + get(target, key, receiver) { 6 + seen.push(key); 7 + return Reflect.get(target, key, receiver); 8 + } 9 + }); 10 + 11 + void (proxy + 3); 12 + 13 + if (seen[0] !== Symbol.toPrimitive) { 14 + console.log("FAIL: proxy get trap should observe Symbol.toPrimitive first"); 15 + pass = false; 16 + } 17 + 18 + if (String(seen.slice(1)) !== "valueOf,toString") { 19 + console.log("FAIL: proxy get trap should observe valueOf then toString during ToPrimitive"); 20 + pass = false; 21 + } 22 + 23 + if (pass) console.log("PASS");
+37
tests/test_reflect_proxy_get_toprimitive.cjs
··· 1 + let pass = true; 2 + 3 + const key = Symbol("reflect-key"); 4 + let seenHint = null; 5 + let trappedKey = null; 6 + 7 + const propertyKeyObject = { 8 + [Symbol.toPrimitive](hint) { 9 + seenHint = hint; 10 + return key; 11 + } 12 + }; 13 + 14 + const proxy = new Proxy({ [key]: 42 }, { 15 + get(target, actualKey, receiver) { 16 + trappedKey = actualKey; 17 + return Reflect.get(target, actualKey, receiver); 18 + } 19 + }); 20 + 21 + const value = Reflect.get(proxy, propertyKeyObject); 22 + if (value !== 42) { 23 + console.log("FAIL: Reflect.get should read using the property key from @@toPrimitive"); 24 + pass = false; 25 + } 26 + 27 + if (seenHint !== "string") { 28 + console.log("FAIL: Reflect.get property key coercion should use string hint"); 29 + pass = false; 30 + } 31 + 32 + if (trappedKey !== key) { 33 + console.log("FAIL: proxy get trap should receive the symbol returned by @@toPrimitive"); 34 + pass = false; 35 + } 36 + 37 + if (pass) console.log("PASS");
+15
tests/test_regexp_replace_large_template.cjs
··· 1 + const assert = require("assert"); 2 + 3 + const chunk = "abc-123-def-456-ghi-789"; 4 + const input = Array(2048).fill(chunk).join("|"); 5 + const re = /([a-z]+)-(\d+)-([a-z]+)-(\d+)-([a-z]+)-(\d+)/g; 6 + const replacement = "$1:$2:$3:$4:$5:$6::" + "X".repeat(2048) + "::$&::$`::$'"; 7 + 8 + const out = input.replace(re, replacement); 9 + 10 + assert.equal(typeof out, "string"); 11 + assert.ok(out.includes("abc:123:def:456:ghi:789")); 12 + assert.ok(out.includes("X".repeat(256))); 13 + assert.ok(out.length > input.length); 14 + 15 + console.log("ok");
+30
tests/test_string_large_space_stats.cjs
··· 1 + const assert = require("assert"); 2 + 3 + const before = Ant.stats().pools.string; 4 + 5 + assert.equal(typeof before.used, "number"); 6 + assert.equal(typeof before.capacity, "number"); 7 + assert.equal(typeof before.blocks, "number"); 8 + assert.equal(typeof before.pooled.used, "number"); 9 + assert.equal(typeof before.largeLive.capacity, "number"); 10 + assert.equal(typeof before.largeReusable.capacity, "number"); 11 + assert.equal(typeof before.largeQuarantine.capacity, "number"); 12 + 13 + const large = "x".repeat(256 * 1024); 14 + assert.equal(large.length, 256 * 1024); 15 + 16 + const after = Ant.stats().pools.string; 17 + 18 + assert.ok(after.capacity >= before.capacity); 19 + assert.ok(after.largeLive.capacity >= before.largeLive.capacity); 20 + assert.ok(after.largeLive.blocks >= before.largeLive.blocks); 21 + 22 + const rope = large + large; 23 + assert.equal(rope.length, large.length * 2); 24 + 25 + const afterRope = Ant.stats().pools.string; 26 + assert.equal(typeof afterRope.largeReusable.blocks, "number"); 27 + assert.equal(typeof afterRope.largeQuarantine.blocks, "number"); 28 + assert.ok(afterRope.capacity >= after.capacity); 29 + 30 + console.log("ok");