From 30a9cfee795704704b3140d5f748adca469cd5e1 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 13 Feb 2026 02:33:25 -0600 Subject: [PATCH] simplify gc model --- source/mach.c | 6 +- source/qbe_helpers.c | 2 +- source/quickjs-internal.h | 36 ++-- source/runtime.c | 410 +++++++++++++------------------------- 4 files changed, 162 insertions(+), 292 deletions(-) diff --git a/source/mach.c b/source/mach.c index ea57276e..bc6e8e9f 100644 --- a/source/mach.c +++ b/source/mach.c @@ -442,7 +442,7 @@ JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count) { if (!frame) return NULL; /* cap56 = slot count (used by gc_object_size) */ - frame->hdr = objhdr_make(slot_count, OBJ_FRAME, 0, 0, 0, 0); + frame->header = objhdr_make(slot_count, OBJ_FRAME, 0, 0, 0, 0); frame->function = JS_NULL; frame->caller = JS_NULL; frame->address = JS_NewInt32(ctx, 0); @@ -1695,7 +1695,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, case MACH_INVOKE: { /* A=frame_slot, B=result_slot */ JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]); - int nr = (int)objhdr_cap56(fr->hdr); + int nr = (int)objhdr_cap56(fr->header); int c_argc = (nr >= 2) ? nr - 2 : 0; JSValue fn_val = fr->function; JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val); @@ -1746,7 +1746,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, case MACH_GOINVOKE: { /* Async invoke: call and discard result */ JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]); - int nr = (int)objhdr_cap56(fr->hdr); + int nr = (int)objhdr_cap56(fr->header); int c_argc = (nr >= 2) ? nr - 2 : 0; ctx->reg_current_frame = frame_ref.val; ctx->current_register_pc = pc > 0 ? pc - 1 : 0; diff --git a/source/qbe_helpers.c b/source/qbe_helpers.c index a5419095..e4f4cf5f 100644 --- a/source/qbe_helpers.c +++ b/source/qbe_helpers.c @@ -333,7 +333,7 @@ void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) { JSValue cell_rt_invoke(JSContext *ctx, JSValue frame_val) { JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val); - int nr_slots = (int)objhdr_cap56(fr->hdr); + int nr_slots = (int)objhdr_cap56(fr->header); int c_argc = (nr_slots >= 2) ? nr_slots - 2 : 0; /* Copy args to C stack */ diff --git a/source/quickjs-internal.h b/source/quickjs-internal.h index 2b6959de..e5922f25 100644 --- a/source/quickjs-internal.h +++ b/source/quickjs-internal.h @@ -361,13 +361,7 @@ struct JSClass { #define JS_MODE_BACKTRACE_BARRIER \ (1 << 3) /* stop backtrace before this frame */ -typedef struct JSFrameRegister { - objhdr_t hdr; // capacity in this is the total number of words of the object, including the 4 words of overhead and all slots - JSValue function; // JSFunction, function object being invoked - JSValue caller; // JSFrameRegister, the frame that called this one - JSValue address; // address of the instruction in the code that should be executed upon return - JSValue slots[]; // inline memory. order is [this][input args][closed over vars][non closed over vars][temporaries] -} JSFrameRegister; /// extra note: when this frame returns, caller should be set to 0. If caller is found to be 0, then the GC can reduce this frame's slots down to [this][input_args][closed over vars]; if no closed over vars it can be totally removed; may happen naturally in GC since it would have no refs? +/* JSFrameRegister is now an alias for JSFrame — see JSFrame definition below */ /* ============================================================ Register-Based VM Data Structures @@ -823,17 +817,18 @@ typedef struct JSCodeRegister { } JSCodeRegister; -/* Frame for closures - used by link-time relocation model where closures - reference outer frames via (depth, slot) addressing. - Stores function as JSValue to survive GC movements. */ +/* Unified frame struct — used by the register VM and closures. + All fields are JSValues so the GC can scan them uniformly. */ typedef struct JSFrame { objhdr_t header; /* OBJ_FRAME, cap56 = slot count */ - JSValue function; /* JSValue for GC safety (use JS_VALUE_GET_FUNCTION) */ - JSValue caller; /* JSValue for GC safety (unused currently) */ - uint32_t return_pc; + JSValue function; /* JSFunction, function object being invoked */ + JSValue caller; /* JSFrame, the frame that called this one */ + JSValue address; /* return PC stored as JS_NewInt32 */ JSValue slots[]; /* [this][args][captured][locals][temps] */ } JSFrame; +typedef JSFrame JSFrameRegister; + static inline objhdr_t objhdr_set_s (objhdr_t h, bool s) { return s ? (h | OBJHDR_S_MASK) : (h & ~OBJHDR_S_MASK); } @@ -903,7 +898,8 @@ typedef struct JSArray { JSValue values[]; /* inline flexible array member */ } JSArray; -/* JSBlob - binary data per memory.md */ +/* JSBlob — not allocated on GC heap (blobs use JSRecord + opaque). + Struct kept for reference; gc_object_size/gc_scan_object do not handle OBJ_BLOB. */ typedef struct JSBlob { objhdr_t mist_hdr; word_t length; @@ -926,7 +922,7 @@ typedef slot JSRecordEntry; typedef struct JSRecord { objhdr_t mist_hdr; - struct JSRecord *proto; + JSValue proto; /* prototype as JSValue (JS_NULL if none) */ word_t len; /* number of entries */ slot slots[]; /* slots[0] reserved: key low32=class_id, key high32=rec_id, val=opaque */ } JSRecord; @@ -1071,6 +1067,13 @@ static JS_BOOL JSText_equal_ascii (const JSText *text, JSValue imm) { enough to call the interrupt callback often. */ #define JS_INTERRUPT_COUNTER_INIT 10000 +/* Auto-rooted C call argv — GC updates values in-place */ +typedef struct CCallRoot { + JSValue *argv; /* points to C-stack-local array */ + int argc; + struct CCallRoot *prev; /* stack for nesting (C -> JS -> C -> ...) */ +} CCallRoot; + struct JSContext { JSRuntime *rt; @@ -1102,6 +1105,7 @@ struct JSContext { JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */ JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */ + CCallRoot *c_call_root; /* stack of auto-rooted C call argv arrays */ int class_count; /* size of class_array and class_proto */ JSClass *class_array; @@ -1234,7 +1238,7 @@ typedef struct JSRegExp { /* Get prototype from object (works for both JSRecord and JSRecord since they * share layout) */ -#define JS_OBJ_GET_PROTO(p) ((JSRecord *)((JSRecord *)(p))->proto) +#define JS_OBJ_GET_PROTO(p) (JS_IsNull(((JSRecord *)(p))->proto) ? NULL : (JSRecord *)JS_VALUE_GET_PTR(((JSRecord *)(p))->proto)) /* Initial capacity for new records (mask = 7, 8 slots total) */ #define JS_RECORD_INITIAL_MASK 7 diff --git a/source/runtime.c b/source/runtime.c index 0b03f3cb..5f0d69ad 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -510,7 +510,7 @@ JSValue rec_get (JSContext *ctx, JSRecord *rec, JSValue k) { while (p) { int slot = rec_find_slot (p, k); if (slot > 0) { return p->slots[slot].val; } - p = p->proto; + p = JS_IsNull (p->proto) ? NULL : JS_VALUE_GET_RECORD (p->proto); } return JS_NULL; } @@ -540,7 +540,7 @@ int rec_resize (JSContext *ctx, JSValue *pobj, uint64_t new_mask) { /* Initialize new record */ new_rec->mist_hdr = objhdr_make (new_mask, OBJ_RECORD, false, false, false, false); - new_rec->proto = rec->proto; + new_rec->proto = rec->proto; /* JSValue — copies directly */ new_rec->len = 0; /* Initialize all slots to empty */ @@ -646,7 +646,7 @@ JSRecord *js_new_record_class (JSContext *ctx, uint32_t initial_mask, JSClassID if (!rec) return NULL; rec->mist_hdr = objhdr_make (initial_mask, OBJ_RECORD, false, false, false, false); - rec->proto = NULL; + rec->proto = JS_NULL; rec->len = 0; /* Initialize all slots to empty (JS_NULL) */ @@ -1038,7 +1038,7 @@ JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *f continue; } - if (type != OBJ_ARRAY && type != OBJ_TEXT && type != OBJ_RECORD && type != OBJ_FUNCTION && type != OBJ_BLOB && type != OBJ_FRAME) { + if (type != OBJ_ARRAY && type != OBJ_TEXT && type != OBJ_RECORD && type != OBJ_FUNCTION && type != OBJ_FRAME) { fprintf (stderr, "gc_copy_value: invalid object type %d at %p (hdr=0x%llx)\n", type, ptr, (unsigned long long)hdr); fprintf (stderr, " This may be an interior pointer or corrupt root\n"); fflush (stderr); @@ -1079,15 +1079,11 @@ void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *fro JSRecord *rec = (JSRecord *)ptr; uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); #ifdef DUMP_GC_DETAIL - printf(" record: slots=%u used=%u proto=%p\n", mask + 1, (uint32_t)rec->len, (void*)rec->proto); + printf(" record: slots=%u used=%u proto=0x%llx\n", mask + 1, (uint32_t)rec->len, (unsigned long long)rec->proto); fflush(stdout); #endif /* Copy prototype */ - if (rec->proto) { - JSValue proto_val = JS_MKPTR (rec->proto); - proto_val = gc_copy_value (ctx, proto_val, from_base, from_end, to_base, to_free, to_end); - rec->proto = (JSRecord *)JS_VALUE_GET_PTR (proto_val); - } + rec->proto = gc_copy_value (ctx, rec->proto, from_base, from_end, to_base, to_free, to_end); /* Copy table entries */ for (uint32_t i = 0; i <= mask; i++) { JSValue k = rec->slots[i].key; @@ -1127,15 +1123,14 @@ void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *fro break; } case OBJ_TEXT: - case OBJ_BLOB: /* No internal references to scan */ break; case OBJ_FRAME: { - /* JSFrame - scan function, caller, and slots */ + /* JSFrame - scan function, caller, address, and slots */ JSFrame *frame = (JSFrame *)ptr; - /* function and caller are now JSValues - copy them directly */ frame->function = gc_copy_value (ctx, frame->function, from_base, from_end, to_base, to_free, to_end); frame->caller = gc_copy_value (ctx, frame->caller, from_base, from_end, to_base, to_free, to_end); + frame->address = gc_copy_value (ctx, frame->address, from_base, from_end, to_base, to_free, to_end); /* Scan all slots */ uint64_t slot_count = objhdr_cap56 (frame->header); for (uint64_t i = 0; i < slot_count; i++) { @@ -1251,6 +1246,13 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) { ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); } + /* Copy auto-rooted C call argv arrays */ + for (CCallRoot *r = ctx->c_call_root; r != NULL; r = r->prev) { + for (int i = 0; i < r->argc; i++) { + r->argv[i] = gc_copy_value (ctx, r->argv[i], 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 @@ -1410,6 +1412,7 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) { /* Initialize register VM frame root */ ctx->reg_current_frame = JS_NULL; + ctx->c_call_root = NULL; /* Initialize per-context execution state (moved from JSRuntime) */ ctx->current_exception = JS_NULL; @@ -2205,7 +2208,7 @@ JSValue JS_NewObjectProtoClass (JSContext *ctx, JSValue proto_val, JSClassID cla /* Set prototype if provided */ if (JS_IsRecord (proto_val)) { - rec->proto = JS_VALUE_GET_RECORD (proto_val); + rec->proto = proto_val; } return JS_MKPTR (rec); @@ -2685,8 +2688,8 @@ int JS_HasProperty (JSContext *ctx, JSValue obj, JSValue prop) { for (;;) { ret = JS_GetOwnPropertyInternal (ctx, NULL, p, prop); if (ret != 0) return ret; - p = p->proto; /* Direct pointer chase is safe - no allocation */ - if (!p) break; + if (JS_IsNull (p->proto)) break; + p = JS_VALUE_GET_RECORD (p->proto); } return FALSE; } @@ -2965,7 +2968,8 @@ int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key) { /* Check own and prototype chain */ while (rec) { if (rec_find_slot (rec, key) > 0) return TRUE; - rec = rec->proto; + if (JS_IsNull (rec->proto)) break; + rec = JS_VALUE_GET_RECORD (rec->proto); } return FALSE; } @@ -3902,8 +3906,8 @@ __maybe_unused void JS_DumpObject (JSRuntime *rt, JSRecord *rec) { printf ("%14p ", (void *)rec); /* Print prototype from JSRecord */ - if (rec->proto) { - printf ("%14p ", (void *)rec->proto); + if (!JS_IsNull (rec->proto)) { + printf ("%14p ", JS_VALUE_GET_PTR (rec->proto)); } else { printf ("%14s ", "-"); } @@ -3993,20 +3997,20 @@ static JSValue js_throw_type_error (JSContext *ctx, JSValue this_val, int argc, } JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv) { - JSCFunctionType func; - JSFunction *f; + JSFunction *f = JS_VALUE_GET_FUNCTION (func_obj); + JSCFunctionEnum cproto = f->u.cfunc.cproto; + JSCFunctionType func = f->u.cfunc.c_function; JSValue ret_val; - JSValue *arg_buf; - int arg_count, i; - JSCFunctionEnum cproto; - f = JS_VALUE_GET_FUNCTION (func_obj); - cproto = f->u.cfunc.cproto; - arg_count = f->length; + /* Auto-root argv: copy to C stack and register as GC root. + GC will update arg_copy[] in-place, so C functions can read + argv[n] across allocation points without manual guarding. */ + JSValue arg_copy[argc > 0 ? argc : 1]; + if (argc > 0) + memcpy (arg_copy, argv, argc * sizeof (JSValue)); - arg_buf = argv; - - func = f->u.cfunc.c_function; + CCallRoot root = { arg_copy, argc, ctx->c_call_root }; + ctx->c_call_root = &root; if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_CALL)) { js_debug dbg; @@ -4016,16 +4020,14 @@ JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, switch (cproto) { case JS_CFUNC_generic: - ret_val = func.generic (ctx, this_obj, argc, arg_buf); + ret_val = func.generic (ctx, this_obj, argc, arg_copy); break; case JS_CFUNC_generic_magic: - ret_val - = func.generic_magic (ctx, this_obj, argc, arg_buf, f->u.cfunc.magic); + ret_val = func.generic_magic (ctx, this_obj, argc, arg_copy, f->u.cfunc.magic); break; case JS_CFUNC_f_f: { double d1; - - if (unlikely (JS_ToFloat64 (ctx, &d1, arg_buf[0]))) { + if (unlikely (JS_ToFloat64 (ctx, &d1, arg_copy[0]))) { ret_val = JS_EXCEPTION; break; } @@ -4033,56 +4035,57 @@ JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, } break; case JS_CFUNC_f_f_f: { double d1, d2; - - if (unlikely (JS_ToFloat64 (ctx, &d1, arg_buf[0]))) { + if (unlikely (JS_ToFloat64 (ctx, &d1, arg_copy[0]))) { ret_val = JS_EXCEPTION; break; } - if (unlikely (JS_ToFloat64 (ctx, &d2, arg_buf[1]))) { + if (unlikely (JS_ToFloat64 (ctx, &d2, arg_copy[1]))) { ret_val = JS_EXCEPTION; break; } ret_val = JS_NewFloat64 (ctx, func.f_f_f (d1, d2)); } break; - /* Fixed-arity fast paths - direct call without argc/argv marshaling */ + /* Fixed-arity fast paths — args passed by value at dispatch time */ case JS_CFUNC_0: ret_val = func.f0 (ctx, this_obj); break; case JS_CFUNC_1: - ret_val = func.f1 (ctx, this_obj, arg_buf[0]); + ret_val = func.f1 (ctx, this_obj, arg_copy[0]); break; case JS_CFUNC_2: - ret_val = func.f2 (ctx, this_obj, arg_buf[0], arg_buf[1]); + ret_val = func.f2 (ctx, this_obj, arg_copy[0], arg_copy[1]); break; case JS_CFUNC_3: - ret_val = func.f3 (ctx, this_obj, arg_buf[0], arg_buf[1], arg_buf[2]); + ret_val = func.f3 (ctx, this_obj, arg_copy[0], arg_copy[1], arg_copy[2]); break; case JS_CFUNC_4: - ret_val = func.f4 (ctx, this_obj, arg_buf[0], arg_buf[1], arg_buf[2], arg_buf[3]); + ret_val = func.f4 (ctx, this_obj, arg_copy[0], arg_copy[1], arg_copy[2], arg_copy[3]); break; /* Pure functions (no this_val) */ case JS_CFUNC_PURE: - ret_val = func.pure (ctx, argc, arg_buf); + ret_val = func.pure (ctx, argc, arg_copy); break; case JS_CFUNC_PURE_0: ret_val = func.pure0 (ctx); break; case JS_CFUNC_PURE_1: - ret_val = func.pure1 (ctx, arg_buf[0]); + ret_val = func.pure1 (ctx, arg_copy[0]); break; case JS_CFUNC_PURE_2: - ret_val = func.pure2 (ctx, arg_buf[0], arg_buf[1]); + ret_val = func.pure2 (ctx, arg_copy[0], arg_copy[1]); break; case JS_CFUNC_PURE_3: - ret_val = func.pure3 (ctx, arg_buf[0], arg_buf[1], arg_buf[2]); + ret_val = func.pure3 (ctx, arg_copy[0], arg_copy[1], arg_copy[2]); break; case JS_CFUNC_PURE_4: - ret_val = func.pure4 (ctx, arg_buf[0], arg_buf[1], arg_buf[2], arg_buf[3]); + ret_val = func.pure4 (ctx, arg_copy[0], arg_copy[1], arg_copy[2], arg_copy[3]); break; default: abort (); } + ctx->c_call_root = root.prev; + if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET)) ctx->trace_hook (ctx, JS_HOOK_RET, NULL, ctx->trace_data); @@ -7868,20 +7871,12 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu } /* array.reduce(arr, fn, initial, reverse) */ -/* GC-safe reduce: re-chase array after each call */ static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; if (!JS_IsArray (argv[0])) return JS_NULL; if (!JS_IsFunction (argv[1])) return JS_NULL; - /* GC-safe: root argv[0] and argv[1] for the duration of this function */ - JSGCRef arr_ref, func_ref; - JS_PushGCRef (ctx, &arr_ref); - JS_PushGCRef (ctx, &func_ref); - arr_ref.val = argv[0]; - func_ref.val = argv[1]; - - JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); + JSArray *arr = JS_VALUE_GET_ARRAY (argv[0]); word_t len = arr->len; int reverse = argc > 3 && JS_ToBool (ctx, argv[3]); @@ -7889,65 +7884,63 @@ static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc, JSValue acc; if (argc < 3 || JS_IsNull (argv[2])) { - if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } - if (len == 1) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return arr->values[0]; } + if (len == 0) return JS_NULL; + if (len == 1) return arr->values[0]; if (reverse) { acc = arr->values[len - 1]; for (word_t i = len - 1; i > 0; i--) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); + arr = JS_VALUE_GET_ARRAY (argv[0]); if (i - 1 >= arr->len) continue; JSValue args[2] = { acc, arr->values[i - 1] }; JS_PUSH_VALUE (ctx, acc); - JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + JSValue new_acc = JS_CallInternal (ctx, argv[1], JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); - if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_IsException (new_acc)) return JS_EXCEPTION; acc = new_acc; } } else { acc = arr->values[0]; for (word_t i = 1; i < len; i++) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); + arr = JS_VALUE_GET_ARRAY (argv[0]); if (i >= arr->len) break; JSValue args[2] = { acc, arr->values[i] }; JS_PUSH_VALUE (ctx, acc); - JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + JSValue new_acc = JS_CallInternal (ctx, argv[1], JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); - if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_IsException (new_acc)) return JS_EXCEPTION; acc = new_acc; } } } else { - if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return argv[2]; } + if (len == 0) return argv[2]; acc = argv[2]; if (reverse) { for (word_t i = len; i > 0; i--) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); + arr = JS_VALUE_GET_ARRAY (argv[0]); if (i - 1 >= arr->len) continue; JSValue args[2] = { acc, arr->values[i - 1] }; JS_PUSH_VALUE (ctx, acc); - JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + JSValue new_acc = JS_CallInternal (ctx, argv[1], JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); - if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_IsException (new_acc)) return JS_EXCEPTION; acc = new_acc; } } else { for (word_t i = 0; i < len; i++) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); + arr = JS_VALUE_GET_ARRAY (argv[0]); if (i >= arr->len) break; JSValue args[2] = { acc, arr->values[i] }; JS_PUSH_VALUE (ctx, acc); - JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + JSValue new_acc = JS_CallInternal (ctx, argv[1], JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); - if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_IsException (new_acc)) return JS_EXCEPTION; acc = new_acc; } } } - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &arr_ref); return acc; } @@ -7957,185 +7950,139 @@ static JSValue js_cell_array_for (JSContext *ctx, JSValue this_val, int argc, JS if (!JS_IsArray (argv[0])) return JS_NULL; if (!JS_IsFunction (argv[1])) return JS_NULL; - /* GC-safe: root argv[0] and argv[1] */ - JSGCRef arr_ref, func_ref; - JS_PushGCRef (ctx, &arr_ref); - JS_PushGCRef (ctx, &func_ref); - arr_ref.val = argv[0]; - func_ref.val = argv[1]; - - JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); + JSArray *arr = JS_VALUE_GET_ARRAY (argv[0]); word_t len = arr->len; - if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } + if (len == 0) return JS_NULL; int reverse = argc > 2 && JS_ToBool (ctx, argv[2]); JSValue exit_val = argc > 3 ? argv[3] : JS_NULL; /* Determine function arity */ - int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; + int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (argv[1]))->length; if (reverse) { for (word_t i = len; i > 0; i--) { - /* Re-chase array each iteration */ - arr = JS_VALUE_GET_ARRAY (arr_ref.val); + arr = JS_VALUE_GET_ARRAY (argv[0]); if (i - 1 >= arr->len) continue; JSValue result; if (arity == 1) { JSValue item = arr->values[i - 1]; - result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); + result = JS_CallInternal (ctx, argv[1], JS_NULL, 1, &item, 0); } else { JSValue args[2]; args[0] = arr->values[i - 1]; args[1] = JS_NewInt32 (ctx, (int32_t)(i - 1)); - result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + result = JS_CallInternal (ctx, argv[1], JS_NULL, 2, args, 0); } - - if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) { - JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); + if (JS_IsException (result)) return JS_EXCEPTION; + if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) return result; - } } } else { for (word_t i = 0; i < len; i++) { - /* Re-chase array each iteration */ - arr = JS_VALUE_GET_ARRAY (arr_ref.val); + arr = JS_VALUE_GET_ARRAY (argv[0]); if (i >= arr->len) break; JSValue result; if (arity == 1) { JSValue item = arr->values[i]; - result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); + result = JS_CallInternal (ctx, argv[1], JS_NULL, 1, &item, 0); } else { JSValue args[2]; args[0] = arr->values[i]; args[1] = JS_NewInt32 (ctx, (int32_t)i); - result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + result = JS_CallInternal (ctx, argv[1], JS_NULL, 2, args, 0); } - - if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) { - JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); + if (JS_IsException (result)) return JS_EXCEPTION; + if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) return result; - } } } - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } -/* array.find(arr, fn, reverse, from) */ -/* array.find(arr, fn, reverse, from) - GC-safe */ +/* array.find(arr, fn_or_value, reverse, from) */ static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; if (!JS_IsArray (argv[0])) return JS_NULL; - /* GC-safe: root argv[0] */ - JSGCRef arr_ref; - JS_PushGCRef (ctx, &arr_ref); - arr_ref.val = argv[0]; - - JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); + JSArray *arr = JS_VALUE_GET_ARRAY (argv[0]); word_t len = arr->len; int reverse = argc > 2 && JS_ToBool (ctx, argv[2]); int32_t from; if (argc > 3 && !JS_IsNull (argv[3])) { - if (JS_ToInt32 (ctx, &from, argv[3])) { JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } + if (JS_ToInt32 (ctx, &from, argv[3])) return JS_NULL; } else { from = reverse ? (int32_t)(len - 1) : 0; } if (!JS_IsFunction (argv[1])) { - /* Compare exactly - no GC concerns since no calls */ JSValue target = argv[1]; if (reverse) { for (int32_t i = from; i >= 0; i--) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); + arr = JS_VALUE_GET_ARRAY (argv[0]); if ((word_t)i >= arr->len) continue; - if (js_strict_eq (ctx, arr->values[i], target)) { - JS_PopGCRef (ctx, &arr_ref); + if (js_strict_eq (ctx, arr->values[i], target)) return JS_NewInt32 (ctx, i); - } } } else { for (word_t i = (word_t)from; i < len; i++) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); + arr = JS_VALUE_GET_ARRAY (argv[0]); if (i >= arr->len) break; - if (js_strict_eq (ctx, arr->values[i], target)) { - JS_PopGCRef (ctx, &arr_ref); + if (js_strict_eq (ctx, arr->values[i], target)) return JS_NewInt32 (ctx, (int32_t)i); - } } } - JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } - /* Use function predicate - must re-chase after each call */ - JSGCRef func_ref; - JS_PushGCRef (ctx, &func_ref); - func_ref.val = argv[1]; - int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; + /* Use function predicate — re-chase after each call */ + int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (argv[1]))->length; if (arity == 2) { if (reverse) { for (int32_t i = from; i >= 0; i--) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); + arr = JS_VALUE_GET_ARRAY (argv[0]); if ((word_t)i >= arr->len) continue; JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) }; - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - if (JS_ToBool (ctx, result)) { - JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); - return JS_NewInt32 (ctx, i); - } + JSValue result = JS_CallInternal (ctx, argv[1], JS_NULL, 2, args, 0); + if (JS_IsException (result)) return JS_EXCEPTION; + if (JS_ToBool (ctx, result)) return JS_NewInt32 (ctx, i); } } else { for (word_t i = (word_t)from; i < len; i++) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); + arr = JS_VALUE_GET_ARRAY (argv[0]); if (i >= arr->len) break; JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, (int32_t)i) }; - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - if (JS_ToBool (ctx, result)) { - JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); - return JS_NewInt32 (ctx, (int32_t)i); - } + JSValue result = JS_CallInternal (ctx, argv[1], JS_NULL, 2, args, 0); + if (JS_IsException (result)) return JS_EXCEPTION; + if (JS_ToBool (ctx, result)) return JS_NewInt32 (ctx, (int32_t)i); } } } else { if (reverse) { for (int32_t i = from; i >= 0; i--) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); + arr = JS_VALUE_GET_ARRAY (argv[0]); if ((word_t)i >= arr->len) continue; JSValue item = arr->values[i]; - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - if (JS_ToBool (ctx, result)) { - JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); - return JS_NewInt32 (ctx, i); - } + JSValue result = JS_CallInternal (ctx, argv[1], JS_NULL, 1, &item, 0); + if (JS_IsException (result)) return JS_EXCEPTION; + if (JS_ToBool (ctx, result)) return JS_NewInt32 (ctx, i); } } else { for (word_t i = (word_t)from; i < len; i++) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); + arr = JS_VALUE_GET_ARRAY (argv[0]); if (i >= arr->len) break; JSValue item = arr->values[i]; - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - if (JS_ToBool (ctx, result)) { - JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); - return JS_NewInt32 (ctx, (int32_t)i); - } + JSValue result = JS_CallInternal (ctx, argv[1], JS_NULL, 1, &item, 0); + if (JS_IsException (result)) return JS_EXCEPTION; + if (JS_ToBool (ctx, result)) return JS_NewInt32 (ctx, (int32_t)i); } } } - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } @@ -8604,55 +8551,29 @@ static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSV if (argc < 1) return JS_NULL; if (!JS_IsFunction (argv[0])) return argv[0]; - JSGCRef func_ref, args_ref; - JS_PushGCRef (ctx, &func_ref); - JS_PushGCRef (ctx, &args_ref); - func_ref.val = argv[0]; - args_ref.val = argc >= 2 ? argv[1] : JS_NULL; + if (argc < 2) + return JS_CallInternal (ctx, argv[0], JS_NULL, 0, NULL, 0); - if (argc < 2) { - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &func_ref); - return result; - } + if (!JS_IsArray (argv[1])) + return JS_CallInternal (ctx, argv[0], JS_NULL, 1, &argv[1], 0); - if (!JS_IsArray (args_ref.val)) { - /* Wrap single value in array */ - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &args_ref.val, 0); - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &func_ref); - return result; - } - - JSArray *arr = JS_VALUE_GET_ARRAY (args_ref.val); + JSArray *arr = JS_VALUE_GET_ARRAY (argv[1]); int len = arr->len; - if (len == 0) { - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &func_ref); - return result; - } + if (len == 0) + return JS_CallInternal (ctx, argv[0], JS_NULL, 0, NULL, 0); JSValue *args = js_malloc (ctx, sizeof (JSValue) * len); - if (!args) { - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &func_ref); - return JS_EXCEPTION; - } - arr = JS_VALUE_GET_ARRAY (args_ref.val); /* re-chase after malloc via rooted ref */ + if (!args) return JS_EXCEPTION; + arr = JS_VALUE_GET_ARRAY (argv[1]); /* re-chase after malloc */ for (int i = 0; i < len; i++) { args[i] = arr->values[i]; } - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, len, args, 0); + JSValue result = JS_CallInternal (ctx, argv[0], JS_NULL, len, args, 0); js_free (ctx, args); - - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &func_ref); return result; } @@ -9233,15 +9154,9 @@ static JSValue js_mach_load (JSContext *ctx, JSValue this_val, int argc, JSValue JSValue env = (argc >= 2 && mist_is_gc_object (argv[1])) ? argv[1] : JS_NULL; - JSGCRef env_ref; - JS_PushGCRef (ctx, &env_ref); - env_ref.val = env; - - JSCodeRegister *code = JS_LoadMachCode (ctx, mc, env_ref.val); + JSCodeRegister *code = JS_LoadMachCode (ctx, mc, env); JS_FreeMachCode (mc); - JSValue result = JS_CallRegisterVM (ctx, code, ctx->global_obj, 0, NULL, env_ref.val, JS_NULL); - JS_PopGCRef (ctx, &env_ref); - return result; + return JS_CallRegisterVM (ctx, code, ctx->global_obj, 0, NULL, env, JS_NULL); } /* mach_eval_mcode(name, mcode_json, env?) - compile mcode IR and run via register VM */ @@ -9281,14 +9196,9 @@ static JSValue js_mach_eval_mcode (JSContext *ctx, JSValue this_val, int argc, J JSValue env = (argc >= 3 && mist_is_gc_object (argv[2])) ? argv[2] : JS_NULL; - JSGCRef env_ref; - JS_PushGCRef (ctx, &env_ref); - env_ref.val = env; - - JSCodeRegister *code = JS_LoadMachCode (ctx, mc, env_ref.val); + JSCodeRegister *code = JS_LoadMachCode (ctx, mc, env); JS_FreeMachCode (mc); - JSValue result = JS_CallRegisterVM (ctx, code, ctx->global_obj, 0, NULL, env_ref.val, JS_NULL); - JS_PopGCRef (ctx, &env_ref); + JSValue result = JS_CallRegisterVM (ctx, code, ctx->global_obj, 0, NULL, env, JS_NULL); JS_FreeCString (ctx, name); return result; } @@ -9328,18 +9238,13 @@ static JSValue js_mach_dump_mcode (JSContext *ctx, JSValue this_val, int argc, J JSValue env = (argc >= 3 && mist_is_gc_object (argv[2])) ? argv[2] : JS_NULL; - JSGCRef env_ref; - JS_PushGCRef (ctx, &env_ref); - env_ref.val = env; - /* Serialize to binary then dump */ size_t bin_size; uint8_t *bin = JS_SerializeMachCode (mc, &bin_size); JS_FreeMachCode (mc); - JS_DumpMachBin (ctx, bin, bin_size, env_ref.val); + JS_DumpMachBin (ctx, bin, bin_size, env); sys_free (bin); - JS_PopGCRef (ctx, &env_ref); JS_FreeCString (ctx, name); return JS_NULL; } @@ -9888,19 +9793,11 @@ static JSValue js_cell_push (JSContext *ctx, JSValue this_val, int argc, JSValue if (!JS_IsArray (argv[0])) return JS_NULL; - JSGCRef arr_ref; - JS_PushGCRef (ctx, &arr_ref); - arr_ref.val = argv[0]; - for (int i = 1; i < argc; i++) { - if (js_intrinsic_array_push (ctx, &arr_ref.val, argv[i]) < 0) { - JS_PopGCRef (ctx, &arr_ref); + if (js_intrinsic_array_push (ctx, &argv[0], argv[i]) < 0) return JS_EXCEPTION; - } } - argv[0] = arr_ref.val; - JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } @@ -10171,50 +10068,34 @@ static JSValue js_cell_call (JSContext *ctx, JSValue this_val, int argc, JSValue if (argc < 1) return JS_ThrowTypeError (ctx, "call requires a function argument"); - JSGCRef func_ref, this_ref, args_ref; - JS_PushGCRef (ctx, &func_ref); - JS_PushGCRef (ctx, &this_ref); - JS_PushGCRef (ctx, &args_ref); - func_ref.val = argv[0]; - this_ref.val = argc >= 2 ? argv[1] : JS_NULL; - args_ref.val = argc >= 3 ? argv[2] : JS_NULL; - - if (!JS_IsFunction (func_ref.val)) { - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &this_ref); - JS_PopGCRef (ctx, &func_ref); + if (!JS_IsFunction (argv[0])) return JS_ThrowTypeError (ctx, "first argument must be a function"); - } - if (argc < 3 || JS_IsNull (args_ref.val)) { - JSValue ret = JS_CallInternal (ctx, func_ref.val, this_ref.val, 0, NULL, 0); - JS_PopGCRef (ctx, &args_ref); + JSGCRef this_ref; + JS_PushGCRef (ctx, &this_ref); + this_ref.val = argc >= 2 ? argv[1] : JS_NULL; + + if (argc < 3 || JS_IsNull (argv[2])) { + JSValue ret = JS_CallInternal (ctx, argv[0], this_ref.val, 0, NULL, 0); JS_PopGCRef (ctx, &this_ref); - JS_PopGCRef (ctx, &func_ref); return ret; } - if (!JS_IsArray (args_ref.val)) { - JS_PopGCRef (ctx, &args_ref); + if (!JS_IsArray (argv[2])) { JS_PopGCRef (ctx, &this_ref); - JS_PopGCRef (ctx, &func_ref); return JS_ThrowTypeError (ctx, "third argument must be an array"); } uint32_t len; - JSValue *tab = build_arg_list (ctx, &len, &args_ref.val); + JSValue *tab = build_arg_list (ctx, &len, &argv[2]); if (!tab) { - JS_PopGCRef (ctx, &args_ref); JS_PopGCRef (ctx, &this_ref); - JS_PopGCRef (ctx, &func_ref); return JS_EXCEPTION; } - JSValue ret = JS_CallInternal (ctx, func_ref.val, this_ref.val, len, tab, 0); + JSValue ret = JS_CallInternal (ctx, argv[0], this_ref.val, len, tab, 0); free_arg_list (ctx, tab, len); - JS_PopGCRef (ctx, &args_ref); JS_PopGCRef (ctx, &this_ref); - JS_PopGCRef (ctx, &func_ref); return ret; } @@ -10395,32 +10276,17 @@ static JSValue js_cell_ends_with(JSContext *ctx, JSValue this_val, int argc, JSV } /* every(arr, pred) — find(arr, x => !pred(x)) == null */ -static JSValue js_cell_every(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { +static JSValue js_cell_every (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; - if (!JS_IsArray(argv[0]) || !JS_IsFunction(argv[1])) return JS_NULL; - JSGCRef arr_ref, fn_ref; - JS_PushGCRef(ctx, &arr_ref); - JS_PushGCRef(ctx, &fn_ref); - arr_ref.val = argv[0]; - fn_ref.val = argv[1]; - JSArray *arr = JS_VALUE_GET_ARRAY(arr_ref.val); + if (!JS_IsArray (argv[0]) || !JS_IsFunction (argv[1])) return JS_NULL; + JSArray *arr = JS_VALUE_GET_ARRAY (argv[0]); for (int i = 0; i < arr->len; i++) { JSValue elem = arr->values[i]; - JSValue r = JS_CallInternal(ctx, fn_ref.val, JS_NULL, 1, &elem, 0); - arr = JS_VALUE_GET_ARRAY(arr_ref.val); - if (JS_IsException(r)) { - JS_PopGCRef(ctx, &fn_ref); - JS_PopGCRef(ctx, &arr_ref); - return r; - } - if (!JS_ToBool(ctx, r)) { - JS_PopGCRef(ctx, &fn_ref); - JS_PopGCRef(ctx, &arr_ref); - return JS_FALSE; - } + JSValue r = JS_CallInternal (ctx, argv[1], JS_NULL, 1, &elem, 0); + arr = JS_VALUE_GET_ARRAY (argv[0]); + if (JS_IsException (r)) return r; + if (!JS_ToBool (ctx, r)) return JS_FALSE; } - JS_PopGCRef(ctx, &fn_ref); - JS_PopGCRef(ctx, &arr_ref); return JS_TRUE; }