This commit is contained in:
2026-02-12 18:53:06 -06:00
parent 73bfa8d7b1
commit de4b3079d4
4 changed files with 339 additions and 240 deletions

View File

@@ -404,6 +404,72 @@ typedef struct { uint16_t line; uint16_t col; } MachLineEntry;
#define MACH_GET_sBx(i) ((int16_t)((i) >> 16))
#define MACH_GET_sJ(i) ((int32_t)((i) & 0xFFFFFF00) >> 8)
/* ============================================================
GC Safepoint Classification for the MACH VM Dispatch Loop
============================================================
Every opcode falls into one of three categories:
[P] Pure inline — never calls C, never allocates. No GC possible.
No frame re-derivation needed after execution.
[N] Non-allocating C call — calls a C function that is guaranteed
to never allocate (e.g. JS_ToBool, js_string_compare_value).
No frame re-derivation needed.
[G] GC safepoint — calls C that may allocate, triggering GC.
After the call, all heap pointers (including `frame`) MUST be
re-derived via: frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
The 18 C entry points that can allocate (GC safepoints):
1. JS_GetProperty — key interning for string keys >7 chars
2. JS_SetProperty — rec_resize when record grows
3. JS_GetPropertyNumber — text substring extraction
4. JS_SetPropertyNumber — array grow
5. JS_NewObject — allocates record
6. JS_NewArray / JS_NewArrayLen — allocates array
7. js_new_register_function — allocates function object (closure)
8. alloc_frame_register — allocates frame via js_mallocz
9. js_call_c_function — arbitrary C code
10. JS_CallInternal — arbitrary bytecode
11. JS_Call — arbitrary call
12. JS_ConcatString — allocates new string
13. JS_ArrayPush — array grow
14. JS_ArrayPop — reads, but frame refresh needed
15. JS_DeleteProperty — mutates record
16. JS_HasProperty — complex traversal
17. js_regexp_constructor — allocates regex
18. reg_vm_binop — polymorphic dispatch (legacy opcodes)
Opcode-level classification:
[P] LOADK, LOADI, LOADNULL, LOADTRUE, LOADFALSE, MOVE, NOP
[P] ADD_INT..MOD_INT, NEG_INT, ADD_FLOAT..MOD_FLOAT, NEG_FLOAT
[P] EQ_INT..GE_INT, EQ_FLOAT..GE_FLOAT, EQ_BOOL, NE_BOOL
[P] IS_IDENTICAL, IS_INT, IS_NUM, IS_TEXT, IS_BOOL, IS_NULL
[P] IS_ARRAY, IS_FUNC, IS_RECORD, IS_STONE, IS_PROXY
[P] NOT, AND, OR, BITNOT, BITAND, BITOR, BITXOR
[P] JMP, JMPTRUE, JMPFALSE, JMPNULL, JMPNOTNULL
[P] RETURN, RETNIL, SETARG, GETUP, SETUP, DISRUPT, THROW
[P] LENGTH (array + imm-ASCII fast path only; text/blob fallback is [G])
[N] EQ_TEXT..GE_TEXT (js_string_compare_value — no allocation)
[N] LNOT (JS_ToBool — no allocation)
[G] ADD..USHR, NEG, INC, DEC, EQ..GE (legacy: reg_vm_binop)
[G] EQ_TOL, NEQ_TOL (tolerance comparison, may fall back)
[G] CONCAT (JS_ConcatString)
[G] GETFIELD, SETFIELD, GETINDEX, SETINDEX (property access)
[G] LOAD_FIELD, STORE_FIELD, LOAD_INDEX, STORE_INDEX
[G] LOAD_DYNAMIC, STORE_DYNAMIC
[G] GETNAME, GETINTRINSIC, GETENV, SET_VAR
[G] NEWOBJECT, NEWRECORD, NEWARRAY (object/array creation)
[G] CLOSURE (js_new_register_function)
[G] FRAME, GOFRAME (alloc_frame_register)
[G] INVOKE, GOINVOKE (function calls)
[G] PUSH (JS_ArrayPush), POP (JS_ArrayPop)
[G] DELETE, DELETEINDEX (JS_DeleteProperty)
[G] HASPROP, IN (JS_HasProperty)
[G] REGEXP (js_regexp_constructor)
============================================================ */
typedef enum MachOpcode {
/* === Legacy opcodes (used by existing .mach files) === */
@@ -549,7 +615,6 @@ typedef enum MachOpcode {
MACH_IS_TEXT, /* R(A) = is_text(R(B)) */
MACH_IS_BOOL, /* R(A) = is_bool(R(B)) */
MACH_IS_NULL, /* R(A) = is_null(R(B)) */
MACH_TYPEOF, /* R(A) = typeof(R(B)) */
/* Logical (mcode-style) */
MACH_NOT, /* R(A) = !R(B) — boolean not (AB) */
@@ -702,7 +767,6 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
[MACH_IS_TEXT] = "is_text",
[MACH_IS_BOOL] = "is_bool",
[MACH_IS_NULL] = "is_null",
[MACH_TYPEOF] = "typeof",
[MACH_NOT] = "not",
[MACH_AND] = "and",
[MACH_OR] = "or",
@@ -825,6 +889,38 @@ static inline objhdr_t *chase(JSValue v) {
return oh;
}
/* Inline type checks — use these in the VM dispatch loop to avoid
function call overhead. The public API (JS_IsArray etc. in quickjs.h)
remains non-inline for external callers; those wrappers live in runtime.c. */
static inline JS_BOOL mist_is_gc_object(JSValue v) {
return JS_IsPtr(v);
}
static inline JS_BOOL mist_is_array(JSValue v) {
return mist_is_gc_object(v) && objhdr_type(*chase(v)) == OBJ_ARRAY;
}
static inline JS_BOOL mist_is_record(JSValue v) {
return mist_is_gc_object(v) && objhdr_type(*chase(v)) == OBJ_RECORD;
}
static inline JS_BOOL mist_is_function(JSValue v) {
return mist_is_gc_object(v) && objhdr_type(*chase(v)) == OBJ_FUNCTION;
}
static inline JS_BOOL mist_is_text(JSValue v) {
return MIST_IsImmediateASCII(v)
|| (mist_is_gc_object(v) && objhdr_type(*chase(v)) == OBJ_TEXT);
}
static inline JS_BOOL mist_is_blob(JSValue v) {
return mist_is_gc_object(v) && objhdr_type(*chase(v)) == OBJ_BLOB;
}
static inline JS_BOOL mist_is_stone(JSValue v) {
return !mist_is_gc_object(v) || objhdr_s(*chase(v));
}
/* Intrinsic array type - tagged as JS_TAG_PTR with mist_hdr type OBJ_ARRAY */
typedef struct JSArray {