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.

gc roots

+238 -141
+1 -11
examples/spec/ant.js
··· 47 47 48 48 test('gc is function', typeof Ant.gc, 'function'); 49 49 const gcResult = Ant.gc(); 50 - test('gc returns object', typeof gcResult, 'object'); 51 - test('gc has heapBefore', typeof gcResult.heapBefore, 'number'); 52 - test('gc has heapAfter', typeof gcResult.heapAfter, 'number'); 53 - test('gc has freed', typeof gcResult.freed, 'number'); 54 - 55 - test('alloc is function', typeof Ant.alloc, 'function'); 56 - const allocResult = Ant.alloc(); 57 - test('alloc returns object', typeof allocResult, 'object'); 58 - test('alloc has arenaSize', typeof allocResult.arenaSize, 'number'); 59 - test('alloc has heapSize', typeof allocResult.heapSize, 'number'); 60 - test('alloc has freeBytes', typeof allocResult.freeBytes, 'number'); 50 + test('gc returns undefined', gcResult, undefined); 61 51 62 52 test('stats is function', typeof Ant.stats, 'function'); 63 53 test('raw is object', typeof Ant.raw, 'object');
+7
include/ant.h
··· 29 29 typedef struct js ant_t; 30 30 typedef unsigned long long u64; 31 31 32 + typedef int jshdl_t; 32 33 typedef uint64_t jsoff_t; 33 34 typedef uint64_t jsval_t; 34 35 ··· 85 86 void js_set_filename(ant_t *, const char *); 86 87 void js_stats(ant_t *, size_t *total, size_t *min, size_t *cstacksize); 87 88 size_t js_getbrk(ant_t *); 89 + 90 + jshdl_t js_root(ant_t *, jsval_t); 91 + jsval_t js_deref(ant_t *, jshdl_t); 92 + 93 + void js_unroot(ant_t *, jshdl_t); 94 + void js_root_update(ant_t *, jshdl_t, jsval_t); 88 95 89 96 jsval_t js_mkundef(void); 90 97 jsval_t js_mknull(void);
+7
include/gc.h
··· 1 + #ifndef GC_H 2 + #define GC_H 3 + 4 + #define GC_FWD_LOAD_FACTOR 70 5 + #define GC_ROOTS_INITIAL_CAP 32 6 + 7 + #endif
+5 -1
include/internal.h
··· 52 52 uint64_t sym_counter; // counter for generating unique symbol IDs 53 53 bool needs_gc; // deferred GC flag, checked at statement boundaries 54 54 bool gc_suppress; // suppress GC during microtask batch processing 55 + jsoff_t gc_alloc_since; // bytes allocated since last GC 55 56 int eval_depth; // recursion depth of js_eval calls 56 57 int parse_depth; // recursion depth of parser (for stack overflow protection) 57 58 bool skip_func_hoist; // skip function declaration hoisting (pre-computed) 58 59 bool fatal_error; // fatal error that should bypass promise rejection handling 59 60 60 - // for-let loop context stack 61 61 struct for_let_ctx *for_let_stack; 62 62 int for_let_stack_len; 63 63 int for_let_stack_cap; 64 + 65 + jsval_t *gc_roots; 66 + int gc_roots_len; 67 + int gc_roots_cap; 64 68 }; 65 69 66 70 enum {
+2
include/modules/process.h
··· 6 6 void init_process_module(void); 7 7 void process_gc_update_roots(GC_FWD_ARGS); 8 8 9 + bool has_active_stdin(void); 10 + 9 11 #endif
+12
include/roots.h
··· 1 + #ifndef ROOTS_H 2 + #define ROOTS_H 3 + 4 + #include "ant.h" 5 + 6 + jshdl_t js_root(struct js *js, jsval_t val); 7 + jsval_t js_deref(struct js *js, jshdl_t h); 8 + 9 + void js_unroot(struct js *js, jshdl_t h); 10 + void js_root_update(struct js *js, jshdl_t h, jsval_t val); 11 + 12 + #endif
+1
libant/meson.build
··· 22 22 ).stdout().strip().split() 23 23 24 24 lib_sources = files( 25 + '../src/roots.c', 25 26 '../src/utils.c', 26 27 '../src/ant.c', 27 28 '../src/gc.c',
+1
meson.build
··· 21 21 ).stdout().strip().split() 22 22 23 23 lib_sources = files( 24 + 'src/roots.c', 24 25 'src/utils.c', 25 26 'src/ant.c', 26 27 'src/gc.c',
+108 -50
src/ant.c
··· 1105 1105 enqueue_coroutine(temp); 1106 1106 } else free_coroutine(temp); 1107 1107 } 1108 + 1109 + if (js->needs_gc && !js->gc_suppress) { 1110 + js->needs_gc = false; 1111 + js_gc_compact(js); 1112 + js->gc_alloc_since = 0; 1113 + } 1108 1114 } 1109 1115 1110 1116 typedef enum { ··· 1117 1123 WORK_FS_OPS = 1 << 6, 1118 1124 WORK_CHILD_PROCS = 1 << 7, 1119 1125 WORK_READLINE = 1 << 8, 1126 + WORK_STDIN = 1 << 9, 1120 1127 } work_flags_t; 1121 1128 1122 1129 static inline work_flags_t get_pending_work(void) { ··· 1130 1137 if (has_pending_fs_ops()) flags |= WORK_FS_OPS; 1131 1138 if (has_pending_child_processes()) flags |= WORK_CHILD_PROCS; 1132 1139 if (has_active_readline_interfaces()) flags |= WORK_READLINE; 1140 + if (has_active_stdin()) flags |= WORK_STDIN; 1133 1141 return flags; 1134 1142 } 1135 1143 1136 1144 #define WORK_TASKS (WORK_MICROTASKS | WORK_TIMERS | WORK_IMMEDIATES | WORK_COROUTINES | WORK_FETCHES) 1137 - #define WORK_PENDING (WORK_TASKS | WORK_FS_OPS | WORK_CHILD_PROCS | WORK_READLINE) 1145 + #define WORK_PENDING (WORK_TASKS | WORK_FS_OPS | WORK_CHILD_PROCS | WORK_READLINE | WORK_STDIN) 1138 1146 #define WORK_BLOCKING (WORK_MICROTASKS | WORK_IMMEDIATES | WORK_COROUTINES_READY) 1139 1147 1140 1148 void js_run_event_loop(struct js *js) { ··· 1144 1152 js_poll_events(js); 1145 1153 work = get_pending_work(); 1146 1154 1147 - if (work & WORK_READLINE) { 1155 + if (work & (WORK_READLINE | WORK_STDIN)) { 1148 1156 uv_run(uv_default_loop(), UV_RUN_NOWAIT); 1149 1157 } 1150 1158 1151 1159 if (!(work & WORK_BLOCKING) && (work & WORK_TIMERS)) { 1160 + if (js->gc_alloc_since > 256 * 1024 * 1024 || js->needs_gc) { 1161 + js->needs_gc = false; 1162 + js_gc_compact(js); 1163 + js->gc_alloc_since = 0; 1164 + } 1152 1165 int64_t ms = get_next_timer_timeout(); 1153 1166 if (ms > 0) usleep(ms > 1000 ? 1000000 : (useconds_t)(ms * 1000)); 1154 - } else if ((work & WORK_READLINE) && !(work & WORK_BLOCKING)) uv_run(uv_default_loop(), UV_RUN_ONCE); 1167 + } else if ( 1168 + (work & (WORK_READLINE | WORK_STDIN)) && !(work & WORK_BLOCKING) 1169 + ) uv_run(uv_default_loop(), UV_RUN_ONCE); 1155 1170 } 1156 1171 1157 1172 js_poll_events(js); ··· 2777 2792 return true; 2778 2793 } 2779 2794 2780 - static jsoff_t js_alloc(struct js *js, size_t size) { 2795 + static inline bool js_has_space(struct js *js, size_t size) { 2796 + return js->brk + size <= js->size; 2797 + } 2798 + 2799 + static bool js_ensure_space(struct js *js, size_t size) { 2800 + if (js_has_space(js, size)) return true; 2801 + if (js_try_grow_memory(js, size) && js_has_space(js, size)) return true; 2802 + 2803 + js->needs_gc = true; 2804 + 2805 + if (js_has_space(js, size)) return true; 2806 + if (js_try_grow_memory(js, size) && js_has_space(js, size)) return true; 2807 + 2808 + return false; 2809 + } 2810 + 2811 + static void js_track_allocation(struct js *js, size_t size) { 2812 + js->brk += (jsoff_t) size; 2813 + js->gc_alloc_since += (jsoff_t) size; 2814 + 2815 + if (js->gc_alloc_since > 256 * 1024 * 1024) js->needs_gc = true; 2816 + } 2817 + 2818 + static inline jsoff_t js_alloc(struct js *js, size_t size) { 2781 2819 size = align64((jsoff_t) size); 2782 - 2820 + if (!js_ensure_space(js, size)) return ~(jsoff_t) 0; 2821 + 2783 2822 jsoff_t ofs = js->brk; 2784 - if (js->brk + size > js->size) { 2785 - if (js_try_grow_memory(js, size)) { 2786 - ofs = js->brk; 2787 - if (js->brk + size > js->size) return ~(jsoff_t) 0; 2788 - } else { 2789 - // js_gc_compact(js); 2790 - ofs = js->brk; 2791 - if (js->brk + size > js->size) { 2792 - if (js_try_grow_memory(js, size)) { 2793 - ofs = js->brk; 2794 - if (js->brk + size > js->size) return ~(jsoff_t) 0; 2795 - } else return ~(jsoff_t) 0; 2796 - } 2797 - } 2798 - } 2823 + js_track_allocation(js, size); 2799 2824 2800 - js->brk += (jsoff_t) size; 2801 2825 return ofs; 2802 2826 } 2803 2827 ··· 10712 10736 } 10713 10737 10714 10738 static jsval_t for_of_iter_array(struct js *js, for_iter_ctx_t *ctx, jsval_t iterable) { 10715 - jsoff_t next_prop_off = loadoff(js, (jsoff_t) vdata(iterable)) & ~(3U | FLAGMASK); 10716 - jsoff_t length = 0, scan = next_prop_off; 10739 + jshdl_t h_iterable = js_root(js, iterable); 10717 10740 10718 - while (scan < js->brk && scan != 0) { 10719 - jsoff_t header = loadoff(js, scan); 10720 - if (is_slot_prop(header)) { scan = next_prop(header); continue; } 10721 - const char *key; jsoff_t klen; 10722 - get_prop_key(js, scan, &key, &klen); 10723 - if (streq(key, klen, "length", 6)) { 10724 - jsval_t val = get_prop_val(js, scan); 10725 - if (vtype(val) == T_NUM) length = (jsoff_t) tod(val); 10726 - break; 10741 + jsoff_t length = 0; { 10742 + jsval_t arr = js_deref(js, h_iterable); 10743 + jsoff_t next_prop_off = loadoff(js, (jsoff_t) vdata(arr)) & ~(3U | FLAGMASK); 10744 + jsoff_t scan = next_prop_off; 10745 + while (scan < js->brk && scan != 0) { 10746 + jsoff_t header = loadoff(js, scan); 10747 + if (is_slot_prop(header)) { scan = next_prop(header); continue; } 10748 + const char *key; jsoff_t klen; 10749 + get_prop_key(js, scan, &key, &klen); 10750 + if (streq(key, klen, "length", 6)) { 10751 + jsval_t val = get_prop_val(js, scan); 10752 + if (vtype(val) == T_NUM) length = (jsoff_t) tod(val); 10753 + break; 10754 + } 10755 + scan = next_prop(header); 10727 10756 } 10728 - scan = next_prop(header); 10729 10757 } 10730 10758 10731 10759 for (jsoff_t i = 0; i < length; i++) { 10760 + jsval_t arr = js_deref(js, h_iterable); 10761 + jsoff_t next_prop_off = loadoff(js, (jsoff_t) vdata(arr)) & ~(3U | FLAGMASK); 10762 + 10732 10763 char idx[16]; 10733 10764 snprintf(idx, sizeof(idx), "%u", (unsigned) i); 10734 10765 jsoff_t idxlen = (jsoff_t) strlen(idx); ··· 10745 10776 } 10746 10777 10747 10778 jsval_t err = for_iter_bind_var(js, ctx, val); 10748 - if (is_err(err)) return err; 10779 + if (is_err(err)) { js_unroot(js, h_iterable); return err; } 10749 10780 10750 10781 jsval_t v = for_iter_exec_body(js, ctx); 10751 - if (is_err(v)) return v; 10782 + if (is_err(v)) { js_unroot(js, h_iterable); return v; } 10752 10783 if (for_iter_handle_continue(js, ctx)) break; 10753 10784 if (js->flags & F_BREAK) break; 10754 - if (js->flags & F_RETURN) return v; 10785 + if (js->flags & F_RETURN) { js_unroot(js, h_iterable); return v; } 10755 10786 } 10756 10787 10788 + js_unroot(js, h_iterable); 10757 10789 return js_mkundef(); 10758 10790 } 10759 10791 10760 10792 static jsval_t for_of_iter_string(struct js *js, for_iter_ctx_t *ctx, jsval_t iterable) { 10761 - jsoff_t slen, soff = vstr(js, iterable, &slen); 10762 - const char *str = (char *) &js->mem[soff]; 10793 + jshdl_t h_iterable = js_root(js, iterable); 10794 + jsoff_t slen; 10795 + (void) vstr(js, iterable, &slen); 10763 10796 10764 10797 for (jsoff_t i = 0; i < slen; i++) { 10798 + jsval_t cur = js_deref(js, h_iterable); 10799 + jsoff_t soff = vstr(js, cur, NULL); 10800 + const char *str = (char *) &js->mem[soff]; 10765 10801 jsval_t char_str = js_mkstr(js, &str[i], 1); 10766 10802 10767 10803 jsval_t err = for_iter_bind_var(js, ctx, char_str); 10768 - if (is_err(err)) return err; 10804 + if (is_err(err)) { js_unroot(js, h_iterable); return err; } 10769 10805 10770 10806 jsval_t v = for_iter_exec_body(js, ctx); 10771 - if (is_err(v)) return v; 10807 + if (is_err(v)) { js_unroot(js, h_iterable); return v; } 10772 10808 if (for_iter_handle_continue(js, ctx)) break; 10773 10809 if (js->flags & F_BREAK) break; 10774 - if (js->flags & F_RETURN) return v; 10810 + if (js->flags & F_RETURN) { js_unroot(js, h_iterable); return v; } 10775 10811 } 10776 10812 10813 + js_unroot(js, h_iterable); 10777 10814 return js_mkundef(); 10778 10815 } 10779 10816 ··· 10798 10835 10799 10836 if (is_err(iterator)) return iterator; 10800 10837 10838 + jshdl_t h_iterator = js_root(js, iterator); 10801 10839 jsval_t out = js_mkundef(); 10840 + 10802 10841 while (true) { 10803 - jsoff_t next_off = lkp_proto(js, iterator, "next", 4); 10804 - if (next_off == 0) return js_mkerr(js, "iterator.next is not a function"); 10842 + jsval_t cur_iter = js_deref(js, h_iterator); 10843 + jsoff_t next_off = lkp_proto(js, cur_iter, "next", 4); 10844 + if (next_off == 0) { js_unroot(js, h_iterator); return js_mkerr(js, "iterator.next is not a function"); } 10805 10845 10806 10846 jsval_t next_method = loadval(js, next_off + sizeof(jsoff_t) * 2); 10807 - if (vtype(next_method) != T_FUNC && vtype(next_method) != T_CFUNC) 10847 + if (vtype(next_method) != T_FUNC && vtype(next_method) != T_CFUNC) { 10848 + js_unroot(js, h_iterator); 10808 10849 return js_mkerr(js, "iterator.next is not a function"); 10850 + } 10809 10851 10810 - push_this(iterator); 10852 + cur_iter = js_deref(js, h_iterator); 10853 + push_this(cur_iter); 10811 10854 jsval_t result = call_js_with_args(js, next_method, NULL, 0); 10812 10855 pop_this(); 10813 10856 JS_RESTORE_STATE(js, saved_state); 10814 10857 js->flags = saved_flags; 10815 10858 10816 - if (is_err(result)) return result; 10859 + if (is_err(result)) { js_unroot(js, h_iterator); return result; } 10817 10860 10818 10861 jsoff_t done_off = lkp(js, result, "done", 4); 10819 10862 jsval_t done_val = done_off ? loadval(js, done_off + sizeof(jsoff_t) * 2) : js_mkundef(); ··· 10824 10867 10825 10868 iter_action_t action = cb(js, value, ctx, &out); 10826 10869 if (action == ITER_BREAK) break; 10827 - if (action == ITER_ERROR) return out; 10870 + if (action == ITER_ERROR) { js_unroot(js, h_iterator); return out; } 10828 10871 } 10829 10872 10873 + js_unroot(js, h_iterator); 10830 10874 return out; 10831 10875 } 10832 10876 ··· 23174 23218 op_val(c, &c->js->for_let_stack[i].body_scope); 23175 23219 op_off(c, &c->js->for_let_stack[i].prop_off); 23176 23220 } 23221 + 23222 + for (int i = 0; i < c->js->gc_roots_len; i++) op_val(c, &c->js->gc_roots[i]); 23177 23223 } 23178 23224 23179 23225 void js_gc_reserve_roots(GC_UPDATE_ARGS) { ··· 23346 23392 res = js_stmt(js); 23347 23393 if (js->needs_gc && js->eval_depth == 1 && !js->gc_suppress) { 23348 23394 js->needs_gc = false; 23349 - // js_gc_compact(js); 23395 + js_gc_compact(js); 23350 23396 } 23351 23397 if (js->flags & F_RETURN) break; 23352 23398 } ··· 23356 23402 return res; 23357 23403 } 23358 23404 23359 - jsval_t js_eval(struct js *js, const char *buf, size_t len) { 23405 + inline jsval_t js_eval(struct js *js, const char *buf, size_t len) { 23360 23406 return js_eval_inherit_strict(js, buf, len, false); 23361 23407 } 23362 23408 23363 23409 static jsval_t js_call_internal(struct js *js, jsval_t func, jsval_t bound_this, jsval_t *args, int nargs, bool use_bound_this) { 23410 + bool saved_gc_suppress = js->gc_suppress; 23411 + js->gc_suppress = true; 23412 + 23364 23413 if (vtype(func) == T_FFI) { 23365 - return ffi_call_by_index(js, (unsigned int)vdata(func), args, nargs); 23414 + jsval_t res = ffi_call_by_index(js, (unsigned int)vdata(func), args, nargs); 23415 + js->gc_suppress = saved_gc_suppress; 23416 + return res; 23366 23417 } else if (vtype(func) == T_CFUNC) { 23367 23418 jsval_t saved_this = js->this_val; 23368 23419 if (use_bound_this) js->this_val = bound_this; 23369 23420 jsval_t (*fn)(struct js *, jsval_t *, int) = (jsval_t(*)(struct js *, jsval_t *, int)) vdata(func); 23370 23421 jsval_t res = fn(js, args, nargs); 23371 23422 js->this_val = saved_this; 23423 + js->gc_suppress = saved_gc_suppress; 23372 23424 return res; 23373 23425 } else if (vtype(func) == T_FUNC) { 23374 23426 jsval_t func_obj = mkval(T_OBJ, vdata(func)); ··· 23396 23448 js->this_val = saved_this; 23397 23449 23398 23450 if (combined_args) free(combined_args); 23451 + js->gc_suppress = saved_gc_suppress; 23399 23452 return res; 23400 23453 } 23401 23454 ··· 23417 23470 jsval_t closure_scope = get_slot(js, func_obj, SLOT_SCOPE); 23418 23471 jsval_t res = start_async_in_coroutine(js, fn, fnlen, closure_scope, final_args, final_nargs); 23419 23472 if (combined_args) free(combined_args); 23473 + js->gc_suppress = saved_gc_suppress; 23420 23474 return res; 23421 23475 } 23422 23476 ··· 23434 23488 delscope(js); 23435 23489 js->scope = saved_scope; 23436 23490 if (combined_args) free(combined_args); 23491 + js->gc_suppress = saved_gc_suppress; 23437 23492 return js_mkerr(js, "failed to parse function"); 23438 23493 } 23439 23494 ··· 23502 23557 js->scope = saved_scope; 23503 23558 if (combined_args) free(combined_args); 23504 23559 23560 + js->gc_suppress = saved_gc_suppress; 23505 23561 return res; 23506 23562 } 23563 + 23564 + js->gc_suppress = saved_gc_suppress; 23507 23565 return js_mkerr(js, "not a function"); 23508 23566 } 23509 23567
+19 -3
src/gc.c
··· 1 + #include "gc.h" 1 2 #include "arena.h" 2 3 #include "internal.h" 3 4 #include "common.h" ··· 5 6 #include <string.h> 6 7 #include <stdlib.h> 7 8 #include <stdio.h> 9 + #include <time.h> 8 10 9 11 #ifdef _WIN32 10 12 #include <windows.h> ··· 29 31 #define MCO_API extern 30 32 #include "minicoro.h" 31 33 32 - #define GC_FWD_LOAD_FACTOR 70 33 - 34 34 static uint8_t *gc_scratch_buf = NULL; 35 35 static size_t gc_scratch_size = 0; 36 + static time_t gc_last_run_time = 0; 36 37 37 38 #define FWD_EMPTY ((jsoff_t)~0) 38 39 #define FWD_TOMBSTONE ((jsoff_t)~1) ··· 507 508 508 509 size_t js_gc_compact(ant_t *js) { 509 510 if (!js || js->brk == 0) return 0; 511 + if (js->brk < 2 * 1024 * 1024) return 0; 512 + 513 + time_t now = time(NULL); 514 + if (now != (time_t)-1 && gc_last_run_time != 0) { 515 + double elapsed = difftime(now, gc_last_run_time); 516 + if (elapsed >= 0.0 && elapsed < 5.0) return 0; 517 + } 510 518 511 519 mco_coro *running = mco_running(); 512 520 int in_coroutine = (running != NULL && running->stack_base != NULL); 521 + 513 522 if (in_coroutine || js_has_pending_coroutines()) return 0; 523 + if (now != (time_t)-1) gc_last_run_time = now; 514 524 515 525 size_t old_brk = js->brk; 516 526 size_t new_size = js->size; ··· 566 576 (void)gc_update_val(&ctx, js->current_func); 567 577 (void)gc_update_val(&ctx, js->thrown_value); 568 578 (void)gc_update_val(&ctx, js->tval); 569 - js_gc_reserve_roots(js, gc_fwd_off_callback, gc_fwd_val_callback, &ctx); 579 + 580 + js_gc_reserve_roots( 581 + js, 582 + gc_fwd_off_callback, 583 + gc_fwd_val_callback, 584 + &ctx 585 + ); 570 586 571 587 gc_drain_work_queue(&ctx); 572 588
+3 -12
src/modules/builtin.c
··· 1 + #include "ant.h" 1 2 #include <compat.h> // IWYU pragma: keep 2 3 3 4 #include <stdlib.h> ··· 53 54 54 55 // Ant.gc() 55 56 static jsval_t js_gc_trigger(struct js *js, jsval_t *args, int nargs) { 56 - (void) args; (void) nargs; 57 - 58 - size_t arena_before = js_getbrk(js); 59 - size_t arena_freed = js_gc_compact(js); 60 - size_t arena_after = js_getbrk(js); 61 - 62 - jsval_t result = js_newobj(js); 63 - js_set(js, result, "arenaBefore", js_mknum((double)arena_before)); 64 - js_set(js, result, "arenaAfter", js_mknum((double)arena_after)); 65 - js_set(js, result, "arenaFreed", js_mknum((double)arena_freed)); 66 - 67 - return result; 57 + js->needs_gc = true; 58 + return js_mkundef(); 68 59 } 69 60 70 61 // Ant.raw.typeof(jsval_t)
+2
src/modules/process.c
··· 1428 1428 } 1429 1429 1430 1430 #undef GC_FWD_EVENTS 1431 + 1432 + bool has_active_stdin(void) { return stdin_reading; }
+30
src/roots.c
··· 1 + #include "ant.h" 2 + #include "gc.h" 3 + #include "arena.h" 4 + #include "internal.h" 5 + 6 + jshdl_t js_root(struct js *js, jsval_t val) { 7 + if (js->gc_roots_len >= js->gc_roots_cap) { 8 + int new_cap = js->gc_roots_cap ? js->gc_roots_cap * 2 : GC_ROOTS_INITIAL_CAP; 9 + jsval_t *new_roots = ant_realloc(js->gc_roots, new_cap * sizeof(jsval_t)); 10 + if (!new_roots) return -1; 11 + js->gc_roots = new_roots; 12 + js->gc_roots_cap = new_cap; 13 + } 14 + int idx = js->gc_roots_len++; 15 + js->gc_roots[idx] = val; 16 + return idx; 17 + } 18 + 19 + jsval_t js_deref(struct js *js, jshdl_t h) { 20 + if (h < 0 || h >= js->gc_roots_len) return js_mkundef(); 21 + return js->gc_roots[h]; 22 + } 23 + 24 + void js_unroot(struct js *js, jshdl_t h) { 25 + if (h >= 0 && h == js->gc_roots_len - 1) js->gc_roots_len--; 26 + } 27 + 28 + void js_root_update(struct js *js, jshdl_t h, jsval_t val) { 29 + if (h >= 0 && h < js->gc_roots_len) js->gc_roots[h] = val; 30 + }
+15 -41
tests/bench_gc.js
··· 1 - // GC Benchmark - tests forwarding table and compaction performance 2 - 3 1 function fmt(bytes) { 4 2 if (bytes < 1024) return bytes + ' B'; 5 3 if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'; ··· 7 5 } 8 6 9 7 function bench(name, fn, iterations = 1) { 10 - // Warmup 11 8 fn(); 12 - Ant.gc(); 13 - 14 - let totalTime = 0; 15 - let totalFreed = 0; 16 9 17 - for (let iter = 0; iter < iterations; iter++) { 18 - fn(); // Create garbage 19 - 20 - const t0 = Date.now(); 21 - const result = Ant.gc(); 22 - const elapsed = Date.now() - t0; 10 + const before = Ant.stats().arenaUsed; 11 + for (let iter = 0; iter < iterations; iter++) fn(); 12 + const afterAlloc = Ant.stats().arenaUsed; 23 13 24 - totalTime += elapsed; 25 - totalFreed += result.arenaFreed; 26 - } 14 + console.log(' Waiting 5s before GC...'); 15 + Ant.sleep(5); 16 + Ant.gc(); 27 17 28 18 console.log(`${name}:`); 29 - console.log(` Time: ${totalTime}ms (${iterations} iterations, avg ${(totalTime / iterations).toFixed(2)}ms)`); 30 - console.log(` Freed: ${fmt(totalFreed)} total`); 19 + console.log(` Before: ${fmt(before)}, After alloc: ${fmt(afterAlloc)}`); 20 + console.log(` Allocated: ${fmt(afterAlloc - before)} (${iterations} iterations)`); 31 21 console.log(''); 32 22 } 33 23 34 - // Test 1: Many small objects (stresses forwarding table with many entries) 35 24 function manySmallObjects() { 36 25 const objects = []; 37 26 for (let i = 0; i < 1000; i++) { 38 27 objects.push({ id: i, value: i * 2 }); 39 28 } 40 - // Let half become garbage 41 - objects.length = 5000; 29 + objects.length = 500; 42 30 } 43 31 44 - // Test 2: Deep object graph (stresses recursive copying) 45 32 function deepObjectGraph() { 46 33 let obj = { value: 0 }; 47 34 for (let i = 1; i < 1000; i++) { ··· 50 37 return obj; 51 38 } 52 39 53 - // Test 3: Wide object (many properties) 54 40 function wideObject() { 55 41 const obj = {}; 56 42 for (let i = 0; i < 1000; i++) { ··· 59 45 return obj; 60 46 } 61 47 62 - // Test 4: Array of strings (tests string copying) 63 48 function stringArray() { 64 49 const arr = []; 65 50 for (let i = 0; i < 5000; i++) { 66 51 arr.push('string_value_' + i + '_with_some_extra_content'); 67 52 } 68 - arr.length = 2500; // Half become garbage 53 + arr.length = 2500; 69 54 } 70 55 71 - // Test 5: Mixed workload 72 56 function mixedWorkload() { 73 57 const data = { 74 58 objects: [], ··· 81 65 data.strings.push('item_' + i); 82 66 } 83 67 84 - // Create some nested structure 85 68 let nested = { level: 0 }; 86 69 for (let i = 1; i < 100; i++) { 87 70 nested = { level: i, prev: nested, data: { a: i, b: i * 2 } }; 88 71 } 89 72 data.nested = nested; 90 73 91 - // Make some garbage 92 74 data.objects.length = 1000; 93 75 data.strings.length = 1000; 94 76 } 95 77 96 - // Test 6: Repeated GC cycles (tests table reuse efficiency) 97 - function repeatedGcCycles() { 98 - for (let cycle = 0; cycle < 10; cycle++) { 99 - const temp = []; 100 - for (let i = 0; i < 1000; i++) { 101 - temp.push({ cycle, i, data: 'x'.repeat(50) }); 102 - } 103 - } 104 - } 105 - 106 78 console.log('=== GC Benchmark ===\n'); 107 79 108 80 let initial = Ant.stats(); ··· 111 83 console.log(' rss:', fmt(initial.rss)); 112 84 console.log(''); 113 85 114 - bench('Many small objects (10k objects, 5k garbage)', manySmallObjects, 5); 86 + bench('Many small objects (1k objects)', manySmallObjects, 5); 115 87 bench('Deep object graph (1000 levels)', deepObjectGraph, 5); 116 88 bench('Wide object (1000 properties)', wideObject, 5); 117 - bench('String array (5000 strings, 2500 garbage)', stringArray, 5); 89 + bench('String array (5000 strings)', stringArray, 5); 118 90 bench('Mixed workload', mixedWorkload, 5); 119 - bench('Repeated GC cycles (10 cycles x 1000 objects)', repeatedGcCycles, 3); 120 91 121 92 let final = Ant.stats(); 122 93 console.log('Final state:'); 123 94 console.log(' arenaUsed:', fmt(final.arenaUsed)); 124 95 console.log(' rss:', fmt(final.rss)); 96 + console.log(''); 97 + console.log('Note: GC runs automatically at safe points.'); 98 + console.log('Use Ant.gc() as a hint to request collection.');
+4 -2
tests/delete.js
··· 4 4 active: true 5 5 }; 6 6 7 - console.log(Ant.gc()); 7 + Ant.gc(); 8 + console.log('GC requested'); 8 9 9 10 console.log(user.age); // Output: 30 10 11 ··· 12 13 13 14 console.log(user.age); // Output: undefined 14 15 console.log(user); // Output: { name: 'Alice', active: true } 15 - console.log(Ant.gc()); 16 + Ant.gc(); 17 + console.log('GC requested');
+2 -2
tests/test_async_gc.js
··· 6 6 console.log('Before await, data length:', data.length); 7 7 8 8 await new Promise(resolve => setTimeout(resolve, 10)); 9 - console.log(Ant.gc()); 9 + Ant.gc(); 10 10 11 11 console.log('After await+GC, data length:', data.length); 12 12 return data.length; ··· 24 24 25 25 main().then(() => { 26 26 console.log('=== async done, calling gc ==='); 27 - console.log(Ant.gc()); 27 + Ant.gc(); 28 28 });
+3 -8
tests/test_gc.js
··· 27 27 28 28 console.log('\n=== Testing Ant.gc() ==='); 29 29 arr = null; 30 - 31 - let gcResult = Ant.gc(); 32 - console.log('GC result:'); 33 - console.log(' arenaBefore:', fmt(gcResult.arenaBefore)); 34 - console.log(' arenaAfter:', fmt(gcResult.arenaAfter)); 35 - console.log(' arenaFreed:', fmt(gcResult.arenaFreed)); 30 + Ant.gc(); 36 31 37 32 console.log('\n=== Stats after GC ==='); 38 33 let stats3 = Ant.stats(); ··· 53 48 console.log(' Before GC - arenaUsed:', fmt(before.arenaUsed)); 54 49 55 50 temp = null; 56 - let gc = Ant.gc(); 57 - console.log(' After GC - arenaFreed:', fmt(gc.arenaFreed)); 51 + Ant.gc(); 52 + console.log(' After GC - arenaUsed:', fmt(Ant.stats().arenaUsed)); 58 53 } 59 54 60 55 console.log('\n=== Final stats ===');
+1 -2
tests/test_gc_cycles.js
··· 6 6 cycleData.iteration = i; 7 7 cycleData['data' + i] = { value: i * 10 }; 8 8 console.log(' Before GC - iteration:', cycleData.iteration); 9 - let result = Ant.gc(); 10 - console.log(' GC result - arenaFreed:', result.arenaFreed); 9 + Ant.gc(); 11 10 console.log(' After GC - iteration:', cycleData.iteration); 12 11 } 13 12
+7
tests/test_gc_debug.js
··· 4 4 console.log('Test 1: Objects'); 5 5 let obj1 = { name: 'test', value: 42, nested: { deep: 'value' } }; 6 6 Ant.gc(); 7 + console.log(' GC requested'); 7 8 console.log(' OK\n'); 8 9 9 10 // Test 2 ··· 12 13 map.set('key1', { data: 'value1' }); 13 14 map.set('key2', [1, 2, 3]); 14 15 Ant.gc(); 16 + console.log(' GC requested'); 15 17 console.log(' get key1:', map.get('key1')); 16 18 console.log(' OK\n'); 17 19 ··· 21 23 set.add('value1'); 22 24 set.add(42); 23 25 Ant.gc(); 26 + console.log(' GC requested'); 24 27 console.log(' OK\n'); 25 28 26 29 // Test 4 ··· 35 38 }); 36 39 descObj.prop = 'updated'; 37 40 Ant.gc(); 41 + console.log(' GC requested'); 38 42 console.log(' prop:', descObj.prop); 39 43 console.log(' OK\n'); 40 44 ··· 44 48 resolve({ result: 'success' }); 45 49 }); 46 50 Ant.gc(); 51 + console.log(' GC requested'); 47 52 console.log(' OK\n'); 48 53 49 54 // Test 6 ··· 57 62 }; 58 63 let proxy = new Proxy(proxyTarget, proxyHandler); 59 64 Ant.gc(); 65 + console.log(' GC requested'); 60 66 console.log(' proxy.x:', proxy.x); 61 67 console.log(' OK\n'); 62 68 ··· 72 78 let counter = makeCounter(); 73 79 counter.inc(); 74 80 Ant.gc(); 81 + console.log(' GC requested'); 75 82 console.log(' count:', counter.get()); 76 83 console.log(' OK\n'); 77 84
+6 -6
tests/test_gc_large.js
··· 33 33 // Free the objects 34 34 largeObjects = null; 35 35 36 - let gc1 = Ant.gc(); 37 - console.log('GC 1: arenaFreed:', fmt(gc1.arenaFreed)); 38 - let gc2 = Ant.gc(); 39 - console.log('GC 2: arenaFreed:', fmt(gc2.arenaFreed)); 40 - let gc3 = Ant.gc(); 41 - console.log('GC 3: arenaFreed:', fmt(gc3.arenaFreed)); 36 + Ant.gc(); 37 + console.log('GC 1 requested'); 38 + Ant.gc(); 39 + console.log('GC 2 requested'); 40 + Ant.gc(); 41 + console.log('GC 3 requested'); 42 42 43 43 let stats3 = Ant.stats(); 44 44 console.log('\nAfter 3x GC:');
+2 -3
tests/test_map_gc.js
··· 18 18 console.log(' map.get("key2"):', map.get('key2')); 19 19 console.log(' map.get("key3"):', map.get('key3')); 20 20 21 - let gc = Ant.gc(); 22 - console.log('\nGC result:'); 23 - console.log(' arenaFreed:', gc.arenaFreed); 21 + Ant.gc(); 22 + console.log('\nGC requested'); 24 23 25 24 console.log('\nAfter GC:'); 26 25 console.log(' map.size:', map.size);