diff --git a/source/quickjs-opcode.h b/source/quickjs-opcode.h index 531ee1b0..a14906a9 100644 --- a/source/quickjs-opcode.h +++ b/source/quickjs-opcode.h @@ -52,6 +52,7 @@ FMT(key) FMT(key_u8) FMT(key_u16) FMT(key_label_u16) +FMT(u8_u16) /* 1 byte + 2 bytes for upvalue access */ #undef FMT #endif /* FMT */ @@ -211,6 +212,15 @@ DEF( or, 1, 2, 1, none) /* template literal concatenation - pops N parts, pushes concatenated string */ DEF(template_concat, 3, 0, 1, npop_u16) +/* Upvalue access (closures via outer_frame chain) */ +DEF( get_up, 4, 0, 1, u8_u16) /* depth:u8, slot:u16 -> value */ +DEF( set_up, 4, 1, 0, u8_u16) /* value, depth:u8, slot:u16 -> */ + +/* Name resolution with bytecode patching */ +DEF( get_name, 5, 0, 1, const) /* cpool_idx -> value, patches itself */ +DEF( get_env_slot, 3, 0, 1, u16) /* slot -> value (patched from get_name) */ +DEF(get_global_slot, 3, 0, 1, u16) /* slot -> value (patched from get_name) */ + /* must be the last non short and non temporary opcode */ DEF( nop, 1, 0, 0, none) diff --git a/source/quickjs.c b/source/quickjs.c index 88e8edf7..6e640b20 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -448,6 +448,17 @@ struct VMFrame { int stack_size_allocated; /* total size allocated for this frame */ }; +/* GC-managed frame for closures - lives on GC heap with OBJ_FRAME type + Used by the link-time relocation model where closures reference + outer frames via (depth, slot) addressing */ +typedef struct JSFrame { + objhdr_t header; /* OBJ_FRAME, cap56 = slot count */ + struct JSFunction *function; + struct JSFrame *caller; + uint32_t return_pc; + JSValue slots[]; /* args, captured, locals, temps */ +} JSFrame; + /* Execution state returned by vm_execute_frame */ typedef enum { VM_EXEC_NORMAL, /* Continue executing current frame */ @@ -1604,7 +1615,7 @@ typedef enum { typedef struct JSFunction { objhdr_t header; /* must come first */ JSValue name; /* function name as JSValue text */ - uint16_t length; /* arity: max allowed arguments */ + int16_t length; /* arity: max allowed arguments (-1 = variadic) */ uint8_t kind; uint8_t free_mark : 1; union { @@ -1616,7 +1627,9 @@ typedef struct JSFunction { } cfunc; struct { struct JSFunctionBytecode *function_bytecode; - JSVarRef **var_refs; + JSVarRef **var_refs; /* legacy closure refs (to be removed) */ + struct JSFrame *outer_frame; /* lexical parent for closures */ + JSValue env_record; /* stone record, module environment */ } func; struct JSBoundFunction *bound_function; } u; @@ -2361,6 +2374,11 @@ static size_t gc_object_size (void *ptr) { } case OBJ_FUNCTION: return gc_align_up (sizeof (JSFunction)); + case OBJ_FRAME: { + /* JSFrame + slots array. cap56 stores slot count */ + uint64_t slot_count = cap; + return gc_align_up (sizeof (JSFrame) + slot_count * sizeof (JSValue)); + } default: /* Unknown type - fatal error, heap is corrupt or scan desync'd */ fflush(stdout); @@ -2482,6 +2500,14 @@ 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 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); } break; } @@ -2499,6 +2525,27 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8 /* Note: cpool, vardefs, closure_var, byte_code_buf are allocated via js_malloc */ break; } + case OBJ_FRAME: { + /* JSFrame - scan function, caller, and slots */ + JSFrame *frame = (JSFrame *)ptr; + /* function and caller are pointers - need to copy them as values */ + if (frame->function) { + JSValue fn_val = JS_MKPTR (frame->function); + fn_val = gc_copy_value (ctx, fn_val, from_base, from_end, to_base, to_free, to_end); + frame->function = (JSFunction *)JS_VALUE_GET_PTR (fn_val); + } + if (frame->caller) { + JSValue caller_val = JS_MKPTR (frame->caller); + caller_val = gc_copy_value (ctx, caller_val, from_base, from_end, to_base, to_free, to_end); + frame->caller = (JSFrame *)JS_VALUE_GET_PTR (caller_val); + } + /* Scan all slots */ + uint64_t slot_count = objhdr_cap56 (frame->header); + for (uint64_t i = 0; i < slot_count; i++) { + frame->slots[i] = gc_copy_value (ctx, frame->slots[i], from_base, from_end, to_base, to_free, to_end); + } + break; + } default: /* Unknown type during scan - fatal error */ fprintf (stderr, "gc_scan_object: unknown object type %d at %p\n", type, ptr); @@ -3033,7 +3080,7 @@ void JS_SetMaxStackSize (JSRuntime *rt, size_t stack_size) { } void JS_UpdateStackTop (JSRuntime *rt) { - rt->stack_top = js_get_stack_pointer (); + rt->stack_top = (const uint8_t *)js_get_stack_pointer (); update_stack_limit (rt); } @@ -3246,7 +3293,7 @@ static no_inline JSText *pretext_realloc (JSContext *ctx, JSText *s, int new_len JSText *new_str = js_realloc (ctx, s, new_size_bytes); if (!new_str) return NULL; new_cap = min_int (new_cap, JS_STRING_LEN_MAX); - objhdr_set_cap56 (&new_str->hdr, new_cap); + new_str->hdr = objhdr_set_cap56 (new_str->hdr, new_cap); return new_str; } @@ -3598,7 +3645,7 @@ static BOOL JS_ConcatStringInPlace (JSContext *ctx, JSText *p1, JSValue op2) { for (int64_t i = 0; i < len2; i++) { string_put (p1, len1 + i, string_get (p2, i)); } - objhdr_set_cap56 (&p1->hdr, new_len); + p1->hdr = objhdr_set_cap56 (p1->hdr, new_len); return TRUE; } return FALSE; @@ -3944,9 +3991,49 @@ static JSValue js_new_function (JSContext *ctx, JSFunctionKind kind) { func->kind = kind; func->name = JS_NULL; func->length = 0; + /* Initialize closure fields for bytecode functions */ + if (kind == JS_FUNC_KIND_BYTECODE) { + func->u.func.outer_frame = NULL; + func->u.func.env_record = JS_NULL; + } return JS_MKPTR (func); } +/* Allocate GC-managed frame for closure support */ +static JSFrame *js_new_frame (JSContext *ctx, JSFunction *func, + JSFrame *caller, uint32_t ret_pc) { + JSFunctionBytecode *b = func->u.func.function_bytecode; + uint16_t slot_count = b->arg_count + b->var_count + b->stack_size; + + size_t size = sizeof (JSFrame) + slot_count * sizeof (JSValue); + JSFrame *f = js_mallocz (ctx, size); + if (!f) return NULL; + + f->header = objhdr_make (slot_count, OBJ_FRAME, false, false, false, false); + f->function = func; + f->caller = caller; + f->return_pc = ret_pc; + + /* Initialize all slots to JS_NULL */ + for (int i = 0; i < slot_count; i++) + f->slots[i] = JS_NULL; + + return f; +} + +/* 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) { + while (depth > 0) { + JSFunction *fn = frame->function; + frame = fn->u.func.outer_frame; + if (!frame) return NULL; + depth--; + } + return &frame->slots[slot]; +} + /* Compute memory used by various object types */ /* XXX: poor man's approach to handling multiply referenced objects */ typedef struct JSMemoryUsage_helper { @@ -4222,15 +4309,15 @@ static void build_backtrace (JSContext *ctx, JSValue error_obj, const char *file dbuf_printf (&dbuf, " at %s", filename); if (line_num != -1) dbuf_printf (&dbuf, ":%d:%d", line_num, col_num); dbuf_putc (&dbuf, '\n'); + /* Use short immediate strings for keys to avoid GC issues */ + JSValue key_fileName = MIST_TryNewImmediateASCII ("file", 4); + JSValue key_lineNumber = MIST_TryNewImmediateASCII ("line", 4); + JSValue key_columnNumber = MIST_TryNewImmediateASCII ("col", 3); str = JS_NewString (ctx, filename); if (JS_IsException (str)) { JS_PopGCRef (ctx, &err_ref); return; } - /* Note: SpiderMonkey does that, could update once there is a standard */ - JSValue key_fileName = JS_KEY_STR (ctx, "fileName"); - JSValue key_lineNumber = JS_KEY_STR (ctx, "lineNumber"); - JSValue key_columnNumber = JS_KEY_STR (ctx, "columnNumber"); if (JS_SetPropertyInternal (ctx, err_ref.val, key_fileName, str) < 0 || JS_SetPropertyInternal (ctx, err_ref.val, key_lineNumber, JS_NewInt32 (ctx, line_num)) < 0 @@ -6242,10 +6329,10 @@ static __maybe_unused void JS_DumpObject (JSRuntime *rt, JSRecord *rec) { static __maybe_unused void JS_DumpGCObject (JSRuntime *rt, objhdr_t *p) { - if (objhdr_type (p) == OBJ_RECORD) { + if (objhdr_type (*p) == OBJ_RECORD) { JS_DumpObject (rt, (JSRecord *)p); } else { - switch (objhdr_type (p)) { + switch (objhdr_type (*p)) { case OBJ_CODE: printf ("[function bytecode]"); break; @@ -6256,7 +6343,7 @@ static __maybe_unused void JS_DumpGCObject (JSRuntime *rt, printf ("[record]"); break; default: - printf ("[unknown %d]", objhdr_type (p)); + printf ("[unknown %d]", objhdr_type (*p)); break; } printf ("\n"); @@ -8885,6 +8972,51 @@ restart: } BREAK; + /* Upvalue access via outer_frame chain (Phase 5) */ + 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; + } + 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; + } + BREAK; + + /* Name resolution with bytecode patching (Phase 6) */ + CASE (OP_get_name) : { + uint32_t idx = get_u32 (pc); pc += 4; + (void)idx; + JS_ThrowInternalError (ctx, "OP_get_name not yet implemented"); + goto exception; + } + BREAK; + + CASE (OP_get_env_slot) : { + int slot = get_u16 (pc); pc += 2; + (void)slot; + JS_ThrowInternalError (ctx, "OP_get_env_slot not yet implemented"); + goto exception; + } + BREAK; + + CASE (OP_get_global_slot) : { + int slot = get_u16 (pc); pc += 2; + (void)slot; + JS_ThrowInternalError (ctx, "OP_get_global_slot not yet implemented"); + goto exception; + } + BREAK; + CASE (OP_nop) : BREAK; CASE (OP_is_null) : if (JS_VALUE_GET_TAG (sp[-1]) == JS_TAG_NULL) { goto set_true;