From 81c88f943941c3b69574b9e8cded213de7b6e2e1 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sat, 21 Feb 2026 02:37:30 -0600 Subject: [PATCH] gx fices --- parse.cm | 4 +++ source/cell.c | 2 ++ source/cell_internal.h | 2 ++ source/mach.c | 54 +++++++++++++++++++++++++++------------ source/qbe_helpers.c | 31 +++++++++++----------- source/quickjs-internal.h | 8 ++++++ source/quickjs.h | 6 +++++ source/runtime.c | 45 +++++++++++++++++++------------- source/scheduler.c | 31 ++++++++++++++++++++++ 9 files changed, 134 insertions(+), 49 deletions(-) diff --git a/parse.cm b/parse.cm index 01e8f577..d9d6a3a9 100644 --- a/parse.cm +++ b/parse.cm @@ -1627,6 +1627,8 @@ var parse = function(tokens, src, filename, tokenizer) { if (r.v != null) { left_node.level = r.level left_node.function_nr = r.def_function_nr + r.v.nr_uses = r.v.nr_uses + 1 + if (r.level > 0) r.v.closure = 1 } else { left_node.level = -1 } @@ -1718,6 +1720,8 @@ var parse = function(tokens, src, filename, tokenizer) { if (r.v != null) { operand.level = r.level operand.function_nr = r.def_function_nr + r.v.nr_uses = r.v.nr_uses + 1 + if (r.level > 0) r.v.closure = 1 } else { operand.level = -1 } diff --git a/source/cell.c b/source/cell.c index 3f2190db..ab221cc3 100644 --- a/source/cell.c +++ b/source/cell.c @@ -273,6 +273,7 @@ void script_startup(cell_rt *prt) JSContext *js = JS_NewContext(g_runtime); JS_SetContextOpaque(js, prt); + JS_SetGCScanExternal(js, actor_gc_scan); prt->context = js; /* Set per-actor heap memory limit */ @@ -574,6 +575,7 @@ int cell_init(int argc, char **argv) cli_rt->context = ctx; JS_SetContextOpaque(ctx, cli_rt); + JS_SetGCScanExternal(ctx, actor_gc_scan); JS_AddGCRef(ctx, &cli_rt->idx_buffer_ref); JS_AddGCRef(ctx, &cli_rt->on_exception_ref); diff --git a/source/cell_internal.h b/source/cell_internal.h index 77ed2f12..533a9630 100644 --- a/source/cell_internal.h +++ b/source/cell_internal.h @@ -100,6 +100,8 @@ void exit_handler(void); void actor_loop(); void actor_initialize(void); void actor_free(cell_rt *actor); +void actor_gc_scan(JSContext *ctx, uint8_t *fb, uint8_t *fe, + uint8_t *tb, uint8_t **tf, uint8_t *te); int scheduler_actor_count(void); void scheduler_enable_quiescence(void); diff --git a/source/mach.c b/source/mach.c index 61bd67f3..dc004f46 100644 --- a/source/mach.c +++ b/source/mach.c @@ -971,7 +971,6 @@ JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JSValue e fn->u.cell.code = code_obj; fn->u.cell.env_record = env_ref.val; fn->u.cell.outer_frame = frame_ref.val; - JSValue out = fn_ref.val; JS_DeleteGCRef(ctx, &fn_ref); JS_PopGCRef(ctx, &frame_ref); @@ -1254,8 +1253,8 @@ void __asan_on_error(void) { const char *file = NULL; uint16_t line = 0; uint32_t pc = is_first ? cur_pc : 0; - if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) { - JSCodeRegister *code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code; + if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code) { + JSCodeRegister *code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code; file = code->filename_cstr; func_name = code->name_cstr; if (!is_first) @@ -1291,7 +1290,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, ctx->suspended_frame_ref.val = JS_NULL; frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code; + code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code; env = fn->u.cell.env_record; pc = ctx->suspended_pc; result = JS_NULL; @@ -1497,28 +1496,34 @@ vm_dispatch: VM_CASE(MACH_LOADK): { int bx = MACH_GET_Bx(instr); + if (bx < (int)code->cpool_count) frame->slots[a] = code->cpool[bx]; VM_BREAK(); } VM_CASE(MACH_LOADI): + frame->slots[a] = JS_NewInt32(ctx, MACH_GET_sBx(instr)); VM_BREAK(); VM_CASE(MACH_LOADNULL): + frame->slots[a] = JS_NULL; VM_BREAK(); VM_CASE(MACH_LOADTRUE): + frame->slots[a] = JS_TRUE; VM_BREAK(); VM_CASE(MACH_LOADFALSE): + frame->slots[a] = JS_FALSE; VM_BREAK(); VM_CASE(MACH_MOVE): + frame->slots[a] = frame->slots[b]; VM_BREAK(); @@ -2070,6 +2075,18 @@ vm_dispatch: fn = JS_VALUE_GET_FUNCTION(target->function); target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame); } + { + uint64_t tcap = objhdr_cap56(target->header); + if ((unsigned)c >= tcap) { + fprintf(stderr, "MACH_SETUP OOB: slot=%d >= target_cap=%llu depth=%d " + "cur_fn=%s (%s) pc=%u\n", + c, (unsigned long long)tcap, depth, + code->name_cstr ? code->name_cstr : "?", + code->filename_cstr ? code->filename_cstr : "?", pc - 1); + fflush(stderr); + VM_BREAK(); + } + } target->slots[c] = frame->slots[a]; VM_BREAK(); } @@ -2162,9 +2179,9 @@ vm_dispatch: const char *callee_file = "?"; { JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(frame->function); - if (callee_fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code) { - if (JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code->name_cstr) callee_name = JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code->name_cstr; - if (JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code->filename_cstr) callee_file = JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code->filename_cstr; + if (callee_fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code) { + if (JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code->name_cstr) callee_name = JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code->name_cstr; + if (JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code->filename_cstr) callee_file = JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code->filename_cstr; } } #endif @@ -2174,11 +2191,12 @@ vm_dispatch: frame_ref.val = JS_MKPTR(frame); int ret_info = JS_VALUE_GET_INT(frame->address); JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code; + code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code; env = fn->u.cell.env_record; pc = ret_info >> 16; int ret_slot = ret_info & 0xFFFF; if (ret_slot != 0xFFFF) { + #ifdef VALIDATE_GC if (JS_IsPtr(result)) { void *rp = JS_VALUE_GET_PTR(result); @@ -2207,11 +2225,14 @@ vm_dispatch: frame_ref.val = JS_MKPTR(frame); int ret_info = JS_VALUE_GET_INT(frame->address); JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code; + code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code; env = fn->u.cell.env_record; pc = ret_info >> 16; int ret_slot = ret_info & 0xFFFF; - if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result; + if (ret_slot != 0xFFFF) { + + frame->slots[ret_slot] = result; + } } VM_BREAK(); @@ -2233,6 +2254,7 @@ vm_dispatch: VM_CASE(MACH_CLOSURE): { int bx = MACH_GET_Bx(instr); + if ((uint32_t)bx < code->func_count) { JSCodeRegister *fn_code = code->functions[bx]; /* Read env fresh from frame->function — C local can be stale */ @@ -2630,7 +2652,7 @@ vm_dispatch: if (fn->kind == JS_FUNC_KIND_REGISTER) { /* Register function: switch frames inline (fast path) */ - JSCodeRegister *fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code; + JSCodeRegister *fn_code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code; JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots); if (!new_frame) { frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); @@ -2640,7 +2662,7 @@ vm_dispatch: fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]); fn_val = fr->function; fn = JS_VALUE_GET_FUNCTION(fn_val); - fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code; + fn_code = JS_VALUE_GET_CODE(FN_READ_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; @@ -2702,7 +2724,7 @@ vm_dispatch: goto disrupt; if (fn->kind == JS_FUNC_KIND_REGISTER) { - JSCodeRegister *fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code; + JSCodeRegister *fn_code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code; int current_slots = (int)objhdr_cap56(frame->header); if (fn_code->nr_slots <= current_slots) { @@ -2730,7 +2752,7 @@ vm_dispatch: fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]); fn_val = fr->function; fn = JS_VALUE_GET_FUNCTION(fn_val); - fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code; + fn_code = JS_VALUE_GET_CODE(FN_READ_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 */ @@ -2769,7 +2791,7 @@ vm_dispatch: frame_ref.val = JS_MKPTR(frame); int ret_info = JS_VALUE_GET_INT(frame->address); JSFunction *ret_fn = JS_VALUE_GET_FUNCTION(frame->function); - code = JS_VALUE_GET_CODE(ret_fn->u.cell.code)->u.reg.code; + code = JS_VALUE_GET_CODE(FN_READ_CODE(ret_fn))->u.reg.code; env = ret_fn->u.cell.env_record; pc = ret_info >> 16; int ret_slot = ret_info & 0xFFFF; @@ -2822,7 +2844,7 @@ vm_dispatch: uint32_t frame_pc = pc; for (;;) { JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code; + code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code; /* Only enter handler if we're not already inside it */ if (code->disruption_pc > 0 && frame_pc < code->disruption_pc) { env = fn->u.cell.env_record; diff --git a/source/qbe_helpers.c b/source/qbe_helpers.c index 72e0ec16..fa59f940 100644 --- a/source/qbe_helpers.c +++ b/source/qbe_helpers.c @@ -786,11 +786,12 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, NativeRTState *st = native_state(ctx); if (!st) return JS_EXCEPTION; JSFunction *f = JS_VALUE_GET_FUNCTION(func_obj); - cell_compiled_fn fn = (cell_compiled_fn)JS_VALUE_GET_CODE(f->u.cell.code)->u.native.fn_ptr; - int nr_slots = JS_VALUE_GET_CODE(f->u.cell.code)->u.native.nr_slots; + JSCode *f_code = JS_VALUE_GET_CODE(FN_READ_CODE(f)); + cell_compiled_fn fn = (cell_compiled_fn)f_code->u.native.fn_ptr; + int nr_slots = f_code->u.native.nr_slots; int arity = f->length; void *prev_dl_handle = st->current_dl_handle; - st->current_dl_handle = JS_VALUE_GET_CODE(f->u.cell.code)->u.native.dl_handle; + st->current_dl_handle = f_code->u.native.dl_handle; #define RETURN_DISPATCH(v) \ do { \ @@ -841,7 +842,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, if (JS_IsFunction(frame->function)) { JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function); if (cur_fn->kind == JS_FUNC_KIND_NATIVE) - st->current_dl_handle = JS_VALUE_GET_CODE(cur_fn->u.cell.code)->u.native.dl_handle; + st->current_dl_handle = JS_VALUE_GET_CODE(FN_READ_CODE(cur_fn))->u.native.dl_handle; } JSValue result = fn(ctx, fp); @@ -874,7 +875,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, JS_RaiseDisrupt(ctx, "not a function"); /* Resume caller with exception pending */ JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function); - fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr; + fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr; JS_PopGCRef(ctx, &callee_ref); continue; } @@ -882,14 +883,14 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(callee_fn_val); if (!cell_check_call_arity(ctx, callee_fn, callee_argc)) { JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function); - fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr; + fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr; JS_PopGCRef(ctx, &callee_ref); continue; } if (callee_fn->kind == JS_FUNC_KIND_NATIVE) { /* Native-to-native call — no C stack growth */ - cell_compiled_fn callee_ptr = (cell_compiled_fn)JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.native.fn_ptr; + cell_compiled_fn callee_ptr = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.native.fn_ptr; if (pending_is_tail) { /* Tail call: replace current frame with the prepared callee frame. */ @@ -930,7 +931,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val); fp = (JSValue *)frame->slots; JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function); - fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr; + fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr; JS_PopGCRef(ctx, &callee_ref); continue; } @@ -967,7 +968,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, /* fn and fp still point to the calling native function's frame. Just resume it — it will detect JS_EXCEPTION in the return slot. */ JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function); - fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr; + fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr; JS_PopGCRef(ctx, &callee_ref); continue; } @@ -998,7 +999,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, fp[ret_slot] = ret; /* Resume caller */ JSFunction *caller_fn = JS_VALUE_GET_FUNCTION(frame->function); - fn = (cell_compiled_fn)JS_VALUE_GET_CODE(caller_fn->u.cell.code)->u.native.fn_ptr; + fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(caller_fn))->u.native.fn_ptr; } else { /* Regular call: store result and resume current function */ int ret_info = JS_VALUE_GET_INT(frame->address); @@ -1007,7 +1008,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, fp[ret_slot] = ret; /* fn stays the same — we resume the same function at next segment */ JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function); - fn = (cell_compiled_fn)JS_VALUE_GET_CODE(cur_fn->u.cell.code)->u.native.fn_ptr; + fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(cur_fn))->u.native.fn_ptr; } } JS_PopGCRef(ctx, &callee_ref); @@ -1040,7 +1041,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, fp[ret_slot] = JS_EXCEPTION; JSFunction *exc_caller_fn = JS_VALUE_GET_FUNCTION(frame->function); - fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_caller_fn->u.cell.code)->u.native.fn_ptr; + fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_caller_fn))->u.native.fn_ptr; continue; } @@ -1064,7 +1065,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, fp[ret_slot] = result; JSFunction *caller_fn = JS_VALUE_GET_FUNCTION(frame->function); - fn = (cell_compiled_fn)JS_VALUE_GET_CODE(caller_fn->u.cell.code)->u.native.fn_ptr; + fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(caller_fn))->u.native.fn_ptr; continue; } @@ -1149,8 +1150,8 @@ JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) { } int nr_slots = (int)nargs + 2; JSFunction *f = JS_VALUE_GET_FUNCTION(fn); - if (f->kind == JS_FUNC_KIND_NATIVE && JS_VALUE_GET_CODE(f->u.cell.code)->u.native.nr_slots > nr_slots) - nr_slots = JS_VALUE_GET_CODE(f->u.cell.code)->u.native.nr_slots; + if (f->kind == JS_FUNC_KIND_NATIVE && JS_VALUE_GET_CODE(FN_READ_CODE(f))->u.native.nr_slots > nr_slots) + nr_slots = JS_VALUE_GET_CODE(FN_READ_CODE(f))->u.native.nr_slots; JSFrameRegister *new_frame = alloc_frame_register(ctx, nr_slots); if (!new_frame) return JS_EXCEPTION; new_frame->function = fn; diff --git a/source/quickjs-internal.h b/source/quickjs-internal.h index 9b806f78..a140d1ab 100644 --- a/source/quickjs-internal.h +++ b/source/quickjs-internal.h @@ -118,6 +118,7 @@ void *js_mallocz (JSContext *ctx, size_t size); #endif #ifdef HAVE_ASAN +#include static struct JSContext *__asan_js_ctx; #endif @@ -747,6 +748,11 @@ struct JSContext { JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags); void *user_opaque; + /* GC callback to scan external C-side roots (actor letters, timers) */ + void (*gc_scan_external)(JSContext *ctx, + uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); + js_hook trace_hook; int trace_type; void *trace_data; @@ -990,6 +996,8 @@ typedef struct JSFunction { } u; } JSFunction; +#define FN_READ_CODE(fn) ((fn)->u.cell.code) + /* ============================================================ Context-Neutral Module Format (Phase 2+) Struct definitions are in quickjs.h diff --git a/source/quickjs.h b/source/quickjs.h index 125da3c5..00fcec24 100644 --- a/source/quickjs.h +++ b/source/quickjs.h @@ -330,6 +330,12 @@ JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size); void JS_FreeContext (JSContext *s); void *JS_GetContextOpaque (JSContext *ctx); void JS_SetContextOpaque (JSContext *ctx, void *opaque); + +typedef void (*JS_GCScanFn)(JSContext *ctx, + uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); +void JS_SetGCScanExternal(JSContext *ctx, JS_GCScanFn fn); + void JS_SetActorSym (JSContext *ctx, JSValue sym); JSValue JS_GetActorSym (JSContext *ctx); JSRuntime *JS_GetRuntime (JSContext *ctx); diff --git a/source/runtime.c b/source/runtime.c index 99d9077a..930bd758 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -52,8 +52,8 @@ void heap_check_fail(void *ptr, JSContext *ctx) { JSFunction *fn = (JSFunction *)JS_VALUE_GET_PTR(frame->function); const char *name = NULL, *file = NULL; uint16_t line = 0; - if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) { - JSCodeRegister *code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code; + if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code) { + JSCodeRegister *code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code; file = code->filename_cstr; name = code->name_cstr; if (!first) @@ -149,7 +149,8 @@ int JS_IsPretext (JSValue v) { } JSValue *JS_PushGCRef (JSContext *ctx, JSGCRef *ref) { - assert(ref != ctx->top_gc_ref && "JS_ROOT used in a loop — same address pushed twice"); + if (ref == ctx->top_gc_ref) + return &ref->val; /* already at top — loop re-entry; just update val */ ref->prev = ctx->top_gc_ref; ctx->top_gc_ref = ref; ref->val = JS_NULL; @@ -1370,14 +1371,14 @@ JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *f /* Frame shortening: returned frames (caller == JS_NULL) only need [this][args][closure_locals] — shrink during copy. */ - if (type == OBJ_FRAME) { + if (0 && type == OBJ_FRAME) { JSFrame *f = (JSFrame *)hdr_ptr; if (JS_IsNull (f->caller) && JS_IsPtr (f->function)) { /* fn may be forwarded, but kind (offset 18) and u.cell.code (offset 24) are past the 16 bytes overwritten by fwd+size. */ JSFunction *fn = (JSFunction *)JS_VALUE_GET_PTR (f->function); if (fn->kind == JS_FUNC_KIND_REGISTER) { - JSCode *jc = (JSCode *)JS_VALUE_GET_PTR (fn->u.cell.code); + JSCode *jc = (JSCode *)JS_VALUE_GET_PTR (FN_READ_CODE(fn)); if (jc && jc->kind == JS_CODE_KIND_REGISTER && jc->u.reg.code && jc->u.reg.code->nr_close_slots > 0) { uint16_t cs = 1 + jc->u.reg.code->arity + jc->u.reg.code->nr_close_slots; @@ -1501,10 +1502,11 @@ void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *fro objhdr_t fh = *(objhdr_t *)JS_VALUE_GET_PTR (frame->function); if (objhdr_type (fh) == OBJ_FUNCTION) { JSFunction *fn = (JSFunction *)JS_VALUE_GET_PTR (frame->function); - if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) { - if (JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code->name_cstr) fname = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code->name_cstr; - if (JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code->filename_cstr) ffile = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code->filename_cstr; - fnslots = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code->nr_slots; + if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code) { + JSCodeRegister *_vc = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code; + if (_vc->name_cstr) fname = _vc->name_cstr; + if (_vc->filename_cstr) ffile = _vc->filename_cstr; + fnslots = _vc->nr_slots; } } } @@ -1610,8 +1612,8 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) { } if (objhdr_type (fnh) == OBJ_FUNCTION) { JSFunction *fnp = (JSFunction *)JS_VALUE_GET_PTR (fn_v); - if (fnp->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fnp->u.cell.code)->u.reg.code && JS_VALUE_GET_CODE(fnp->u.cell.code)->u.reg.code->name_cstr) - fn_name = JS_VALUE_GET_CODE(fnp->u.cell.code)->u.reg.code->name_cstr; + if (fnp->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(fnp))->u.reg.code && JS_VALUE_GET_CODE(FN_READ_CODE(fnp))->u.reg.code->name_cstr) + fn_name = JS_VALUE_GET_CODE(FN_READ_CODE(fnp))->u.reg.code->name_cstr; } } fprintf (stderr, "VALIDATE_GC: pre-gc frame %p slot[%llu] -> %p (chased %p) bad type %d (hdr=0x%llx) fn=%s\n", @@ -1708,6 +1710,10 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) { } } + /* Scan external C-side roots (actor letters, timers) */ + if (ctx->gc_scan_external) + ctx->gc_scan_external(ctx, from_base, from_end, to_base, &to_free, to_end); + /* Cheney scan: scan copied objects to find more references */ uint8_t *scan = to_base; #ifdef DUMP_GC_DETAIL @@ -1821,7 +1827,6 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) { } } - /* Return old block (in poison mode, just poison it and leak) */ heap_block_free (rt, from_base, old_heap_size); /* Update context with new block */ @@ -2147,6 +2152,10 @@ void JS_SetContextOpaque (JSContext *ctx, void *opaque) { ctx->user_opaque = opaque; } +void JS_SetGCScanExternal(JSContext *ctx, JS_GCScanFn fn) { + ctx->gc_scan_external = fn; +} + void JS_SetActorSym (JSContext *ctx, JSValue sym) { ctx->actor_sym = sym; } @@ -4813,7 +4822,7 @@ JSValue JS_CallInternal (JSContext *ctx, JSValue func_obj, JSValue this_obj, case JS_FUNC_KIND_C_DATA: return js_call_c_function (ctx, func_obj, this_obj, argc, argv); case JS_FUNC_KIND_REGISTER: - return JS_CallRegisterVM (ctx, JS_VALUE_GET_CODE(f->u.cell.code)->u.reg.code, this_obj, argc, argv, + return JS_CallRegisterVM (ctx, JS_VALUE_GET_CODE(FN_READ_CODE(f))->u.reg.code, this_obj, argc, argv, f->u.cell.env_record, f->u.cell.outer_frame); case JS_FUNC_KIND_NATIVE: return cell_native_dispatch (ctx, func_obj, this_obj, argc, argv); @@ -4846,7 +4855,7 @@ JSValue JS_Call (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, J case JS_FUNC_KIND_C: return js_call_c_function (ctx, func_obj, this_obj, argc, argv); case JS_FUNC_KIND_REGISTER: - return JS_CallRegisterVM (ctx, JS_VALUE_GET_CODE(f->u.cell.code)->u.reg.code, this_obj, argc, argv, + return JS_CallRegisterVM (ctx, JS_VALUE_GET_CODE(FN_READ_CODE(f))->u.reg.code, this_obj, argc, argv, f->u.cell.env_record, f->u.cell.outer_frame); case JS_FUNC_KIND_NATIVE: return cell_native_dispatch (ctx, func_obj, this_obj, argc, argv); @@ -11614,8 +11623,8 @@ JSValue JS_GetStack(JSContext *ctx) { if (!JS_IsFunction(frame->function)) break; JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) { - JSCodeRegister *code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code; + if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code) { + JSCodeRegister *code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code; uint32_t pc = is_first ? cur_pc : (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); frames[count].fn = code->name_cstr; frames[count].file = code->filename_cstr; @@ -11674,8 +11683,8 @@ void JS_CrashPrintStack(JSContext *ctx) { if (!JS_IsFunction(frame->function)) break; JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) { - JSCodeRegister *code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code; + if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code) { + JSCodeRegister *code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code; uint32_t pc = is_first ? cur_pc : (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); const char *name = code->name_cstr ? code->name_cstr : ""; diff --git a/source/scheduler.c b/source/scheduler.c index 415d694e..e5a2ad78 100644 --- a/source/scheduler.c +++ b/source/scheduler.c @@ -9,6 +9,7 @@ #include "stb_ds.h" #include "cell.h" +#include "quickjs-internal.h" #include "cell_internal.h" #ifdef _WIN32 @@ -857,6 +858,36 @@ ENDTURN_SLOW: pthread_mutex_unlock(actor->mutex); } +/* GC callback: scan actor's letters and timers so the copying GC + can relocate JSValues stored in C-side data structures. */ +void actor_gc_scan(JSContext *ctx, + uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) +{ + cell_rt *actor = JS_GetContextOpaque(ctx); + if (!actor) return; + + /* Lock msg_mutex to synchronize with the timer thread, which reads + timers and writes letters under the same lock. */ + pthread_mutex_lock(actor->msg_mutex); + + for (int i = 0; i < arrlen(actor->letters); i++) { + if (actor->letters[i].type == LETTER_CALLBACK) { + actor->letters[i].callback = gc_copy_value(ctx, + actor->letters[i].callback, + from_base, from_end, to_base, to_free, to_end); + } + } + + for (int i = 0; i < hmlen(actor->timers); i++) { + actor->timers[i].value = gc_copy_value(ctx, + actor->timers[i].value, + from_base, from_end, to_base, to_free, to_end); + } + + pthread_mutex_unlock(actor->msg_mutex); +} + void actor_clock(cell_rt *actor, JSValue fn) { letter l;