trampoline
This commit is contained in:
212
source/quickjs.c
212
source/quickjs.c
@@ -311,14 +311,22 @@ typedef struct JSStackFrame {
|
||||
|
||||
/* Heap-allocated VM frame for trampoline execution */
|
||||
struct VMFrame {
|
||||
struct VMFrame *prev_frame;
|
||||
struct JSFunctionBytecode *b; /* current function bytecode */
|
||||
JSContext *ctx; /* execution context / realm */
|
||||
const uint8_t *pc; /* program counter */
|
||||
JSValue *sp; /* current stack pointer */
|
||||
JSValue *stack_base; /* base of operand stack (points into heap) */
|
||||
JSValue *arg_buf; /* arguments (points into stack_base region) */
|
||||
JSValue *var_buf; /* local variables (points into stack_base region) */
|
||||
|
||||
/* Offset-based storage (safe with realloc) */
|
||||
int value_stack_base; /* base index into ctx->value_stack */
|
||||
int sp_offset; /* sp offset from base */
|
||||
int arg_buf_offset; /* arg buffer offset from base (or -1 if aliased) */
|
||||
int var_buf_offset; /* var buffer offset from base */
|
||||
|
||||
/* Continuation info for return */
|
||||
const uint8_t *ret_pc; /* where to resume in caller */
|
||||
int ret_sp_offset; /* caller's sp before call (for cleanup) */
|
||||
int call_argc; /* number of args to clean up */
|
||||
int call_has_this; /* whether call had this (method call) */
|
||||
|
||||
JSValue cur_func; /* current function object */
|
||||
JSValue this_obj; /* this binding */
|
||||
JSValue new_target; /* new.target binding */
|
||||
@@ -326,7 +334,7 @@ struct VMFrame {
|
||||
struct list_head var_ref_list; /* list of JSVarRef.var_ref_link */
|
||||
int arg_count;
|
||||
int js_mode;
|
||||
int stack_size_allocated; /* total size of stack_base allocation */
|
||||
int stack_size_allocated; /* total size allocated for this frame */
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
@@ -418,6 +426,14 @@ struct JSContext {
|
||||
js_hook trace_hook;
|
||||
int trace_type;
|
||||
void *trace_data;
|
||||
|
||||
/* Trampoline VM stacks (per actor/context) */
|
||||
struct VMFrame *frame_stack; /* array of frames */
|
||||
int frame_stack_top; /* current frame index (-1 = empty) */
|
||||
int frame_stack_capacity; /* allocated capacity */
|
||||
JSValue *value_stack; /* array of JSValues for locals/operands */
|
||||
int value_stack_top; /* current top index */
|
||||
int value_stack_capacity; /* allocated capacity */
|
||||
};
|
||||
|
||||
typedef union JSFloat64Union {
|
||||
@@ -1838,6 +1854,27 @@ JSContext *JS_NewContextRaw(JSRuntime *rt)
|
||||
ctx->class_proto[i] = JS_NULL;
|
||||
ctx->array_ctor = JS_NULL;
|
||||
ctx->regexp_ctor = JS_NULL;
|
||||
|
||||
/* Initialize VM stacks for trampoline */
|
||||
ctx->frame_stack_capacity = 512;
|
||||
ctx->frame_stack = js_malloc_rt(rt, sizeof(struct VMFrame) * ctx->frame_stack_capacity);
|
||||
if (!ctx->frame_stack) {
|
||||
js_free_rt(rt, ctx->class_proto);
|
||||
js_free_rt(rt, ctx);
|
||||
return NULL;
|
||||
}
|
||||
ctx->frame_stack_top = -1;
|
||||
|
||||
ctx->value_stack_capacity = 65536; /* 64K JSValue slots */
|
||||
ctx->value_stack = js_malloc_rt(rt, sizeof(JSValue) * ctx->value_stack_capacity);
|
||||
if (!ctx->value_stack) {
|
||||
js_free_rt(rt, ctx->frame_stack);
|
||||
js_free_rt(rt, ctx->class_proto);
|
||||
js_free_rt(rt, ctx);
|
||||
return NULL;
|
||||
}
|
||||
ctx->value_stack_top = 0;
|
||||
|
||||
JS_AddIntrinsicBasicObjects(ctx);
|
||||
rt->js = ctx;
|
||||
return ctx;
|
||||
@@ -1988,6 +2025,12 @@ void JS_FreeContext(JSContext *ctx)
|
||||
|
||||
js_free_shape_null(ctx->rt, ctx->array_shape);
|
||||
|
||||
/* Free VM stacks */
|
||||
if (ctx->frame_stack)
|
||||
js_free_rt(rt, ctx->frame_stack);
|
||||
if (ctx->value_stack)
|
||||
js_free_rt(rt, ctx->value_stack);
|
||||
|
||||
list_del(&ctx->link);
|
||||
remove_gc_object(&ctx->header);
|
||||
js_free_rt(ctx->rt, ctx);
|
||||
@@ -12723,25 +12766,30 @@ static void profile_record_prop_site(JSRuntime *rt, JSFunctionBytecode *b, uint3
|
||||
}
|
||||
#endif
|
||||
|
||||
/* VM frame management for trampoline */
|
||||
static struct VMFrame *vm_push_frame(JSRuntime *rt, JSContext *ctx,
|
||||
/* VM frame management for trampoline - no malloc/free! */
|
||||
static struct VMFrame *vm_push_frame(JSContext *ctx,
|
||||
JSValueConst func_obj, JSValueConst this_obj,
|
||||
JSValueConst new_target,
|
||||
int argc, JSValue *argv, int flags)
|
||||
int argc, JSValue *argv, int flags,
|
||||
const uint8_t *ret_pc, int ret_sp_offset,
|
||||
int call_argc, int call_has_this)
|
||||
{
|
||||
JSObject *p;
|
||||
JSFunctionBytecode *b;
|
||||
struct VMFrame *frame;
|
||||
int total_slots, i, arg_allocated_size;
|
||||
JSValue *stack_base, *arg_buf, *var_buf;
|
||||
|
||||
p = JS_VALUE_GET_OBJ(func_obj);
|
||||
b = p->u.func.function_bytecode;
|
||||
|
||||
frame = js_malloc(ctx, sizeof(struct VMFrame));
|
||||
if (!frame)
|
||||
/* Check frame stack capacity */
|
||||
if (ctx->frame_stack_top + 1 >= ctx->frame_stack_capacity) {
|
||||
/* TODO: grow frame stack (for now, fail) */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Calculate space needed: args + vars + stack */
|
||||
/* Calculate space needed: args + vars + operand stack */
|
||||
if (argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV)) {
|
||||
arg_allocated_size = b->arg_count;
|
||||
} else {
|
||||
@@ -12749,33 +12797,42 @@ static struct VMFrame *vm_push_frame(JSRuntime *rt, JSContext *ctx,
|
||||
}
|
||||
total_slots = arg_allocated_size + b->var_count + b->stack_size;
|
||||
|
||||
/* Allocate VM stack space */
|
||||
frame->stack_base = js_mallocz(ctx, sizeof(JSValue) * total_slots);
|
||||
if (!frame->stack_base) {
|
||||
js_free(ctx, frame);
|
||||
/* Check value stack capacity */
|
||||
if (ctx->value_stack_top + total_slots > ctx->value_stack_capacity) {
|
||||
/* TODO: grow value stack (for now, fail) */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Push frame */
|
||||
ctx->frame_stack_top++;
|
||||
frame = &ctx->frame_stack[ctx->frame_stack_top];
|
||||
|
||||
/* Store offsets (safe with realloc) */
|
||||
frame->value_stack_base = ctx->value_stack_top;
|
||||
frame->stack_size_allocated = total_slots;
|
||||
|
||||
/* Set up pointers into stack */
|
||||
/* Get pointers for initialization (only used locally) */
|
||||
stack_base = &ctx->value_stack[frame->value_stack_base];
|
||||
|
||||
if (arg_allocated_size) {
|
||||
frame->arg_buf = frame->stack_base;
|
||||
frame->arg_buf_offset = 0;
|
||||
arg_buf = stack_base;
|
||||
for (i = 0; i < min_int(argc, b->arg_count); i++)
|
||||
frame->arg_buf[i] = JS_DupValue(ctx, argv[i]);
|
||||
arg_buf[i] = JS_DupValue(ctx, argv[i]);
|
||||
for (; i < b->arg_count; i++)
|
||||
frame->arg_buf[i] = JS_NULL;
|
||||
arg_buf[i] = JS_NULL;
|
||||
frame->arg_count = b->arg_count;
|
||||
} else {
|
||||
frame->arg_buf = argv;
|
||||
frame->arg_buf_offset = -1; /* signal: args are aliased from caller */
|
||||
frame->arg_count = argc;
|
||||
}
|
||||
|
||||
frame->var_buf = frame->stack_base + arg_allocated_size;
|
||||
frame->var_buf_offset = arg_allocated_size;
|
||||
var_buf = stack_base + arg_allocated_size;
|
||||
for (i = 0; i < b->var_count; i++)
|
||||
frame->var_buf[i] = JS_NULL;
|
||||
var_buf[i] = JS_NULL;
|
||||
|
||||
frame->sp = frame->var_buf + b->var_count;
|
||||
frame->sp_offset = arg_allocated_size + b->var_count;
|
||||
|
||||
/* Initialize frame metadata */
|
||||
frame->cur_func = JS_DupValue(ctx, func_obj);
|
||||
@@ -12788,23 +12845,35 @@ static struct VMFrame *vm_push_frame(JSRuntime *rt, JSContext *ctx,
|
||||
frame->var_refs = p->u.func.var_refs;
|
||||
init_list_head(&frame->var_ref_list);
|
||||
|
||||
/* Link to previous frame */
|
||||
frame->prev_frame = rt->current_vm_frame;
|
||||
rt->current_vm_frame = frame;
|
||||
/* Continuation info for return */
|
||||
frame->ret_pc = ret_pc;
|
||||
frame->ret_sp_offset = ret_sp_offset;
|
||||
frame->call_argc = call_argc;
|
||||
frame->call_has_this = call_has_this;
|
||||
|
||||
/* Bump value stack top */
|
||||
ctx->value_stack_top += total_slots;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
static void vm_pop_frame(JSRuntime *rt, struct VMFrame *frame)
|
||||
static void vm_pop_frame(JSContext *ctx)
|
||||
{
|
||||
struct VMFrame *frame;
|
||||
int i;
|
||||
struct list_head *el, *el1;
|
||||
JSContext *ctx = frame->ctx;
|
||||
JSValue *stack_base;
|
||||
|
||||
if (ctx->frame_stack_top < 0)
|
||||
return;
|
||||
|
||||
frame = &ctx->frame_stack[ctx->frame_stack_top];
|
||||
stack_base = &ctx->value_stack[frame->value_stack_base];
|
||||
|
||||
/* Close variable references */
|
||||
if (!list_empty(&frame->var_ref_list)) {
|
||||
list_for_each_safe(el, el1, &frame->var_ref_list) {
|
||||
JSVarRef *var_ref = list_entry(el, JSVarRef, var_ref_link);
|
||||
struct JSVarRef *var_ref = list_entry(el, struct JSVarRef, var_ref_link);
|
||||
var_ref->value = JS_DupValue(ctx, *var_ref->pvalue);
|
||||
var_ref->pvalue = &var_ref->value;
|
||||
var_ref->is_detached = TRUE;
|
||||
@@ -12812,22 +12881,91 @@ static void vm_pop_frame(JSRuntime *rt, struct VMFrame *frame)
|
||||
}
|
||||
}
|
||||
|
||||
/* Free all values in stack */
|
||||
/* Free all values in this frame's value stack region */
|
||||
for (i = 0; i < frame->stack_size_allocated; i++)
|
||||
JS_FreeValue(ctx, frame->stack_base[i]);
|
||||
JS_FreeValue(ctx, stack_base[i]);
|
||||
|
||||
/* Free frame values */
|
||||
JS_FreeValue(ctx, frame->cur_func);
|
||||
JS_FreeValue(ctx, frame->this_obj);
|
||||
JS_FreeValue(ctx, frame->new_target);
|
||||
|
||||
/* Free allocations */
|
||||
js_free(ctx, frame->stack_base);
|
||||
/* Pop frame and value stack */
|
||||
ctx->value_stack_top -= frame->stack_size_allocated;
|
||||
ctx->frame_stack_top--;
|
||||
}
|
||||
|
||||
/* Update runtime pointer */
|
||||
rt->current_vm_frame = frame->prev_frame;
|
||||
/* Helper: get pointer from offset (used in hot path) */
|
||||
static inline JSValue *vm_frame_get_stack_ptr(JSContext *ctx, struct VMFrame *frame, int offset)
|
||||
{
|
||||
return &ctx->value_stack[frame->value_stack_base + offset];
|
||||
}
|
||||
|
||||
js_free(ctx, frame);
|
||||
/* Helper: get current sp pointer for a frame */
|
||||
static inline JSValue *vm_frame_get_sp(JSContext *ctx, struct VMFrame *frame)
|
||||
{
|
||||
return &ctx->value_stack[frame->value_stack_base + frame->sp_offset];
|
||||
}
|
||||
|
||||
/* Helper: get var_buf pointer for a frame */
|
||||
static inline JSValue *vm_frame_get_var_buf(JSContext *ctx, struct VMFrame *frame)
|
||||
{
|
||||
return &ctx->value_stack[frame->value_stack_base + frame->var_buf_offset];
|
||||
}
|
||||
|
||||
/* Helper: get arg_buf pointer for a frame (or NULL if aliased) */
|
||||
static inline JSValue *vm_frame_get_arg_buf(JSContext *ctx, struct VMFrame *frame)
|
||||
{
|
||||
if (frame->arg_buf_offset < 0)
|
||||
return NULL; /* aliased */
|
||||
return &ctx->value_stack[frame->value_stack_base + frame->arg_buf_offset];
|
||||
}
|
||||
|
||||
/* Trampoline VM dispatcher - runs frames without C recursion */
|
||||
static JSValue JS_CallTrampoline(JSContext *caller_ctx, JSValueConst func_obj,
|
||||
JSValueConst this_obj, JSValueConst new_target,
|
||||
int argc, JSValue *argv, int flags)
|
||||
{
|
||||
JSRuntime *rt = caller_ctx->rt;
|
||||
JSObject *p;
|
||||
JSValue ret_val = JS_NULL;
|
||||
struct VMFrame *frame;
|
||||
|
||||
/* Check if function is callable */
|
||||
if (js_poll_interrupts(caller_ctx))
|
||||
return JS_EXCEPTION;
|
||||
if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT))
|
||||
return JS_ThrowTypeError(caller_ctx, "not a function");
|
||||
|
||||
p = JS_VALUE_GET_OBJ(func_obj);
|
||||
if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) {
|
||||
JSClassCall *call_func;
|
||||
call_func = rt->class_array[p->class_id].call;
|
||||
if (!call_func)
|
||||
return JS_ThrowTypeError(caller_ctx, "not a function");
|
||||
return call_func(caller_ctx, func_obj, this_obj, argc,
|
||||
(JSValueConst *)argv, flags);
|
||||
}
|
||||
|
||||
/* Push initial frame (entry point, no continuation) */
|
||||
frame = vm_push_frame(caller_ctx, func_obj, this_obj, new_target,
|
||||
argc, argv, flags,
|
||||
NULL, 0, 0, 0);
|
||||
if (!frame)
|
||||
return JS_ThrowStackOverflow(caller_ctx);
|
||||
|
||||
/* Trampoline loop - execute frames without C recursion */
|
||||
while (caller_ctx->frame_stack_top >= 0) {
|
||||
/* 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 */
|
||||
vm_pop_frame(caller_ctx);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
|
||||
|
||||
Reference in New Issue
Block a user