runs
This commit is contained in:
265
source/quickjs.c
265
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) : "<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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user