trampoline

This commit is contained in:
2025-12-29 01:35:57 -06:00
parent 0720368c48
commit fa616ee444

View File

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