new opcodes

This commit is contained in:
2026-02-03 17:21:41 -06:00
parent acc9878b36
commit 43faad95e0
2 changed files with 154 additions and 12 deletions

View File

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

View File

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