diff --git a/source/mach.c b/source/mach.c index 21797be2..93341ac9 100644 --- a/source/mach.c +++ b/source/mach.c @@ -25,7 +25,409 @@ #include "quickjs-internal.h" +/* ============================================================ + Mach VM instruction definitions (private to mach.c) + ============================================================ */ +/* Encoding macros */ +#define MACH_ABC(op, a, b, c) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(b)<<16) | ((uint32_t)(c)<<24)) +#define MACH_ABx(op, a, bx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(bx)<<16)) +#define MACH_AsBx(op, a, sbx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(uint16_t)(sbx)<<16)) +#define MACH_sJ(op, sj) ((uint32_t)(op) | (((uint32_t)(sj) & 0xFFFFFF) << 8)) + +/* Decoding macros */ +#define MACH_GET_OP(i) ((i) & 0xFF) +#define MACH_GET_A(i) (((i) >> 8) & 0xFF) +#define MACH_GET_B(i) (((i) >> 16) & 0xFF) +#define MACH_GET_C(i) (((i) >> 24) & 0xFF) +#define MACH_GET_Bx(i) ((i) >> 16) +#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) === */ + + /* Constants & Loading */ + MACH_LOADK, /* R(A) = K(Bx) — load from constant pool (ABx) */ + MACH_LOADI, /* R(A) = (int16_t)sBx — load small integer (AsBx) */ + MACH_LOADNULL, /* R(A) = null (A only) */ + MACH_LOADTRUE, /* R(A) = true (A only) */ + MACH_LOADFALSE, /* R(A) = false (A only) */ + + /* Movement */ + MACH_MOVE, /* R(A) = R(B) */ + + /* Generic arithmetic (ABC) — used by legacy .mach */ + MACH_ADD, /* R(A) = R(B) + R(C) */ + MACH_SUB, /* R(A) = R(B) - R(C) */ + MACH_MUL, /* R(A) = R(B) * R(C) */ + MACH_DIV, /* R(A) = R(B) / R(C) */ + MACH_MOD, /* R(A) = R(B) % R(C) */ + MACH_POW, /* R(A) = R(B) ** R(C) */ + MACH_NEG, /* R(A) = -R(B) */ + MACH_REMAINDER, /* R(A) = remainder(R(B), R(C)) */ + MACH_MAX, /* R(A) = max(R(B), R(C)) */ + MACH_MIN, /* R(A) = min(R(B), R(C)) */ + MACH_ABS, /* R(A) = abs(R(B)) */ + MACH_SIGN, /* R(A) = sign(R(B)) */ + MACH_FRACTION, /* R(A) = fraction(R(B)) */ + MACH_INTEGER, /* R(A) = integer(R(B)) */ + MACH_FLOOR, /* R(A) = floor(R(B), R(C)) */ + MACH_CEILING, /* R(A) = ceiling(R(B), R(C)) */ + MACH_ROUND, /* R(A) = round(R(B), R(C)) */ + MACH_TRUNC, /* R(A) = trunc(R(B), R(C)) */ + MACH__DEAD_INC, /* reserved — was MACH_INC, never emitted */ + MACH__DEAD_DEC, /* reserved — was MACH_DEC, never emitted */ + + /* Generic comparison (ABC) — used by legacy .mach */ + MACH_EQ, /* R(A) = (R(B) == R(C)) */ + MACH_NEQ, /* R(A) = (R(B) != R(C)) */ + MACH_LT, /* R(A) = (R(B) < R(C)) */ + MACH_LE, /* R(A) = (R(B) <= R(C)) */ + MACH_GT, /* R(A) = (R(B) > R(C)) */ + MACH_GE, /* R(A) = (R(B) >= R(C)) */ + + /* Logical/Bitwise — used by legacy .mach */ + MACH_LNOT, /* R(A) = !R(B) */ + MACH_BNOT, /* R(A) = ~R(B) */ + MACH_BAND, /* R(A) = R(B) & R(C) */ + MACH_BOR, /* R(A) = R(B) | R(C) */ + MACH_BXOR, /* R(A) = R(B) ^ R(C) */ + MACH_SHL, /* R(A) = R(B) << R(C) */ + MACH_SHR, /* R(A) = R(B) >> R(C) */ + MACH_USHR, /* R(A) = R(B) >>> R(C) */ + + /* Property access — used by legacy .mach */ + MACH_GETFIELD, /* R(A) = R(B)[K(C)] — named property */ + MACH_SETFIELD, /* R(A)[K(B)] = R(C) — named property */ + MACH_GETINDEX, /* R(A) = R(B)[R(C)] — computed property */ + MACH_SETINDEX, /* R(A)[R(B)] = R(C) — computed property */ + + /* Unbound variable access (ABx) */ + MACH_GETNAME, /* R(A) = resolve(K(Bx)) — compiler placeholder, patched by link */ + MACH_GETINTRINSIC, /* R(A) = global[K(Bx)] — post-link, intrinsic/built-in */ + MACH_GETENV, /* R(A) = env[K(Bx)] — post-link, module environment */ + + /* Closure access (ABC) */ + MACH_GETUP, /* R(A) = outer_frame[B].slots[C] */ + MACH_SETUP, /* outer_frame[B].slots[C] = R(A) */ + + /* Control flow */ + MACH_JMP, /* pc += sJ — unconditional (isJ format) */ + MACH_JMPTRUE, /* if R(A): pc += sBx — (iAsBx format) */ + MACH_JMPFALSE, /* if !R(A): pc += sBx — (iAsBx format) */ + MACH_JMPNULL, /* if R(A)==null: pc += sBx */ + + /* Function calls — Lua-style consecutive registers (legacy .mach) */ + MACH_RETURN, /* Return R(A) */ + MACH_RETNIL, /* Return null */ + + /* Object/array creation — legacy .mach */ + MACH_NEWOBJECT, /* R(A) = {} */ + MACH_NEWARRAY, /* R(A) = new array, B = element count in R(A+1)..R(A+B) */ + MACH_CLOSURE, /* R(A) = closure(functions[Bx]) (ABx) */ + + MACH_THROW, /* disrupt — trigger disruption */ + + MACH_PUSH, /* push R(B) onto array R(A) */ + MACH_POP, /* R(A) = pop last element from array R(B) */ + + MACH_DELETE, /* R(A) = delete R(B)[K(C)] — named property delete */ + MACH_DELETEINDEX, /* R(A) = delete R(B)[R(C)] — computed property delete */ + MACH_HASPROP, /* R(A) = R(C) in R(B) — has property check */ + MACH_REGEXP, /* R(A) = regexp(K(B), K(C)) — regex literal */ + + MACH_EQ_TOL, /* R(A) = eq_tol(R(B), R(B+1), R(B+2)), C=3 */ + MACH_NEQ_TOL, /* R(A) = ne_tol(R(B), R(B+1), R(B+2)), C=3 */ + + MACH_NOP, + + /* === New mcode-derived opcodes (1:1 mapping to mcode IR) === */ + + /* Text */ + MACH_CONCAT, /* R(A) = R(B) ++ R(C) — string concatenation */ + + /* Typed integer comparisons (ABC) */ + MACH_EQ_INT, /* R(A) = (R(B) == R(C)) — int */ + MACH_NE_INT, /* R(A) = (R(B) != R(C)) — int */ + MACH_LT_INT, /* R(A) = (R(B) < R(C)) — int */ + MACH_LE_INT, /* R(A) = (R(B) <= R(C)) — int */ + MACH_GT_INT, /* R(A) = (R(B) > R(C)) — int */ + MACH_GE_INT, /* R(A) = (R(B) >= R(C)) — int */ + + /* Typed float comparisons (ABC) */ + MACH_EQ_FLOAT, /* R(A) = (R(B) == R(C)) — float */ + MACH_NE_FLOAT, /* R(A) = (R(B) != R(C)) — float */ + MACH_LT_FLOAT, /* R(A) = (R(B) < R(C)) — float */ + MACH_LE_FLOAT, /* R(A) = (R(B) <= R(C)) — float */ + MACH_GT_FLOAT, /* R(A) = (R(B) > R(C)) — float */ + MACH_GE_FLOAT, /* R(A) = (R(B) >= R(C)) — float */ + + /* Typed text comparisons (ABC) */ + MACH_EQ_TEXT, /* R(A) = (R(B) == R(C)) — text */ + MACH_NE_TEXT, /* R(A) = (R(B) != R(C)) — text */ + MACH_LT_TEXT, /* R(A) = (R(B) < R(C)) — text */ + MACH_LE_TEXT, /* R(A) = (R(B) <= R(C)) — text */ + MACH_GT_TEXT, /* R(A) = (R(B) > R(C)) — text */ + MACH_GE_TEXT, /* R(A) = (R(B) >= R(C)) — text */ + + /* Typed bool comparisons (ABC) */ + MACH_EQ_BOOL, /* R(A) = (R(B) == R(C)) — bool */ + MACH_NE_BOOL, /* R(A) = (R(B) != R(C)) — bool */ + + /* Special comparisons */ + MACH_IS_IDENTICAL, /* R(A) = (R(B) === R(C)) — identity check (ABC) */ + + /* Type checks (AB) */ + MACH_IS_INT, /* R(A) = is_int(R(B)) */ + MACH_IS_NUM, /* R(A) = is_num(R(B)) */ + 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)) */ + + /* Logical (mcode-style) */ + MACH_NOT, /* R(A) = !R(B) — boolean not (AB) */ + MACH_AND, /* R(A) = R(B) && R(C) (ABC) */ + MACH_OR, /* R(A) = R(B) || R(C) (ABC) */ + + /* Bitwise (mcode names) */ + MACH_BITNOT, /* R(A) = ~R(B) (AB) */ + MACH_BITAND, /* R(A) = R(B) & R(C) (ABC) */ + MACH_BITOR, /* R(A) = R(B) | R(C) (ABC) */ + MACH_BITXOR, /* R(A) = R(B) ^ R(C) (ABC) */ + + /* Property access (mcode names) */ + MACH_LOAD_FIELD, /* R(A) = R(B).K(C) — named property (ABC) */ + MACH_STORE_FIELD, /* R(A).K(B) = R(C) — named property (ABC) */ + MACH_LOAD_INDEX, /* R(A) = R(B)[R(C)] — integer index (ABC) */ + MACH_STORE_INDEX, /* R(A)[R(B)] = R(C) — integer index (ABC) */ + MACH_LOAD_DYNAMIC, /* R(A) = R(B)[R(C)] — dynamic key (ABC) */ + MACH_STORE_DYNAMIC, /* R(A)[R(B)] = R(C) — dynamic key (ABC) */ + + /* Object/Array creation (mcode names) */ + MACH_NEWRECORD, /* R(A) = {} — new empty record (A only) */ + + /* Decomposed function calls (mcode-style) */ + MACH_FRAME, /* R(A) = frame(R(B), C) — alloc call frame (ABC) */ + MACH_SETARG, /* frame R(A)[B] = R(C) — set arg in frame (ABC) */ + MACH_INVOKE, /* R(B) = invoke(R(A)) — call frame, result in R(B) (AB) */ + MACH_GOFRAME, /* R(A) = goframe(R(B), C) — async frame (ABC) */ + MACH_GOINVOKE, /* goinvoke(R(A)) — async invoke, no result (A only) */ + + /* Control flow */ + MACH_JMPNOTNULL, /* if R(A)!=null: pc += sBx (iAsBx) */ + + /* Error handling */ + MACH_DISRUPT, /* trigger disruption (A only) */ + + /* Misc */ + MACH_IN, /* R(A) = (R(B) in R(C)) — has property (ABC) */ + + /* Extended type checks (AB) */ + MACH_IS_ARRAY, /* R(A) = is_array(R(B)) */ + MACH_IS_FUNC, /* R(A) = is_function(R(B)) */ + MACH_IS_RECORD, /* R(A) = is_object(R(B)) */ + MACH_IS_STONE, /* R(A) = is_stone(R(B)) */ + MACH_LENGTH, /* R(A) = length(R(B)) — array/text/blob length */ + MACH_IS_PROXY, /* R(A) = is_function(R(B)) && R(B).length == 2 */ + + MACH_OP_COUNT +} MachOpcode; + +static const char *mach_opcode_names[MACH_OP_COUNT] = { + /* Legacy */ + [MACH_LOADK] = "loadk", + [MACH_LOADI] = "loadi", + [MACH_LOADNULL] = "loadnull", + [MACH_LOADTRUE] = "loadtrue", + [MACH_LOADFALSE] = "loadfalse", + [MACH_MOVE] = "move", + [MACH_ADD] = "add", + [MACH_SUB] = "sub", + [MACH_MUL] = "mul", + [MACH_DIV] = "div", + [MACH_MOD] = "mod", + [MACH_POW] = "pow", + [MACH_NEG] = "neg", + [MACH_REMAINDER] = "remainder", + [MACH_MAX] = "max", + [MACH_MIN] = "min", + [MACH_ABS] = "abs", + [MACH_SIGN] = "sign", + [MACH_FRACTION] = "fraction", + [MACH_INTEGER] = "integer", + [MACH_FLOOR] = "floor", + [MACH_CEILING] = "ceiling", + [MACH_ROUND] = "round", + [MACH_TRUNC] = "trunc", + [MACH__DEAD_INC] = "dead_inc", + [MACH__DEAD_DEC] = "dead_dec", + [MACH_EQ] = "eq", + [MACH_NEQ] = "neq", + [MACH_LT] = "lt", + [MACH_LE] = "le", + [MACH_GT] = "gt", + [MACH_GE] = "ge", + [MACH_LNOT] = "lnot", + [MACH_BNOT] = "bnot", + [MACH_BAND] = "band", + [MACH_BOR] = "bor", + [MACH_BXOR] = "bxor", + [MACH_SHL] = "shl", + [MACH_SHR] = "shr", + [MACH_USHR] = "ushr", + [MACH_GETFIELD] = "getfield", + [MACH_SETFIELD] = "setfield", + [MACH_GETINDEX] = "getindex", + [MACH_SETINDEX] = "setindex", + [MACH_GETNAME] = "getname", + [MACH_GETINTRINSIC] = "getintrinsic", + [MACH_GETENV] = "getenv", + [MACH_GETUP] = "getup", + [MACH_SETUP] = "setup", + [MACH_JMP] = "jmp", + [MACH_JMPTRUE] = "jmptrue", + [MACH_JMPFALSE] = "jmpfalse", + [MACH_JMPNULL] = "jmpnull", + [MACH_RETURN] = "return", + [MACH_RETNIL] = "retnil", + [MACH_NEWOBJECT] = "newobject", + [MACH_NEWARRAY] = "newarray", + [MACH_CLOSURE] = "closure", + [MACH_THROW] = "throw", + [MACH_PUSH] = "push", + [MACH_POP] = "pop", + [MACH_DELETE] = "delete", + [MACH_DELETEINDEX] = "deleteindex", + [MACH_HASPROP] = "hasprop", + [MACH_REGEXP] = "regexp", + [MACH_EQ_TOL] = "eq_tol", + [MACH_NEQ_TOL] = "neq_tol", + [MACH_NOP] = "nop", + /* Mcode-derived */ + [MACH_CONCAT] = "concat", + [MACH_EQ_INT] = "eq_int", + [MACH_NE_INT] = "ne_int", + [MACH_LT_INT] = "lt_int", + [MACH_LE_INT] = "le_int", + [MACH_GT_INT] = "gt_int", + [MACH_GE_INT] = "ge_int", + [MACH_EQ_FLOAT] = "eq_float", + [MACH_NE_FLOAT] = "ne_float", + [MACH_LT_FLOAT] = "lt_float", + [MACH_LE_FLOAT] = "le_float", + [MACH_GT_FLOAT] = "gt_float", + [MACH_GE_FLOAT] = "ge_float", + [MACH_EQ_TEXT] = "eq_text", + [MACH_NE_TEXT] = "ne_text", + [MACH_LT_TEXT] = "lt_text", + [MACH_LE_TEXT] = "le_text", + [MACH_GT_TEXT] = "gt_text", + [MACH_GE_TEXT] = "ge_text", + [MACH_EQ_BOOL] = "eq_bool", + [MACH_NE_BOOL] = "ne_bool", + [MACH_IS_IDENTICAL] = "is_identical", + [MACH_IS_INT] = "is_int", + [MACH_IS_NUM] = "is_num", + [MACH_IS_TEXT] = "is_text", + [MACH_IS_BOOL] = "is_bool", + [MACH_IS_NULL] = "is_null", + [MACH_NOT] = "not", + [MACH_AND] = "and", + [MACH_OR] = "or", + [MACH_BITNOT] = "bitnot", + [MACH_BITAND] = "bitand", + [MACH_BITOR] = "bitor", + [MACH_BITXOR] = "bitxor", + [MACH_LOAD_FIELD] = "load_field", + [MACH_STORE_FIELD] = "store_field", + [MACH_LOAD_INDEX] = "load_index", + [MACH_STORE_INDEX] = "store_index", + [MACH_LOAD_DYNAMIC] = "load_dynamic", + [MACH_STORE_DYNAMIC] = "store_dynamic", + [MACH_NEWRECORD] = "newrecord", + [MACH_FRAME] = "frame", + [MACH_SETARG] = "setarg", + [MACH_INVOKE] = "invoke", + [MACH_GOFRAME] = "goframe", + [MACH_GOINVOKE] = "goinvoke", + [MACH_JMPNOTNULL] = "jmpnotnull", + [MACH_DISRUPT] = "disrupt", + [MACH_IN] = "in", + /* Extended type checks */ + [MACH_IS_ARRAY] = "is_array", + [MACH_IS_FUNC] = "is_func", + [MACH_IS_RECORD] = "is_record", + [MACH_IS_STONE] = "is_stone", + [MACH_LENGTH] = "length", + [MACH_IS_PROXY] = "is_proxy", +}; /* ---- Compile-time constant pool entry ---- */ /* Stores raw data during compilation; converted to JSValues when loading into context */ diff --git a/source/quickjs-internal.h b/source/quickjs-internal.h index db5546d6..ea83d804 100644 --- a/source/quickjs-internal.h +++ b/source/quickjs-internal.h @@ -384,406 +384,6 @@ typedef uint32_t MachInstr32; typedef struct { uint16_t line; uint16_t col; } MachLineEntry; -/* Encoding macros */ -#define MACH_ABC(op, a, b, c) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(b)<<16) | ((uint32_t)(c)<<24)) -#define MACH_ABx(op, a, bx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(bx)<<16)) -#define MACH_AsBx(op, a, sbx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(uint16_t)(sbx)<<16)) -#define MACH_sJ(op, sj) ((uint32_t)(op) | (((uint32_t)(sj) & 0xFFFFFF) << 8)) - -/* Decoding macros */ -#define MACH_GET_OP(i) ((i) & 0xFF) -#define MACH_GET_A(i) (((i) >> 8) & 0xFF) -#define MACH_GET_B(i) (((i) >> 16) & 0xFF) -#define MACH_GET_C(i) (((i) >> 24) & 0xFF) -#define MACH_GET_Bx(i) ((i) >> 16) -#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) === */ - - /* Constants & Loading */ - MACH_LOADK, /* R(A) = K(Bx) — load from constant pool (ABx) */ - MACH_LOADI, /* R(A) = (int16_t)sBx — load small integer (AsBx) */ - MACH_LOADNULL, /* R(A) = null (A only) */ - MACH_LOADTRUE, /* R(A) = true (A only) */ - MACH_LOADFALSE, /* R(A) = false (A only) */ - - /* Movement */ - MACH_MOVE, /* R(A) = R(B) */ - - /* Generic arithmetic (ABC) — used by legacy .mach */ - MACH_ADD, /* R(A) = R(B) + R(C) */ - MACH_SUB, /* R(A) = R(B) - R(C) */ - MACH_MUL, /* R(A) = R(B) * R(C) */ - MACH_DIV, /* R(A) = R(B) / R(C) */ - MACH_MOD, /* R(A) = R(B) % R(C) */ - MACH_POW, /* R(A) = R(B) ** R(C) */ - MACH_NEG, /* R(A) = -R(B) */ - MACH_REMAINDER, /* R(A) = remainder(R(B), R(C)) */ - MACH_MAX, /* R(A) = max(R(B), R(C)) */ - MACH_MIN, /* R(A) = min(R(B), R(C)) */ - MACH_ABS, /* R(A) = abs(R(B)) */ - MACH_SIGN, /* R(A) = sign(R(B)) */ - MACH_FRACTION, /* R(A) = fraction(R(B)) */ - MACH_INTEGER, /* R(A) = integer(R(B)) */ - MACH_FLOOR, /* R(A) = floor(R(B), R(C)) */ - MACH_CEILING, /* R(A) = ceiling(R(B), R(C)) */ - MACH_ROUND, /* R(A) = round(R(B), R(C)) */ - MACH_TRUNC, /* R(A) = trunc(R(B), R(C)) */ - MACH__DEAD_INC, /* reserved — was MACH_INC, never emitted */ - MACH__DEAD_DEC, /* reserved — was MACH_DEC, never emitted */ - - /* Generic comparison (ABC) — used by legacy .mach */ - MACH_EQ, /* R(A) = (R(B) == R(C)) */ - MACH_NEQ, /* R(A) = (R(B) != R(C)) */ - MACH_LT, /* R(A) = (R(B) < R(C)) */ - MACH_LE, /* R(A) = (R(B) <= R(C)) */ - MACH_GT, /* R(A) = (R(B) > R(C)) */ - MACH_GE, /* R(A) = (R(B) >= R(C)) */ - - /* Logical/Bitwise — used by legacy .mach */ - MACH_LNOT, /* R(A) = !R(B) */ - MACH_BNOT, /* R(A) = ~R(B) */ - MACH_BAND, /* R(A) = R(B) & R(C) */ - MACH_BOR, /* R(A) = R(B) | R(C) */ - MACH_BXOR, /* R(A) = R(B) ^ R(C) */ - MACH_SHL, /* R(A) = R(B) << R(C) */ - MACH_SHR, /* R(A) = R(B) >> R(C) */ - MACH_USHR, /* R(A) = R(B) >>> R(C) */ - - /* Property access — used by legacy .mach */ - MACH_GETFIELD, /* R(A) = R(B)[K(C)] — named property */ - MACH_SETFIELD, /* R(A)[K(B)] = R(C) — named property */ - MACH_GETINDEX, /* R(A) = R(B)[R(C)] — computed property */ - MACH_SETINDEX, /* R(A)[R(B)] = R(C) — computed property */ - - /* Unbound variable access (ABx) */ - MACH_GETNAME, /* R(A) = resolve(K(Bx)) — compiler placeholder, patched by link */ - MACH_GETINTRINSIC, /* R(A) = global[K(Bx)] — post-link, intrinsic/built-in */ - MACH_GETENV, /* R(A) = env[K(Bx)] — post-link, module environment */ - - /* Closure access (ABC) */ - MACH_GETUP, /* R(A) = outer_frame[B].slots[C] */ - MACH_SETUP, /* outer_frame[B].slots[C] = R(A) */ - - /* Control flow */ - MACH_JMP, /* pc += sJ — unconditional (isJ format) */ - MACH_JMPTRUE, /* if R(A): pc += sBx — (iAsBx format) */ - MACH_JMPFALSE, /* if !R(A): pc += sBx — (iAsBx format) */ - MACH_JMPNULL, /* if R(A)==null: pc += sBx */ - - /* Function calls — Lua-style consecutive registers (legacy .mach) */ - MACH_RETURN, /* Return R(A) */ - MACH_RETNIL, /* Return null */ - - /* Object/array creation — legacy .mach */ - MACH_NEWOBJECT, /* R(A) = {} */ - MACH_NEWARRAY, /* R(A) = new array, B = element count in R(A+1)..R(A+B) */ - MACH_CLOSURE, /* R(A) = closure(functions[Bx]) (ABx) */ - - MACH_THROW, /* disrupt — trigger disruption */ - - MACH_PUSH, /* push R(B) onto array R(A) */ - MACH_POP, /* R(A) = pop last element from array R(B) */ - - MACH_DELETE, /* R(A) = delete R(B)[K(C)] — named property delete */ - MACH_DELETEINDEX, /* R(A) = delete R(B)[R(C)] — computed property delete */ - MACH_HASPROP, /* R(A) = R(C) in R(B) — has property check */ - MACH_REGEXP, /* R(A) = regexp(K(B), K(C)) — regex literal */ - - MACH_EQ_TOL, /* R(A) = eq_tol(R(B), R(B+1), R(B+2)), C=3 */ - MACH_NEQ_TOL, /* R(A) = ne_tol(R(B), R(B+1), R(B+2)), C=3 */ - - MACH_NOP, - - /* === New mcode-derived opcodes (1:1 mapping to mcode IR) === */ - - /* Text */ - MACH_CONCAT, /* R(A) = R(B) ++ R(C) — string concatenation */ - - /* Typed integer comparisons (ABC) */ - MACH_EQ_INT, /* R(A) = (R(B) == R(C)) — int */ - MACH_NE_INT, /* R(A) = (R(B) != R(C)) — int */ - MACH_LT_INT, /* R(A) = (R(B) < R(C)) — int */ - MACH_LE_INT, /* R(A) = (R(B) <= R(C)) — int */ - MACH_GT_INT, /* R(A) = (R(B) > R(C)) — int */ - MACH_GE_INT, /* R(A) = (R(B) >= R(C)) — int */ - - /* Typed float comparisons (ABC) */ - MACH_EQ_FLOAT, /* R(A) = (R(B) == R(C)) — float */ - MACH_NE_FLOAT, /* R(A) = (R(B) != R(C)) — float */ - MACH_LT_FLOAT, /* R(A) = (R(B) < R(C)) — float */ - MACH_LE_FLOAT, /* R(A) = (R(B) <= R(C)) — float */ - MACH_GT_FLOAT, /* R(A) = (R(B) > R(C)) — float */ - MACH_GE_FLOAT, /* R(A) = (R(B) >= R(C)) — float */ - - /* Typed text comparisons (ABC) */ - MACH_EQ_TEXT, /* R(A) = (R(B) == R(C)) — text */ - MACH_NE_TEXT, /* R(A) = (R(B) != R(C)) — text */ - MACH_LT_TEXT, /* R(A) = (R(B) < R(C)) — text */ - MACH_LE_TEXT, /* R(A) = (R(B) <= R(C)) — text */ - MACH_GT_TEXT, /* R(A) = (R(B) > R(C)) — text */ - MACH_GE_TEXT, /* R(A) = (R(B) >= R(C)) — text */ - - /* Typed bool comparisons (ABC) */ - MACH_EQ_BOOL, /* R(A) = (R(B) == R(C)) — bool */ - MACH_NE_BOOL, /* R(A) = (R(B) != R(C)) — bool */ - - /* Special comparisons */ - MACH_IS_IDENTICAL, /* R(A) = (R(B) === R(C)) — identity check (ABC) */ - - /* Type checks (AB) */ - MACH_IS_INT, /* R(A) = is_int(R(B)) */ - MACH_IS_NUM, /* R(A) = is_num(R(B)) */ - 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)) */ - - /* Logical (mcode-style) */ - MACH_NOT, /* R(A) = !R(B) — boolean not (AB) */ - MACH_AND, /* R(A) = R(B) && R(C) (ABC) */ - MACH_OR, /* R(A) = R(B) || R(C) (ABC) */ - - /* Bitwise (mcode names) */ - MACH_BITNOT, /* R(A) = ~R(B) (AB) */ - MACH_BITAND, /* R(A) = R(B) & R(C) (ABC) */ - MACH_BITOR, /* R(A) = R(B) | R(C) (ABC) */ - MACH_BITXOR, /* R(A) = R(B) ^ R(C) (ABC) */ - - /* Property access (mcode names) */ - MACH_LOAD_FIELD, /* R(A) = R(B).K(C) — named property (ABC) */ - MACH_STORE_FIELD, /* R(A).K(B) = R(C) — named property (ABC) */ - MACH_LOAD_INDEX, /* R(A) = R(B)[R(C)] — integer index (ABC) */ - MACH_STORE_INDEX, /* R(A)[R(B)] = R(C) — integer index (ABC) */ - MACH_LOAD_DYNAMIC, /* R(A) = R(B)[R(C)] — dynamic key (ABC) */ - MACH_STORE_DYNAMIC, /* R(A)[R(B)] = R(C) — dynamic key (ABC) */ - - /* Object/Array creation (mcode names) */ - MACH_NEWRECORD, /* R(A) = {} — new empty record (A only) */ - - /* Decomposed function calls (mcode-style) */ - MACH_FRAME, /* R(A) = frame(R(B), C) — alloc call frame (ABC) */ - MACH_SETARG, /* frame R(A)[B] = R(C) — set arg in frame (ABC) */ - MACH_INVOKE, /* R(B) = invoke(R(A)) — call frame, result in R(B) (AB) */ - MACH_GOFRAME, /* R(A) = goframe(R(B), C) — async frame (ABC) */ - MACH_GOINVOKE, /* goinvoke(R(A)) — async invoke, no result (A only) */ - - /* Control flow */ - MACH_JMPNOTNULL, /* if R(A)!=null: pc += sBx (iAsBx) */ - - /* Error handling */ - MACH_DISRUPT, /* trigger disruption (A only) */ - - /* Misc */ - MACH_IN, /* R(A) = (R(B) in R(C)) — has property (ABC) */ - - /* Extended type checks (AB) */ - MACH_IS_ARRAY, /* R(A) = is_array(R(B)) */ - MACH_IS_FUNC, /* R(A) = is_function(R(B)) */ - MACH_IS_RECORD, /* R(A) = is_object(R(B)) */ - MACH_IS_STONE, /* R(A) = is_stone(R(B)) */ - MACH_LENGTH, /* R(A) = length(R(B)) — array/text/blob length */ - MACH_IS_PROXY, /* R(A) = is_function(R(B)) && R(B).length == 2 */ - - MACH_OP_COUNT -} MachOpcode; - -static const char *mach_opcode_names[MACH_OP_COUNT] = { - /* Legacy */ - [MACH_LOADK] = "loadk", - [MACH_LOADI] = "loadi", - [MACH_LOADNULL] = "loadnull", - [MACH_LOADTRUE] = "loadtrue", - [MACH_LOADFALSE] = "loadfalse", - [MACH_MOVE] = "move", - [MACH_ADD] = "add", - [MACH_SUB] = "sub", - [MACH_MUL] = "mul", - [MACH_DIV] = "div", - [MACH_MOD] = "mod", - [MACH_POW] = "pow", - [MACH_NEG] = "neg", - [MACH_REMAINDER] = "remainder", - [MACH_MAX] = "max", - [MACH_MIN] = "min", - [MACH_ABS] = "abs", - [MACH_SIGN] = "sign", - [MACH_FRACTION] = "fraction", - [MACH_INTEGER] = "integer", - [MACH_FLOOR] = "floor", - [MACH_CEILING] = "ceiling", - [MACH_ROUND] = "round", - [MACH_TRUNC] = "trunc", - [MACH__DEAD_INC] = "dead_inc", - [MACH__DEAD_DEC] = "dead_dec", - [MACH_EQ] = "eq", - [MACH_NEQ] = "neq", - [MACH_LT] = "lt", - [MACH_LE] = "le", - [MACH_GT] = "gt", - [MACH_GE] = "ge", - [MACH_LNOT] = "lnot", - [MACH_BNOT] = "bnot", - [MACH_BAND] = "band", - [MACH_BOR] = "bor", - [MACH_BXOR] = "bxor", - [MACH_SHL] = "shl", - [MACH_SHR] = "shr", - [MACH_USHR] = "ushr", - [MACH_GETFIELD] = "getfield", - [MACH_SETFIELD] = "setfield", - [MACH_GETINDEX] = "getindex", - [MACH_SETINDEX] = "setindex", - [MACH_GETNAME] = "getname", - [MACH_GETINTRINSIC] = "getintrinsic", - [MACH_GETENV] = "getenv", - [MACH_GETUP] = "getup", - [MACH_SETUP] = "setup", - [MACH_JMP] = "jmp", - [MACH_JMPTRUE] = "jmptrue", - [MACH_JMPFALSE] = "jmpfalse", - [MACH_JMPNULL] = "jmpnull", - [MACH_RETURN] = "return", - [MACH_RETNIL] = "retnil", - [MACH_NEWOBJECT] = "newobject", - [MACH_NEWARRAY] = "newarray", - [MACH_CLOSURE] = "closure", - [MACH_THROW] = "throw", - [MACH_PUSH] = "push", - [MACH_POP] = "pop", - [MACH_DELETE] = "delete", - [MACH_DELETEINDEX] = "deleteindex", - [MACH_HASPROP] = "hasprop", - [MACH_REGEXP] = "regexp", - [MACH_EQ_TOL] = "eq_tol", - [MACH_NEQ_TOL] = "neq_tol", - [MACH_NOP] = "nop", - /* Mcode-derived */ - [MACH_CONCAT] = "concat", - [MACH_EQ_INT] = "eq_int", - [MACH_NE_INT] = "ne_int", - [MACH_LT_INT] = "lt_int", - [MACH_LE_INT] = "le_int", - [MACH_GT_INT] = "gt_int", - [MACH_GE_INT] = "ge_int", - [MACH_EQ_FLOAT] = "eq_float", - [MACH_NE_FLOAT] = "ne_float", - [MACH_LT_FLOAT] = "lt_float", - [MACH_LE_FLOAT] = "le_float", - [MACH_GT_FLOAT] = "gt_float", - [MACH_GE_FLOAT] = "ge_float", - [MACH_EQ_TEXT] = "eq_text", - [MACH_NE_TEXT] = "ne_text", - [MACH_LT_TEXT] = "lt_text", - [MACH_LE_TEXT] = "le_text", - [MACH_GT_TEXT] = "gt_text", - [MACH_GE_TEXT] = "ge_text", - [MACH_EQ_BOOL] = "eq_bool", - [MACH_NE_BOOL] = "ne_bool", - [MACH_IS_IDENTICAL] = "is_identical", - [MACH_IS_INT] = "is_int", - [MACH_IS_NUM] = "is_num", - [MACH_IS_TEXT] = "is_text", - [MACH_IS_BOOL] = "is_bool", - [MACH_IS_NULL] = "is_null", - [MACH_NOT] = "not", - [MACH_AND] = "and", - [MACH_OR] = "or", - [MACH_BITNOT] = "bitnot", - [MACH_BITAND] = "bitand", - [MACH_BITOR] = "bitor", - [MACH_BITXOR] = "bitxor", - [MACH_LOAD_FIELD] = "load_field", - [MACH_STORE_FIELD] = "store_field", - [MACH_LOAD_INDEX] = "load_index", - [MACH_STORE_INDEX] = "store_index", - [MACH_LOAD_DYNAMIC] = "load_dynamic", - [MACH_STORE_DYNAMIC] = "store_dynamic", - [MACH_NEWRECORD] = "newrecord", - [MACH_FRAME] = "frame", - [MACH_SETARG] = "setarg", - [MACH_INVOKE] = "invoke", - [MACH_GOFRAME] = "goframe", - [MACH_GOINVOKE] = "goinvoke", - [MACH_JMPNOTNULL] = "jmpnotnull", - [MACH_DISRUPT] = "disrupt", - [MACH_IN] = "in", - /* Extended type checks */ - [MACH_IS_ARRAY] = "is_array", - [MACH_IS_FUNC] = "is_func", - [MACH_IS_RECORD] = "is_record", - [MACH_IS_STONE] = "is_stone", - [MACH_LENGTH] = "length", - [MACH_IS_PROXY] = "is_proxy", -}; - /* Compiled register-based code (off-heap, never GC'd). Created by JS_CompileMach from AST JSON. */ typedef struct JSCodeRegister {