closures work
This commit is contained in:
107
source/quickjs.c
107
source/quickjs.c
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user