This commit is contained in:
2025-12-31 11:09:18 -06:00
parent e21cd4e70b
commit 334f3a789b
2 changed files with 213 additions and 57 deletions

View File

@@ -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) : "<anon>",
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) : "<anon>",
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) : "<anon>",
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) : "<anon>");
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) : "<anon>");
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) : "<anon>");
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) : "<anon>");
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) : "<anon>",
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 : "<unknown>";
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) : "<anon>",
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) : "<anon>",
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) : "<anon>",
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;
}

View File

@@ -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