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.

implement pre-generational gc fixes

+199 -40
+1
include/gc.h
··· 11 11 #define GC_UPDATE_ARGS ant_t *js, jsoff_t (*fwd_off)(void *ctx, jsoff_t old), GC_FWD_ARGS 12 12 #define GC_OP_VAL_ARGS void (*op_val)(void *ctx, jsval_t *val), void *ctx 13 13 14 + void js_maybe_gc(ant_t *js); 14 15 size_t js_gc_compact(ant_t *js); 15 16 16 17 #endif
+2
include/reactor.h
··· 21 21 #define WORK_BLOCKING (WORK_MICROTASKS | WORK_IMMEDIATES | WORK_COROUTINES_READY) 22 22 #define WORK_ASYNC (WORK_READLINE | WORK_STDIN | WORK_TIMERS | WORK_FETCHES | WORK_FS_OPS | WORK_CHILD_PROCS) 23 23 24 + #define UV_CHECK_ALIVE uv_loop_alive(uv_default_loop()) 25 + 24 26 void js_poll_events(ant_t *js) ; 25 27 void js_run_event_loop(ant_t *js); 26 28
+46 -21
src/ant.c
··· 38 38 #include <sys/time.h> 39 39 #include <sys/stat.h> 40 40 #include <sys/resource.h> 41 + #include <execinfo.h> 41 42 #endif 42 43 43 44 #include "modules/fs.h" ··· 9951 9952 bool needs_scope; 9952 9953 bool has_func_decl; 9953 9954 bool has_func_decl_checked; 9954 - jsval_t loop_scope; 9955 + jshdl_t loop_scope_handle; 9955 9956 } loop_block_ctx_t; 9956 9957 9957 9958 static void loop_block_init(struct js *js, loop_block_ctx_t *ctx) { ··· 9959 9960 ctx->needs_scope = false; 9960 9961 ctx->has_func_decl = false; 9961 9962 ctx->has_func_decl_checked = false; 9962 - ctx->loop_scope = js_mkundef(); 9963 + ctx->loop_scope_handle = -1; 9963 9964 9964 9965 if (ctx->is_block && !(js->flags & F_NOEXEC)) { 9965 9966 jsoff_t saved_pos = js->pos; ··· 9974 9975 js->tok = saved_tok; 9975 9976 js->consumed = saved_consumed; 9976 9977 9977 - if (ctx->needs_scope) ctx->loop_scope = js_mkscope(js); 9978 + if (ctx->needs_scope) ctx->loop_scope_handle = js_root(js, js_mkscope(js)); 9978 9979 } 9979 9980 } 9980 9981 ··· 10004 10005 10005 10006 static inline void loop_block_sync_scope(struct js *js, loop_block_ctx_t *ctx) { 10006 10007 struct for_let_ctx *flc = for_let_current(js); 10007 - if (flc && vtype(flc->body_scope) == T_OBJ) ctx->loop_scope = flc->body_scope; 10008 + if (flc && vtype(flc->body_scope) == T_OBJ) js_root_update(js, ctx->loop_scope_handle, flc->body_scope); 10008 10009 } 10009 10010 10010 - #define loop_block_clear(js, ctx) if ((ctx)->needs_scope) scope_clear_props(js, (ctx)->loop_scope) 10011 - #define loop_block_cleanup(js, ctx) if ((ctx)->needs_scope) delscope(js) 10011 + #define loop_block_clear(js, ctx) \ 10012 + if ((ctx)->needs_scope) scope_clear_props(js, js_deref(js, (ctx)->loop_scope_handle)) 10013 + 10014 + #define loop_block_cleanup(js, ctx) \ 10015 + do { if ((ctx)->needs_scope) { js_unroot(js, (ctx)->loop_scope_handle); delscope(js); } } while(0) 10012 10016 10013 10017 static jsval_t js_if(struct js *js) { 10014 10018 js->consumed = 1; ··· 10129 10133 uint8_t obj_type = vtype(obj); 10130 10134 if (obj_type == T_NULL || obj_type == T_UNDEF) return js_mkundef(); 10131 10135 if (obj_type == T_STR) return for_iter_string_indices(js, ctx, obj); 10136 + 10132 10137 if (obj_type != T_OBJ && obj_type != T_ARR && obj_type != T_FUNC) 10133 10138 return js_mkerr(js, "for-in requires object"); 10134 10139 10135 10140 jsval_t iter_obj = (obj_type == T_FUNC) ? mkval(T_OBJ, vdata(obj)) : obj; 10136 - jsoff_t iter_obj_off = (jsoff_t)vdata(iter_obj); 10137 - jsoff_t prop_off = loadoff(js, iter_obj_off) & ~(3U | FLAGMASK); 10138 - 10139 - jsval_t prim = get_slot(js, obj, SLOT_PRIMITIVE); 10141 + jsval_t prim = get_slot(js, iter_obj, SLOT_PRIMITIVE); 10140 10142 if (vtype(prim) == T_STR) return for_iter_string_indices(js, ctx, prim); 10141 10143 10144 + jshdl_t h_obj = js_root(js, iter_obj); 10142 10145 const char *tag_sym_key = get_toStringTag_sym_key(); 10143 10146 size_t tag_sym_len = tag_sym_key ? strlen(tag_sym_key) : 0; 10144 10147 10145 - while (prop_off < js->brk && prop_off != 0) { 10148 + jsoff_t prop_idx = 0; 10149 + char key_buf[256]; 10150 + 10151 + for (;;) { 10152 + jsval_t cur_obj = js_deref(js, h_obj); 10153 + jsoff_t cur_obj_off = (jsoff_t)vdata(cur_obj); 10154 + jsoff_t prop_off = loadoff(js, cur_obj_off) & ~(3U | FLAGMASK); 10155 + 10156 + jsoff_t cur_idx = 0; 10157 + while (prop_off < js->brk && prop_off != 0 && cur_idx < prop_idx) { 10158 + jsoff_t header = loadoff(js, prop_off); 10159 + prop_off = next_prop(header); cur_idx++; 10160 + } 10161 + 10162 + if (prop_off >= js->brk || prop_off == 0) break; 10146 10163 jsoff_t header = loadoff(js, prop_off); 10147 - if (is_slot_prop(header)) { prop_off = next_prop(header); continue; } 10164 + if (is_slot_prop(header)) { prop_idx++; continue; } 10148 10165 10149 10166 jsoff_t koff = loadoff(js, prop_off + (jsoff_t)sizeof(prop_off)); 10150 10167 jsoff_t klen = offtolen(loadoff(js, koff)); ··· 10154 10171 if (!skip && tag_sym_key) skip = streq(key, klen, tag_sym_key, tag_sym_len); 10155 10172 10156 10173 if (!skip) { 10157 - descriptor_entry_t *desc = lookup_descriptor(iter_obj_off, key, klen); 10174 + descriptor_entry_t *desc = lookup_descriptor(cur_obj_off, key, klen); 10158 10175 if (desc && !desc->enumerable) skip = true; 10159 10176 } 10160 10177 10161 10178 if (!skip) { 10179 + size_t copy_len = klen < sizeof(key_buf) - 1 ? klen : sizeof(key_buf) - 1; 10180 + memcpy(key_buf, key, copy_len); 10181 + key_buf[copy_len] = '\0'; 10182 + 10162 10183 jsval_t out; 10163 - int rc = for_iter_step(js, ctx, js_mkstr(js, key, klen), &out); 10164 - if (rc) return (rc == 2) ? out : js_mkundef(); 10184 + int rc = for_iter_step(js, ctx, js_mkstr(js, key_buf, (jsoff_t)copy_len), &out); 10185 + if (rc) { js_unroot(js, h_obj); return (rc == 2) ? out : js_mkundef(); } 10165 10186 } 10166 10187 10167 - prop_off = next_prop(header); 10188 + prop_idx++; 10168 10189 } 10169 10190 10191 + js_unroot(js, h_obj); 10170 10192 return js_mkundef(); 10171 10193 } 10172 10194 ··· 10593 10615 loop_block_ctx_t loop_ctx = {0}; 10594 10616 if (exe) { 10595 10617 loop_block_init(js, &loop_ctx); 10596 - if (is_let_loop && let_var_len > 0 && loop_ctx.needs_scope) for_let_set_body_scope(js, loop_ctx.loop_scope); 10618 + if (is_let_loop && let_var_len > 0 && loop_ctx.needs_scope) for_let_set_body_scope(js, js_deref(js, loop_ctx.loop_scope_handle)); 10597 10619 } 10598 10620 10599 10621 js->flags |= F_NOEXEC; ··· 10603 10625 pos4 = js->pos; 10604 10626 10605 10627 while (!(flags & F_NOEXEC)) { 10628 + js_maybe_gc(js); 10606 10629 js->flags = flags, js->pos = pos1, js->consumed = 1; 10607 10630 if (next(js) != TOK_SEMICOLON) { 10608 10631 v = resolveprop(js, js_expr(js)); ··· 10711 10734 10712 10735 if (exe) { 10713 10736 while (true) { 10737 + js_maybe_gc(js); 10714 10738 js->flags = flags; 10715 10739 js->pos = cond_start; 10716 10740 js->consumed = 1; ··· 10806 10830 10807 10831 if (exe) { 10808 10832 do { 10833 + js_maybe_gc(js); 10809 10834 js->pos = body_start; 10810 10835 js->consumed = 1; 10811 10836 js->flags = (flags & ~F_NOEXEC) | F_LOOP; ··· 21952 21977 child_process_gc_update_roots(op_val, c); 21953 21978 readline_gc_update_roots(op_val, c); 21954 21979 process_gc_update_roots(op_val, c); 21955 - collections_gc_reserve_roots(op_val, c); 21956 21980 21957 21981 for (int i = 0; i < c->js->for_let_stack_len; i++) { 21958 21982 op_val(c, &c->js->for_let_stack[i].body_scope); ··· 21979 22003 21980 22004 gc_cb_ctx_t cb_ctx = { fwd_off, fwd_val, ctx, js }; 21981 22005 gc_roots_common(gc_reserve_off_cb, gc_reserve_val_cb, &cb_ctx); 22006 + collections_gc_reserve_roots(gc_reserve_val_cb, &cb_ctx); 21982 22007 21983 22008 promise_data_entry_t *pd, *pd_tmp; 21984 22009 HASH_ITER(hh, promise_registry, pd, pd_tmp) { ··· 22049 22074 HASH_DEL(proxy_registry, proxy); 22050 22075 jsoff_t old_off = proxy->obj_offset; 22051 22076 jsoff_t new_off = fwd_off(ctx, old_off); 22052 - if (new_off == old_off && old_off != 0) { free(proxy); continue; } 22077 + if (new_off == 0) { free(proxy); continue; } 22053 22078 proxy->obj_offset = new_off; 22054 22079 FWD_VAL(proxy->target); FWD_VAL(proxy->handler); 22055 22080 HASH_ADD(hh, new_proxy_registry, obj_offset, sizeof(jsoff_t), proxy); ··· 22061 22086 HASH_DEL(accessor_registry, acc); 22062 22087 jsoff_t old_off = acc->obj_offset; 22063 22088 jsoff_t new_off = fwd_off(ctx, old_off); 22064 - if (new_off == old_off && old_off != 0) { free(acc); continue; } 22089 + if (new_off == 0) { free(acc); continue; } 22065 22090 acc->obj_offset = new_off; 22066 22091 HASH_ADD(hh, new_acc_registry, obj_offset, sizeof(jsoff_t), acc); 22067 22092 } ··· 22072 22097 HASH_DEL(desc_registry, desc); 22073 22098 jsoff_t old_off = (jsoff_t)(desc->key >> 32); 22074 22099 jsoff_t new_off = fwd_off(ctx, old_off); 22075 - if (new_off == old_off && old_off != 0) { free(desc); continue; } 22100 + if (new_off == 0) { free(desc->prop_name); free(desc); continue; } 22076 22101 if (desc->has_getter) FWD_VAL(desc->getter); 22077 22102 if (desc->has_setter) FWD_VAL(desc->setter); 22078 22103 desc->key = ((uint64_t)new_off << 32) | (uint32_t)(desc->key & 0xFFFFFFFF);
+12
src/gc.c
··· 623 623 624 624 return (old_brk > ctx.new_brk ? old_brk - ctx.new_brk : 0); 625 625 } 626 + 627 + void js_maybe_gc(ant_t *js) { 628 + jsoff_t thresh = js->brk / 2; 629 + if (thresh < 4 * 1024 * 1024) thresh = 4 * 1024 * 1024; 630 + if (thresh > 64 * 1024 * 1024) thresh = 64 * 1024 * 1024; 631 + 632 + if (js->gc_alloc_since > thresh || js->needs_gc) { 633 + js->needs_gc = false; 634 + js_gc_compact(js); 635 + js->gc_alloc_since = 0; 636 + } 637 + }
+6 -4
src/modules/collections.c
··· 993 993 994 994 void collections_gc_reserve_roots(void (*op_val)(void *, jsval_t *), void *ctx) { 995 995 for (size_t i = 0; i < map_registry_count; i++) { 996 - map_entry_t **head = map_registry[i].head; 996 + jsval_t map_obj = mkval(T_OBJ, map_registry[i].obj_offset); 997 + op_val(ctx, &map_obj); map_entry_t **head = map_registry[i].head; 997 998 if (head && *head) { 998 999 map_entry_t *entry, *tmp; 999 1000 HASH_ITER(hh, *head, entry, tmp) op_val(ctx, &entry->value); ··· 1001 1002 } 1002 1003 1003 1004 for (size_t i = 0; i < set_registry_count; i++) { 1004 - set_entry_t **head = set_registry[i].head; 1005 + jsval_t set_obj = mkval(T_OBJ, set_registry[i].obj_offset); 1006 + op_val(ctx, &set_obj); set_entry_t **head = set_registry[i].head; 1005 1007 if (head && *head) { 1006 1008 set_entry_t *entry, *tmp; 1007 1009 HASH_ITER(hh, *head, entry, tmp) op_val(ctx, &entry->value); ··· 1036 1038 jsoff_t old_off = map_registry[i].obj_offset; 1037 1039 jsoff_t new_off = fwd_off(ctx, old_off); 1038 1040 1039 - if (new_off == old_off && old_off != 0) { 1041 + if (new_off == 0) { 1040 1042 free_map_entries(map_registry[i].head); 1041 1043 free(map_registry[i].head); 1042 1044 continue; ··· 1060 1062 jsoff_t old_off = set_registry[i].obj_offset; 1061 1063 jsoff_t new_off = fwd_off(ctx, old_off); 1062 1064 1063 - if (new_off == old_off && old_off != 0) { 1065 + if (new_off == 0) { 1064 1066 free_set_entries(set_registry[i].head); 1065 1067 free(set_registry[i].head); 1066 1068 continue;
+16
src/pkg/fetcher.zig
··· 83 83 use_tls: bool, 84 84 connected: i32, 85 85 connect_pending: bool, 86 + closing: bool, 86 87 write_buf: std.ArrayListUnmanaged(u8), 87 88 requests: [MAX_PENDING_REQUESTS]RequestState, 88 89 request_count: usize, ··· 128 129 .use_tls = use_tls, 129 130 .connected = 0, 130 131 .connect_pending = false, 132 + .closing = false, 131 133 .write_buf = .{}, 132 134 .requests = undefined, 133 135 .request_count = 0, ··· 166 168 } 167 169 168 170 pub fn deinit(self: *Http2Client) void { 171 + self.closing = true; 172 + self.connect_pending = false; 173 + 169 174 for (&self.requests) |*req| { 170 175 req.on_data = null; 171 176 req.on_complete = null; ··· 257 262 const tls: *tlsuv.stream_t = @ptrCast(@alignCast(handle)); 258 263 const client: *Http2Client = @ptrCast(@alignCast(tls.data)); 259 264 client.connected = -2; 265 + client.connect_pending = false; 260 266 } 261 267 262 268 fn findRequest(self: *Http2Client, stream_id: i32) ?*RequestState { ··· 351 357 } 352 358 353 359 fn flush(self: *Http2Client) !void { 360 + if (self.closing) return error.ConnectionFailed; 354 361 if (self.h2_session) |session| while (nghttp2.nghttp2_session_want_write(session) != 0) if (nghttp2.nghttp2_session_send(session) != 0) break; 355 362 if (self.write_buf.items.len > 0) { 356 363 const data = try self.allocator.dupe(u8, self.write_buf.items); ··· 381 388 const tls: *tlsuv.stream_t = @ptrCast(@alignCast(stream)); 382 389 const client: *Http2Client = @ptrCast(@alignCast(tls.data)); 383 390 defer if (buf.base) |b| std.c.free(b); 391 + if (client.closing) return; 384 392 if (nread < 0) { 385 393 for (client.requests[0..client.request_count]) |*req| if (!req.done) { 386 394 req.done = true; ··· 399 407 fn onConnect(req: *uv.connect_t, status: c_int) callconv(.c) void { 400 408 const ctx: *ConnectCtx = @ptrCast(@alignCast(req.data)); 401 409 defer ctx.client.allocator.destroy(ctx); 410 + ctx.client.connect_pending = false; 411 + if (ctx.client.closing) { 412 + ctx.client.connected = -1; 413 + _ = tlsuv.tlsuv_stream_close(&ctx.client.tls, onStreamClose); 414 + return; 415 + } 402 416 if (status < 0) { 403 417 ctx.client.connected = -1; 404 418 return; ··· 416 430 const ConnectCtx = struct { client: *Http2Client, req: uv.connect_t }; 417 431 418 432 fn ensureConnected(self: *Http2Client) !void { 433 + if (self.closing) return error.ConnectionFailed; 419 434 if (self.connected > 0) return; 420 435 if (self.connected < 0) return error.ConnectionFailed; 421 436 ··· 443 458 } 444 459 445 460 pub fn initiateConnectAsync(self: *Http2Client) !void { 461 + if (self.closing) return error.ConnectionFailed; 446 462 if (self.connected > 0) return; 447 463 if (self.connected < 0) return error.ConnectionFailed; 448 464 if (self.connect_pending) return;
+9 -15
src/reactor.c
··· 59 59 return flags; 60 60 } 61 61 62 - static void maybe_gc(ant_t *js) { 63 - jsoff_t thresh = js->brk / 2; 64 - if (thresh < 4 * 1024 * 1024) thresh = 4 * 1024 * 1024; 65 - 66 - if (js->gc_alloc_since > thresh || js->needs_gc) { 67 - js->needs_gc = false; 68 - js_gc_compact(js); 69 - js->gc_alloc_since = 0; 70 - } 71 - } 72 - 73 62 void js_run_event_loop(ant_t *js) { 74 63 work_flags_t work; 64 + int uv_alive = UV_CHECK_ALIVE; 75 65 76 - while ((work = get_pending_work()) & WORK_PENDING) { 66 + while (((work = get_pending_work()) & WORK_PENDING) || uv_alive) { 77 67 js_poll_events(js); 68 + 78 69 work = get_pending_work(); 70 + uv_alive = UV_CHECK_ALIVE; 79 71 80 72 if (work & WORK_BLOCKING) uv_run(uv_default_loop(), UV_RUN_NOWAIT); 81 - else if (work & WORK_ASYNC) { maybe_gc(js); uv_run(uv_default_loop(), UV_RUN_ONCE); } 82 - else if (work & WORK_COROUTINES) break; 73 + else if ((work & WORK_ASYNC) || uv_alive) { 74 + js_maybe_gc(js); 75 + uv_run(uv_default_loop(), UV_RUN_ONCE); 76 + } else if (work & WORK_COROUTINES) break; 83 77 } 84 78 85 79 js_poll_events(js); 86 - } 80 + }
+14
tests/test_event_loop_timers.js
··· 1 + let fired = false; 2 + 3 + setTimeout(() => { 4 + fired = true; 5 + console.log('timer fired'); 6 + }, 5); 7 + 8 + // If the event loop exits early, this will print false. 9 + setTimeout(() => { 10 + if (!fired) { 11 + throw new Error('timer status: missing'); 12 + } 13 + console.log('timer status: ok'); 14 + }, 10);
+93
tests/test_timer_chain_iter.js
··· 1 + const WIDTH = 150; 2 + const HEIGHT = 40; 3 + 4 + const cells = new Map(); 5 + const directions = [ 6 + [-1, 1], 7 + [0, 1], 8 + [1, 1], 9 + [-1, 0], 10 + [1, 0], 11 + [-1, -1], 12 + [0, -1], 13 + [1, -1] 14 + ]; 15 + 16 + const key_for = (x, y) => `${x}-${y}`; 17 + 18 + for (let y = 0; y < HEIGHT; y++) { 19 + for (let x = 0; x < WIDTH; x++) { 20 + cells.set(key_for(x, y), { 21 + x, 22 + y, 23 + alive: Math.random() <= 0.2, 24 + next_state: false, 25 + neighbours: [] 26 + }); 27 + } 28 + } 29 + 30 + for (const cell of cells.values()) { 31 + for (const [dx, dy] of directions) { 32 + const nx = cell.x + dx; 33 + const ny = cell.y + dy; 34 + if (nx < 0 || ny < 0 || nx >= WIDTH || ny >= HEIGHT) continue; 35 + const neighbour = cells.get(key_for(nx, ny)); 36 + if (neighbour) cell.neighbours.push(neighbour); 37 + } 38 + } 39 + 40 + const dotick = () => { 41 + for (const cell of cells.values()) { 42 + let alive_neighbours = 0; 43 + for (const neighbour of cell.neighbours) { 44 + if (neighbour.alive) alive_neighbours++; 45 + } 46 + 47 + if (!cell.alive && alive_neighbours === 3) { 48 + cell.next_state = true; 49 + } else if (alive_neighbours < 2 || alive_neighbours > 3) { 50 + cell.next_state = false; 51 + } else { 52 + cell.next_state = cell.alive; 53 + } 54 + } 55 + 56 + for (const cell of cells.values()) { 57 + cell.alive = cell.next_state; 58 + } 59 + }; 60 + 61 + const render = () => { 62 + let rendering = ''; 63 + for (let y = 0; y < HEIGHT; y++) { 64 + for (let x = 0; x < WIDTH; x++) { 65 + const cell = cells.get(key_for(x, y)); 66 + rendering += cell && cell.alive ? 'o' : ' '; 67 + } 68 + rendering += '\n'; 69 + } 70 + return rendering; 71 + }; 72 + 73 + let ticks = 0; 74 + const start = performance.now(); 75 + const duration = 30000; 76 + const batch_size = 100; 77 + 78 + const run = () => { 79 + for (let i = 0; i < batch_size && performance.now() - start < duration; i++) { 80 + dotick(); 81 + render(); 82 + } 83 + 84 + ticks += batch_size; 85 + if (performance.now() - start >= duration) { 86 + console.log('done', ticks); 87 + return; 88 + } 89 + 90 + setTimeout(run, 0); 91 + }; 92 + 93 + setTimeout(run, 0);