From 334f3a789b8a83207b9a0f2f21116892d46791b2 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Wed, 31 Dec 2025 11:09:18 -0600 Subject: [PATCH] runs --- source/quickjs.c | 265 +++++++++++++++++++++++++++++++++++++---------- test.ce | 5 + 2 files changed, 213 insertions(+), 57 deletions(-) diff --git a/source/quickjs.c b/source/quickjs.c index b5f41aa9..9a8dd34a 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -363,6 +363,8 @@ typedef struct { int call_argc; int call_has_this; int is_tail_call; + int is_constructor; /* 1 if this is a constructor call (new) */ + int pre_argc; /* number of stack slots before argv (func, this/new_target) */ } VMCallInfo; typedef enum { @@ -13085,16 +13087,57 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj, JS_ThrowTypeError(caller_ctx, "not a function"); goto exception_unwind; } - - ret_val = call_func(caller_ctx, call_info.func_obj, call_info.this_obj, - call_info.argc, (JSValueConst *)call_info.argv, 0); - if (JS_IsException(ret_val)) + + int call_flags = 0; + JSValueConst this_for_call = call_info.this_obj; + + if (call_info.is_constructor) { + /* Constructor call: use JS_CALL_FLAG_CONSTRUCTOR and pass new_target as this + (QuickJS convention for C constructors) */ + call_flags = JS_CALL_FLAG_CONSTRUCTOR; + this_for_call = call_info.new_target; +#ifdef DEBUG_VM + printf("[C-CTOR] calling C constructor class_id=%d argc=%d new_target_tag=%d\n", + callee_p->class_id, call_info.argc, JS_VALUE_GET_TAG(call_info.new_target)); +#endif + } + + ret_val = call_func(caller_ctx, call_info.func_obj, this_for_call, + call_info.argc, (JSValueConst *)call_info.argv, call_flags); + +#ifdef DEBUG_VM + if (call_info.is_constructor) { + printf("[C-CTOR] returned tag=%d exception=%d\n", + JS_VALUE_GET_TAG(ret_val), JS_IsException(ret_val)); + } +#endif + + if (JS_IsException(ret_val)) { +#ifdef DEBUG_VM + printf("[EXCEPTION] C call failed: caller=%s is_ctor=%d callee_class=%d\n", + frame->b->func_name ? JS_AtomToCString(caller_ctx, frame->b->func_name) : "", + call_info.is_constructor, callee_p->class_id); +#endif goto exception_unwind; + } if (call_info.is_tail_call) { + /* Tail-call to C function: call completed, now we must return + from the current frame (not resume it). A tail-call has no + continuation in the calling function. */ const uint8_t *ret_pc = frame->ret_pc; int ret_sp_offset = frame->ret_sp_offset; +#ifdef DEBUG_VM + { + JSFunctionBytecode *caller_b = frame->b; + printf("[TAILCALL-C] C func returned, popping frame=%s " + "will_return_to ret_pc=%p ret_sp_offset=%d\n", + caller_b->func_name ? JS_AtomToCString(caller_ctx, caller_b->func_name) : "", + ret_pc, ret_sp_offset); + } +#endif + vm_pop_frame(caller_ctx); if (caller_ctx->frame_stack_top < initial_frame_top) { @@ -13104,6 +13147,20 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj, frame = &caller_ctx->frame_stack[caller_ctx->frame_stack_top]; +#ifdef DEBUG_VM + { + JSFunctionBytecode *parent_b = frame->b; + const uint8_t *bc = parent_b->byte_code_buf; + int bc_len = parent_b->byte_code_len; + printf("[TAILCALL-C] resuming frame=%s ret_pc_offset=%td bc_len=%d\n", + parent_b->func_name ? JS_AtomToCString(caller_ctx, parent_b->func_name) : "", + ret_pc - bc, bc_len); + if (ret_pc < bc || ret_pc >= bc + bc_len) { + printf(" ERROR: ret_pc=%p not in [%p, %p)\n", ret_pc, bc, bc + bc_len); + } + } +#endif + JSValue *stack_base = &caller_ctx->value_stack[frame->value_stack_base]; JSValue *current_sp = stack_base + frame->sp_offset; JSValue *target_sp = stack_base + ret_sp_offset; @@ -13113,11 +13170,32 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj, *current_sp++ = ret_val; frame->sp_offset = current_sp - stack_base; frame->pc = ret_pc; + +#ifdef DEBUG_VM + { + JSFunctionBytecode *fb = frame->b; + const uint8_t *bc = fb->byte_code_buf; + int bc_len = fb->byte_code_len; + if (frame->pc < bc || frame->pc >= bc + bc_len) { + printf("[TAILCALL-C] ERROR: frame->pc=%p not in [%p, %p) func=%s\n", + frame->pc, bc, bc + bc_len, + fb->func_name ? JS_AtomToCString(caller_ctx, fb->func_name) : ""); + abort(); + } + uint8_t op = *frame->pc; + if (op == 0 || op == OP_invalid) { + printf("[TAILCALL-C] BAD resume opcode=0x%02x at offset=%td func=%s\n", + op, frame->pc - bc, + fb->func_name ? JS_AtomToCString(caller_ctx, fb->func_name) : ""); + abort(); + } + } +#endif break; } - /* Manual stack cleanup for C call */ + /* Manual stack cleanup for regular C call (not tail-call) */ frame = &caller_ctx->frame_stack[caller_ctx->frame_stack_top]; JSValue *stack_base = &caller_ctx->value_stack[frame->value_stack_base]; JSValue *current_sp = stack_base + frame->sp_offset; @@ -13129,16 +13207,57 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj, *current_sp++ = ret_val; frame->sp_offset = current_sp - stack_base; frame->pc = call_info.ret_pc; + +#ifdef DEBUG_VM + { + JSFunctionBytecode *fb = frame->b; + const uint8_t *bc = fb->byte_code_buf; + int bc_len = fb->byte_code_len; + if (frame->pc < bc || frame->pc >= bc + bc_len) { + printf("[C-CALL] ERROR: frame->pc=%p not in [%p, %p) func=%s\n", + frame->pc, bc, bc + bc_len, + fb->func_name ? JS_AtomToCString(caller_ctx, fb->func_name) : ""); + abort(); + } + uint8_t op = *frame->pc; + if (op == 0 || op == OP_invalid) { + printf("[C-CALL] BAD resume opcode=0x%02x at offset=%td func=%s\n", + op, frame->pc - bc, + fb->func_name ? JS_AtomToCString(caller_ctx, fb->func_name) : ""); + abort(); + } + } +#endif break; } if (call_info.is_tail_call) { - /* Tail call optimization */ + /* Tail call optimization for bytecode callee. + Key insight: a tail-call has no continuation in the current function. + The callee should return directly to our caller, so we save our + frame's continuation BEFORE popping and use that for the new frame. */ JSValue *saved_argv = NULL; JSValue func_saved = JS_DupValue(caller_ctx, call_info.func_obj); JSValue this_saved = JS_DupValue(caller_ctx, call_info.this_obj); JSValue new_target_saved = JS_DupValue(caller_ctx, call_info.new_target); + /* Save current frame's continuation BEFORE popping - this is where + the tail-called function should return to (our caller). */ + const uint8_t *saved_ret_pc = frame->ret_pc; + int saved_ret_sp_offset = frame->ret_sp_offset; + int saved_call_argc = frame->call_argc; + int saved_call_has_this = frame->call_has_this; + +#ifdef DEBUG_VM + { + JSFunctionBytecode *caller_b = frame->b; + printf("[TAILCALL-BC] popping frame=%s will_return_to ret_pc=%p " + "ret_sp_offset=%d (owner=caller's caller)\n", + caller_b->func_name ? JS_AtomToCString(caller_ctx, caller_b->func_name) : "", + saved_ret_pc, saved_ret_sp_offset); + } +#endif + if (call_info.argc > 0) { saved_argv = js_malloc(caller_ctx, sizeof(JSValue) * call_info.argc); if (!saved_argv) { @@ -13154,18 +13273,13 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj, vm_pop_frame(caller_ctx); - if (caller_ctx->frame_stack_top < initial_frame_top) { - frame = vm_push_frame(caller_ctx, func_saved, this_saved, new_target_saved, - call_info.argc, saved_argv, JS_CALL_FLAG_COPY_ARGV, - NULL, 0, 0, 0); - } else { - struct VMFrame *parent = &caller_ctx->frame_stack[caller_ctx->frame_stack_top]; - frame = vm_push_frame(caller_ctx, func_saved, this_saved, new_target_saved, - call_info.argc, saved_argv, JS_CALL_FLAG_COPY_ARGV, - parent->ret_pc, parent->ret_sp_offset, - parent->call_argc, parent->call_has_this); - } - + /* Push new frame with the saved continuation from the popped frame. + This makes the tail-called function return directly to our caller. */ + frame = vm_push_frame(caller_ctx, func_saved, this_saved, new_target_saved, + call_info.argc, saved_argv, JS_CALL_FLAG_COPY_ARGV, + saved_ret_pc, saved_ret_sp_offset, + saved_call_argc, saved_call_has_this); + JS_FreeValue(caller_ctx, func_saved); JS_FreeValue(caller_ctx, this_saved); JS_FreeValue(caller_ctx, new_target_saved); @@ -13560,34 +13674,45 @@ CASE(OP_push_i32): call_info->call_argc = call_argc; call_info->call_has_this = 0; call_info->is_tail_call = (opcode == OP_tail_call); + call_info->is_constructor = 0; /* not a constructor call */ + call_info->pre_argc = 1; /* 1 slot before argv: func */ /* Move refs back to heap frame */ if (!list_empty(&sf->var_ref_list)) { - list_splice(&frame->var_ref_list, &sf->var_ref_list); /* dst=frame, src=sf */ + list_splice(&frame->var_ref_list, &sf->var_ref_list); + init_list_head(&sf->var_ref_list); /* reinit source after splice */ } rt->current_stack_frame = sf->prev_frame; - /* Debug: print call info */ +#ifdef DEBUG_VM + /* Debug: print call info with tail-call awareness */ { - const char *caller_name = b->debug.filename ? b->debug.filename : ""; - if (b->debug.pc2line_len > 0) { - JSAtom func_name_atom = b->debug.filename ? b->func_name : JS_ATOM_NULL; - caller_name = JS_AtomGetStr(ctx, NULL, 0, func_name_atom); - } int pc_offset = (int)(pc - b->byte_code_buf); int ret_pc_offset = call_info->ret_pc ? (int)(call_info->ret_pc - b->byte_code_buf) : -1; - - printf("[VM_EXEC_CALL] caller=%s pc_offset=%d ret_pc_offset=%d bytes=[", + int bc_len = b->byte_code_len; + int at_end = (ret_pc_offset >= bc_len); + + printf("[VM_EXEC_CALL] caller=%s bc_len=%d pc_off=%d ret_pc_off=%d", b->func_name ? JS_AtomToCString(ctx, b->func_name) : "", - pc_offset, ret_pc_offset); - if (call_info->ret_pc) { + bc_len, pc_offset, ret_pc_offset); + + if (call_info->is_tail_call) { + printf(" TAIL_CALL=1"); + if (at_end) { + printf(" (ret_pc at END - expected for tail call, will use frame->ret_pc instead)"); + } + } + + printf(" bytes=["); + if (call_info->ret_pc && !at_end) { for (int di = 0; di < 8 && call_info->ret_pc + di < b->byte_code_buf + b->byte_code_len; di++) printf("%02x ", call_info->ret_pc[di]); + } else if (at_end) { + printf("END"); } - printf("] argc=%d has_this=%d is_tail=%d ", - call_info->call_argc, call_info->call_has_this, - call_info->is_tail_call); - + printf("] argc=%d has_this=%d ", + call_info->call_argc, call_info->call_has_this); + if (JS_VALUE_GET_TAG(call_info->func_obj) == JS_TAG_OBJECT) { JSObject *callee_p = JS_VALUE_GET_OBJ(call_info->func_obj); if (callee_p->class_id == JS_CLASS_BYTECODE_FUNCTION) { @@ -13601,6 +13726,7 @@ CASE(OP_push_i32): printf("callee=non-object\n"); } } +#endif return VM_EXEC_CALL; } @@ -13617,7 +13743,7 @@ CASE(OP_push_i32): frame->sp_offset = sp - stack_buf; call_info->func_obj = call_argv[-2]; - call_info->this_obj = JS_NULL; + call_info->this_obj = JS_NULL; /* constructor creates its own this */ call_info->new_target = call_argv[-1]; call_info->argc = call_argc; call_info->argv = call_argv; @@ -13625,31 +13751,38 @@ CASE(OP_push_i32): /* call_argv[-2] is func, call_argv[-1] is new.target */ call_info->ret_sp_offset = (call_argv - 2) - stack_buf; call_info->call_argc = call_argc; - call_info->call_has_this = 1; /* consumes 2 slots before args */ + call_info->call_has_this = 0; /* NOT a this-call, it's a constructor */ call_info->is_tail_call = 0; + call_info->is_constructor = 1; /* this is a constructor call (new) */ + call_info->pre_argc = 2; /* 2 slots before argv: func + new_target */ /* Move refs back to heap frame */ - if (!list_empty(&sf->var_ref_list)) { - list_splice(&frame->var_ref_list, &sf->var_ref_list); /* dst=frame, src=sf */ + if (!list_empty(&sf->var_ref_list)) { + list_splice(&frame->var_ref_list, &sf->var_ref_list); + init_list_head(&sf->var_ref_list); /* reinit source after splice */ } rt->current_stack_frame = sf->prev_frame; - /* Debug: print call info */ +#ifdef DEBUG_VM + /* Debug: print call info for constructor */ { int pc_offset = (int)(pc - b->byte_code_buf); int ret_pc_offset = call_info->ret_pc ? (int)(call_info->ret_pc - b->byte_code_buf) : -1; - - printf("[VM_EXEC_CALL] caller=%s pc_offset=%d ret_pc_offset=%d bytes=[", + int sp_off_before = frame->sp_offset; + + printf("[VM_EXEC_CALL] caller=%s bc_len=%d pc_off=%d ret_pc_off=%d " + "sp_off=%d ret_sp_off=%d IS_CTOR=1 new_target_tag=%d bytes=[", b->func_name ? JS_AtomToCString(ctx, b->func_name) : "", - pc_offset, ret_pc_offset); + b->byte_code_len, pc_offset, ret_pc_offset, + sp_off_before, call_info->ret_sp_offset, + JS_VALUE_GET_TAG(call_info->new_target)); if (call_info->ret_pc) { for (int di = 0; di < 8 && call_info->ret_pc + di < b->byte_code_buf + b->byte_code_len; di++) printf("%02x ", call_info->ret_pc[di]); } - printf("] argc=%d has_this=%d is_tail=%d ", - call_info->call_argc, call_info->call_has_this, - call_info->is_tail_call); - + printf("] argc=%d pre_argc=%d ", + call_info->call_argc, call_info->pre_argc); + if (JS_VALUE_GET_TAG(call_info->func_obj) == JS_TAG_OBJECT) { JSObject *callee_p = JS_VALUE_GET_OBJ(call_info->func_obj); if (callee_p->class_id == JS_CLASS_BYTECODE_FUNCTION) { @@ -13663,6 +13796,7 @@ CASE(OP_push_i32): printf("callee=non-object\n"); } } +#endif return VM_EXEC_CALL; } @@ -13690,29 +13824,45 @@ CASE(OP_push_i32): call_info->call_argc = call_argc; call_info->call_has_this = 1; /* consumes 2 slots before args */ call_info->is_tail_call = (opcode == OP_tail_call_method); - + call_info->is_constructor = 0; /* not a constructor call */ + call_info->pre_argc = 2; /* 2 slots before argv: this + func */ + /* Move refs back to heap frame */ - if (!list_empty(&sf->var_ref_list)) { - list_splice(&frame->var_ref_list, &sf->var_ref_list); /* dst=frame, src=sf */ + if (!list_empty(&sf->var_ref_list)) { + list_splice(&frame->var_ref_list, &sf->var_ref_list); + init_list_head(&sf->var_ref_list); /* reinit source after splice */ } rt->current_stack_frame = sf->prev_frame; - /* Debug: print call info */ +#ifdef DEBUG_VM + /* Debug: print call info with tail-call awareness */ { int pc_offset = (int)(pc - b->byte_code_buf); int ret_pc_offset = call_info->ret_pc ? (int)(call_info->ret_pc - b->byte_code_buf) : -1; - - printf("[VM_EXEC_CALL] caller=%s pc_offset=%d ret_pc_offset=%d bytes=[", + int bc_len = b->byte_code_len; + int at_end = (ret_pc_offset >= bc_len); + + printf("[VM_EXEC_CALL] caller=%s bc_len=%d pc_off=%d ret_pc_off=%d", b->func_name ? JS_AtomToCString(ctx, b->func_name) : "", - pc_offset, ret_pc_offset); - if (call_info->ret_pc) { + bc_len, pc_offset, ret_pc_offset); + + if (call_info->is_tail_call) { + printf(" TAIL_CALL=1"); + if (at_end) { + printf(" (ret_pc at END - expected for tail call, will use frame->ret_pc instead)"); + } + } + + printf(" bytes=["); + if (call_info->ret_pc && !at_end) { for (int di = 0; di < 8 && call_info->ret_pc + di < b->byte_code_buf + b->byte_code_len; di++) printf("%02x ", call_info->ret_pc[di]); + } else if (at_end) { + printf("END"); } - printf("] argc=%d has_this=%d is_tail=%d ", - call_info->call_argc, call_info->call_has_this, - call_info->is_tail_call); - + printf("] argc=%d has_this=%d ", + call_info->call_argc, call_info->call_has_this); + if (JS_VALUE_GET_TAG(call_info->func_obj) == JS_TAG_OBJECT) { JSObject *callee_p = JS_VALUE_GET_OBJ(call_info->func_obj); if (callee_p->class_id == JS_CLASS_BYTECODE_FUNCTION) { @@ -13726,6 +13876,7 @@ CASE(OP_push_i32): printf("callee=non-object\n"); } } +#endif return VM_EXEC_CALL; } diff --git a/test.ce b/test.ce index 8b3f4415..1bb5b7b4 100644 --- a/test.ce +++ b/test.ce @@ -5,6 +5,11 @@ var time = use('time') var json = use('json') var blob = use('blob') +var newblob = new blob(100) +for (var i in newblob) log.console(i) +newblob.write_bit(true) +log.console(newblob.length) + if (!args) args = [] var target_pkg = null // null = current package