closures work

This commit is contained in:
2026-02-03 18:42:14 -06:00
parent 04c569eab1
commit 41e3a6d91a

View File

@@ -313,7 +313,7 @@ struct JSFunctionBytecode;
#define JS_VALUE_GET_TEXT(v) ((JSText *)chase (v))
#define JS_VALUE_GET_BLOB(v) ((JSBlob *)JS_VALUE_GET_PTR (v))
#define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)JS_VALUE_GET_PTR (v))
#define JS_VALUE_GET_FRAME(v) ((JSFrame *)JS_VALUE_GET_PTR (v))
#define JS_VALUE_GET_FRAME(v) ((JSFrame *)chase (v))
#define JS_VALUE_GET_CODE(v) ((JSFunctionBytecode *)JS_VALUE_GET_PTR (v))
#define JS_VALUE_GET_STRING(v) ((JSText *)chase (v))
@@ -419,6 +419,9 @@ typedef struct JSStackFrame {
instruction after the call */
int arg_count;
int js_mode; /* not supported for C functions */
JSValue js_frame; /* GC-managed JSFrame (use JS_VALUE_GET_FRAME to access) */
JSValue *stack_buf; /* operand stack base (for GC scanning) */
JSValue **p_sp; /* pointer to current sp (for GC scanning) */
} JSStackFrame;
/* Heap-allocated VM frame for trampoline execution */
@@ -1628,7 +1631,7 @@ typedef struct JSFunction {
struct {
struct JSFunctionBytecode *function_bytecode;
JSVarRef **var_refs; /* legacy closure refs (to be removed) */
struct JSFrame *outer_frame; /* lexical parent for closures */
JSValue outer_frame; /* JSFrame JSValue, lexical parent for closures */
JSValue env_record; /* stone record, module environment */
} func;
struct JSBoundFunction *bound_function;
@@ -2500,12 +2503,8 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8
if (b->has_debug) {
b->debug.filename = gc_copy_value (ctx, b->debug.filename, from_base, from_end, to_base, to_free, to_end);
}
/* Scan outer_frame (for closures) */
if (fn->u.func.outer_frame) {
JSValue frame_val = JS_MKPTR (fn->u.func.outer_frame);
frame_val = gc_copy_value (ctx, frame_val, from_base, from_end, to_base, to_free, to_end);
fn->u.func.outer_frame = (JSFrame *)JS_VALUE_GET_PTR (frame_val);
}
/* Scan outer_frame (for closures) - it's already a JSValue */
fn->u.func.outer_frame = gc_copy_value (ctx, fn->u.func.outer_frame, from_base, from_end, to_base, to_free, to_end);
/* Scan env_record (stone record / module environment) */
fn->u.func.env_record = gc_copy_value (ctx, fn->u.func.env_record, from_base, from_end, to_base, to_free, to_end);
}
@@ -2691,8 +2690,34 @@ static int ctx_gc (JSContext *ctx, int allow_grow) {
sf->cur_func = gc_copy_value (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end);
/* Also scan bytecode cpool if it's a bytecode object outside GC heap */
gc_scan_bytecode_cpool (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end);
/* Scan arg_buf and var_buf - they point into value_stack which is scanned separately,
but we should update cur_func which may be a function/bytecode */
/* Scan arg_buf and var_buf contents - they're on C stack but hold JSValues */
if (JS_IsFunction(sf->cur_func)) {
JSFunction *fn = JS_VALUE_GET_FUNCTION(sf->cur_func);
if (fn->kind == JS_FUNC_KIND_BYTECODE && fn->u.func.function_bytecode) {
JSFunctionBytecode *b = fn->u.func.function_bytecode;
/* Scan arg_buf */
if (sf->arg_buf) {
for (int i = 0; i < sf->arg_count; i++) {
sf->arg_buf[i] = gc_copy_value(ctx, sf->arg_buf[i], from_base, from_end, to_base, &to_free, to_end);
}
}
/* Scan var_buf */
if (sf->var_buf) {
for (int i = 0; i < b->var_count; i++) {
sf->var_buf[i] = gc_copy_value(ctx, sf->var_buf[i], from_base, from_end, to_base, &to_free, to_end);
}
}
/* Scan js_frame if present */
sf->js_frame = gc_copy_value(ctx, sf->js_frame, from_base, from_end, to_base, &to_free, to_end);
/* Scan operand stack */
if (sf->stack_buf && sf->p_sp) {
JSValue *sp = *sf->p_sp;
for (JSValue *p = sf->stack_buf; p < sp; p++) {
*p = gc_copy_value(ctx, *p, from_base, from_end, to_base, &to_free, to_end);
}
}
}
}
}
/* Copy JS_PUSH_VALUE/JS_POP_VALUE roots */
@@ -3993,7 +4018,7 @@ static JSValue js_new_function (JSContext *ctx, JSFunctionKind kind) {
func->length = 0;
/* Initialize closure fields for bytecode functions */
if (kind == JS_FUNC_KIND_BYTECODE) {
func->u.func.outer_frame = NULL;
func->u.func.outer_frame = JS_NULL;
func->u.func.env_record = JS_NULL;
}
return JS_MKPTR (func);
@@ -4023,12 +4048,16 @@ static JSFrame *js_new_frame (JSContext *ctx, JSFunction *func,
/* Get pointer to an upvalue in outer scope frame chain.
depth=0 is current frame, depth=1 is immediate outer, etc.
Returns NULL if depth exceeds the frame chain. */
static inline JSValue *get_upvalue_ptr (JSFrame *frame, int depth, int slot) {
Returns NULL if depth exceeds the frame chain.
frame_val is a JSValue containing a JSFrame pointer. */
static inline JSValue *get_upvalue_ptr (JSValue frame_val, int depth, int slot) {
if (JS_IsNull(frame_val)) return NULL;
JSFrame *frame = JS_VALUE_GET_FRAME(frame_val);
while (depth > 0) {
JSFunction *fn = frame->function;
frame = fn->u.func.outer_frame;
if (!frame) return NULL;
frame_val = fn->u.func.outer_frame;
if (JS_IsNull(frame_val)) return NULL;
frame = JS_VALUE_GET_FRAME(frame_val);
depth--;
}
return &frame->slots[slot];
@@ -6784,10 +6813,9 @@ static JSVarRef *get_var_ref (JSContext *ctx, JSStackFrame *sf, int var_idx, BOO
return var_ref;
}
}
/* create a new one */
var_ref = js_malloc (ctx, sizeof (JSVarRef));
/* create a new one - use regular heap, not GC heap, to avoid pointer invalidation */
var_ref = pjs_mallocz (sizeof (JSVarRef));
if (!var_ref) return NULL;
/* ref_count not needed with copying GC */
var_ref->is_detached = FALSE;
list_add_tail (&var_ref->var_ref_link, &sf->var_ref_list);
var_ref->pvalue = pvalue;
@@ -6797,13 +6825,17 @@ static JSVarRef *get_var_ref (JSContext *ctx, JSStackFrame *sf, int var_idx, BOO
static JSValue js_closure2 (JSContext *ctx, JSValue func_obj, JSFunctionBytecode *b, JSVarRef **cur_var_refs, JSStackFrame *sf) {
JSFunction *f;
JSVarRef **var_refs;
JSGCRef func_obj_ref;
int i;
f = JS_VALUE_GET_FUNCTION (func_obj);
f->u.func.function_bytecode = b;
f->u.func.var_refs = NULL;
f->u.func.outer_frame = JS_NULL; /* Not used in var_refs model */
if (b->closure_var_count) {
var_refs = js_mallocz (ctx, sizeof (var_refs[0]) * b->closure_var_count);
/* Use pjs_mallocz for var_refs - regular heap, not GC-managed */
var_refs = pjs_mallocz(sizeof(var_refs[0]) * b->closure_var_count);
if (!var_refs) goto fail;
f->u.func.var_refs = var_refs;
for (i = 0; i < b->closure_var_count; i++) {
@@ -6811,8 +6843,14 @@ static JSValue js_closure2 (JSContext *ctx, JSValue func_obj, JSFunctionBytecode
JSVarRef *var_ref;
if (cv->is_local) {
/* reuse the existing variable reference if it already exists */
/* Protect func_obj from GC during get_var_ref */
JS_PUSH_VALUE(ctx, func_obj);
var_ref = get_var_ref (ctx, sf, cv->var_idx, cv->is_arg);
JS_POP_VALUE(ctx, func_obj);
if (!var_ref) goto fail;
/* Re-fetch f and var_refs after potential GC */
f = JS_VALUE_GET_FUNCTION (func_obj);
var_refs = f->u.func.var_refs;
} else {
var_ref = cur_var_refs[cv->var_idx];
/* No ref_count increment needed with copying GC */
@@ -6920,6 +6958,9 @@ static JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue thi
sf->js_mode = 0;
sf->cur_func = (JSValue)func_obj;
sf->arg_count = argc;
sf->js_frame = JS_NULL; /* C functions don't have JSFrame */
sf->stack_buf = NULL; /* C functions don't have operand stack */
sf->p_sp = NULL;
arg_buf = argv;
if (unlikely (argc < arg_count)) {
@@ -7115,10 +7156,11 @@ static JSValue JS_CallInternal (JSContext *caller_ctx, JSValue func_obj, JSValue
sf->cur_func = (JSValue)func_obj;
init_list_head (&sf->var_ref_list);
var_refs = f->u.func.var_refs;
sf->js_frame = JS_NULL; /* Will be created lazily if needed for closures */
local_buf = alloca (alloca_size);
if (unlikely (arg_allocated_size)) {
int n = min_int (argc, b->arg_count);
local_buf = alloca(alloca_size);
if (unlikely(arg_allocated_size)) {
int n = min_int(argc, b->arg_count);
arg_buf = local_buf;
for (i = 0; i < n; i++)
arg_buf[i] = argv[i];
@@ -7136,6 +7178,8 @@ static JSValue JS_CallInternal (JSContext *caller_ctx, JSValue func_obj, JSValue
stack_buf = var_buf + b->var_count;
sp = stack_buf;
pc = b->byte_code_buf;
sf->stack_buf = stack_buf;
sf->p_sp = &sp; /* GC uses this to find current stack top */
sf->prev_frame = rt->current_stack_frame;
rt->current_stack_frame = sf;
ctx = b->realm; /* set the current realm */
@@ -8938,19 +8982,24 @@ restart:
CASE (OP_get_up) : {
int depth = *pc++;
int slot = get_u16 (pc); pc += 2;
(void)depth; (void)slot;
JS_ThrowInternalError (ctx, "OP_get_up not yet implemented");
goto exception;
JSValue *p = get_upvalue_ptr(sf->js_frame, depth, slot);
if (!p) {
JS_ThrowInternalError(ctx, "invalid upvalue: depth=%d slot=%d", depth, slot);
goto exception;
}
*sp++ = *p;
}
BREAK;
CASE (OP_set_up) : {
int depth = *pc++;
int slot = get_u16 (pc); pc += 2;
(void)depth; (void)slot;
--sp;
JS_ThrowInternalError (ctx, "OP_set_up not yet implemented");
goto exception;
JSValue *p = get_upvalue_ptr(sf->js_frame, depth, slot);
if (!p) {
JS_ThrowInternalError(ctx, "invalid upvalue: depth=%d slot=%d", depth, slot);
goto exception;
}
*p = *--sp;
}
BREAK;