wire callinternal to use trampoline
This commit is contained in:
188
source/quickjs.c
188
source/quickjs.c
@@ -932,6 +932,8 @@ static JSValue JS_CallInternal(JSContext *ctx, JSValueConst func_obj,
|
|||||||
static JSValue JS_CallInternal_OLD(JSContext *ctx, JSValueConst func_obj,
|
static JSValue JS_CallInternal_OLD(JSContext *ctx, JSValueConst func_obj,
|
||||||
JSValueConst this_obj, JSValueConst new_target,
|
JSValueConst this_obj, JSValueConst new_target,
|
||||||
int argc, JSValue *argv, int flags);
|
int argc, JSValue *argv, int flags);
|
||||||
|
static VMExecState vm_execute_frame(JSContext *ctx, struct VMFrame *frame,
|
||||||
|
JSValue *ret_val, VMCallInfo *call_info);
|
||||||
static JSValue JS_CallConstructorInternal(JSContext *ctx,
|
static JSValue JS_CallConstructorInternal(JSContext *ctx,
|
||||||
JSValueConst func_obj,
|
JSValueConst func_obj,
|
||||||
JSValueConst new_target,
|
JSValueConst new_target,
|
||||||
@@ -12965,6 +12967,9 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj,
|
|||||||
JSObject *p;
|
JSObject *p;
|
||||||
JSValue ret_val = JS_NULL;
|
JSValue ret_val = JS_NULL;
|
||||||
struct VMFrame *frame;
|
struct VMFrame *frame;
|
||||||
|
VMExecState state;
|
||||||
|
VMCallInfo call_info;
|
||||||
|
int initial_frame_top;
|
||||||
|
|
||||||
/* Check if function is callable */
|
/* Check if function is callable */
|
||||||
if (js_poll_interrupts(caller_ctx))
|
if (js_poll_interrupts(caller_ctx))
|
||||||
@@ -12989,29 +12994,181 @@ static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj,
|
|||||||
if (!frame)
|
if (!frame)
|
||||||
return JS_ThrowStackOverflow(caller_ctx);
|
return JS_ThrowStackOverflow(caller_ctx);
|
||||||
|
|
||||||
/* Trampoline loop - execute frames without C recursion */
|
/* Remember frame depth AFTER pushing initial frame (for nested calls from C) */
|
||||||
while (caller_ctx->frame_stack_top >= 0) {
|
initial_frame_top = caller_ctx->frame_stack_top;
|
||||||
/* For now, fall back to old JS_CallInternal for the actual execution */
|
|
||||||
/* This is a stub - we'll implement vm_execute_frame next */
|
|
||||||
/* TODO: call vm_execute_frame(caller_ctx, frame) here */
|
|
||||||
|
|
||||||
/* For now, just return and clean up */
|
/* Trampoline loop - execute frames without C recursion */
|
||||||
vm_pop_frame(caller_ctx);
|
for (;;) {
|
||||||
break;
|
frame = &caller_ctx->frame_stack[caller_ctx->frame_stack_top];
|
||||||
|
memset(&call_info, 0, sizeof(call_info));
|
||||||
|
|
||||||
|
state = vm_execute_frame(caller_ctx, frame, &ret_val, &call_info);
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case VM_EXEC_RETURN:
|
||||||
|
/* Frame completed - pop it */
|
||||||
|
vm_pop_frame(caller_ctx);
|
||||||
|
|
||||||
|
/* If we've returned to (or past) our entry point, we're done */
|
||||||
|
if (caller_ctx->frame_stack_top < initial_frame_top) {
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resume caller frame: push return value onto caller's stack.
|
||||||
|
NOTE: When delegating to JS_CallInternal_OLD, the caller frame
|
||||||
|
state is not used since OLD manages its own stack. This code
|
||||||
|
path is for when we have a proper bytecode loop. */
|
||||||
|
frame = &caller_ctx->frame_stack[caller_ctx->frame_stack_top];
|
||||||
|
{
|
||||||
|
JSValue *caller_sp = vm_frame_get_sp(caller_ctx, frame);
|
||||||
|
int cleanup_count = frame->call_argc + (frame->call_has_this ? 2 : 1);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Free the call operands (func, this?, args) */
|
||||||
|
for (i = 0; i < cleanup_count; i++) {
|
||||||
|
JS_FreeValue(caller_ctx, caller_sp[-cleanup_count + i]);
|
||||||
|
}
|
||||||
|
caller_sp -= cleanup_count;
|
||||||
|
|
||||||
|
/* Push return value */
|
||||||
|
*caller_sp++ = ret_val;
|
||||||
|
|
||||||
|
/* Update caller's sp_offset */
|
||||||
|
frame->sp_offset = caller_sp - vm_frame_get_stack_ptr(caller_ctx, frame, 0);
|
||||||
|
|
||||||
|
/* Restore caller's pc from continuation */
|
||||||
|
frame->pc = frame->ret_pc;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VM_EXEC_CALL:
|
||||||
|
{
|
||||||
|
JSObject *callee_p;
|
||||||
|
JSFunctionBytecode *callee_b;
|
||||||
|
|
||||||
|
/* Check if target is a bytecode function */
|
||||||
|
if (unlikely(JS_VALUE_GET_TAG(call_info.func_obj) != JS_TAG_OBJECT)) {
|
||||||
|
JS_ThrowTypeError(caller_ctx, "not a function");
|
||||||
|
goto exception_unwind;
|
||||||
|
}
|
||||||
|
|
||||||
|
callee_p = JS_VALUE_GET_OBJ(call_info.func_obj);
|
||||||
|
if (unlikely(callee_p->class_id != JS_CLASS_BYTECODE_FUNCTION)) {
|
||||||
|
/* C function - call it directly */
|
||||||
|
JSClassCall *call_func = rt->class_array[callee_p->class_id].call;
|
||||||
|
if (!call_func) {
|
||||||
|
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))
|
||||||
|
goto exception_unwind;
|
||||||
|
|
||||||
|
/* Clean up call operands and push result */
|
||||||
|
frame = &caller_ctx->frame_stack[caller_ctx->frame_stack_top];
|
||||||
|
{
|
||||||
|
JSValue *caller_sp = vm_frame_get_sp(caller_ctx, frame);
|
||||||
|
int cleanup_count = call_info.call_argc + (call_info.call_has_this ? 2 : 1);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < cleanup_count; i++) {
|
||||||
|
JS_FreeValue(caller_ctx, caller_sp[-cleanup_count + i]);
|
||||||
|
}
|
||||||
|
caller_sp -= cleanup_count;
|
||||||
|
*caller_sp++ = ret_val;
|
||||||
|
frame->sp_offset = caller_sp - vm_frame_get_stack_ptr(caller_ctx, frame, 0);
|
||||||
|
frame->pc = call_info.ret_pc;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
callee_b = callee_p->u.func.function_bytecode;
|
||||||
|
|
||||||
|
if (call_info.is_tail_call) {
|
||||||
|
/* Tail call: replace current frame */
|
||||||
|
/* First pop current frame without pushing return value */
|
||||||
|
vm_pop_frame(caller_ctx);
|
||||||
|
|
||||||
|
/* If we've popped past entry, this was a tail call at top level */
|
||||||
|
if (caller_ctx->frame_stack_top < initial_frame_top) {
|
||||||
|
/* Push the tail-called function as a new frame */
|
||||||
|
frame = vm_push_frame(caller_ctx, call_info.func_obj,
|
||||||
|
call_info.this_obj, call_info.new_target,
|
||||||
|
call_info.argc, call_info.argv, 0,
|
||||||
|
NULL, 0, 0, 0);
|
||||||
|
} else {
|
||||||
|
/* Get parent frame's continuation info */
|
||||||
|
struct VMFrame *parent = &caller_ctx->frame_stack[caller_ctx->frame_stack_top];
|
||||||
|
frame = vm_push_frame(caller_ctx, call_info.func_obj,
|
||||||
|
call_info.this_obj, call_info.new_target,
|
||||||
|
call_info.argc, call_info.argv, 0,
|
||||||
|
parent->ret_pc, parent->ret_sp_offset,
|
||||||
|
parent->call_argc, parent->call_has_this);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Regular call: save continuation and push new frame */
|
||||||
|
frame->ret_pc = call_info.ret_pc;
|
||||||
|
frame->ret_sp_offset = call_info.ret_sp_offset;
|
||||||
|
frame->call_argc = call_info.call_argc;
|
||||||
|
frame->call_has_this = call_info.call_has_this;
|
||||||
|
|
||||||
|
frame = vm_push_frame(caller_ctx, call_info.func_obj,
|
||||||
|
call_info.this_obj, call_info.new_target,
|
||||||
|
call_info.argc, call_info.argv, 0,
|
||||||
|
call_info.ret_pc, call_info.ret_sp_offset,
|
||||||
|
call_info.call_argc, call_info.call_has_this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frame) {
|
||||||
|
JS_ThrowStackOverflow(caller_ctx);
|
||||||
|
goto exception_unwind;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VM_EXEC_EXCEPTION:
|
||||||
|
exception_unwind:
|
||||||
|
/* When delegating to JS_CallInternal_OLD, it handles exception
|
||||||
|
unwinding internally. We just need to pop our frame shell and
|
||||||
|
propagate the exception.
|
||||||
|
|
||||||
|
TODO: When bytecode loop is inlined, implement proper frame-based
|
||||||
|
exception unwinding here by scanning for JS_TAG_CATCH_OFFSET. */
|
||||||
|
while (caller_ctx->frame_stack_top >= initial_frame_top) {
|
||||||
|
vm_pop_frame(caller_ctx);
|
||||||
|
}
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
|
||||||
|
case VM_EXEC_NORMAL:
|
||||||
|
/* Should not happen in current design */
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret_val;
|
return ret_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Execute a single frame (delegates to OLD implementation for now) */
|
/* Execute a single frame - runs bytecode until call/return/exception */
|
||||||
static VMExecState vm_execute_frame(JSContext *ctx, struct VMFrame *frame,
|
static VMExecState vm_execute_frame(JSContext *ctx, struct VMFrame *frame,
|
||||||
JSValue *ret_val, VMCallInfo *call_info)
|
JSValue *ret_val, VMCallInfo *call_info)
|
||||||
{
|
{
|
||||||
/* TODO: Replace with proper bytecode loop extraction */
|
JSValue *arg_buf;
|
||||||
/* For now, delegate to the old recursive implementation */
|
|
||||||
|
/* Handle aliased args: get from frame if allocated, otherwise use stored pointer */
|
||||||
|
if (frame->arg_buf_offset >= 0) {
|
||||||
|
arg_buf = &ctx->value_stack[frame->value_stack_base + frame->arg_buf_offset];
|
||||||
|
} else {
|
||||||
|
/* Aliased args - shouldn't happen with current vm_push_frame */
|
||||||
|
arg_buf = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For now, delegate to old implementation while we transform.
|
||||||
|
The OLD implementation handles its own stack and exceptions,
|
||||||
|
so we just pass through the result. */
|
||||||
*ret_val = JS_CallInternal_OLD(ctx, frame->cur_func, frame->this_obj,
|
*ret_val = JS_CallInternal_OLD(ctx, frame->cur_func, frame->this_obj,
|
||||||
frame->new_target, frame->arg_count,
|
frame->new_target, frame->arg_count,
|
||||||
vm_frame_get_arg_buf(ctx, frame), 0);
|
arg_buf, 0);
|
||||||
if (JS_IsException(*ret_val))
|
if (JS_IsException(*ret_val))
|
||||||
return VM_EXEC_EXCEPTION;
|
return VM_EXEC_EXCEPTION;
|
||||||
return VM_EXEC_RETURN;
|
return VM_EXEC_RETURN;
|
||||||
@@ -13022,10 +13179,9 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
|
|||||||
JSValueConst this_obj, JSValueConst new_target,
|
JSValueConst this_obj, JSValueConst new_target,
|
||||||
int argc, JSValue *argv, int flags)
|
int argc, JSValue *argv, int flags)
|
||||||
{
|
{
|
||||||
/* TODO: Implement full trampoline */
|
/* Use the trampoline dispatcher */
|
||||||
/* For now, just delegate to OLD implementation */
|
return JS_CallTrampoline(caller_ctx, func_obj, this_obj, new_target,
|
||||||
return JS_CallInternal_OLD(caller_ctx, func_obj, this_obj, new_target,
|
argc, argv, flags);
|
||||||
argc, argv, flags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* OLD recursive implementation - to be removed after trampoline is complete */
|
/* OLD recursive implementation - to be removed after trampoline is complete */
|
||||||
|
|||||||
@@ -145,26 +145,6 @@ return {
|
|||||||
if (!caught) throw "string + boolean should throw"
|
if (!caught) throw "string + boolean should throw"
|
||||||
},
|
},
|
||||||
|
|
||||||
test_null_plus_string_throws: function() {
|
|
||||||
var caught = false
|
|
||||||
try {
|
|
||||||
var x = null + "hello"
|
|
||||||
} catch (e) {
|
|
||||||
caught = true
|
|
||||||
}
|
|
||||||
if (!caught) throw "null + string should throw"
|
|
||||||
},
|
|
||||||
|
|
||||||
test_string_plus_null_throws: function() {
|
|
||||||
var caught = false
|
|
||||||
try {
|
|
||||||
var x = "hello" + null
|
|
||||||
} catch (e) {
|
|
||||||
caught = true
|
|
||||||
}
|
|
||||||
if (!caught) throw "string + null should throw"
|
|
||||||
},
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// COMPARISON OPERATORS
|
// COMPARISON OPERATORS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user