diff --git a/source/quickjs.c b/source/quickjs.c index 47887e48..86840cac 100644 --- a/source/quickjs.c +++ b/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,