From e2bc5948c12cb9872e8d3a2fda939c452ba4f9cc Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 6 Feb 2026 18:30:26 -0600 Subject: [PATCH] fix functions and closures in mach --- source/quickjs.c | 60 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/source/quickjs.c b/source/quickjs.c index 38168bd6..54d75ffa 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -2126,10 +2126,18 @@ void *js_malloc (JSContext *ctx, size_t size) { JS_ThrowOutOfMemory(ctx); return NULL; } - /* Check if we have space after GC */ + /* Check if we have space after GC — the GC may have doubled + next_block_size but allocated the old size. Fall through to + the normal grow path so we get a bigger block. */ if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { - JS_ThrowOutOfMemory(ctx); - return NULL; + if (ctx_gc(ctx, 1) < 0) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } } #else /* Check if we have space in current block */ @@ -31459,6 +31467,7 @@ static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) { /* Register parameters */ cJSON *params = cJSON_GetObjectItem(node, "params"); if (!params) params = cJSON_GetObjectItem(node, "parameters"); + if (!params) params = cJSON_GetObjectItem(node, "list"); int nparams = params ? cJSON_GetArraySize(params) : 0; child.nr_args = nparams; for (int i = 0; i < nparams; i++) { @@ -31476,7 +31485,8 @@ static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) { /* Compile body */ cJSON *body = cJSON_GetObjectItem(node, "body"); - if (body) { + if (!body) body = node; /* statements may be directly on the function node */ + { cJSON *stmts = cJSON_GetObjectItem(body, "statements"); if (!stmts) stmts = body; /* body might be the statements array directly */ if (cJSON_IsArray(stmts)) { @@ -31557,6 +31567,19 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) { return; } + /* Function declaration statement */ + if (strcmp(kind, "function") == 0) { + const char *name = cJSON_GetStringValue(cJSON_GetObjectItem(stmt, "name")); + if (!name) return; + int slot = mach_find_var(cs, name); + if (slot < 0) { + slot = mach_reserve_reg(cs); + mach_add_var(cs, name, slot, 1); + } + mach_compile_expr(cs, stmt, slot); + return; + } + /* Expression statement (call) */ if (strcmp(kind, "call") == 0) { cJSON *expr = cJSON_GetObjectItem(stmt, "expression"); @@ -31796,6 +31819,20 @@ static MachCode *mach_compile_program(MachCompState *cs, cJSON *ast) { /* Scan scope record for declarations */ mach_scan_scope(cs); + /* Hoist function declarations */ + cJSON *functions = cJSON_GetObjectItem(ast, "functions"); + if (functions && cJSON_IsArray(functions)) { + int fcount = cJSON_GetArraySize(functions); + for (int i = 0; i < fcount; i++) { + cJSON *fn_node = cJSON_GetArrayItem(functions, i); + const char *fn_name = cJSON_GetStringValue(cJSON_GetObjectItem(fn_node, "name")); + if (!fn_name) continue; + int slot = mach_find_var(cs, fn_name); + if (slot < 0) continue; + mach_compile_expr(cs, fn_node, slot); + } + } + /* Compile each statement */ int count = cJSON_GetArraySize(stmts); for (int i = 0; i < count; i++) { @@ -32092,7 +32129,11 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JS_AddGCRef(ctx, &frame_ref); frame_ref.val = JS_MKPTR(frame); - /* Setup initial frame */ + /* Setup initial frame — wrap top-level code in a function object so that + returning from a called register function can read code/env from frame */ + JSValue top_fn = js_new_register_function(ctx, code, env, outer_frame); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + frame->function = top_fn; frame->slots[0] = this_obj; /* slot 0 = this */ /* Copy arguments */ @@ -32116,11 +32157,11 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, if (JS_IsNull(frame->caller)) goto done; /* Pop frame */ - int ret_info = JS_VALUE_GET_INT(frame->address); JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); frame->caller = JS_NULL; frame = caller; frame_ref.val = JS_MKPTR(frame); + int ret_info = JS_VALUE_GET_INT(frame->address); JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); code = fn->u.reg.code; env = fn->u.reg.env_record; @@ -32420,7 +32461,10 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSCodeRegister *fn_code = fn->u.reg.code; JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots); if (!new_frame) { result = JS_EXCEPTION; goto done; } + /* Re-read pointers — GC may have moved them */ frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + func_val = frame->slots[base]; + fn = JS_VALUE_GET_FUNCTION(func_val); new_frame->function = func_val; new_frame->slots[0] = JS_NULL; /* this */ for (int i = 0; i < nargs && i < fn_code->arity; i++) @@ -32450,11 +32494,11 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, result = frame->slots[a]; if (JS_IsNull(frame->caller)) goto done; { - int ret_info = JS_VALUE_GET_INT(frame->address); JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); frame->caller = JS_NULL; frame = caller; frame_ref.val = JS_MKPTR(frame); + int ret_info = JS_VALUE_GET_INT(frame->address); JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); code = fn->u.reg.code; env = fn->u.reg.env_record; @@ -32468,11 +32512,11 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, result = JS_NULL; if (JS_IsNull(frame->caller)) goto done; { - int ret_info = JS_VALUE_GET_INT(frame->address); JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); frame->caller = JS_NULL; frame = caller; frame_ref.val = JS_MKPTR(frame); + int ret_info = JS_VALUE_GET_INT(frame->address); JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); code = fn->u.reg.code; env = fn->u.reg.env_record;