From 7fc4a205f6884f3afc11fa0c5058e4143409adca Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sun, 15 Feb 2026 19:45:17 -0600 Subject: [PATCH] go reuses frames --- source/mach.c | 63 ++++++++++++++++++++++++++++++------------------ source/runtime.c | 14 +++++++++++ 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/source/mach.c b/source/mach.c index 8abbe712..8529e515 100644 --- a/source/mach.c +++ b/source/mach.c @@ -1970,33 +1970,48 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val); if (fn->kind == JS_FUNC_KIND_REGISTER) { - /* Register function: tail call by replacing current frame */ JSCodeRegister *fn_code = fn->u.reg.code; - JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots); - if (!new_frame) { + int current_slots = (int)objhdr_cap56(frame->header); + + if (fn_code->nr_slots <= current_slots) { + /* FAST PATH: reuse current frame — no allocation */ + int copy_count = (c_argc < fn_code->arity) ? c_argc : fn_code->arity; + frame->slots[0] = fr->slots[0]; /* this */ + for (int i = 0; i < copy_count; i++) + frame->slots[1 + i] = fr->slots[1 + i]; + /* Null out remaining slots (locals/temps) */ + for (int i = 1 + copy_count; i < current_slots; i++) + frame->slots[i] = JS_NULL; + frame->function = fn_val; + /* caller stays the same — we're reusing this frame */ + code = fn_code; + env = fn->u.reg.env_record; + pc = code->entry_point; + } else { + /* SLOW PATH: callee needs more slots, must allocate */ + JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots); + if (!new_frame) { + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + goto disrupt; + } frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - goto disrupt; + fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]); + fn_val = fr->function; + fn = JS_VALUE_GET_FUNCTION(fn_val); + fn_code = fn->u.reg.code; + new_frame->function = fn_val; + int copy_count = (c_argc < fn_code->arity) ? c_argc : fn_code->arity; + new_frame->slots[0] = fr->slots[0]; /* this */ + for (int i = 0; i < copy_count; i++) + new_frame->slots[1 + i] = fr->slots[1 + i]; + new_frame->caller = frame->caller; + frame->caller = JS_NULL; + frame = new_frame; + frame_ref.val = JS_MKPTR(frame); + code = fn_code; + env = fn->u.reg.env_record; + pc = code->entry_point; } - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]); - fn_val = fr->function; - fn = JS_VALUE_GET_FUNCTION(fn_val); - fn_code = fn->u.reg.code; - new_frame->function = fn_val; - /* Copy this + args from call frame to new frame */ - int copy_count = (c_argc < fn_code->arity) ? c_argc : fn_code->arity; - new_frame->slots[0] = fr->slots[0]; /* this */ - for (int i = 0; i < copy_count; i++) - new_frame->slots[1 + i] = fr->slots[1 + i]; - /* Tail call: callee returns to OUR caller, not to us */ - new_frame->caller = frame->caller; - frame->caller = JS_NULL; /* detach current frame */ - /* Switch to callee */ - frame = new_frame; - frame_ref.val = JS_MKPTR(frame); - code = fn_code; - env = fn->u.reg.env_record; - pc = code->entry_point; } else { /* C/bytecode function: call it, then return result to our caller */ ctx->reg_current_frame = frame_ref.val; diff --git a/source/runtime.c b/source/runtime.c index f3e351bf..14b97e22 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -1492,6 +1492,10 @@ void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *fro allow_grow: if true, grow heap when recovery is poor alloc_size: the allocation that triggered GC — used to size the new block */ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) { +#ifdef DUMP_GC_TIMING + struct timespec gc_t0, gc_t1; + clock_gettime(CLOCK_MONOTONIC, &gc_t0); +#endif JSRuntime *rt = ctx->rt; size_t old_used = ctx->heap_free - ctx->heap_base; size_t old_heap_size = ctx->current_block_size; @@ -1692,6 +1696,16 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) { ctx->gc_bytes_copied += new_used; size_t recovered = old_used > new_used ? old_used - new_used : 0; +#ifdef DUMP_GC_TIMING + clock_gettime(CLOCK_MONOTONIC, &gc_t1); + double gc_ms = (gc_t1.tv_sec - gc_t0.tv_sec) * 1000.0 + + (gc_t1.tv_nsec - gc_t0.tv_nsec) / 1e6; + fprintf(stderr, "GC #%u: %.2f ms | copied %zu KB | old %zu KB -> new %zu KB | recovered %zu KB (%.0f%%)\n", + ctx->gc_count, gc_ms, + new_used / 1024, old_used / 1024, new_size / 1024, + recovered / 1024, + old_used > 0 ? (100.0 * recovered / old_used) : 0.0); +#endif ctx->heap_base = to_base; ctx->heap_free = to_free;