3982 lines
138 KiB
C
3982 lines
138 KiB
C
#include "pit_internal.h"
|
|
#include <assert.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, WARYTRUE, WARYFALSE, JMPEMPTY
|
|
[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 */
|
|
MACH_STONE_TEXT, /* stone(R(A)) — freeze mutable text before escape */
|
|
|
|
/* 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_IS_BLOB, /* R(A) = is_blob(R(B)) */
|
|
MACH_IS_DATA, /* R(A) = is_data(R(B)) — plain record, not array/func/blob */
|
|
MACH_IS_TRUE, /* R(A) = (R(B) === true) */
|
|
MACH_IS_FALSE, /* R(A) = (R(B) === false) */
|
|
MACH_IS_FIT, /* R(A) = is_fit(R(B)) — safe integer */
|
|
MACH_IS_CHAR, /* R(A) = is_character(R(B)) — single char text */
|
|
MACH_IS_DIGIT, /* R(A) = is_digit(R(B)) */
|
|
MACH_IS_LETTER, /* R(A) = is_letter(R(B)) */
|
|
MACH_IS_LOWER, /* R(A) = is_lower(R(B)) */
|
|
MACH_IS_UPPER, /* R(A) = is_upper(R(B)) */
|
|
MACH_IS_WS, /* R(A) = is_whitespace(R(B)) */
|
|
MACH_IS_ACTOR, /* R(A) = is_actor(R(B)) — has actor_sym property */
|
|
MACH_APPLY, /* R(A) = apply(R(B), R(C)) — call fn with args from array (ABC) */
|
|
MACH_WARYTRUE, /* if toBool(R(A)): pc += sBx — coercing (iAsBx) */
|
|
MACH_WARYFALSE, /* if !toBool(R(A)): pc += sBx — coercing (iAsBx) */
|
|
MACH_JMPEMPTY, /* if R(A)==empty_text: pc += sBx (iAsBx) */
|
|
|
|
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_STONE_TEXT] = "stone_text",
|
|
[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",
|
|
[MACH_IS_BLOB] = "is_blob",
|
|
[MACH_IS_DATA] = "is_data",
|
|
[MACH_IS_TRUE] = "is_true",
|
|
[MACH_IS_FALSE] = "is_false",
|
|
[MACH_IS_FIT] = "is_fit",
|
|
[MACH_IS_CHAR] = "is_char",
|
|
[MACH_IS_DIGIT] = "is_digit",
|
|
[MACH_IS_LETTER] = "is_letter",
|
|
[MACH_IS_LOWER] = "is_lower",
|
|
[MACH_IS_UPPER] = "is_upper",
|
|
[MACH_IS_WS] = "is_ws",
|
|
[MACH_IS_ACTOR] = "is_actor",
|
|
[MACH_APPLY] = "apply",
|
|
[MACH_WARYTRUE] = "wary_true",
|
|
[MACH_WARYFALSE] = "wary_false",
|
|
[MACH_JMPEMPTY] = "jump_empty",
|
|
};
|
|
|
|
/* ---- Compile-time constant pool entry ---- */
|
|
/* Stores raw data during compilation; converted to JSValues when loading into context */
|
|
typedef enum { MACH_CP_INT, MACH_CP_FLOAT, MACH_CP_STR } MachCPType;
|
|
typedef struct {
|
|
MachCPType type;
|
|
union {
|
|
int32_t ival; /* integer constant */
|
|
double fval; /* float constant */
|
|
char *str; /* owned C string */
|
|
};
|
|
} MachCPoolEntry;
|
|
|
|
/* ---- Compiled output (context-free) ---- */
|
|
typedef struct MachCode {
|
|
uint16_t arity;
|
|
uint16_t nr_close_slots;
|
|
uint16_t nr_slots;
|
|
uint16_t entry_point;
|
|
|
|
uint32_t cpool_count;
|
|
MachCPoolEntry *cpool;
|
|
|
|
uint32_t instr_count;
|
|
MachInstr32 *instructions;
|
|
|
|
uint32_t func_count;
|
|
struct MachCode **functions;
|
|
|
|
char *name; /* owned C string, or NULL */
|
|
MachLineEntry *line_table; /* [instr_count], parallel to instructions[] */
|
|
char *filename; /* source filename (sys_malloc'd) */
|
|
uint16_t disruption_pc; /* start of disruption handler (0 = none) */
|
|
} MachCode;
|
|
|
|
/* ---- Helpers ---- */
|
|
|
|
static JSValue *mach_materialize_cpool(JSContext *ctx, MachCPoolEntry *entries, int count) {
|
|
if (count == 0) { sys_free(entries); return NULL; }
|
|
JSValue *cpool = js_malloc_rt(count * sizeof(JSValue));
|
|
for (int i = 0; i < count; i++) {
|
|
switch (entries[i].type) {
|
|
case MACH_CP_INT:
|
|
cpool[i] = JS_NewInt32(ctx, entries[i].ival);
|
|
break;
|
|
case MACH_CP_FLOAT:
|
|
cpool[i] = JS_NewFloat64(ctx, entries[i].fval);
|
|
break;
|
|
case MACH_CP_STR:
|
|
cpool[i] = js_key_new(ctx, entries[i].str);
|
|
break;
|
|
}
|
|
}
|
|
return cpool;
|
|
}
|
|
|
|
static int mach_check_call_arity(JSContext *ctx, JSFunction *fn, int argc) {
|
|
if (unlikely(fn->length >= 0 && argc > fn->length)) {
|
|
char buf[KEY_GET_STR_BUF_SIZE];
|
|
JS_RaiseDisrupt(ctx, "too many arguments for %s: expected %d, got %d",
|
|
JS_KeyGetStr(ctx, buf, KEY_GET_STR_BUF_SIZE, fn->name), fn->length, argc);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Scan backwards from pc to find what loaded the callee register.
|
|
Returns the name in buf, or NULL if unknown. */
|
|
static const char *mach_callee_name(JSContext *ctx, JSCodeRegister *code,
|
|
uint32_t pc, int reg,
|
|
char *buf, size_t buf_size) {
|
|
int hops = 4; /* limit move-chain depth */
|
|
for (int i = (int)pc - 2; i >= 0 && hops > 0; i--) {
|
|
MachInstr32 prev = code->instructions[i];
|
|
int prev_op = MACH_GET_OP(prev);
|
|
int prev_a = MACH_GET_A(prev);
|
|
if (prev_a != reg) continue;
|
|
if (prev_op == MACH_GETENV || prev_op == MACH_GETINTRINSIC) {
|
|
int bx = MACH_GET_Bx(prev);
|
|
if ((uint32_t)bx < code->cpool_count && JS_IsText(code->cpool[bx]))
|
|
return JS_KeyGetStr(ctx, buf, buf_size, code->cpool[bx]);
|
|
}
|
|
if (prev_op == MACH_LOAD_FIELD) {
|
|
int ci = MACH_GET_C(prev);
|
|
if ((uint32_t)ci < code->cpool_count && JS_IsText(code->cpool[ci]))
|
|
return JS_KeyGetStr(ctx, buf, buf_size, code->cpool[ci]);
|
|
}
|
|
if (prev_op == MACH_MOVE) {
|
|
reg = MACH_GET_B(prev);
|
|
hops--;
|
|
continue;
|
|
}
|
|
break; /* some other op wrote to this reg — give up */
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* ---- Link pass: resolve GETNAME to GETINTRINSIC or GETENV ---- */
|
|
|
|
static void mach_link_code(JSContext *ctx, JSCodeRegister *code, JSValue env) {
|
|
if (!JS_IsNull(env) && !JS_IsStone(env)) {
|
|
fprintf(stderr, "mach_link_code: ERROR env not stone (code=%s file=%s)\n",
|
|
code->name_cstr ? code->name_cstr : "<unknown>",
|
|
code->filename_cstr ? code->filename_cstr : "<unknown>");
|
|
abort();
|
|
}
|
|
JSGCRef env_ref;
|
|
JS_PushGCRef(ctx, &env_ref);
|
|
env_ref.val = env;
|
|
for (uint32_t i = 0; i < code->instr_count; i++) {
|
|
MachInstr32 instr = code->instructions[i];
|
|
if (MACH_GET_OP(instr) != MACH_GETNAME) continue;
|
|
int a = MACH_GET_A(instr);
|
|
int bx = MACH_GET_Bx(instr);
|
|
int in_env = 0;
|
|
if (!JS_IsNull(env_ref.val) && (uint32_t)bx < code->cpool_count) {
|
|
in_env = JS_HasProperty(ctx, env_ref.val, code->cpool[bx]);
|
|
}
|
|
code->instructions[i] = MACH_ABx(in_env ? MACH_GETENV : MACH_GETINTRINSIC, a, bx);
|
|
}
|
|
for (uint32_t i = 0; i < code->func_count; i++)
|
|
if (code->functions[i]) mach_link_code(ctx, code->functions[i], env_ref.val);
|
|
JS_PopGCRef(ctx, &env_ref);
|
|
}
|
|
|
|
/* Free a MachCode tree (compiled but not yet loaded) */
|
|
void JS_FreeMachCode(MachCode *mc) {
|
|
if (!mc) return;
|
|
sys_free(mc->instructions);
|
|
for (uint32_t i = 0; i < mc->cpool_count; i++) {
|
|
if (mc->cpool[i].type == MACH_CP_STR)
|
|
sys_free(mc->cpool[i].str);
|
|
}
|
|
sys_free(mc->cpool);
|
|
for (uint32_t i = 0; i < mc->func_count; i++)
|
|
JS_FreeMachCode(mc->functions[i]);
|
|
sys_free(mc->functions);
|
|
sys_free(mc->name);
|
|
sys_free(mc->line_table);
|
|
sys_free(mc->filename);
|
|
sys_free(mc);
|
|
}
|
|
|
|
/* ---- MachCode binary serialization ---- */
|
|
|
|
static size_t mach_serialized_size(MachCode *mc) {
|
|
/* Header: 6 x uint16_t + 1 x uint16_t padding = 14 bytes (but let's be explicit) */
|
|
size_t sz = 6 * sizeof(uint16_t); /* arity, nr_close_slots, nr_slots, entry_point, disruption_pc, padding */
|
|
|
|
/* name */
|
|
sz += sizeof(uint32_t);
|
|
if (mc->name) sz += strlen(mc->name);
|
|
|
|
/* filename */
|
|
sz += sizeof(uint32_t);
|
|
if (mc->filename) sz += strlen(mc->filename);
|
|
|
|
/* cpool */
|
|
sz += sizeof(uint32_t); /* cpool_count */
|
|
for (uint32_t i = 0; i < mc->cpool_count; i++) {
|
|
sz += 1; /* type tag */
|
|
switch (mc->cpool[i].type) {
|
|
case MACH_CP_INT: sz += sizeof(int32_t); break;
|
|
case MACH_CP_FLOAT: sz += sizeof(double); break;
|
|
case MACH_CP_STR:
|
|
sz += sizeof(uint32_t);
|
|
sz += strlen(mc->cpool[i].str);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* instructions + line_table */
|
|
sz += sizeof(uint32_t); /* instr_count */
|
|
sz += mc->instr_count * sizeof(MachInstr32);
|
|
sz += mc->instr_count * sizeof(MachLineEntry);
|
|
|
|
/* nested functions */
|
|
sz += sizeof(uint32_t); /* func_count */
|
|
for (uint32_t i = 0; i < mc->func_count; i++)
|
|
sz += mach_serialized_size(mc->functions[i]);
|
|
|
|
return sz;
|
|
}
|
|
|
|
static void mach_serialize_write(MachCode *mc, uint8_t **p) {
|
|
uint8_t *w = *p;
|
|
|
|
/* Header fields */
|
|
memcpy(w, &mc->arity, 2); w += 2;
|
|
memcpy(w, &mc->nr_close_slots, 2); w += 2;
|
|
memcpy(w, &mc->nr_slots, 2); w += 2;
|
|
memcpy(w, &mc->entry_point, 2); w += 2;
|
|
memcpy(w, &mc->disruption_pc, 2); w += 2;
|
|
uint16_t pad = 0;
|
|
memcpy(w, &pad, 2); w += 2;
|
|
|
|
/* name */
|
|
uint32_t name_len = mc->name ? (uint32_t)strlen(mc->name) : 0;
|
|
memcpy(w, &name_len, 4); w += 4;
|
|
if (name_len) { memcpy(w, mc->name, name_len); w += name_len; }
|
|
|
|
/* filename */
|
|
uint32_t fn_len = mc->filename ? (uint32_t)strlen(mc->filename) : 0;
|
|
memcpy(w, &fn_len, 4); w += 4;
|
|
if (fn_len) { memcpy(w, mc->filename, fn_len); w += fn_len; }
|
|
|
|
/* cpool */
|
|
memcpy(w, &mc->cpool_count, 4); w += 4;
|
|
for (uint32_t i = 0; i < mc->cpool_count; i++) {
|
|
uint8_t tag = (uint8_t)mc->cpool[i].type;
|
|
*w++ = tag;
|
|
switch (mc->cpool[i].type) {
|
|
case MACH_CP_INT:
|
|
memcpy(w, &mc->cpool[i].ival, 4); w += 4;
|
|
break;
|
|
case MACH_CP_FLOAT:
|
|
memcpy(w, &mc->cpool[i].fval, 8); w += 8;
|
|
break;
|
|
case MACH_CP_STR: {
|
|
uint32_t slen = (uint32_t)strlen(mc->cpool[i].str);
|
|
memcpy(w, &slen, 4); w += 4;
|
|
memcpy(w, mc->cpool[i].str, slen); w += slen;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* instructions */
|
|
memcpy(w, &mc->instr_count, 4); w += 4;
|
|
memcpy(w, mc->instructions, mc->instr_count * sizeof(MachInstr32));
|
|
w += mc->instr_count * sizeof(MachInstr32);
|
|
|
|
/* line_table (write zeros if NULL) */
|
|
if (mc->line_table) {
|
|
memcpy(w, mc->line_table, mc->instr_count * sizeof(MachLineEntry));
|
|
} else {
|
|
memset(w, 0, mc->instr_count * sizeof(MachLineEntry));
|
|
}
|
|
w += mc->instr_count * sizeof(MachLineEntry);
|
|
|
|
/* nested functions */
|
|
memcpy(w, &mc->func_count, 4); w += 4;
|
|
*p = w;
|
|
for (uint32_t i = 0; i < mc->func_count; i++)
|
|
mach_serialize_write(mc->functions[i], p);
|
|
}
|
|
|
|
uint8_t *JS_SerializeMachCode(MachCode *mc, size_t *out_size) {
|
|
if (!mc) return NULL;
|
|
size_t sz = mach_serialized_size(mc);
|
|
uint8_t *buf = sys_malloc(sz);
|
|
if (!buf) return NULL;
|
|
uint8_t *p = buf;
|
|
mach_serialize_write(mc, &p);
|
|
*out_size = sz;
|
|
return buf;
|
|
}
|
|
|
|
#define DESER_CHECK(cond) do { if (!(cond)) goto fail; } while(0)
|
|
|
|
static MachCode *mach_deserialize_read(const uint8_t **p, const uint8_t *end) {
|
|
const uint8_t *r = *p;
|
|
|
|
/* Need at least the fixed header: 6 * uint16_t = 12 bytes */
|
|
if (r + 12 > end) return NULL;
|
|
|
|
MachCode *mc = sys_malloc(sizeof(MachCode));
|
|
if (!mc) return NULL;
|
|
memset(mc, 0, sizeof(MachCode));
|
|
|
|
memcpy(&mc->arity, r, 2); r += 2;
|
|
memcpy(&mc->nr_close_slots, r, 2); r += 2;
|
|
memcpy(&mc->nr_slots, r, 2); r += 2;
|
|
memcpy(&mc->entry_point, r, 2); r += 2;
|
|
memcpy(&mc->disruption_pc, r, 2); r += 2;
|
|
r += 2; /* skip padding */
|
|
|
|
/* name */
|
|
DESER_CHECK(r + 4 <= end);
|
|
uint32_t name_len;
|
|
memcpy(&name_len, r, 4); r += 4;
|
|
DESER_CHECK(r + name_len <= end);
|
|
if (name_len) {
|
|
mc->name = sys_malloc(name_len + 1);
|
|
memcpy(mc->name, r, name_len);
|
|
mc->name[name_len] = '\0';
|
|
r += name_len;
|
|
}
|
|
|
|
/* filename */
|
|
DESER_CHECK(r + 4 <= end);
|
|
uint32_t fn_len;
|
|
memcpy(&fn_len, r, 4); r += 4;
|
|
DESER_CHECK(r + fn_len <= end);
|
|
if (fn_len) {
|
|
mc->filename = sys_malloc(fn_len + 1);
|
|
memcpy(mc->filename, r, fn_len);
|
|
mc->filename[fn_len] = '\0';
|
|
r += fn_len;
|
|
}
|
|
|
|
/* cpool */
|
|
DESER_CHECK(r + 4 <= end);
|
|
memcpy(&mc->cpool_count, r, 4); r += 4;
|
|
if (mc->cpool_count) {
|
|
mc->cpool = sys_malloc(mc->cpool_count * sizeof(MachCPoolEntry));
|
|
for (uint32_t i = 0; i < mc->cpool_count; i++) {
|
|
DESER_CHECK(r + 1 <= end);
|
|
uint8_t tag = *r++;
|
|
mc->cpool[i].type = (MachCPType)tag;
|
|
switch (tag) {
|
|
case MACH_CP_INT:
|
|
DESER_CHECK(r + 4 <= end);
|
|
memcpy(&mc->cpool[i].ival, r, 4); r += 4;
|
|
break;
|
|
case MACH_CP_FLOAT:
|
|
DESER_CHECK(r + 8 <= end);
|
|
memcpy(&mc->cpool[i].fval, r, 8); r += 8;
|
|
break;
|
|
case MACH_CP_STR: {
|
|
DESER_CHECK(r + 4 <= end);
|
|
uint32_t slen;
|
|
memcpy(&slen, r, 4); r += 4;
|
|
DESER_CHECK(r + slen <= end);
|
|
mc->cpool[i].str = sys_malloc(slen + 1);
|
|
memcpy(mc->cpool[i].str, r, slen);
|
|
mc->cpool[i].str[slen] = '\0';
|
|
r += slen;
|
|
break;
|
|
}
|
|
default:
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* instructions */
|
|
DESER_CHECK(r + 4 <= end);
|
|
memcpy(&mc->instr_count, r, 4); r += 4;
|
|
DESER_CHECK(r + mc->instr_count * sizeof(MachInstr32) <= end);
|
|
mc->instructions = sys_malloc(mc->instr_count * sizeof(MachInstr32));
|
|
memcpy(mc->instructions, r, mc->instr_count * sizeof(MachInstr32));
|
|
r += mc->instr_count * sizeof(MachInstr32);
|
|
|
|
/* line_table */
|
|
DESER_CHECK(r + mc->instr_count * sizeof(MachLineEntry) <= end);
|
|
mc->line_table = sys_malloc(mc->instr_count * sizeof(MachLineEntry));
|
|
memcpy(mc->line_table, r, mc->instr_count * sizeof(MachLineEntry));
|
|
r += mc->instr_count * sizeof(MachLineEntry);
|
|
|
|
/* nested functions */
|
|
DESER_CHECK(r + 4 <= end);
|
|
memcpy(&mc->func_count, r, 4); r += 4;
|
|
if (mc->func_count) {
|
|
mc->functions = sys_malloc(mc->func_count * sizeof(MachCode *));
|
|
for (uint32_t i = 0; i < mc->func_count; i++) {
|
|
mc->functions[i] = mach_deserialize_read(&r, end);
|
|
if (!mc->functions[i]) {
|
|
mc->func_count = i; /* only free what we allocated */
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
*p = r;
|
|
return mc;
|
|
|
|
fail:
|
|
JS_FreeMachCode(mc);
|
|
return NULL;
|
|
}
|
|
|
|
#undef DESER_CHECK
|
|
|
|
MachCode *JS_DeserializeMachCode(const uint8_t *data, size_t size) {
|
|
if (!data || size == 0) return NULL;
|
|
const uint8_t *p = data;
|
|
const uint8_t *end = data + size;
|
|
MachCode *mc = mach_deserialize_read(&p, end);
|
|
if (mc && p != end) {
|
|
/* Trailing data — treat as error */
|
|
JS_FreeMachCode(mc);
|
|
return NULL;
|
|
}
|
|
return mc;
|
|
}
|
|
|
|
/* Load a MachCode into a JSCodeRegister (materializes JSValues, needs ctx) */
|
|
JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env) {
|
|
/* Protect env from GC — materialize/link calls can trigger collection */
|
|
JSGCRef env_ref;
|
|
JS_PushGCRef(ctx, &env_ref);
|
|
env_ref.val = env;
|
|
|
|
JSCodeRegister *code = js_mallocz_rt(sizeof(JSCodeRegister));
|
|
code->arity = mc->arity;
|
|
code->nr_close_slots = mc->nr_close_slots;
|
|
code->nr_slots = mc->nr_slots;
|
|
code->entry_point = mc->entry_point;
|
|
code->instr_count = mc->instr_count;
|
|
code->instructions = mc->instructions; /* transfer ownership */
|
|
mc->instructions = NULL;
|
|
|
|
/* Materialize cpool: raw -> JSValue */
|
|
code->cpool_count = mc->cpool_count;
|
|
code->cpool = mach_materialize_cpool(ctx, mc->cpool, mc->cpool_count);
|
|
|
|
/* Recursively load nested functions */
|
|
code->func_count = mc->func_count;
|
|
if (mc->func_count > 0) {
|
|
code->functions = js_malloc_rt(mc->func_count * sizeof(JSCodeRegister *));
|
|
for (uint32_t i = 0; i < mc->func_count; i++)
|
|
code->functions[i] = JS_LoadMachCode(ctx, mc->functions[i], env_ref.val);
|
|
} else {
|
|
code->functions = NULL;
|
|
}
|
|
|
|
/* Intern function name */
|
|
code->name = mc->name ? js_key_new(ctx, mc->name) : JS_NULL;
|
|
|
|
/* Transfer debug info */
|
|
code->line_table = mc->line_table;
|
|
mc->line_table = NULL;
|
|
code->filename_cstr = mc->filename ? js_strdup_rt(mc->filename) : NULL;
|
|
code->name_cstr = mc->name ? js_strdup_rt(mc->name) : NULL;
|
|
|
|
code->disruption_pc = mc->disruption_pc;
|
|
|
|
/* Link: resolve GETNAME to GETENV/GETINTRINSIC */
|
|
mach_link_code(ctx, code, env_ref.val);
|
|
|
|
JS_PopGCRef(ctx, &env_ref);
|
|
return code;
|
|
}
|
|
|
|
/* Free a JSCodeRegister and all nested functions */
|
|
static void js_free_code_register(JSCodeRegister *code) {
|
|
if (!code) return;
|
|
js_free_rt(code->instructions);
|
|
js_free_rt(code->cpool);
|
|
for (uint32_t i = 0; i < code->func_count; i++) {
|
|
js_free_code_register(code->functions[i]);
|
|
}
|
|
js_free_rt(code->functions);
|
|
js_free_rt(code->line_table);
|
|
js_free_rt(code->filename_cstr);
|
|
js_free_rt(code->name_cstr);
|
|
js_free_rt(code);
|
|
}
|
|
|
|
|
|
/* ============================================================
|
|
MACH VM — register-based bytecode interpreter
|
|
============================================================ */
|
|
|
|
/* Allocate a JSFrameRegister on the GC heap */
|
|
JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count) {
|
|
size_t size = sizeof(JSFrameRegister) + slot_count * sizeof(JSValue);
|
|
JSFrameRegister *frame = js_malloc(ctx, size);
|
|
if (!frame) return NULL;
|
|
|
|
/* cap56 = slot count (used by gc_object_size) */
|
|
frame->header = objhdr_make(slot_count, OBJ_FRAME, 0, 0, 0, 0);
|
|
frame->function = JS_NULL;
|
|
frame->caller = JS_NULL;
|
|
frame->address = JS_NewInt32(ctx, 0);
|
|
|
|
/* Initialize slots to null */
|
|
for (int i = 0; i < slot_count; i++) {
|
|
frame->slots[i] = JS_NULL;
|
|
}
|
|
|
|
return frame;
|
|
}
|
|
|
|
static JSValue js_new_register_code(JSContext *ctx, JSCodeRegister *code) {
|
|
JSCode *jc;
|
|
if (!code) return JS_EXCEPTION;
|
|
jc = ct_alloc(ctx, sizeof(JSCode), 8);
|
|
if (!jc) return JS_EXCEPTION;
|
|
memset(jc, 0, sizeof(JSCode));
|
|
jc->header = objhdr_make(0, OBJ_CODE, 0, 0, 0, 0);
|
|
jc->kind = JS_CODE_KIND_REGISTER;
|
|
jc->arity = (int16_t)code->arity;
|
|
jc->u.reg.code = code;
|
|
return JS_MKPTR(jc);
|
|
}
|
|
|
|
static JSValue js_new_native_code(JSContext *ctx, void *fn_ptr, void *dl_handle,
|
|
uint16_t nr_slots, int arity) {
|
|
JSCode *jc = ct_alloc(ctx, sizeof(JSCode), 8);
|
|
if (!jc) return JS_EXCEPTION;
|
|
memset(jc, 0, sizeof(JSCode));
|
|
jc->header = objhdr_make(0, OBJ_CODE, 0, 0, 0, 0);
|
|
jc->kind = JS_CODE_KIND_NATIVE;
|
|
jc->arity = (int16_t)arity;
|
|
jc->u.native.fn_ptr = fn_ptr;
|
|
jc->u.native.dl_handle = dl_handle;
|
|
jc->u.native.nr_slots = nr_slots;
|
|
return JS_MKPTR(jc);
|
|
}
|
|
|
|
/* Create a register-based function from JSCodeRegister */
|
|
JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JSValue env, JSValue outer_frame) {
|
|
/* Protect env and outer_frame from GC — js_mallocz can trigger
|
|
collection which moves heap objects, invalidating stack-local copies */
|
|
JSGCRef env_ref, frame_ref;
|
|
JS_PushGCRef(ctx, &env_ref);
|
|
env_ref.val = env;
|
|
JS_PushGCRef(ctx, &frame_ref);
|
|
frame_ref.val = outer_frame;
|
|
|
|
JSGCRef fn_ref;
|
|
JSFunction *fn;
|
|
JSValue code_obj;
|
|
|
|
JS_AddGCRef(ctx, &fn_ref);
|
|
fn_ref.val = JS_NULL;
|
|
|
|
fn = js_mallocz(ctx, sizeof(JSFunction));
|
|
if (!fn) {
|
|
JS_DeleteGCRef(ctx, &fn_ref);
|
|
JS_PopGCRef(ctx, &frame_ref);
|
|
JS_PopGCRef(ctx, &env_ref);
|
|
return JS_EXCEPTION;
|
|
}
|
|
fn_ref.val = JS_MKPTR(fn);
|
|
|
|
fn->header = objhdr_make(0, OBJ_FUNCTION, 0, 0, 0, 0);
|
|
fn->kind = JS_FUNC_KIND_REGISTER;
|
|
fn->length = code->arity;
|
|
fn->name = code->name;
|
|
code_obj = js_new_register_code(ctx, code);
|
|
if (JS_IsException(code_obj)) {
|
|
JS_DeleteGCRef(ctx, &fn_ref);
|
|
JS_PopGCRef(ctx, &frame_ref);
|
|
JS_PopGCRef(ctx, &env_ref);
|
|
return JS_EXCEPTION;
|
|
}
|
|
fn = JS_VALUE_GET_FUNCTION(fn_ref.val);
|
|
fn->u.cell.code = code_obj;
|
|
fn->u.cell.env_record = env_ref.val;
|
|
fn->u.cell.outer_frame = frame_ref.val;
|
|
JSValue out = fn_ref.val;
|
|
JS_DeleteGCRef(ctx, &fn_ref);
|
|
JS_PopGCRef(ctx, &frame_ref);
|
|
JS_PopGCRef(ctx, &env_ref);
|
|
return out;
|
|
}
|
|
|
|
JSValue js_new_native_function_with_code(JSContext *ctx, JSValue code_obj, int arity, JSValue outer_frame, JSValue env_record) {
|
|
JSGCRef frame_ref;
|
|
JSGCRef fn_ref;
|
|
JSFunction *fn;
|
|
JS_PushGCRef(ctx, &frame_ref);
|
|
frame_ref.val = outer_frame;
|
|
JS_AddGCRef(ctx, &fn_ref);
|
|
fn_ref.val = JS_NULL;
|
|
|
|
fn = js_mallocz(ctx, sizeof(JSFunction));
|
|
if (!fn) {
|
|
JS_DeleteGCRef(ctx, &fn_ref);
|
|
JS_PopGCRef(ctx, &frame_ref);
|
|
return JS_EXCEPTION;
|
|
}
|
|
fn_ref.val = JS_MKPTR(fn);
|
|
|
|
fn->header = objhdr_make(0, OBJ_FUNCTION, 0, 0, 0, 0);
|
|
fn->kind = JS_FUNC_KIND_NATIVE;
|
|
fn->length = arity;
|
|
fn->name = JS_NULL;
|
|
fn = JS_VALUE_GET_FUNCTION(fn_ref.val);
|
|
fn->u.cell.code = code_obj;
|
|
fn->u.cell.env_record = env_record;
|
|
fn->u.cell.outer_frame = frame_ref.val;
|
|
|
|
JSValue out = fn_ref.val;
|
|
JS_DeleteGCRef(ctx, &fn_ref);
|
|
JS_PopGCRef(ctx, &frame_ref);
|
|
return out;
|
|
}
|
|
|
|
/* Create a native (QBE-compiled) function */
|
|
JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle,
|
|
uint16_t nr_slots, int arity, JSValue outer_frame, JSValue env) {
|
|
JSValue code_obj = js_new_native_code(ctx, fn_ptr, dl_handle, nr_slots, arity);
|
|
if (JS_IsException(code_obj))
|
|
return JS_EXCEPTION;
|
|
return js_new_native_function_with_code(ctx, code_obj, arity, outer_frame, env);
|
|
}
|
|
|
|
/* Binary operations helper */
|
|
static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
|
|
/* Fast path for integers */
|
|
if (JS_VALUE_IS_BOTH_INT(a, b)) {
|
|
int32_t ia = JS_VALUE_GET_INT(a);
|
|
int32_t ib = JS_VALUE_GET_INT(b);
|
|
switch (op) {
|
|
case MACH_ADD: {
|
|
int64_t r = (int64_t)ia + (int64_t)ib;
|
|
if (r >= INT32_MIN && r <= INT32_MAX)
|
|
return JS_NewInt32(ctx, (int32_t)r);
|
|
return JS_NewFloat64(ctx, (double)r);
|
|
}
|
|
case MACH_SUB: {
|
|
int64_t r = (int64_t)ia - (int64_t)ib;
|
|
if (r >= INT32_MIN && r <= INT32_MAX)
|
|
return JS_NewInt32(ctx, (int32_t)r);
|
|
return JS_NewFloat64(ctx, (double)r);
|
|
}
|
|
case MACH_MUL: {
|
|
int64_t r = (int64_t)ia * (int64_t)ib;
|
|
if (r >= INT32_MIN && r <= INT32_MAX)
|
|
return JS_NewInt32(ctx, (int32_t)r);
|
|
return JS_NewFloat64(ctx, (double)r);
|
|
}
|
|
case MACH_DIV:
|
|
if (ib == 0) return JS_NULL;
|
|
if (ia % ib == 0) return JS_NewInt32(ctx, ia / ib);
|
|
return JS_NewFloat64(ctx, (double)ia / (double)ib);
|
|
case MACH_MOD:
|
|
if (ib == 0) return JS_NULL;
|
|
return JS_NewFloat64(ctx, (double)ia - ((double)ib * floor((double)ia / (double)ib)));
|
|
case MACH_EQ:
|
|
return JS_NewBool(ctx, ia == ib);
|
|
case MACH_NEQ:
|
|
return JS_NewBool(ctx, ia != ib);
|
|
case MACH_LT:
|
|
return JS_NewBool(ctx, ia < ib);
|
|
case MACH_LE:
|
|
return JS_NewBool(ctx, ia <= ib);
|
|
case MACH_GT:
|
|
return JS_NewBool(ctx, ia > ib);
|
|
case MACH_GE:
|
|
return JS_NewBool(ctx, ia >= ib);
|
|
case MACH_BAND:
|
|
return JS_NewInt32(ctx, ia & ib);
|
|
case MACH_BOR:
|
|
return JS_NewInt32(ctx, ia | ib);
|
|
case MACH_BXOR:
|
|
return JS_NewInt32(ctx, ia ^ ib);
|
|
case MACH_SHL:
|
|
return JS_NewInt32(ctx, ia << (ib & 31));
|
|
case MACH_SHR:
|
|
return JS_NewInt32(ctx, ia >> (ib & 31));
|
|
case MACH_USHR:
|
|
return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31));
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Comparison ops allow mixed types — return false for mismatches */
|
|
if (op >= MACH_EQ && op <= MACH_GE) {
|
|
/* Fast path: identical values (chase pointers for forwarded objects) */
|
|
{
|
|
JSValue ca = JS_IsPtr(a) ? JS_MKPTR(chase(a)) : a;
|
|
JSValue cb = JS_IsPtr(b) ? JS_MKPTR(chase(b)) : b;
|
|
if (ca == cb) {
|
|
if (op == MACH_EQ || op == MACH_LE || op == MACH_GE) return JS_TRUE;
|
|
if (op == MACH_NEQ) return JS_FALSE;
|
|
}
|
|
}
|
|
if (JS_IsNumber(a) && JS_IsNumber(b)) {
|
|
double da, db;
|
|
JS_ToFloat64(ctx, &da, a);
|
|
JS_ToFloat64(ctx, &db, b);
|
|
switch (op) {
|
|
case MACH_EQ: return JS_NewBool(ctx, da == db);
|
|
case MACH_NEQ: return JS_NewBool(ctx, da != db);
|
|
case MACH_LT: return JS_NewBool(ctx, da < db);
|
|
case MACH_LE: return JS_NewBool(ctx, da <= db);
|
|
case MACH_GT: return JS_NewBool(ctx, da > db);
|
|
case MACH_GE: return JS_NewBool(ctx, da >= db);
|
|
default: break;
|
|
}
|
|
}
|
|
/* String comparisons */
|
|
if (mist_is_text(a) && mist_is_text(b)) {
|
|
int cmp = js_string_compare_value(ctx, a, b, FALSE);
|
|
switch (op) {
|
|
case MACH_EQ: return JS_NewBool(ctx, cmp == 0);
|
|
case MACH_NEQ: return JS_NewBool(ctx, cmp != 0);
|
|
case MACH_LT: return JS_NewBool(ctx, cmp < 0);
|
|
case MACH_LE: return JS_NewBool(ctx, cmp <= 0);
|
|
case MACH_GT: return JS_NewBool(ctx, cmp > 0);
|
|
case MACH_GE: return JS_NewBool(ctx, cmp >= 0);
|
|
default: break;
|
|
}
|
|
}
|
|
/* Null comparisons */
|
|
if (JS_IsNull(a) && JS_IsNull(b)) {
|
|
if (op == MACH_EQ || op == MACH_LE || op == MACH_GE)
|
|
return JS_TRUE;
|
|
return JS_FALSE;
|
|
}
|
|
/* Boolean comparisons */
|
|
if (JS_IsBool(a) && JS_IsBool(b)) {
|
|
int ba = JS_VALUE_GET_BOOL(a);
|
|
int bb = JS_VALUE_GET_BOOL(b);
|
|
switch (op) {
|
|
case MACH_EQ: return JS_NewBool(ctx, ba == bb);
|
|
case MACH_NEQ: return JS_NewBool(ctx, ba != bb);
|
|
case MACH_LT: return JS_NewBool(ctx, ba < bb);
|
|
case MACH_LE: return JS_NewBool(ctx, ba <= bb);
|
|
case MACH_GT: return JS_NewBool(ctx, ba > bb);
|
|
case MACH_GE: return JS_NewBool(ctx, ba >= bb);
|
|
default: break;
|
|
}
|
|
}
|
|
/* Different types for ordering comparisons: disrupt */
|
|
if (op >= MACH_LT && op <= MACH_GE)
|
|
return JS_RaiseDisrupt(ctx, "cannot compare: operands must be same type");
|
|
/* EQ/NEQ with different types: false/true */
|
|
if (op == MACH_NEQ) return JS_NewBool(ctx, 1);
|
|
return JS_NewBool(ctx, 0);
|
|
}
|
|
|
|
/* Numeric operations — both must be numeric */
|
|
if (JS_IsNumber(a) && JS_IsNumber(b)) {
|
|
double da, db;
|
|
JS_ToFloat64(ctx, &da, a);
|
|
JS_ToFloat64(ctx, &db, b);
|
|
switch (op) {
|
|
case MACH_ADD: {
|
|
double r = da + db;
|
|
if (!isfinite(r)) return JS_NULL;
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
case MACH_SUB: {
|
|
double r = da - db;
|
|
if (!isfinite(r)) return JS_NULL;
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
case MACH_MUL: {
|
|
double r = da * db;
|
|
if (!isfinite(r)) return JS_NULL;
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
case MACH_DIV: {
|
|
if (db == 0.0) return JS_NULL;
|
|
double r = da / db;
|
|
if (!isfinite(r)) return JS_NULL;
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
case MACH_MOD: {
|
|
if (db == 0.0) return JS_NULL;
|
|
if (isnan(da) || isnan(db)) return JS_NULL;
|
|
if (da == 0.0) return JS_NewFloat64(ctx, 0.0);
|
|
double r = da - (db * floor(da / db));
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
case MACH_POW: {
|
|
double r = pow(da, db);
|
|
if (!isfinite(r) && isfinite(da) && isfinite(db)) return JS_NULL;
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
case MACH_BAND: case MACH_BOR: case MACH_BXOR:
|
|
case MACH_SHL: case MACH_SHR: case MACH_USHR: {
|
|
int32_t ia = (int32_t)da;
|
|
int32_t ib = (int32_t)db;
|
|
switch (op) {
|
|
case MACH_BAND: return JS_NewInt32(ctx, ia & ib);
|
|
case MACH_BOR: return JS_NewInt32(ctx, ia | ib);
|
|
case MACH_BXOR: return JS_NewInt32(ctx, ia ^ ib);
|
|
case MACH_SHL: return JS_NewInt32(ctx, ia << (ib & 31));
|
|
case MACH_SHR: return JS_NewInt32(ctx, ia >> (ib & 31));
|
|
case MACH_USHR: return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31));
|
|
default: break;
|
|
}
|
|
}
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
/* Type mismatch — disrupt */
|
|
return JS_RaiseDisrupt(ctx, "type mismatch in binary operation");
|
|
}
|
|
|
|
static inline int mach_get_number(JSValue v, double *out) {
|
|
uint32_t tag = JS_VALUE_GET_TAG(v);
|
|
if (tag == JS_TAG_INT) {
|
|
*out = (double)JS_VALUE_GET_INT(v);
|
|
return 0;
|
|
}
|
|
if (JS_TAG_IS_FLOAT64(tag)) {
|
|
*out = JS_VALUE_GET_FLOAT64(v);
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static inline int mach_get_place(JSContext *ctx, JSValue v, int32_t *out) {
|
|
uint32_t tag = JS_VALUE_GET_NORM_TAG(v);
|
|
if (tag == JS_TAG_INT || tag == JS_TAG_BOOL || tag == JS_TAG_NULL || tag == JS_TAG_FLOAT64) {
|
|
return JS_ToInt32(ctx, out, v);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static inline double mach_apply_place(double d, int32_t place, double (*f)(double)) {
|
|
if (place == 0)
|
|
return f(d);
|
|
double mult = pow(10.0, -(double)place);
|
|
return f(d * mult) / mult;
|
|
}
|
|
|
|
|
|
#ifdef HAVE_ASAN
|
|
void __asan_on_error(void) {
|
|
JSContext *ctx = __asan_js_ctx;
|
|
if (!ctx) return;
|
|
if (JS_IsNull(ctx->reg_current_frame)) return;
|
|
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame);
|
|
uint32_t cur_pc = ctx->current_register_pc;
|
|
fprintf(stderr, "\n=== ASAN error: VM stack trace ===\n");
|
|
int is_first = 1;
|
|
while (frame) {
|
|
if (!mist_is_function(frame->function)) break;
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
const char *func_name = NULL;
|
|
const char *file = NULL;
|
|
uint16_t line = 0;
|
|
uint32_t pc = is_first ? cur_pc : 0;
|
|
if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code) {
|
|
JSCodeRegister *code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
|
|
file = code->filename_cstr;
|
|
func_name = code->name_cstr;
|
|
if (!is_first)
|
|
pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16);
|
|
if (code->line_table && pc < code->instr_count)
|
|
line = code->line_table[pc].line;
|
|
}
|
|
fprintf(stderr, " %s (%s:%u)\n",
|
|
func_name ? func_name : "<anonymous>",
|
|
file ? file : "<unknown>", line);
|
|
if (!JS_IsPtr(frame->caller)) break;
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
|
is_first = 0;
|
|
}
|
|
fprintf(stderr, "=================================\n");
|
|
}
|
|
#endif
|
|
|
|
/* Main register VM execution loop */
|
|
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
|
JSValue this_obj, int argc, JSValue *argv,
|
|
JSValue env, JSValue outer_frame) {
|
|
JSGCRef frame_ref;
|
|
JSFrameRegister *frame;
|
|
uint32_t pc;
|
|
JSValue result;
|
|
|
|
/* Resume path: if VM was suspended, restore state and jump into dispatch */
|
|
if (ctx->suspended) {
|
|
ctx->suspended = 0;
|
|
JS_AddGCRef(ctx, &frame_ref);
|
|
frame_ref.val = ctx->suspended_frame_ref.val;
|
|
ctx->suspended_frame_ref.val = JS_NULL;
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
|
|
env = fn->u.cell.env_record;
|
|
pc = ctx->suspended_pc;
|
|
result = JS_NULL;
|
|
#ifdef HAVE_ASAN
|
|
__asan_js_ctx = ctx;
|
|
#endif
|
|
goto vm_dispatch;
|
|
}
|
|
|
|
{
|
|
/* Normal path: set up a new call */
|
|
/* Protect env and outer_frame from GC — alloc_frame_register can trigger
|
|
collection which moves heap objects, invalidating stack-local copies */
|
|
JSGCRef env_gc, of_gc;
|
|
JS_PushGCRef(ctx, &env_gc);
|
|
env_gc.val = env;
|
|
JS_PushGCRef(ctx, &of_gc);
|
|
of_gc.val = outer_frame;
|
|
|
|
/* Protect argv and this_obj from GC using JSGCRef.
|
|
alloc_frame_register and js_new_register_function can trigger GC. */
|
|
int nargs_copy = (argc < code->arity) ? argc : code->arity;
|
|
JSGCRef this_gc;
|
|
JS_PushGCRef(ctx, &this_gc);
|
|
this_gc.val = this_obj;
|
|
JSGCRef arg_gcs[nargs_copy > 0 ? nargs_copy : 1];
|
|
for (int i = 0; i < nargs_copy; i++) {
|
|
JS_PushGCRef(ctx, &arg_gcs[i]);
|
|
arg_gcs[i].val = argv[i];
|
|
}
|
|
|
|
/* Allocate initial frame */
|
|
frame = alloc_frame_register(ctx, code->nr_slots);
|
|
if (!frame) {
|
|
for (int i = nargs_copy - 1; i >= 0; i--) JS_PopGCRef(ctx, &arg_gcs[i]);
|
|
JS_PopGCRef(ctx, &this_gc);
|
|
JS_PopGCRef(ctx, &of_gc);
|
|
JS_PopGCRef(ctx, &env_gc);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* Protect frame from GC */
|
|
JS_AddGCRef(ctx, &frame_ref);
|
|
frame_ref.val = JS_MKPTR(frame);
|
|
#ifdef HAVE_ASAN
|
|
__asan_js_ctx = ctx;
|
|
#endif
|
|
|
|
/* Setup initial frame — wrap top-level code in a function object so that
|
|
returning from a called register function can read code/env from frame */
|
|
JSValue top_fn = js_new_register_function(ctx, code, env_gc.val, of_gc.val);
|
|
env = env_gc.val; /* refresh — GC may have moved env during allocation */
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
frame->function = top_fn;
|
|
frame->caller = JS_NewInt32(ctx, 0); /* sentinel: active top-level, not eligible for GC shortening */
|
|
frame->slots[0] = this_gc.val; /* slot 0 = this */
|
|
|
|
/* Copy arguments from GC-safe refs */
|
|
for (int i = 0; i < nargs_copy; i++) {
|
|
frame->slots[1 + i] = arg_gcs[i].val;
|
|
}
|
|
for (int i = nargs_copy - 1; i >= 0; i--) JS_PopGCRef(ctx, &arg_gcs[i]);
|
|
JS_PopGCRef(ctx, &this_gc);
|
|
JS_PopGCRef(ctx, &of_gc);
|
|
JS_PopGCRef(ctx, &env_gc);
|
|
|
|
pc = code->entry_point;
|
|
result = JS_NULL;
|
|
|
|
/* Fire trace hook for top-level register function entry */
|
|
if (unlikely(ctx->trace_hook) && (ctx->trace_type & JS_HOOK_CALL)) {
|
|
js_debug dbg = {0};
|
|
if (code->name_cstr)
|
|
snprintf(dbg.name, sizeof(dbg.name), "%s", code->name_cstr);
|
|
if (code->filename_cstr)
|
|
snprintf(dbg.filename, sizeof(dbg.filename), "%s", code->filename_cstr);
|
|
if (code->line_table)
|
|
dbg.line = code->line_table[code->entry_point].line;
|
|
dbg.param_n = code->arity;
|
|
dbg.unique = (int)(uintptr_t)code;
|
|
ctx->trace_hook(ctx, JS_HOOK_CALL, &dbg, ctx->trace_data);
|
|
}
|
|
} /* end normal path block */
|
|
|
|
vm_dispatch:
|
|
/* Execution loop — 32-bit instruction dispatch */
|
|
for (;;) {
|
|
#ifndef NDEBUG
|
|
if (pc >= code->instr_count) {
|
|
fprintf(stderr, "mach VM: pc %u overran code->instr_count %u (missing RETURN/RETNIL?)\n",
|
|
pc, code->instr_count);
|
|
result = JS_RaiseDisrupt(ctx, "pc overrun");
|
|
goto done;
|
|
}
|
|
#endif
|
|
|
|
MachInstr32 instr;
|
|
int op, a, b, c;
|
|
|
|
#ifdef __GNUC__
|
|
/* Computed goto dispatch — each opcode gets its own branch predictor entry */
|
|
/* Use a macro to generate consistent dispatch table entries and labels */
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Woverride-init"
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic ignored "-Winitializer-overrides"
|
|
#endif
|
|
#define DT(x) [x] = &&op_##x
|
|
static const void *dispatch_table[256] = {
|
|
[0 ... 255] = &&op_DEFAULT,
|
|
DT(MACH_LOADK), DT(MACH_LOADI),
|
|
DT(MACH_LOADNULL), DT(MACH_LOADTRUE),
|
|
DT(MACH_LOADFALSE), DT(MACH_MOVE),
|
|
DT(MACH_ADD), DT(MACH_SUB),
|
|
DT(MACH_MUL), DT(MACH_DIV),
|
|
DT(MACH_MOD), DT(MACH_POW),
|
|
DT(MACH_NEG),
|
|
DT(MACH_REMAINDER), DT(MACH_MAX),
|
|
DT(MACH_MIN), DT(MACH_ABS),
|
|
DT(MACH_SIGN), DT(MACH_FRACTION),
|
|
DT(MACH_INTEGER), DT(MACH_FLOOR),
|
|
DT(MACH_CEILING), DT(MACH_ROUND),
|
|
DT(MACH_TRUNC),
|
|
DT(MACH_EQ), DT(MACH_NEQ),
|
|
DT(MACH_LT), DT(MACH_LE),
|
|
DT(MACH_GT), DT(MACH_GE),
|
|
DT(MACH_LNOT), DT(MACH_BNOT),
|
|
DT(MACH_BAND), DT(MACH_BOR),
|
|
DT(MACH_BXOR), DT(MACH_SHL),
|
|
DT(MACH_SHR), DT(MACH_USHR),
|
|
DT(MACH_GETFIELD), DT(MACH_SETFIELD),
|
|
DT(MACH_GETINDEX), DT(MACH_SETINDEX),
|
|
DT(MACH_GETNAME), DT(MACH_GETINTRINSIC),
|
|
DT(MACH_GETENV),
|
|
DT(MACH_GETUP), DT(MACH_SETUP),
|
|
DT(MACH_JMP), DT(MACH_JMPTRUE),
|
|
DT(MACH_JMPFALSE), DT(MACH_JMPNULL),
|
|
DT(MACH_RETURN), DT(MACH_RETNIL),
|
|
DT(MACH_NEWOBJECT), DT(MACH_NEWARRAY),
|
|
DT(MACH_CLOSURE),
|
|
DT(MACH_THROW), DT(MACH_PUSH), DT(MACH_POP),
|
|
DT(MACH_DELETE), DT(MACH_DELETEINDEX),
|
|
DT(MACH_HASPROP), DT(MACH_REGEXP),
|
|
DT(MACH_EQ_TOL), DT(MACH_NEQ_TOL),
|
|
DT(MACH_NOP),
|
|
DT(MACH_CONCAT), DT(MACH_STONE_TEXT),
|
|
DT(MACH_IS_IDENTICAL),
|
|
DT(MACH_IS_INT), DT(MACH_IS_NUM),
|
|
DT(MACH_IS_TEXT), DT(MACH_IS_BOOL),
|
|
DT(MACH_IS_NULL),
|
|
DT(MACH_NOT), DT(MACH_AND), DT(MACH_OR),
|
|
DT(MACH_BITNOT), DT(MACH_BITAND),
|
|
DT(MACH_BITOR), DT(MACH_BITXOR),
|
|
DT(MACH_LOAD_FIELD), DT(MACH_STORE_FIELD),
|
|
DT(MACH_LOAD_INDEX), DT(MACH_STORE_INDEX),
|
|
DT(MACH_LOAD_DYNAMIC), DT(MACH_STORE_DYNAMIC),
|
|
DT(MACH_NEWRECORD),
|
|
DT(MACH_FRAME), DT(MACH_SETARG),
|
|
DT(MACH_INVOKE), DT(MACH_GOFRAME),
|
|
DT(MACH_GOINVOKE),
|
|
DT(MACH_JMPNOTNULL),
|
|
DT(MACH_DISRUPT),
|
|
DT(MACH_IN),
|
|
DT(MACH_IS_ARRAY), DT(MACH_IS_FUNC),
|
|
DT(MACH_IS_RECORD), DT(MACH_IS_STONE),
|
|
DT(MACH_LENGTH), DT(MACH_IS_PROXY),
|
|
DT(MACH_IS_BLOB), DT(MACH_IS_DATA),
|
|
DT(MACH_IS_TRUE), DT(MACH_IS_FALSE),
|
|
DT(MACH_IS_FIT), DT(MACH_IS_CHAR),
|
|
DT(MACH_IS_DIGIT), DT(MACH_IS_LETTER),
|
|
DT(MACH_IS_LOWER), DT(MACH_IS_UPPER),
|
|
DT(MACH_IS_WS), DT(MACH_IS_ACTOR),
|
|
DT(MACH_APPLY),
|
|
DT(MACH_WARYTRUE), DT(MACH_WARYFALSE), DT(MACH_JMPEMPTY),
|
|
};
|
|
#pragma GCC diagnostic pop
|
|
#undef DT
|
|
#define VM_DECODE() do { \
|
|
ctx->reg_current_frame = frame_ref.val; \
|
|
ctx->current_register_pc = pc; \
|
|
instr = code->instructions[pc++]; \
|
|
op = MACH_GET_OP(instr); \
|
|
a = MACH_GET_A(instr); \
|
|
b = MACH_GET_B(instr); \
|
|
c = MACH_GET_C(instr); \
|
|
} while(0)
|
|
#define VM_DISPATCH() do { VM_DECODE(); goto *dispatch_table[op]; } while(0)
|
|
/* VM_CASE: dual-label — goto target for computed goto + case for switch fallback */
|
|
#define VM_CASE(x) op_##x: case x
|
|
#define VM_DEFAULT op_DEFAULT: default
|
|
#define VM_BREAK() VM_DISPATCH()
|
|
#else
|
|
#define VM_DECODE() do { \
|
|
ctx->reg_current_frame = frame_ref.val; \
|
|
ctx->current_register_pc = pc; \
|
|
instr = code->instructions[pc++]; \
|
|
op = MACH_GET_OP(instr); \
|
|
a = MACH_GET_A(instr); \
|
|
b = MACH_GET_B(instr); \
|
|
c = MACH_GET_C(instr); \
|
|
} while(0)
|
|
#define VM_DISPATCH() do { VM_DECODE(); } while(0)
|
|
#define VM_CASE(x) case x
|
|
#define VM_DEFAULT default
|
|
#define VM_BREAK() break
|
|
#endif
|
|
|
|
VM_DECODE();
|
|
#ifdef __GNUC__
|
|
goto *dispatch_table[op];
|
|
#endif
|
|
switch (op) {
|
|
VM_CASE(MACH_NOP):
|
|
VM_BREAK();
|
|
|
|
VM_CASE(MACH_LOADK): {
|
|
int bx = MACH_GET_Bx(instr);
|
|
|
|
if (bx < (int)code->cpool_count)
|
|
frame->slots[a] = code->cpool[bx];
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_LOADI):
|
|
|
|
frame->slots[a] = JS_NewInt32(ctx, MACH_GET_sBx(instr));
|
|
VM_BREAK();
|
|
|
|
VM_CASE(MACH_LOADNULL):
|
|
|
|
frame->slots[a] = JS_NULL;
|
|
VM_BREAK();
|
|
|
|
VM_CASE(MACH_LOADTRUE):
|
|
|
|
frame->slots[a] = JS_TRUE;
|
|
VM_BREAK();
|
|
|
|
VM_CASE(MACH_LOADFALSE):
|
|
|
|
frame->slots[a] = JS_FALSE;
|
|
VM_BREAK();
|
|
|
|
VM_CASE(MACH_MOVE):
|
|
|
|
frame->slots[a] = frame->slots[b];
|
|
VM_BREAK();
|
|
|
|
/* Arithmetic — mcode guarantees both operands are numbers */
|
|
VM_CASE(MACH_ADD): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
int64_t r = (int64_t)JS_VALUE_GET_INT(left) + (int64_t)JS_VALUE_GET_INT(right);
|
|
frame->slots[a] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
|
|
} else {
|
|
double da, db, r;
|
|
JS_ToFloat64(ctx, &da, left);
|
|
JS_ToFloat64(ctx, &db, right);
|
|
r = da + db;
|
|
frame->slots[a] = !isfinite(r) ? JS_NULL : JS_NewFloat64(ctx, r);
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_SUB): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
int64_t r = (int64_t)JS_VALUE_GET_INT(left) - (int64_t)JS_VALUE_GET_INT(right);
|
|
frame->slots[a] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
|
|
} else {
|
|
double da, db, r;
|
|
JS_ToFloat64(ctx, &da, left);
|
|
JS_ToFloat64(ctx, &db, right);
|
|
r = da - db;
|
|
frame->slots[a] = !isfinite(r) ? JS_NULL : JS_NewFloat64(ctx, r);
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_MUL): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
int64_t r = (int64_t)JS_VALUE_GET_INT(left) * (int64_t)JS_VALUE_GET_INT(right);
|
|
frame->slots[a] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
|
|
} else {
|
|
double da, db, r;
|
|
JS_ToFloat64(ctx, &da, left);
|
|
JS_ToFloat64(ctx, &db, right);
|
|
r = da * db;
|
|
frame->slots[a] = !isfinite(r) ? JS_NULL : JS_NewFloat64(ctx, r);
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_DIV): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
int32_t ia = JS_VALUE_GET_INT(left), ib = JS_VALUE_GET_INT(right);
|
|
if (ib != 0 && ia % ib == 0)
|
|
frame->slots[a] = JS_NewInt32(ctx, ia / ib);
|
|
else if (ib != 0)
|
|
frame->slots[a] = JS_NewFloat64(ctx, (double)ia / (double)ib);
|
|
else
|
|
frame->slots[a] = JS_NULL;
|
|
} else {
|
|
double da, db, r;
|
|
JS_ToFloat64(ctx, &da, left);
|
|
JS_ToFloat64(ctx, &db, right);
|
|
if (db == 0.0) { frame->slots[a] = JS_NULL; }
|
|
else {
|
|
r = da / db;
|
|
frame->slots[a] = !isfinite(r) ? JS_NULL : JS_NewFloat64(ctx, r);
|
|
}
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_MOD): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
double da, db;
|
|
if (mach_get_number(left, &da) != 0 || mach_get_number(right, &db) != 0 || db == 0.0 ||
|
|
isnan(da) || isnan(db)) {
|
|
frame->slots[a] = JS_NULL;
|
|
} else {
|
|
if (da == 0.0) {
|
|
frame->slots[a] = JS_NewFloat64(ctx, 0.0);
|
|
} else {
|
|
frame->slots[a] = JS_NewFloat64(ctx, da - (db * floor(da / db)));
|
|
}
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_POW): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
double r = pow((double)JS_VALUE_GET_INT(left), (double)JS_VALUE_GET_INT(right));
|
|
if (!isfinite(r)) frame->slots[a] = JS_NULL;
|
|
else if (r >= INT32_MIN && r <= INT32_MAX && r == (int32_t)r)
|
|
frame->slots[a] = JS_NewInt32(ctx, (int32_t)r);
|
|
else
|
|
frame->slots[a] = JS_NewFloat64(ctx, r);
|
|
} else {
|
|
double da, db, r;
|
|
JS_ToFloat64(ctx, &da, left);
|
|
JS_ToFloat64(ctx, &db, right);
|
|
r = pow(da, db);
|
|
frame->slots[a] = (!isfinite(r) && isfinite(da) && isfinite(db)) ? JS_NULL : JS_NewFloat64(ctx, r);
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_REMAINDER): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
double da, db;
|
|
if (mach_get_number(left, &da) != 0 || mach_get_number(right, &db) != 0 || db == 0.0) {
|
|
frame->slots[a] = JS_NULL;
|
|
} else {
|
|
frame->slots[a] = JS_NewFloat64(ctx, da - (trunc(da / db) * db));
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_MAX): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
double da, db;
|
|
if (mach_get_number(left, &da) != 0 || mach_get_number(right, &db) != 0) {
|
|
frame->slots[a] = JS_NULL;
|
|
} else {
|
|
frame->slots[a] = JS_NewFloat64(ctx, da > db ? da : db);
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_MIN): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
double da, db;
|
|
if (mach_get_number(left, &da) != 0 || mach_get_number(right, &db) != 0) {
|
|
frame->slots[a] = JS_NULL;
|
|
} else {
|
|
frame->slots[a] = JS_NewFloat64(ctx, da < db ? da : db);
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_ABS): {
|
|
JSValue v = frame->slots[b];
|
|
double d;
|
|
if (mach_get_number(v, &d) != 0) {
|
|
frame->slots[a] = JS_NULL;
|
|
} else {
|
|
frame->slots[a] = JS_NewFloat64(ctx, fabs(d));
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_SIGN): {
|
|
JSValue v = frame->slots[b];
|
|
double d;
|
|
if (mach_get_number(v, &d) != 0) {
|
|
frame->slots[a] = JS_NULL;
|
|
} else if (d < 0) {
|
|
frame->slots[a] = JS_NewInt32(ctx, -1);
|
|
} else if (d > 0) {
|
|
frame->slots[a] = JS_NewInt32(ctx, 1);
|
|
} else {
|
|
frame->slots[a] = JS_NewInt32(ctx, 0);
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_FRACTION): {
|
|
JSValue v = frame->slots[b];
|
|
double d;
|
|
if (mach_get_number(v, &d) != 0) {
|
|
frame->slots[a] = JS_NULL;
|
|
} else {
|
|
frame->slots[a] = JS_NewFloat64(ctx, d - trunc(d));
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_INTEGER): {
|
|
JSValue v = frame->slots[b];
|
|
double d;
|
|
if (mach_get_number(v, &d) != 0) {
|
|
frame->slots[a] = JS_NULL;
|
|
} else {
|
|
frame->slots[a] = JS_NewFloat64(ctx, trunc(d));
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_FLOOR):
|
|
VM_CASE(MACH_CEILING):
|
|
VM_CASE(MACH_ROUND):
|
|
VM_CASE(MACH_TRUNC): {
|
|
JSValue v = frame->slots[b];
|
|
JSValue pval = frame->slots[c];
|
|
double d, r;
|
|
int32_t place = 0;
|
|
if (mach_get_number(v, &d) != 0) {
|
|
frame->slots[a] = JS_NULL;
|
|
VM_BREAK();
|
|
}
|
|
if (!JS_IsNull(pval) && mach_get_place(ctx, pval, &place) != 0) {
|
|
frame->slots[a] = JS_NULL;
|
|
VM_BREAK();
|
|
}
|
|
if (op == MACH_FLOOR) {
|
|
r = mach_apply_place(d, place, floor);
|
|
} else if (op == MACH_CEILING) {
|
|
r = mach_apply_place(d, place, ceil);
|
|
} else if (op == MACH_ROUND) {
|
|
r = mach_apply_place(d, place, round);
|
|
} else {
|
|
r = mach_apply_place(d, place, trunc);
|
|
}
|
|
frame->slots[a] = JS_NewFloat64(ctx, r);
|
|
VM_BREAK();
|
|
}
|
|
|
|
/* Comparison — inline integer fast paths */
|
|
VM_CASE(MACH_EQ): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) == JS_VALUE_GET_INT(right));
|
|
} else {
|
|
JSValue res = reg_vm_binop(ctx, MACH_EQ, left, right);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_NEQ): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) != JS_VALUE_GET_INT(right));
|
|
} else {
|
|
JSValue res = reg_vm_binop(ctx, MACH_NEQ, left, right);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_LT): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) < JS_VALUE_GET_INT(right));
|
|
} else {
|
|
JSValue res = reg_vm_binop(ctx, MACH_LT, left, right);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_LE): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) <= JS_VALUE_GET_INT(right));
|
|
} else {
|
|
JSValue res = reg_vm_binop(ctx, MACH_LE, left, right);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_GT): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) > JS_VALUE_GET_INT(right));
|
|
} else {
|
|
JSValue res = reg_vm_binop(ctx, MACH_GT, left, right);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_GE): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) >= JS_VALUE_GET_INT(right));
|
|
} else {
|
|
JSValue res = reg_vm_binop(ctx, MACH_GE, left, right);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
/* Bitwise — inline integer fast paths */
|
|
VM_CASE(MACH_BAND): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
frame->slots[a] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) & JS_VALUE_GET_INT(right));
|
|
} else {
|
|
JSValue res = reg_vm_binop(ctx, MACH_BAND, left, right);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_BOR): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
frame->slots[a] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) | JS_VALUE_GET_INT(right));
|
|
} else {
|
|
JSValue res = reg_vm_binop(ctx, MACH_BOR, left, right);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_BXOR): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
frame->slots[a] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) ^ JS_VALUE_GET_INT(right));
|
|
} else {
|
|
JSValue res = reg_vm_binop(ctx, MACH_BXOR, left, right);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_SHL): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
frame->slots[a] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) << (JS_VALUE_GET_INT(right) & 31));
|
|
} else {
|
|
JSValue res = reg_vm_binop(ctx, MACH_SHL, left, right);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_SHR): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
frame->slots[a] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) >> (JS_VALUE_GET_INT(right) & 31));
|
|
} else {
|
|
JSValue res = reg_vm_binop(ctx, MACH_SHR, left, right);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_USHR): {
|
|
JSValue left = frame->slots[b], right = frame->slots[c];
|
|
if (JS_VALUE_IS_BOTH_INT(left, right)) {
|
|
frame->slots[a] = JS_NewInt32(ctx, (uint32_t)JS_VALUE_GET_INT(left) >> (JS_VALUE_GET_INT(right) & 31));
|
|
} else {
|
|
JSValue res = reg_vm_binop(ctx, MACH_USHR, left, right);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_EQ_TOL):
|
|
VM_CASE(MACH_NEQ_TOL): {
|
|
/* A=dest, B=base, C=3; args in R(B), R(B+1), R(B+2) */
|
|
JSValue left = frame->slots[b];
|
|
JSValue right = frame->slots[b + 1];
|
|
JSValue tol = frame->slots[b + 2];
|
|
BOOL is_eq_op = (op == MACH_EQ_TOL);
|
|
if (JS_IsNumber(left) && JS_IsNumber(right) && JS_IsNumber(tol)) {
|
|
double da, db, dt;
|
|
JS_ToFloat64(ctx, &da, left);
|
|
JS_ToFloat64(ctx, &db, right);
|
|
JS_ToFloat64(ctx, &dt, tol);
|
|
BOOL eq = fabs(da - db) <= dt;
|
|
frame->slots[a] = JS_NewBool(ctx, is_eq_op ? eq : !eq);
|
|
} else if (mist_is_text(left) && mist_is_text(right) && JS_VALUE_GET_TAG(tol) == JS_TAG_BOOL && JS_VALUE_GET_BOOL(tol)) {
|
|
BOOL eq = js_string_compare_value_nocase(ctx, left, right) == 0;
|
|
frame->slots[a] = JS_NewBool(ctx, is_eq_op ? eq : !eq);
|
|
} else {
|
|
/* Fall through to standard eq/neq */
|
|
JSValue res = reg_vm_binop(ctx, is_eq_op ? MACH_EQ : MACH_NEQ, left, right);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) { goto disrupt; }
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_NEG): {
|
|
JSValue v = frame->slots[b];
|
|
if (JS_IsInt(v)) {
|
|
int32_t i = JS_VALUE_GET_INT(v);
|
|
if (i == INT32_MIN)
|
|
frame->slots[a] = JS_NewFloat64(ctx, -(double)i);
|
|
else
|
|
frame->slots[a] = JS_NewInt32(ctx, -i);
|
|
} else {
|
|
double d;
|
|
JS_ToFloat64(ctx, &d, v);
|
|
frame->slots[a] = JS_NewFloat64(ctx, -d);
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_LNOT): {
|
|
int bval = JS_ToBool(ctx, frame->slots[b]);
|
|
frame->slots[a] = JS_NewBool(ctx, !bval);
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_BNOT): {
|
|
int32_t i;
|
|
JS_ToInt32(ctx, &i, frame->slots[b]);
|
|
frame->slots[a] = JS_NewInt32(ctx, ~i);
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_GETFIELD): {
|
|
JSValue obj = frame->slots[b];
|
|
JSValue key = code->cpool[c];
|
|
/* Non-proxy functions (arity != 2) can't have properties read */
|
|
if (mist_is_function(obj)) {
|
|
JSFunction *fn_chk = JS_VALUE_GET_FUNCTION(obj);
|
|
if (fn_chk->length != 2) {
|
|
JS_RaiseDisrupt(ctx, "cannot read property of non-proxy function");
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
goto disrupt;
|
|
}
|
|
}
|
|
JSValue val = JS_GetProperty(ctx, obj, key);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(val)) goto disrupt;
|
|
frame->slots[a] = val;
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_SETFIELD): {
|
|
/* R(A)[K(B)] = R(C) */
|
|
JSValue obj = frame->slots[a];
|
|
JSValue key = code->cpool[b];
|
|
JSValue val = frame->slots[c];
|
|
int ret = JS_SetProperty(ctx, obj, key, val);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (ret < 0) goto disrupt;
|
|
mach_resolve_forward(&frame->slots[a]);
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_GETINDEX): {
|
|
/* R(A) = R(B)[R(C)] — mcode guarantees R(C) is int */
|
|
JSValue obj = frame->slots[b];
|
|
JSValue val = JS_GetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[c]));
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(val)) goto disrupt;
|
|
frame->slots[a] = val;
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_SETINDEX): {
|
|
/* R(A)[R(B)] = R(C) — mcode guarantees R(B) is int */
|
|
JSValue obj = frame->slots[a];
|
|
JSValue val = frame->slots[c];
|
|
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(r)) goto disrupt;
|
|
mach_resolve_forward(&frame->slots[a]);
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_GETINTRINSIC): {
|
|
int bx = MACH_GET_Bx(instr);
|
|
JSValue key = code->cpool[bx];
|
|
JSValue val = JS_GetProperty(ctx, ctx->global_obj, key);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsNull(val)) {
|
|
int has = JS_HasProperty(ctx, ctx->global_obj, key);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (has <= 0) {
|
|
char buf[128];
|
|
JS_KeyGetStr(ctx, buf, sizeof(buf), key);
|
|
JS_RaiseDisrupt(ctx, "'%s' is not defined", buf);
|
|
goto disrupt;
|
|
}
|
|
}
|
|
frame->slots[a] = val;
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_GETENV): {
|
|
/* Read env fresh from frame->function — C local env can go stale after GC */
|
|
int bx = MACH_GET_Bx(instr);
|
|
JSValue key = code->cpool[bx];
|
|
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.cell.env_record;
|
|
JSValue val = JS_GetProperty(ctx, cur_env, key);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
frame->slots[a] = val;
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_GETNAME): {
|
|
/* Runtime fallback: try env then global (should not appear in linked code) */
|
|
int bx = MACH_GET_Bx(instr);
|
|
JSValue key = code->cpool[bx];
|
|
JSValue val = JS_NULL;
|
|
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.cell.env_record;
|
|
if (!JS_IsNull(cur_env)) {
|
|
val = JS_GetProperty(ctx, cur_env, key);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
}
|
|
if (JS_IsNull(val) || JS_IsException(val)) {
|
|
val = JS_GetProperty(ctx, ctx->global_obj, key);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
}
|
|
frame->slots[a] = val;
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_GETUP): {
|
|
/* R(A) = outer_frame[B].slots[C] — walk lexical scope chain */
|
|
int depth = b;
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
|
|
assert(depth > 0);
|
|
assert(target != NULL);
|
|
for (int d = 1; d < depth; d++) {
|
|
fn = JS_VALUE_GET_FUNCTION(target->function);
|
|
JSFrameRegister *next = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
|
|
assert(next != NULL);
|
|
target = next;
|
|
}
|
|
stone_mutable_text(target->slots[c]);
|
|
frame->slots[a] = target->slots[c];
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_SETUP): {
|
|
/* outer_frame[B].slots[C] = R(A) — walk lexical scope chain */
|
|
int depth = b;
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
|
|
assert(depth > 0);
|
|
assert(target != NULL);
|
|
for (int d = 1; d < depth; d++) {
|
|
fn = JS_VALUE_GET_FUNCTION(target->function);
|
|
target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
|
|
assert(target != NULL);
|
|
}
|
|
assert((unsigned)c < objhdr_cap56(target->header));
|
|
target->slots[c] = frame->slots[a];
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_JMP): {
|
|
int offset = MACH_GET_sJ(instr);
|
|
pc = (uint32_t)((int32_t)pc + offset);
|
|
if (offset < 0) {
|
|
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
|
|
if (pf == 2) {
|
|
result = JS_RaiseDisrupt(ctx, "interrupted");
|
|
goto done;
|
|
}
|
|
if (pf == 1) {
|
|
if (ctx->vm_call_depth > 0)
|
|
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
|
|
else
|
|
goto suspend;
|
|
}
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_JMPTRUE): {
|
|
if (frame->slots[a] == JS_TRUE) {
|
|
int offset = MACH_GET_sBx(instr);
|
|
pc = (uint32_t)((int32_t)pc + offset);
|
|
if (offset < 0) {
|
|
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
|
|
if (pf == 2) {
|
|
result = JS_RaiseDisrupt(ctx, "interrupted");
|
|
goto done;
|
|
}
|
|
if (pf == 1) {
|
|
if (ctx->vm_call_depth > 0)
|
|
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
|
|
else
|
|
goto suspend;
|
|
}
|
|
}
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_JMPFALSE): {
|
|
if (frame->slots[a] == JS_FALSE) {
|
|
int offset = MACH_GET_sBx(instr);
|
|
pc = (uint32_t)((int32_t)pc + offset);
|
|
if (offset < 0) {
|
|
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
|
|
if (pf == 2) {
|
|
result = JS_RaiseDisrupt(ctx, "interrupted");
|
|
goto done;
|
|
}
|
|
if (pf == 1) {
|
|
if (ctx->vm_call_depth > 0)
|
|
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
|
|
else
|
|
goto suspend;
|
|
}
|
|
}
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_JMPNULL): {
|
|
if (JS_IsNull(frame->slots[a])) {
|
|
int offset = MACH_GET_sBx(instr);
|
|
pc = (uint32_t)((int32_t)pc + offset);
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_RETURN):
|
|
stone_mutable_text(frame->slots[a]);
|
|
result = frame->slots[a];
|
|
if (!JS_IsPtr(frame->caller)) goto done;
|
|
if (unlikely(ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET))
|
|
ctx->trace_hook(ctx, JS_HOOK_RET, NULL, ctx->trace_data);
|
|
{
|
|
#ifdef VALIDATE_GC
|
|
const char *callee_name = "?";
|
|
const char *callee_file = "?";
|
|
{
|
|
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
if (callee_fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code) {
|
|
if (JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code->name_cstr) callee_name = JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code->name_cstr;
|
|
if (JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code->filename_cstr) callee_file = JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code->filename_cstr;
|
|
}
|
|
}
|
|
#endif
|
|
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
|
frame->caller = JS_NULL;
|
|
frame = caller;
|
|
frame_ref.val = JS_MKPTR(frame);
|
|
int ret_info = JS_VALUE_GET_INT(frame->address);
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
|
|
env = fn->u.cell.env_record;
|
|
pc = ret_info >> 16;
|
|
int ret_slot = ret_info & 0xFFFF;
|
|
if (ret_slot != 0xFFFF) {
|
|
|
|
#ifdef VALIDATE_GC
|
|
if (JS_IsPtr(result)) {
|
|
void *rp = JS_VALUE_GET_PTR(result);
|
|
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
|
|
if (!is_ct_ptr(ctx, rp))
|
|
fprintf(stderr, "VALIDATE_GC: stale RETURN into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u callee=%s (%s) caller=%s (%s)\n",
|
|
ret_slot, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc,
|
|
callee_name, callee_file,
|
|
code->name_cstr ? code->name_cstr : "?",
|
|
code->filename_cstr ? code->filename_cstr : "?");
|
|
}
|
|
}
|
|
#endif
|
|
frame->slots[ret_slot] = result;
|
|
}
|
|
}
|
|
VM_BREAK();
|
|
|
|
VM_CASE(MACH_RETNIL):
|
|
result = JS_NULL;
|
|
if (!JS_IsPtr(frame->caller)) goto done;
|
|
if (unlikely(ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET))
|
|
ctx->trace_hook(ctx, JS_HOOK_RET, NULL, ctx->trace_data);
|
|
{
|
|
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
|
frame->caller = JS_NULL;
|
|
frame = caller;
|
|
frame_ref.val = JS_MKPTR(frame);
|
|
int ret_info = JS_VALUE_GET_INT(frame->address);
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
|
|
env = fn->u.cell.env_record;
|
|
pc = ret_info >> 16;
|
|
int ret_slot = ret_info & 0xFFFF;
|
|
if (ret_slot != 0xFFFF) {
|
|
|
|
frame->slots[ret_slot] = result;
|
|
}
|
|
}
|
|
VM_BREAK();
|
|
|
|
VM_CASE(MACH_NEWOBJECT): {
|
|
JSValue obj = JS_NewObject(ctx);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(obj)) { goto disrupt; }
|
|
frame->slots[a] = obj;
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_NEWARRAY): {
|
|
JSValue arr = JS_NewArrayCap(ctx, b);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(arr)) { goto disrupt; }
|
|
frame->slots[a] = arr;
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_CLOSURE): {
|
|
int bx = MACH_GET_Bx(instr);
|
|
|
|
if ((uint32_t)bx < code->func_count) {
|
|
JSCodeRegister *fn_code = code->functions[bx];
|
|
/* Read env fresh from frame->function — C local can be stale */
|
|
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.cell.env_record;
|
|
JSValue fn_val = js_new_register_function(ctx, fn_code, cur_env, frame_ref.val);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
frame->slots[a] = fn_val;
|
|
} else {
|
|
frame->slots[a] = JS_NULL;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_PUSH): {
|
|
/* push R(B) onto array R(A) — mcode guarantees R(A) is array */
|
|
JSValue arr = frame->slots[a];
|
|
JSValue val = frame->slots[b];
|
|
JSGCRef arr_gc;
|
|
JS_PushGCRef(ctx, &arr_gc);
|
|
arr_gc.val = arr;
|
|
int rc = JS_ArrayPush(ctx, &arr_gc.val, val);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
JS_PopGCRef(ctx, &arr_gc);
|
|
if (rc < 0) goto disrupt;
|
|
if (arr_gc.val != arr) frame->slots[a] = arr_gc.val;
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_POP): {
|
|
/* R(A) = pop last element from array R(B) — mcode guarantees R(B) is array */
|
|
JSValue arr = frame->slots[b];
|
|
JSValue val = JS_ArrayPop(ctx, arr);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(val)) goto disrupt;
|
|
frame->slots[a] = val;
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_DELETE): {
|
|
JSValue obj = frame->slots[b];
|
|
JSValue key = code->cpool[c];
|
|
int ret = JS_DeleteProperty(ctx, obj, key);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (ret < 0) goto disrupt;
|
|
frame->slots[a] = JS_NewBool(ctx, ret >= 0);
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_DELETEINDEX): {
|
|
JSValue obj = frame->slots[b];
|
|
JSValue key = frame->slots[c];
|
|
int ret = JS_DeleteProperty(ctx, obj, key);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (ret < 0) goto disrupt;
|
|
frame->slots[a] = JS_NewBool(ctx, ret >= 0);
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_HASPROP): {
|
|
JSValue obj = frame->slots[b];
|
|
JSValue key = frame->slots[c];
|
|
int has = JS_HasProperty(ctx, obj, key);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
frame->slots[a] = JS_NewBool(ctx, has > 0);
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_REGEXP): {
|
|
JSValue argv[2];
|
|
argv[0] = code->cpool[b]; /* pattern */
|
|
argv[1] = code->cpool[c]; /* flags */
|
|
JSValue re = js_regexp_constructor(ctx, JS_NULL, 2, argv);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(re)) goto disrupt;
|
|
frame->slots[a] = re;
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_THROW):
|
|
goto disrupt;
|
|
|
|
/* === New mcode-derived opcodes === */
|
|
|
|
/* Text concatenation — with in-place append fast path for s = s + x */
|
|
VM_CASE(MACH_CONCAT): {
|
|
if (a == b) {
|
|
/* Self-assign pattern: slot[a] = slot[a] + slot[c] */
|
|
JSValue left = frame->slots[a];
|
|
JSValue right = frame->slots[c];
|
|
/* Inline fast path: mutable heap text with enough capacity */
|
|
if (JS_IsPtr(left)) {
|
|
JSText *s = (JSText *)chase(left);
|
|
int slen = (int)s->length;
|
|
int rlen = js_string_value_len(right);
|
|
int cap = (int)objhdr_cap56(s->hdr);
|
|
if (objhdr_type(s->hdr) == OBJ_TEXT
|
|
&& !(s->hdr & OBJHDR_S_MASK)
|
|
&& slen + rlen <= cap) {
|
|
/* Append in-place — zero allocation, no GC possible */
|
|
for (int i = 0; i < rlen; i++)
|
|
string_put(s, slen + i, js_string_value_get(right, i));
|
|
s->length = slen + rlen;
|
|
VM_BREAK();
|
|
}
|
|
}
|
|
/* Slow path: allocate with growth factor, leave unstoned */
|
|
JSValue res = JS_ConcatStringGrow(ctx, frame->slots[b], frame->slots[c]);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
} else {
|
|
/* Different target: use existing exact-fit stoned path */
|
|
JSValue res = JS_ConcatString(ctx, frame->slots[b], frame->slots[c]);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(res)) goto disrupt;
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
/* Stone mutable text — compiler-emitted at escape points */
|
|
VM_CASE(MACH_STONE_TEXT):
|
|
stone_mutable_text(frame->slots[a]);
|
|
VM_BREAK();
|
|
|
|
/* Identity check */
|
|
VM_CASE(MACH_IS_IDENTICAL): {
|
|
JSValue va = JS_IsPtr(frame->slots[b]) ? JS_MKPTR(chase(frame->slots[b])) : frame->slots[b];
|
|
JSValue vb = JS_IsPtr(frame->slots[c]) ? JS_MKPTR(chase(frame->slots[c])) : frame->slots[c];
|
|
frame->slots[a] = JS_NewBool(ctx, va == vb);
|
|
VM_BREAK();
|
|
}
|
|
|
|
/* Type checks */
|
|
VM_CASE(MACH_IS_INT):
|
|
frame->slots[a] = JS_NewBool(ctx, JS_IsInt(frame->slots[b]));
|
|
VM_BREAK();
|
|
VM_CASE(MACH_IS_NUM):
|
|
frame->slots[a] = JS_NewBool(ctx, JS_IsNumber(frame->slots[b]));
|
|
VM_BREAK();
|
|
VM_CASE(MACH_IS_TEXT):
|
|
frame->slots[a] = JS_NewBool(ctx, mist_is_text(frame->slots[b]));
|
|
VM_BREAK();
|
|
VM_CASE(MACH_IS_BOOL):
|
|
frame->slots[a] = JS_NewBool(ctx, JS_IsBool(frame->slots[b]));
|
|
VM_BREAK();
|
|
VM_CASE(MACH_IS_NULL):
|
|
frame->slots[a] = JS_NewBool(ctx, JS_IsNull(frame->slots[b]));
|
|
VM_BREAK();
|
|
VM_CASE(MACH_IS_ARRAY):
|
|
frame->slots[a] = JS_NewBool(ctx, mist_is_array(frame->slots[b]));
|
|
VM_BREAK();
|
|
VM_CASE(MACH_IS_FUNC):
|
|
frame->slots[a] = JS_NewBool(ctx, mist_is_function(frame->slots[b]));
|
|
VM_BREAK();
|
|
VM_CASE(MACH_IS_RECORD):
|
|
frame->slots[a] = JS_NewBool(ctx, mist_is_record(frame->slots[b]));
|
|
VM_BREAK();
|
|
VM_CASE(MACH_IS_STONE):
|
|
frame->slots[a] = JS_NewBool(ctx, mist_is_stone(frame->slots[b]));
|
|
VM_BREAK();
|
|
VM_CASE(MACH_LENGTH): {
|
|
JSValue v = frame->slots[b];
|
|
if (mist_is_array(v)) {
|
|
JSArray *arr = JS_VALUE_GET_ARRAY(v);
|
|
frame->slots[a] = JS_NewInt32(ctx, (int32_t)arr->len);
|
|
} else if (MIST_IsImmediateASCII(v)) {
|
|
frame->slots[a] = JS_NewInt32(ctx, MIST_GetImmediateASCIILen(v));
|
|
} else {
|
|
/* fallback to C for text/blob/function (still a GC safepoint) */
|
|
JSValue res = JS_CellLength(ctx, v);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
frame->slots[a] = res;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_IS_PROXY): {
|
|
JSValue v = frame->slots[b];
|
|
int is_proxy = 0;
|
|
if (mist_is_function(v)) {
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(v);
|
|
is_proxy = (fn->length == 2);
|
|
}
|
|
frame->slots[a] = JS_NewBool(ctx, is_proxy);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_IS_BLOB):
|
|
frame->slots[a] = JS_NewBool(ctx, mist_is_blob(frame->slots[b]));
|
|
VM_BREAK();
|
|
VM_CASE(MACH_IS_DATA): {
|
|
JSValue v = frame->slots[b];
|
|
int result = 0;
|
|
if (mist_is_gc_object(v) && !mist_is_array(v)
|
|
&& !mist_is_function(v) && !mist_is_blob(v))
|
|
result = 1;
|
|
frame->slots[a] = JS_NewBool(ctx, result);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_IS_TRUE):
|
|
frame->slots[a] = JS_NewBool(ctx, frame->slots[b] == JS_TRUE);
|
|
VM_BREAK();
|
|
VM_CASE(MACH_IS_FALSE):
|
|
frame->slots[a] = JS_NewBool(ctx, frame->slots[b] == JS_FALSE);
|
|
VM_BREAK();
|
|
VM_CASE(MACH_IS_FIT): {
|
|
JSValue v = frame->slots[b];
|
|
int result = 0;
|
|
if (JS_IsInt(v)) {
|
|
result = 1;
|
|
} else if (JS_IsShortFloat(v)) {
|
|
double d = JS_VALUE_GET_FLOAT64(v);
|
|
result = (isfinite(d) && trunc(d) == d && fabs(d) <= 9007199254740992.0);
|
|
}
|
|
frame->slots[a] = JS_NewBool(ctx, result);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_IS_CHAR): {
|
|
JSValue v = frame->slots[b];
|
|
int result = 0;
|
|
if (MIST_IsImmediateASCII(v))
|
|
result = (MIST_GetImmediateASCIILen(v) == 1);
|
|
else if (mist_is_text(v))
|
|
result = (js_string_value_len(v) == 1);
|
|
frame->slots[a] = JS_NewBool(ctx, result);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_IS_DIGIT): {
|
|
JSValue v = frame->slots[b];
|
|
int result = 0;
|
|
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
|
|
int ch = MIST_GetImmediateASCIIChar(v, 0);
|
|
result = (ch >= '0' && ch <= '9');
|
|
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
|
|
uint32_t ch = js_string_value_get(v, 0);
|
|
result = (ch >= '0' && ch <= '9');
|
|
}
|
|
frame->slots[a] = JS_NewBool(ctx, result);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_IS_LETTER): {
|
|
JSValue v = frame->slots[b];
|
|
int result = 0;
|
|
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
|
|
int ch = MIST_GetImmediateASCIIChar(v, 0);
|
|
result = ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'));
|
|
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
|
|
uint32_t ch = js_string_value_get(v, 0);
|
|
result = ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'));
|
|
}
|
|
frame->slots[a] = JS_NewBool(ctx, result);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_IS_LOWER): {
|
|
JSValue v = frame->slots[b];
|
|
int result = 0;
|
|
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
|
|
int ch = MIST_GetImmediateASCIIChar(v, 0);
|
|
result = (ch >= 'a' && ch <= 'z');
|
|
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
|
|
uint32_t ch = js_string_value_get(v, 0);
|
|
result = (ch >= 'a' && ch <= 'z');
|
|
}
|
|
frame->slots[a] = JS_NewBool(ctx, result);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_IS_UPPER): {
|
|
JSValue v = frame->slots[b];
|
|
int result = 0;
|
|
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
|
|
int ch = MIST_GetImmediateASCIIChar(v, 0);
|
|
result = (ch >= 'A' && ch <= 'Z');
|
|
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
|
|
uint32_t ch = js_string_value_get(v, 0);
|
|
result = (ch >= 'A' && ch <= 'Z');
|
|
}
|
|
frame->slots[a] = JS_NewBool(ctx, result);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_IS_WS): {
|
|
JSValue v = frame->slots[b];
|
|
int result = 0;
|
|
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
|
|
int ch = MIST_GetImmediateASCIIChar(v, 0);
|
|
result = (ch == ' ' || ch == '\t' || ch == '\n'
|
|
|| ch == '\r' || ch == '\f' || ch == '\v');
|
|
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
|
|
uint32_t ch = js_string_value_get(v, 0);
|
|
result = (ch == ' ' || ch == '\t' || ch == '\n'
|
|
|| ch == '\r' || ch == '\f' || ch == '\v');
|
|
}
|
|
frame->slots[a] = JS_NewBool(ctx, result);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_IS_ACTOR): {
|
|
JSValue v = frame->slots[b];
|
|
int result = 0;
|
|
if (mist_is_record(v) && !JS_IsNull(ctx->actor_sym)) {
|
|
result = JS_HasPropertyKey(ctx, v, ctx->actor_sym) > 0;
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
}
|
|
frame->slots[a] = JS_NewBool(ctx, result);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_APPLY): {
|
|
/* A=dest, B=fn, C=arr_or_val */
|
|
JSValue fn_val = frame->slots[b];
|
|
JSValue arg_val = frame->slots[c];
|
|
if (!mist_is_function(fn_val)) {
|
|
frame->slots[a] = fn_val;
|
|
VM_BREAK();
|
|
}
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
|
|
JSValue ret;
|
|
ctx->reg_current_frame = frame_ref.val;
|
|
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
|
ctx->vm_call_depth++;
|
|
if (!mist_is_array(arg_val)) {
|
|
/* Non-array: use as single argument */
|
|
if (!mach_check_call_arity(ctx, fn, 1)) {
|
|
ctx->vm_call_depth--;
|
|
goto disrupt;
|
|
}
|
|
ret = JS_CallInternal(ctx, fn_val, JS_NULL, 1, &arg_val, 0);
|
|
} else {
|
|
JSArray *arr = JS_VALUE_GET_ARRAY(arg_val);
|
|
int len = arr->len;
|
|
if (!mach_check_call_arity(ctx, fn, len)) {
|
|
ctx->vm_call_depth--;
|
|
goto disrupt;
|
|
}
|
|
if (len == 0) {
|
|
ret = JS_CallInternal(ctx, fn_val, JS_NULL, 0, NULL, 0);
|
|
} else {
|
|
JSValue *args = alloca(sizeof(JSValue) * len);
|
|
for (int i = 0; i < len; i++)
|
|
args[i] = arr->values[i];
|
|
ret = JS_CallInternal(ctx, fn_val, JS_NULL, len, args, 0);
|
|
}
|
|
}
|
|
ctx->vm_call_depth--;
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
ctx->reg_current_frame = JS_NULL;
|
|
if (JS_IsException(ret)) goto disrupt;
|
|
frame->slots[a] = ret;
|
|
VM_BREAK();
|
|
}
|
|
/* Logical */
|
|
VM_CASE(MACH_NOT): {
|
|
int bval = JS_ToBool(ctx, frame->slots[b]);
|
|
frame->slots[a] = JS_NewBool(ctx, !bval);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_AND): {
|
|
JSValue left = frame->slots[b];
|
|
if (!JS_ToBool(ctx, left))
|
|
frame->slots[a] = left;
|
|
else
|
|
frame->slots[a] = frame->slots[c];
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_OR): {
|
|
JSValue left = frame->slots[b];
|
|
if (JS_ToBool(ctx, left))
|
|
frame->slots[a] = left;
|
|
else
|
|
frame->slots[a] = frame->slots[c];
|
|
VM_BREAK();
|
|
}
|
|
|
|
/* Bitwise (mcode names — delegate to same code as legacy) */
|
|
VM_CASE(MACH_BITNOT): {
|
|
int32_t i;
|
|
JS_ToInt32(ctx, &i, frame->slots[b]);
|
|
frame->slots[a] = JS_NewInt32(ctx, ~i);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_BITAND): VM_CASE(MACH_BITOR): VM_CASE(MACH_BITXOR): {
|
|
int32_t ia, ib;
|
|
JS_ToInt32(ctx, &ia, frame->slots[b]);
|
|
JS_ToInt32(ctx, &ib, frame->slots[c]);
|
|
int32_t r;
|
|
if (op == MACH_BITAND) r = ia & ib;
|
|
else if (op == MACH_BITOR) r = ia | ib;
|
|
else r = ia ^ ib;
|
|
frame->slots[a] = JS_NewInt32(ctx, r);
|
|
VM_BREAK();
|
|
}
|
|
|
|
/* Property access (mcode names) */
|
|
VM_CASE(MACH_LOAD_FIELD): {
|
|
JSValue obj = frame->slots[b];
|
|
JSValue key = code->cpool[c];
|
|
if (mist_is_function(obj)) {
|
|
JS_RaiseDisrupt(ctx, "cannot read property of function");
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
goto disrupt;
|
|
}
|
|
JSValue val = JS_GetProperty(ctx, obj, key);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(val)) goto disrupt;
|
|
frame->slots[a] = val;
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_STORE_FIELD): {
|
|
JSValue obj = frame->slots[a];
|
|
JSValue key = code->cpool[b];
|
|
JSValue val = frame->slots[c];
|
|
int ret = JS_SetProperty(ctx, obj, key, val);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (ret < 0) goto disrupt;
|
|
mach_resolve_forward(&frame->slots[a]);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_LOAD_INDEX): {
|
|
/* R(A) = R(B)[R(C)] — mcode guarantees R(C) is int */
|
|
JSValue obj = frame->slots[b];
|
|
JSValue val = JS_GetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[c]));
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(val)) goto disrupt;
|
|
frame->slots[a] = val;
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_STORE_INDEX): {
|
|
/* R(A)[R(B)] = R(C) — mcode guarantees R(B) is int */
|
|
JSValue obj = frame->slots[a];
|
|
JSValue val = frame->slots[c];
|
|
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(r)) goto disrupt;
|
|
mach_resolve_forward(&frame->slots[a]);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_LOAD_DYNAMIC): {
|
|
JSValue obj = frame->slots[b];
|
|
JSValue key = frame->slots[c];
|
|
JSValue val;
|
|
if (JS_IsInt(key))
|
|
val = JS_GetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(key));
|
|
else
|
|
val = JS_GetProperty(ctx, obj, key);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(val)) goto disrupt;
|
|
frame->slots[a] = val;
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_STORE_DYNAMIC): {
|
|
JSValue obj = frame->slots[a];
|
|
JSValue key = frame->slots[b];
|
|
JSValue val = frame->slots[c];
|
|
int ret;
|
|
if (JS_IsInt(key)) {
|
|
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(key), val);
|
|
ret = JS_IsException(r) ? -1 : 0;
|
|
} else if (mist_is_array(obj)) {
|
|
JS_RaiseDisrupt(ctx, "array index must be a number");
|
|
ret = -1;
|
|
} else if (JS_IsBool(key) || JS_IsNull(key) || mist_is_array(key) || mist_is_function(key)) {
|
|
JS_RaiseDisrupt(ctx, "object key must be text");
|
|
ret = -1;
|
|
} else {
|
|
ret = JS_SetProperty(ctx, obj, key, val);
|
|
}
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (ret < 0) goto disrupt;
|
|
mach_resolve_forward(&frame->slots[a]);
|
|
VM_BREAK();
|
|
}
|
|
|
|
/* New record */
|
|
VM_CASE(MACH_NEWRECORD): {
|
|
JSValue obj = b > 0 ? JS_NewObjectCap(ctx, b) : JS_NewObject(ctx);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
if (JS_IsException(obj)) goto disrupt;
|
|
frame->slots[a] = obj;
|
|
VM_BREAK();
|
|
}
|
|
|
|
/* Decomposed function calls (inlined from qbe_helpers) */
|
|
VM_CASE(MACH_FRAME):
|
|
VM_CASE(MACH_GOFRAME): {
|
|
/* A=frame_slot, B=func_reg, C=argc */
|
|
JSValue func_val = frame->slots[b];
|
|
if (!mist_is_function(func_val)) {
|
|
char nbuf[KEY_GET_STR_BUF_SIZE];
|
|
const char *name = mach_callee_name(ctx, code, pc, b, nbuf, sizeof(nbuf));
|
|
if (name)
|
|
JS_RaiseDisrupt(ctx, "%s is not a function", name);
|
|
else
|
|
JS_RaiseDisrupt(ctx, "not a function");
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
goto disrupt;
|
|
}
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val);
|
|
int nr;
|
|
if (fn->kind == JS_FUNC_KIND_REGISTER) {
|
|
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
|
|
nr = fn_code->nr_slots;
|
|
if (nr < c + 2) nr = c + 2; /* safety: never smaller than argc+2 */
|
|
} else {
|
|
nr = c + 2;
|
|
}
|
|
JSFrameRegister *call_frame = alloc_frame_register(ctx, nr);
|
|
if (!call_frame) {
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
goto disrupt;
|
|
}
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
func_val = frame->slots[b]; /* re-read after GC */
|
|
call_frame->function = func_val;
|
|
call_frame->address = JS_NewInt32(ctx, c); /* store actual argc */
|
|
frame->slots[a] = JS_MKPTR(call_frame);
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_SETARG): {
|
|
/* A=frame_slot, B=arg_idx, C=val_reg */
|
|
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
|
|
fr->slots[b] = frame->slots[c];
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_INVOKE): {
|
|
/* A=frame_slot, B=result_slot */
|
|
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
|
|
int c_argc = JS_VALUE_GET_INT(fr->address); /* actual argc stored by FRAME */
|
|
JSValue fn_val = fr->function;
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
|
|
if (!mach_check_call_arity(ctx, fn, c_argc))
|
|
goto disrupt;
|
|
|
|
if (fn->kind == JS_FUNC_KIND_REGISTER) {
|
|
/* Register function: FRAME already allocated nr_slots — just switch */
|
|
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
|
|
/* Fire trace hook for register-to-register call */
|
|
if (unlikely(ctx->trace_hook) && (ctx->trace_type & JS_HOOK_CALL)) {
|
|
js_debug dbg = {0};
|
|
if (fn_code->name_cstr)
|
|
snprintf(dbg.name, sizeof(dbg.name), "%s", fn_code->name_cstr);
|
|
if (fn_code->filename_cstr)
|
|
snprintf(dbg.filename, sizeof(dbg.filename), "%s", fn_code->filename_cstr);
|
|
if (fn_code->line_table)
|
|
dbg.line = fn_code->line_table[fn_code->entry_point].line;
|
|
dbg.param_n = fn_code->arity;
|
|
dbg.unique = (int)(uintptr_t)fn_code;
|
|
ctx->trace_hook(ctx, JS_HOOK_CALL, &dbg, ctx->trace_data);
|
|
}
|
|
/* Save return info */
|
|
frame->address = JS_NewInt32(ctx, (pc << 16) | b);
|
|
fr->caller = JS_MKPTR(frame);
|
|
frame = fr;
|
|
frame_ref.val = JS_MKPTR(frame);
|
|
code = fn_code;
|
|
env = fn->u.cell.env_record;
|
|
pc = code->entry_point;
|
|
} else {
|
|
/* C, native, or bytecode function */
|
|
ctx->reg_current_frame = frame_ref.val;
|
|
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
|
ctx->vm_call_depth++;
|
|
JSValue ret;
|
|
if (fn->kind == JS_FUNC_KIND_C)
|
|
ret = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
|
else if (fn->kind == JS_FUNC_KIND_NATIVE)
|
|
ret = cell_native_dispatch(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
|
else
|
|
ret = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1], 0);
|
|
ctx->vm_call_depth--;
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
ctx->reg_current_frame = JS_NULL;
|
|
if (JS_IsException(ret)) goto disrupt;
|
|
#ifdef VALIDATE_GC
|
|
if (JS_IsPtr(ret)) {
|
|
void *rp = JS_VALUE_GET_PTR(ret);
|
|
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
|
|
if (!is_ct_ptr(ctx, rp)) {
|
|
int magic = (fn->kind == JS_FUNC_KIND_C) ? fn->u.cfunc.magic : -1;
|
|
void *cfp = (fn->kind == JS_FUNC_KIND_C) ? (void *)fn->u.cfunc.c_function.generic : NULL;
|
|
fprintf(stderr, "VALIDATE_GC: stale INVOKE result into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u kind=%d magic=%d cfunc=%p caller=%s (%s)\n",
|
|
b, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc - 1, fn->kind,
|
|
magic, cfp,
|
|
code->name_cstr ? code->name_cstr : "?",
|
|
code->filename_cstr ? code->filename_cstr : "?");
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
frame->slots[b] = ret;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
VM_CASE(MACH_GOINVOKE): {
|
|
/* Tail call: replace current frame with callee */
|
|
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
|
|
int c_argc = JS_VALUE_GET_INT(fr->address); /* actual argc stored by FRAME */
|
|
JSValue fn_val = fr->function;
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
|
|
if (!mach_check_call_arity(ctx, fn, c_argc))
|
|
goto disrupt;
|
|
|
|
if (fn->kind == JS_FUNC_KIND_REGISTER) {
|
|
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
|
|
/* Tail call: fire RET for current, CALL for new */
|
|
if (unlikely(ctx->trace_hook)) {
|
|
if (ctx->trace_type & JS_HOOK_RET)
|
|
ctx->trace_hook(ctx, JS_HOOK_RET, NULL, ctx->trace_data);
|
|
if (ctx->trace_type & JS_HOOK_CALL) {
|
|
js_debug dbg = {0};
|
|
if (fn_code->name_cstr)
|
|
snprintf(dbg.name, sizeof(dbg.name), "%s", fn_code->name_cstr);
|
|
if (fn_code->filename_cstr)
|
|
snprintf(dbg.filename, sizeof(dbg.filename), "%s", fn_code->filename_cstr);
|
|
if (fn_code->line_table)
|
|
dbg.line = fn_code->line_table[fn_code->entry_point].line;
|
|
dbg.param_n = fn_code->arity;
|
|
dbg.unique = (int)(uintptr_t)fn_code;
|
|
ctx->trace_hook(ctx, JS_HOOK_CALL, &dbg, ctx->trace_data);
|
|
}
|
|
}
|
|
int current_slots = (int)objhdr_cap56(frame->header);
|
|
|
|
if (fn_code->nr_slots <= current_slots) {
|
|
/* FAST PATH: reuse current frame — no allocation */
|
|
int copy_count = (c_argc < fn_code->arity) ? c_argc : fn_code->arity;
|
|
frame->slots[0] = fr->slots[0]; /* this */
|
|
for (int i = 0; i < copy_count; i++)
|
|
frame->slots[1 + i] = fr->slots[1 + i];
|
|
/* Null out remaining slots (locals/temps) */
|
|
for (int i = 1 + copy_count; i < current_slots; i++)
|
|
frame->slots[i] = JS_NULL;
|
|
frame->function = fn_val;
|
|
/* caller stays the same — we're reusing this frame */
|
|
code = fn_code;
|
|
env = fn->u.cell.env_record;
|
|
pc = code->entry_point;
|
|
} else {
|
|
/* SLOW PATH: GOFRAME already allocated nr_slots — use fr directly */
|
|
fr->caller = frame->caller;
|
|
frame->caller = JS_NULL;
|
|
frame = fr;
|
|
frame_ref.val = JS_MKPTR(frame);
|
|
code = fn_code;
|
|
env = fn->u.cell.env_record;
|
|
pc = code->entry_point;
|
|
}
|
|
} else {
|
|
/* C, native, or bytecode function: call it, then return result to our caller */
|
|
ctx->reg_current_frame = frame_ref.val;
|
|
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
|
ctx->vm_call_depth++;
|
|
JSValue ret;
|
|
if (fn->kind == JS_FUNC_KIND_C)
|
|
ret = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
|
else if (fn->kind == JS_FUNC_KIND_NATIVE)
|
|
ret = cell_native_dispatch(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
|
else
|
|
ret = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1], 0);
|
|
ctx->vm_call_depth--;
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
ctx->reg_current_frame = JS_NULL;
|
|
if (JS_IsException(ret)) goto disrupt;
|
|
/* Tail-return: act like MACH_RETURN with the result */
|
|
result = ret;
|
|
if (!JS_IsPtr(frame->caller)) goto done;
|
|
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
|
frame->caller = JS_NULL;
|
|
frame = caller;
|
|
frame_ref.val = JS_MKPTR(frame);
|
|
int ret_info = JS_VALUE_GET_INT(frame->address);
|
|
JSFunction *ret_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
code = JS_VALUE_GET_CODE(FN_READ_CODE(ret_fn))->u.reg.code;
|
|
env = ret_fn->u.cell.env_record;
|
|
pc = ret_info >> 16;
|
|
int ret_slot = ret_info & 0xFFFF;
|
|
if (ret_slot != 0xFFFF) frame->slots[ret_slot] = ret;
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
/* Jump if not null */
|
|
VM_CASE(MACH_JMPNOTNULL): {
|
|
if (!JS_IsNull(frame->slots[a])) {
|
|
int offset = MACH_GET_sBx(instr);
|
|
pc = (uint32_t)((int32_t)pc + offset);
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
/* Wary jumps — coerce via JS_ToBool (old JMPTRUE/JMPFALSE behavior) */
|
|
VM_CASE(MACH_WARYTRUE): {
|
|
JSValue v = frame->slots[a];
|
|
int cond;
|
|
if (v == JS_TRUE) cond = 1;
|
|
else if (v == JS_FALSE || v == JS_NULL) cond = 0;
|
|
else cond = JS_ToBool(ctx, v);
|
|
if (cond) {
|
|
int offset = MACH_GET_sBx(instr);
|
|
pc = (uint32_t)((int32_t)pc + offset);
|
|
if (offset < 0) {
|
|
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
|
|
if (pf == 2) {
|
|
result = JS_RaiseDisrupt(ctx, "interrupted");
|
|
goto done;
|
|
}
|
|
if (pf == 1) {
|
|
if (ctx->vm_call_depth > 0)
|
|
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
|
|
else
|
|
goto suspend;
|
|
}
|
|
}
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_WARYFALSE): {
|
|
JSValue v = frame->slots[a];
|
|
int cond;
|
|
if (v == JS_TRUE) cond = 1;
|
|
else if (v == JS_FALSE || v == JS_NULL) cond = 0;
|
|
else cond = JS_ToBool(ctx, v);
|
|
if (!cond) {
|
|
int offset = MACH_GET_sBx(instr);
|
|
pc = (uint32_t)((int32_t)pc + offset);
|
|
if (offset < 0) {
|
|
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
|
|
if (pf == 2) {
|
|
result = JS_RaiseDisrupt(ctx, "interrupted");
|
|
goto done;
|
|
}
|
|
if (pf == 1) {
|
|
if (ctx->vm_call_depth > 0)
|
|
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
|
|
else
|
|
goto suspend;
|
|
}
|
|
}
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_CASE(MACH_JMPEMPTY): {
|
|
if (frame->slots[a] == JS_EMPTY_TEXT) {
|
|
int offset = MACH_GET_sBx(instr);
|
|
pc = (uint32_t)((int32_t)pc + offset);
|
|
}
|
|
VM_BREAK();
|
|
}
|
|
|
|
/* Disrupt (mcode alias) */
|
|
VM_CASE(MACH_DISRUPT):
|
|
goto disrupt;
|
|
|
|
/* Has-property check (mcode name) */
|
|
VM_CASE(MACH_IN): {
|
|
JSValue key = frame->slots[b];
|
|
JSValue obj = frame->slots[c];
|
|
int has = JS_HasProperty(ctx, obj, key);
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
frame->slots[a] = JS_NewBool(ctx, has > 0);
|
|
VM_BREAK();
|
|
}
|
|
|
|
VM_DEFAULT:
|
|
result = JS_RaiseDisrupt(ctx, "unknown register VM opcode %d: %s", op, mach_opcode_names[op]);
|
|
goto done;
|
|
}
|
|
continue;
|
|
|
|
disrupt:
|
|
/* Save debug info for stack traces */
|
|
ctx->reg_current_frame = frame_ref.val;
|
|
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
|
/* Search frame chain for a disruption handler.
|
|
Use frame_pc to track each frame's execution point:
|
|
- For the faulting frame, it's the current pc.
|
|
- For unwound caller frames, read from frame->address.
|
|
Keep frame_ref.val at the faulting frame and preserve caller
|
|
links so the trace walk can show the full call chain. */
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); /* re-chase after GC */
|
|
{
|
|
uint32_t frame_pc = pc;
|
|
for (;;) {
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
|
|
/* Only enter handler if we're not already inside it */
|
|
if (code->disruption_pc > 0 && frame_pc < code->disruption_pc) {
|
|
env = fn->u.cell.env_record;
|
|
pc = code->disruption_pc;
|
|
ctx->disruption_reported = FALSE;
|
|
frame_ref.val = JS_MKPTR(frame); /* root handler frame for GC */
|
|
ctx->current_exception = JS_NULL;
|
|
break;
|
|
}
|
|
if (!JS_IsPtr(frame->caller)) {
|
|
/* Stack trace was already included in the JS_RaiseDisrupt log via the callback. */
|
|
ctx->disruption_reported = TRUE;
|
|
frame_ref.val = JS_MKPTR(frame); /* update root for GC / done */
|
|
ctx->current_exception = JS_NULL;
|
|
result = JS_EXCEPTION;
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
goto done;
|
|
}
|
|
/* Advance to caller — keep chain intact (no nulling caller links) */
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
|
frame_pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16);
|
|
}
|
|
}
|
|
}
|
|
|
|
suspend:
|
|
ctx->suspended = 1;
|
|
ctx->suspended_pc = pc;
|
|
ctx->suspended_frame_ref.val = frame_ref.val;
|
|
result = JS_SUSPENDED;
|
|
JS_DeleteGCRef(ctx, &frame_ref);
|
|
#ifdef HAVE_ASAN
|
|
__asan_js_ctx = NULL;
|
|
#endif
|
|
return result;
|
|
|
|
done:
|
|
/* Fire trace hook for top-level register function return */
|
|
if (unlikely(ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET))
|
|
ctx->trace_hook(ctx, JS_HOOK_RET, NULL, ctx->trace_data);
|
|
#ifdef HAVE_ASAN
|
|
__asan_js_ctx = NULL;
|
|
#endif
|
|
ctx->reg_current_frame = JS_NULL;
|
|
if (JS_IsException(result)) {
|
|
ctx->reg_current_frame = frame_ref.val;
|
|
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
|
}
|
|
if (JS_IsPtr(frame_ref.val)) {
|
|
JSFrameRegister *f = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
|
f->caller = JS_NULL; /* mark as returned so GC can shorten */
|
|
}
|
|
JS_DeleteGCRef(ctx, &frame_ref);
|
|
return result;
|
|
}
|
|
|
|
JSValue JS_ResumeRegisterVM(JSContext *ctx) {
|
|
if (!ctx->suspended)
|
|
return JS_RaiseDisrupt(ctx, "no suspended VM to resume");
|
|
/* ctx->suspended is set; JS_CallRegisterVM will take the resume path */
|
|
return JS_CallRegisterVM(ctx, NULL, JS_NULL, 0, NULL, JS_NULL, JS_NULL);
|
|
}
|
|
|
|
/* ============================================================
|
|
MCODE Lowering — mcode JSON IR → MachInstr32
|
|
============================================================ */
|
|
|
|
typedef struct {
|
|
MachInstr32 *code;
|
|
int code_count, code_cap;
|
|
MachCPoolEntry *cpool;
|
|
int cpool_count, cpool_cap;
|
|
MachLineEntry *lines;
|
|
int nr_slots;
|
|
struct { const char *name; int pc; } *labels;
|
|
int label_count, label_cap;
|
|
struct { int pc; const char *label; int is_sJ; int reg_a; } *patches;
|
|
int patch_count, patch_cap;
|
|
int *flat_to_pc;
|
|
int flat_count;
|
|
} McodeLowerState;
|
|
|
|
static void ml_emit(McodeLowerState *s, MachInstr32 instr, int line, int col) {
|
|
if (s->code_count >= s->code_cap) {
|
|
int nc = s->code_cap ? s->code_cap * 2 : 64;
|
|
s->code = sys_realloc(s->code, nc * sizeof(MachInstr32));
|
|
s->lines = sys_realloc(s->lines, nc * sizeof(MachLineEntry));
|
|
s->code_cap = nc;
|
|
}
|
|
s->lines[s->code_count] = (MachLineEntry){(uint16_t)line, (uint16_t)col};
|
|
s->code[s->code_count++] = instr;
|
|
}
|
|
|
|
static int ml_cpool_int(McodeLowerState *s, int32_t val) {
|
|
for (int i = 0; i < s->cpool_count; i++)
|
|
if (s->cpool[i].type == MACH_CP_INT && s->cpool[i].ival == val) return i;
|
|
if (s->cpool_count >= s->cpool_cap) {
|
|
int nc = s->cpool_cap ? s->cpool_cap * 2 : 16;
|
|
s->cpool = sys_realloc(s->cpool, nc * sizeof(MachCPoolEntry));
|
|
s->cpool_cap = nc;
|
|
}
|
|
s->cpool[s->cpool_count] = (MachCPoolEntry){.type = MACH_CP_INT, .ival = val};
|
|
return s->cpool_count++;
|
|
}
|
|
|
|
static int ml_cpool_float(McodeLowerState *s, double val) {
|
|
for (int i = 0; i < s->cpool_count; i++)
|
|
if (s->cpool[i].type == MACH_CP_FLOAT && s->cpool[i].fval == val) return i;
|
|
if (s->cpool_count >= s->cpool_cap) {
|
|
int nc = s->cpool_cap ? s->cpool_cap * 2 : 16;
|
|
s->cpool = sys_realloc(s->cpool, nc * sizeof(MachCPoolEntry));
|
|
s->cpool_cap = nc;
|
|
}
|
|
s->cpool[s->cpool_count] = (MachCPoolEntry){.type = MACH_CP_FLOAT, .fval = val};
|
|
return s->cpool_count++;
|
|
}
|
|
|
|
static int ml_cpool_str(McodeLowerState *s, const char *str) {
|
|
if (!str) { fprintf(stderr, "ml_cpool_str: NULL string\n"); str = ""; }
|
|
for (int i = 0; i < s->cpool_count; i++)
|
|
if (s->cpool[i].type == MACH_CP_STR && strcmp(s->cpool[i].str, str) == 0) return i;
|
|
if (s->cpool_count >= s->cpool_cap) {
|
|
int nc = s->cpool_cap ? s->cpool_cap * 2 : 16;
|
|
s->cpool = sys_realloc(s->cpool, nc * sizeof(MachCPoolEntry));
|
|
s->cpool_cap = nc;
|
|
}
|
|
char *dup = sys_malloc(strlen(str) + 1);
|
|
memcpy(dup, str, strlen(str) + 1);
|
|
s->cpool[s->cpool_count] = (MachCPoolEntry){.type = MACH_CP_STR, .str = dup};
|
|
return s->cpool_count++;
|
|
}
|
|
|
|
static void ml_label(McodeLowerState *s, const char *name) {
|
|
if (s->label_count >= s->label_cap) {
|
|
int nc = s->label_cap ? s->label_cap * 2 : 32;
|
|
s->labels = sys_realloc(s->labels, nc * sizeof(s->labels[0]));
|
|
s->label_cap = nc;
|
|
}
|
|
s->labels[s->label_count].name = name;
|
|
s->labels[s->label_count].pc = s->code_count;
|
|
s->label_count++;
|
|
}
|
|
|
|
static void ml_patch(McodeLowerState *s, int pc, const char *label, int is_sJ, int reg_a) {
|
|
if (s->patch_count >= s->patch_cap) {
|
|
int nc = s->patch_cap ? s->patch_cap * 2 : 32;
|
|
s->patches = sys_realloc(s->patches, nc * sizeof(s->patches[0]));
|
|
s->patch_cap = nc;
|
|
}
|
|
s->patches[s->patch_count].pc = pc;
|
|
s->patches[s->patch_count].label = label;
|
|
s->patches[s->patch_count].is_sJ = is_sJ;
|
|
s->patches[s->patch_count].reg_a = reg_a;
|
|
s->patch_count++;
|
|
}
|
|
|
|
static int ml_int(cJSON *arr, int idx) {
|
|
return (int)cJSON_GetArrayItem(arr, idx)->valuedouble;
|
|
}
|
|
|
|
/* Lower one function's mcode instructions to MachInstr32 */
|
|
static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
|
McodeLowerState s = {0};
|
|
int nr_args = (int)cJSON_GetNumberValue(
|
|
cJSON_GetObjectItemCaseSensitive(fobj, "nr_args"));
|
|
int nr_close = (int)cJSON_GetNumberValue(
|
|
cJSON_GetObjectItemCaseSensitive(fobj, "nr_close_slots"));
|
|
s.nr_slots = (int)cJSON_GetNumberValue(
|
|
cJSON_GetObjectItemCaseSensitive(fobj, "nr_slots"));
|
|
if (s.nr_slots > 255) {
|
|
cJSON *nm_chk = cJSON_GetObjectItemCaseSensitive(fobj, "name");
|
|
const char *fn_name = nm_chk ? cJSON_GetStringValue(nm_chk) : "<anonymous>";
|
|
fprintf(stderr, "FATAL: function '%s' has %d slots (max 255). "
|
|
"Ensure the streamline optimizer ran before mach compilation.\n",
|
|
fn_name, s.nr_slots);
|
|
abort();
|
|
}
|
|
int dis_raw = (int)cJSON_GetNumberValue(
|
|
cJSON_GetObjectItemCaseSensitive(fobj, "disruption_pc"));
|
|
cJSON *nm = cJSON_GetObjectItemCaseSensitive(fobj, "name");
|
|
const char *name = nm ? cJSON_GetStringValue(nm) : NULL;
|
|
cJSON *fn_j = cJSON_GetObjectItemCaseSensitive(fobj, "filename");
|
|
const char *fname = fn_j ? cJSON_GetStringValue(fn_j) : filename;
|
|
cJSON *instrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
|
|
int n = instrs ? cJSON_GetArraySize(instrs) : 0;
|
|
|
|
s.flat_to_pc = sys_malloc((n + 1) * sizeof(int));
|
|
s.flat_count = n;
|
|
|
|
{ cJSON *it = instrs ? instrs->child : NULL;
|
|
for (int i = 0; it; i++, it = it->next) {
|
|
s.flat_to_pc[i] = s.code_count;
|
|
if (cJSON_IsString(it)) {
|
|
ml_label(&s, it->valuestring);
|
|
continue;
|
|
}
|
|
int sz = cJSON_GetArraySize(it);
|
|
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
|
|
int line = ml_int(it, sz - 2);
|
|
int col = ml_int(it, sz - 1);
|
|
|
|
#define A1 ml_int(it,1)
|
|
#define A2 ml_int(it,2)
|
|
#define A3 ml_int(it,3)
|
|
#define A4 ml_int(it,4)
|
|
#define EM(instr) ml_emit(&s, (instr), line, col)
|
|
#define ABC3(opc) EM(MACH_ABC(opc, A1, A2, A3))
|
|
#define AB2(opc) EM(MACH_ABC(opc, A1, A2, 0))
|
|
|
|
if (strcmp(op, "access") == 0) {
|
|
int dest = A1;
|
|
cJSON *val = cJSON_GetArrayItem(it, 2);
|
|
if (cJSON_IsNumber(val)) {
|
|
double dv = val->valuedouble;
|
|
int32_t iv = (int32_t)dv;
|
|
if ((double)iv == dv && iv >= -32768 && iv <= 32767)
|
|
EM(MACH_AsBx(MACH_LOADI, dest, iv));
|
|
else if ((double)iv == dv)
|
|
EM(MACH_ABx(MACH_LOADK, dest, ml_cpool_int(&s, iv)));
|
|
else
|
|
EM(MACH_ABx(MACH_LOADK, dest, ml_cpool_float(&s, dv)));
|
|
} else if (cJSON_IsString(val)) {
|
|
EM(MACH_ABx(MACH_LOADK, dest, ml_cpool_str(&s, val->valuestring)));
|
|
} else if (cJSON_IsObject(val)) {
|
|
const char *vn = cJSON_GetStringValue(
|
|
cJSON_GetObjectItemCaseSensitive(val, "name"));
|
|
EM(MACH_ABx(MACH_GETNAME, dest, ml_cpool_str(&s, vn)));
|
|
}
|
|
}
|
|
else if (strcmp(op, "int") == 0) {
|
|
int dest = A1, v = A2;
|
|
if (v >= -32768 && v <= 32767)
|
|
EM(MACH_AsBx(MACH_LOADI, dest, v));
|
|
else
|
|
EM(MACH_ABx(MACH_LOADK, dest, ml_cpool_int(&s, v)));
|
|
}
|
|
else if (strcmp(op, "true") == 0) { EM(MACH_ABC(MACH_LOADTRUE, A1, 0, 0)); }
|
|
else if (strcmp(op, "false") == 0) { EM(MACH_ABC(MACH_LOADFALSE, A1, 0, 0)); }
|
|
else if (strcmp(op, "null") == 0) { EM(MACH_ABC(MACH_LOADNULL, A1, 0, 0)); }
|
|
else if (strcmp(op, "move") == 0) { AB2(MACH_MOVE); }
|
|
/* Text */
|
|
else if (strcmp(op, "concat") == 0) { ABC3(MACH_CONCAT); }
|
|
else if (strcmp(op, "stone_text") == 0) { EM(MACH_ABC(MACH_STONE_TEXT, A1, 0, 0)); }
|
|
/* Generic arithmetic */
|
|
else if (strcmp(op, "add") == 0) { ABC3(MACH_ADD); }
|
|
else if (strcmp(op, "subtract") == 0) { ABC3(MACH_SUB); }
|
|
else if (strcmp(op, "multiply") == 0) { ABC3(MACH_MUL); }
|
|
else if (strcmp(op, "divide") == 0) { ABC3(MACH_DIV); }
|
|
else if (strcmp(op, "modulo") == 0) { ABC3(MACH_MOD); }
|
|
else if (strcmp(op, "pow") == 0) { ABC3(MACH_POW); }
|
|
else if (strcmp(op, "negate") == 0) { AB2(MACH_NEG); }
|
|
else if (strcmp(op, "remainder") == 0) { ABC3(MACH_REMAINDER); }
|
|
else if (strcmp(op, "max") == 0) { ABC3(MACH_MAX); }
|
|
else if (strcmp(op, "min") == 0) { ABC3(MACH_MIN); }
|
|
else if (strcmp(op, "abs") == 0) { AB2(MACH_ABS); }
|
|
else if (strcmp(op, "sign") == 0) { AB2(MACH_SIGN); }
|
|
else if (strcmp(op, "fraction") == 0) { AB2(MACH_FRACTION); }
|
|
else if (strcmp(op, "integer") == 0) { AB2(MACH_INTEGER); }
|
|
else if (strcmp(op, "floor") == 0) { ABC3(MACH_FLOOR); }
|
|
else if (strcmp(op, "ceiling") == 0) { ABC3(MACH_CEILING); }
|
|
else if (strcmp(op, "round") == 0) { ABC3(MACH_ROUND); }
|
|
else if (strcmp(op, "trunc") == 0) { ABC3(MACH_TRUNC); }
|
|
/* Generic comparisons */
|
|
else if (strcmp(op, "eq") == 0) { ABC3(MACH_EQ); }
|
|
else if (strcmp(op, "ne") == 0) { ABC3(MACH_NEQ); }
|
|
else if (strcmp(op, "lt") == 0) { ABC3(MACH_LT); }
|
|
else if (strcmp(op, "le") == 0) { ABC3(MACH_LE); }
|
|
else if (strcmp(op, "gt") == 0) { ABC3(MACH_GT); }
|
|
else if (strcmp(op, "ge") == 0) { ABC3(MACH_GE); }
|
|
/* Special comparisons */
|
|
else if (strcmp(op, "is_identical") == 0) { ABC3(MACH_IS_IDENTICAL); }
|
|
else if (strcmp(op, "eq_tol") == 0) {
|
|
/* A=dest, B=a, C=b; tolerance in operand 4 */
|
|
int dest = A1, ra = A2, rb = A3, rt = A4;
|
|
/* Move to consecutive scratch slots at end of frame */
|
|
int base = s.nr_slots;
|
|
s.nr_slots += 3;
|
|
EM(MACH_ABC(MACH_MOVE, base, ra, 0));
|
|
EM(MACH_ABC(MACH_MOVE, base + 1, rb, 0));
|
|
EM(MACH_ABC(MACH_MOVE, base + 2, rt, 0));
|
|
EM(MACH_ABC(MACH_EQ_TOL, dest, base, 3));
|
|
}
|
|
else if (strcmp(op, "ne_tol") == 0) {
|
|
int dest = A1, ra = A2, rb = A3, rt = A4;
|
|
int base = s.nr_slots;
|
|
s.nr_slots += 3;
|
|
EM(MACH_ABC(MACH_MOVE, base, ra, 0));
|
|
EM(MACH_ABC(MACH_MOVE, base + 1, rb, 0));
|
|
EM(MACH_ABC(MACH_MOVE, base + 2, rt, 0));
|
|
EM(MACH_ABC(MACH_NEQ_TOL, dest, base, 3));
|
|
}
|
|
/* Type checks */
|
|
else if (strcmp(op, "is_int") == 0) { AB2(MACH_IS_INT); }
|
|
else if (strcmp(op, "is_num") == 0) { AB2(MACH_IS_NUM); }
|
|
else if (strcmp(op, "is_text") == 0) { AB2(MACH_IS_TEXT); }
|
|
else if (strcmp(op, "is_bool") == 0) { AB2(MACH_IS_BOOL); }
|
|
else if (strcmp(op, "is_null") == 0) { AB2(MACH_IS_NULL); }
|
|
else if (strcmp(op, "is_array") == 0) { AB2(MACH_IS_ARRAY); }
|
|
else if (strcmp(op, "is_func") == 0) { AB2(MACH_IS_FUNC); }
|
|
else if (strcmp(op, "is_record") == 0) { AB2(MACH_IS_RECORD); }
|
|
else if (strcmp(op, "is_stone") == 0) { AB2(MACH_IS_STONE); }
|
|
else if (strcmp(op, "length") == 0) { AB2(MACH_LENGTH); }
|
|
else if (strcmp(op, "is_proxy") == 0) { AB2(MACH_IS_PROXY); }
|
|
else if (strcmp(op, "is_blob") == 0) { AB2(MACH_IS_BLOB); }
|
|
else if (strcmp(op, "is_data") == 0) { AB2(MACH_IS_DATA); }
|
|
else if (strcmp(op, "is_true") == 0) { AB2(MACH_IS_TRUE); }
|
|
else if (strcmp(op, "is_false") == 0) { AB2(MACH_IS_FALSE); }
|
|
else if (strcmp(op, "is_fit") == 0) { AB2(MACH_IS_FIT); }
|
|
else if (strcmp(op, "is_char") == 0) { AB2(MACH_IS_CHAR); }
|
|
else if (strcmp(op, "is_digit") == 0) { AB2(MACH_IS_DIGIT); }
|
|
else if (strcmp(op, "is_letter") == 0) { AB2(MACH_IS_LETTER); }
|
|
else if (strcmp(op, "is_lower") == 0) { AB2(MACH_IS_LOWER); }
|
|
else if (strcmp(op, "is_upper") == 0) { AB2(MACH_IS_UPPER); }
|
|
else if (strcmp(op, "is_ws") == 0) { AB2(MACH_IS_WS); }
|
|
else if (strcmp(op, "is_actor") == 0) { AB2(MACH_IS_ACTOR); }
|
|
else if (strcmp(op, "apply") == 0) { ABC3(MACH_APPLY); }
|
|
/* Logical */
|
|
else if (strcmp(op, "not") == 0) { AB2(MACH_NOT); }
|
|
else if (strcmp(op, "and") == 0) { ABC3(MACH_AND); }
|
|
else if (strcmp(op, "or") == 0) { ABC3(MACH_OR); }
|
|
/* Bitwise */
|
|
else if (strcmp(op, "bitnot") == 0) { AB2(MACH_BITNOT); }
|
|
else if (strcmp(op, "bitand") == 0) { ABC3(MACH_BITAND); }
|
|
else if (strcmp(op, "bitor") == 0) { ABC3(MACH_BITOR); }
|
|
else if (strcmp(op, "bitxor") == 0) { ABC3(MACH_BITXOR); }
|
|
else if (strcmp(op, "shl") == 0) { ABC3(MACH_SHL); }
|
|
else if (strcmp(op, "shr") == 0) { ABC3(MACH_SHR); }
|
|
else if (strcmp(op, "ushr") == 0) { ABC3(MACH_USHR); }
|
|
/* Property access */
|
|
else if (strcmp(op, "load_field") == 0) {
|
|
int dest = A1, obj = A2;
|
|
cJSON *key_item = cJSON_GetArrayItem(it, 3);
|
|
if (cJSON_IsString(key_item)) {
|
|
int ki = ml_cpool_str(&s, key_item->valuestring);
|
|
if (ki <= 255) {
|
|
EM(MACH_ABC(MACH_LOAD_FIELD, dest, obj, ki));
|
|
} else {
|
|
/* cpool index > 255: load key via LOADK, then use dynamic access */
|
|
int tmp = s.nr_slots++;
|
|
EM(MACH_ABx(MACH_LOADK, tmp, ki));
|
|
EM(MACH_ABC(MACH_LOAD_DYNAMIC, dest, obj, tmp));
|
|
}
|
|
} else {
|
|
/* key is a register — fall back to dynamic access */
|
|
int key_reg = (int)key_item->valuedouble;
|
|
EM(MACH_ABC(MACH_LOAD_DYNAMIC, dest, obj, key_reg));
|
|
}
|
|
}
|
|
else if (strcmp(op, "store_field") == 0) {
|
|
int obj = A1, val = A2;
|
|
cJSON *key_item = cJSON_GetArrayItem(it, 3);
|
|
if (cJSON_IsString(key_item)) {
|
|
int ki = ml_cpool_str(&s, key_item->valuestring);
|
|
if (ki <= 255) {
|
|
EM(MACH_ABC(MACH_STORE_FIELD, obj, ki, val));
|
|
} else {
|
|
/* cpool index > 255: load key via LOADK, then use dynamic access */
|
|
int tmp = s.nr_slots++;
|
|
EM(MACH_ABx(MACH_LOADK, tmp, ki));
|
|
EM(MACH_ABC(MACH_STORE_DYNAMIC, obj, tmp, val));
|
|
}
|
|
} else {
|
|
/* key is a register — fall back to dynamic access */
|
|
int key_reg = (int)key_item->valuedouble;
|
|
EM(MACH_ABC(MACH_STORE_DYNAMIC, obj, key_reg, val));
|
|
}
|
|
}
|
|
else if (strcmp(op, "load_index") == 0) { ABC3(MACH_LOAD_INDEX); }
|
|
else if (strcmp(op, "store_index") == 0) {
|
|
/* mcode: store_index obj val idx → VM: R(A)[R(B)] = R(C) */
|
|
EM(MACH_ABC(MACH_STORE_INDEX, A1, A3, A2));
|
|
}
|
|
else if (strcmp(op, "load_dynamic") == 0) { ABC3(MACH_LOAD_DYNAMIC); }
|
|
else if (strcmp(op, "store_dynamic") == 0) {
|
|
/* mcode: store_dynamic obj val key → VM: R(A)[R(B)] = R(C) */
|
|
EM(MACH_ABC(MACH_STORE_DYNAMIC, A1, A3, A2));
|
|
}
|
|
/* Delete */
|
|
else if (strcmp(op, "delete") == 0) {
|
|
int dest = A1, obj = A2;
|
|
cJSON *key_item = cJSON_GetArrayItem(it, 3);
|
|
if (cJSON_IsString(key_item)) {
|
|
int ki = ml_cpool_str(&s, key_item->valuestring);
|
|
if (ki <= 255) {
|
|
EM(MACH_ABC(MACH_DELETE, dest, obj, ki));
|
|
} else {
|
|
int tmp = s.nr_slots++;
|
|
EM(MACH_ABx(MACH_LOADK, tmp, ki));
|
|
EM(MACH_ABC(MACH_DELETEINDEX, dest, obj, tmp));
|
|
}
|
|
} else {
|
|
int key_reg = (int)key_item->valuedouble;
|
|
EM(MACH_ABC(MACH_DELETEINDEX, dest, obj, key_reg));
|
|
}
|
|
}
|
|
/* Array/Object creation */
|
|
else if (strcmp(op, "array") == 0) {
|
|
EM(MACH_ABC(MACH_NEWARRAY, A1, A2, 0));
|
|
}
|
|
else if (strcmp(op, "record") == 0) {
|
|
EM(MACH_ABC(MACH_NEWRECORD, A1, A2, 0));
|
|
}
|
|
/* Push/Pop */
|
|
else if (strcmp(op, "push") == 0) {
|
|
EM(MACH_ABC(MACH_PUSH, A1, A2, 0));
|
|
}
|
|
else if (strcmp(op, "pop") == 0) {
|
|
EM(MACH_ABC(MACH_POP, A1, A2, 0));
|
|
}
|
|
/* Closure access */
|
|
else if (strcmp(op, "get") == 0) {
|
|
/* mcode: get dest slot level → GETUP A=dest B=level C=slot */
|
|
EM(MACH_ABC(MACH_GETUP, A1, A3, A2));
|
|
}
|
|
else if (strcmp(op, "put") == 0) {
|
|
/* mcode: put src slot level → SETUP A=src B=level C=slot */
|
|
EM(MACH_ABC(MACH_SETUP, A1, A3, A2));
|
|
}
|
|
/* Function creation */
|
|
else if (strcmp(op, "function") == 0) {
|
|
EM(MACH_ABx(MACH_CLOSURE, A1, A2));
|
|
}
|
|
/* Decomposed function calls */
|
|
else if (strcmp(op, "frame") == 0) {
|
|
EM(MACH_ABC(MACH_FRAME, A1, A2, A3));
|
|
}
|
|
else if (strcmp(op, "setarg") == 0) {
|
|
EM(MACH_ABC(MACH_SETARG, A1, A2, A3));
|
|
}
|
|
else if (strcmp(op, "invoke") == 0 || strcmp(op, "tail_invoke") == 0) {
|
|
EM(MACH_ABC(MACH_INVOKE, A1, A2, 0));
|
|
}
|
|
else if (strcmp(op, "goframe") == 0) {
|
|
EM(MACH_ABC(MACH_GOFRAME, A1, A2, A3));
|
|
}
|
|
else if (strcmp(op, "goinvoke") == 0) {
|
|
EM(MACH_ABC(MACH_GOINVOKE, A1, 0, 0));
|
|
}
|
|
/* Control flow */
|
|
else if (strcmp(op, "jump") == 0) {
|
|
const char *lbl = cJSON_GetArrayItem(it, 1)->valuestring;
|
|
int pc_now = s.code_count;
|
|
EM(MACH_sJ(MACH_JMP, 0));
|
|
ml_patch(&s, pc_now, lbl, 1, 0);
|
|
}
|
|
else if (strcmp(op, "jump_true") == 0) {
|
|
int reg = A1;
|
|
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
|
|
int pc_now = s.code_count;
|
|
EM(MACH_AsBx(MACH_JMPTRUE, reg, 0));
|
|
ml_patch(&s, pc_now, lbl, 0, reg);
|
|
}
|
|
else if (strcmp(op, "jump_false") == 0) {
|
|
int reg = A1;
|
|
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
|
|
int pc_now = s.code_count;
|
|
EM(MACH_AsBx(MACH_JMPFALSE, reg, 0));
|
|
ml_patch(&s, pc_now, lbl, 0, reg);
|
|
}
|
|
else if (strcmp(op, "jump_not_null") == 0) {
|
|
int reg = A1;
|
|
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
|
|
int pc_now = s.code_count;
|
|
EM(MACH_AsBx(MACH_JMPNOTNULL, reg, 0));
|
|
ml_patch(&s, pc_now, lbl, 0, reg);
|
|
}
|
|
else if (strcmp(op, "jump_null") == 0) {
|
|
int reg = A1;
|
|
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
|
|
int pc_now = s.code_count;
|
|
EM(MACH_AsBx(MACH_JMPNULL, reg, 0));
|
|
ml_patch(&s, pc_now, lbl, 0, reg);
|
|
}
|
|
else if (strcmp(op, "wary_true") == 0) {
|
|
int reg = A1;
|
|
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
|
|
int pc_now = s.code_count;
|
|
EM(MACH_AsBx(MACH_WARYTRUE, reg, 0));
|
|
ml_patch(&s, pc_now, lbl, 0, reg);
|
|
}
|
|
else if (strcmp(op, "wary_false") == 0) {
|
|
int reg = A1;
|
|
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
|
|
int pc_now = s.code_count;
|
|
EM(MACH_AsBx(MACH_WARYFALSE, reg, 0));
|
|
ml_patch(&s, pc_now, lbl, 0, reg);
|
|
}
|
|
else if (strcmp(op, "jump_empty") == 0) {
|
|
int reg = A1;
|
|
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
|
|
int pc_now = s.code_count;
|
|
EM(MACH_AsBx(MACH_JMPEMPTY, reg, 0));
|
|
ml_patch(&s, pc_now, lbl, 0, reg);
|
|
}
|
|
/* Return / error */
|
|
else if (strcmp(op, "return") == 0) {
|
|
EM(MACH_ABC(MACH_RETURN, A1, 0, 0));
|
|
}
|
|
else if (strcmp(op, "disrupt") == 0) {
|
|
EM(MACH_ABC(MACH_DISRUPT, 0, 0, 0));
|
|
}
|
|
/* Misc */
|
|
else if (strcmp(op, "in") == 0) {
|
|
EM(MACH_ABC(MACH_IN, A1, A2, A3));
|
|
}
|
|
else if (strcmp(op, "regexp") == 0) {
|
|
int dest = A1;
|
|
const char *pat = cJSON_GetArrayItem(it, 2)->valuestring;
|
|
const char *flg = cJSON_GetArrayItem(it, 3)->valuestring;
|
|
EM(MACH_ABC(MACH_REGEXP, dest, ml_cpool_str(&s, pat), ml_cpool_str(&s, flg)));
|
|
}
|
|
else {
|
|
/* Unknown opcode — emit NOP */
|
|
EM(MACH_ABC(MACH_NOP, 0, 0, 0));
|
|
}
|
|
} }
|
|
/* Sentinel for flat_to_pc */
|
|
s.flat_to_pc[n] = s.code_count;
|
|
|
|
#undef A1
|
|
#undef A2
|
|
#undef A3
|
|
#undef A4
|
|
#undef EM
|
|
#undef ABC3
|
|
#undef AB2
|
|
|
|
/* Resolve pending jump patches */
|
|
for (int i = 0; i < s.patch_count; i++) {
|
|
int target = -1;
|
|
for (int j = 0; j < s.label_count; j++) {
|
|
if (strcmp(s.labels[j].name, s.patches[i].label) == 0) {
|
|
target = s.labels[j].pc;
|
|
break;
|
|
}
|
|
}
|
|
if (target < 0) {
|
|
fprintf(stderr, "mcode_lower: unknown label '%s'\n", s.patches[i].label);
|
|
continue;
|
|
}
|
|
int offset = target - (s.patches[i].pc + 1);
|
|
if (s.patches[i].is_sJ) {
|
|
int old_op = MACH_GET_OP(s.code[s.patches[i].pc]);
|
|
s.code[s.patches[i].pc] = MACH_sJ(old_op, offset);
|
|
} else {
|
|
int old_op = MACH_GET_OP(s.code[s.patches[i].pc]);
|
|
s.code[s.patches[i].pc] = MACH_AsBx(old_op, s.patches[i].reg_a, offset);
|
|
}
|
|
}
|
|
|
|
MachCode *mc = sys_malloc(sizeof(MachCode));
|
|
memset(mc, 0, sizeof(MachCode));
|
|
mc->arity = nr_args;
|
|
mc->nr_close_slots = nr_close;
|
|
mc->nr_slots = s.nr_slots;
|
|
mc->entry_point = 0;
|
|
mc->instr_count = s.code_count;
|
|
mc->instructions = s.code;
|
|
mc->cpool_count = s.cpool_count;
|
|
mc->cpool = s.cpool;
|
|
mc->line_table = s.lines;
|
|
if (name && name[0]) {
|
|
mc->name = sys_malloc(strlen(name) + 1);
|
|
memcpy(mc->name, name, strlen(name) + 1);
|
|
}
|
|
if (fname) {
|
|
mc->filename = sys_malloc(strlen(fname) + 1);
|
|
memcpy(mc->filename, fname, strlen(fname) + 1);
|
|
}
|
|
mc->disruption_pc = 0;
|
|
if (dis_raw > 0 && dis_raw < s.flat_count)
|
|
mc->disruption_pc = s.flat_to_pc[dis_raw];
|
|
|
|
sys_free(s.flat_to_pc);
|
|
sys_free(s.labels);
|
|
sys_free(s.patches);
|
|
return mc;
|
|
}
|
|
|
|
/* Assign nested functions to each MachCode based on CLOSURE instructions.
|
|
all_funcs is the flat array from the mcode JSON; compiled[i] is the
|
|
compiled MachCode for all_funcs[i]. mc is a compiled MachCode (main or
|
|
one of the flat functions). Scans its instructions for MACH_CLOSURE Bx
|
|
and builds mc->functions with remapped local indices. */
|
|
static void mcode_assign_children(MachCode *mc, MachCode **compiled, int total) {
|
|
/* Count unique CLOSURE references */
|
|
int *refs = NULL;
|
|
int ref_count = 0, ref_cap = 0;
|
|
for (uint32_t i = 0; i < mc->instr_count; i++) {
|
|
if (MACH_GET_OP(mc->instructions[i]) == MACH_CLOSURE) {
|
|
int bx = MACH_GET_Bx(mc->instructions[i]);
|
|
/* Check if already in refs */
|
|
int found = 0;
|
|
for (int j = 0; j < ref_count; j++) {
|
|
if (refs[j] == bx) { found = 1; break; }
|
|
}
|
|
if (!found) {
|
|
if (ref_count >= ref_cap) {
|
|
ref_cap = ref_cap ? ref_cap * 2 : 8;
|
|
refs = sys_realloc(refs, ref_cap * sizeof(int));
|
|
}
|
|
refs[ref_count++] = bx;
|
|
}
|
|
}
|
|
}
|
|
if (ref_count == 0) { sys_free(refs); return; }
|
|
|
|
/* Build local functions array (preserve original order by flat index) */
|
|
/* Sort refs by value to maintain consistent ordering */
|
|
for (int i = 0; i < ref_count - 1; i++)
|
|
for (int j = i + 1; j < ref_count; j++)
|
|
if (refs[i] > refs[j]) { int t = refs[i]; refs[i] = refs[j]; refs[j] = t; }
|
|
|
|
mc->func_count = ref_count;
|
|
mc->functions = sys_malloc(ref_count * sizeof(MachCode *));
|
|
for (int i = 0; i < ref_count; i++)
|
|
mc->functions[i] = (refs[i] < total) ? compiled[refs[i]] : NULL;
|
|
|
|
/* Remap CLOSURE Bx: flat index → local index */
|
|
for (uint32_t i = 0; i < mc->instr_count; i++) {
|
|
if (MACH_GET_OP(mc->instructions[i]) == MACH_CLOSURE) {
|
|
int a = MACH_GET_A(mc->instructions[i]);
|
|
int bx = MACH_GET_Bx(mc->instructions[i]);
|
|
for (int j = 0; j < ref_count; j++) {
|
|
if (refs[j] == bx) {
|
|
mc->instructions[i] = MACH_ABx(MACH_CLOSURE, a, j);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sys_free(refs);
|
|
}
|
|
|
|
|
|
/* Compile mcode JSON IR to MachCode binary.
|
|
mcode_json has: functions[], main{}, filename, name */
|
|
MachCode *mach_compile_mcode(cJSON *mcode_json) {
|
|
const char *filename = cJSON_GetStringValue(
|
|
cJSON_GetObjectItemCaseSensitive(mcode_json, "filename"));
|
|
cJSON *funcs_arr = cJSON_GetObjectItemCaseSensitive(mcode_json, "functions");
|
|
int func_count = funcs_arr ? cJSON_GetArraySize(funcs_arr) : 0;
|
|
|
|
cJSON *main_obj = cJSON_GetObjectItemCaseSensitive(mcode_json, "main");
|
|
|
|
/* Slot compression is handled by the streamline optimizer before mach
|
|
compilation. mcode_lower_func() asserts nr_slots <= 255. */
|
|
|
|
/* Compile all flat functions */
|
|
MachCode **compiled = NULL;
|
|
if (func_count > 0) {
|
|
compiled = sys_malloc(func_count * sizeof(MachCode *));
|
|
memset(compiled, 0, func_count * sizeof(MachCode *));
|
|
{ cJSON *fobj = funcs_arr->child;
|
|
for (int i = 0; fobj; i++, fobj = fobj->next)
|
|
compiled[i] = mcode_lower_func(fobj, filename);
|
|
}
|
|
}
|
|
|
|
/* Compile main */
|
|
MachCode *main_code = mcode_lower_func(main_obj, filename);
|
|
|
|
/* Assign nested functions to each compiled unit */
|
|
for (int i = 0; i < func_count; i++)
|
|
mcode_assign_children(compiled[i], compiled, func_count);
|
|
mcode_assign_children(main_code, compiled, func_count);
|
|
|
|
sys_free(compiled);
|
|
return main_code;
|
|
}
|
|
|
|
|
|
/* ============================================================
|
|
MACH Public API
|
|
============================================================ */
|
|
|
|
/* Print a single constant pool value for dump output */
|
|
static void dump_cpool_value(JSContext *ctx, JSValue val) {
|
|
uint32_t tag = JS_VALUE_GET_TAG(val);
|
|
|
|
if (JS_IsPtr(val)) {
|
|
void *ptr = JS_VALUE_GET_PTR(val);
|
|
objhdr_t hdr = *(objhdr_t *)ptr;
|
|
uint8_t mist_type = objhdr_type(hdr);
|
|
if (mist_type == OBJ_TEXT) {
|
|
const char *str = JS_ToCString(ctx, val);
|
|
if (str) {
|
|
printf("\"%s\"", str);
|
|
JS_FreeCString(ctx, str);
|
|
} else {
|
|
printf("<string>");
|
|
}
|
|
return;
|
|
}
|
|
printf("<ptr type=%d>", mist_type);
|
|
return;
|
|
}
|
|
|
|
switch (tag) {
|
|
case JS_TAG_INT:
|
|
printf("%d", JS_VALUE_GET_INT(val));
|
|
break;
|
|
case JS_TAG_BOOL:
|
|
printf("%s", JS_VALUE_GET_BOOL(val) ? "true" : "false");
|
|
break;
|
|
case JS_TAG_NULL:
|
|
printf("null");
|
|
break;
|
|
case JS_TAG_SHORT_FLOAT:
|
|
printf("%g", JS_VALUE_GET_FLOAT64(val));
|
|
break;
|
|
case JS_TAG_STRING_IMM: {
|
|
const char *str = JS_ToCString(ctx, val);
|
|
if (str) {
|
|
printf("\"%s\"", str);
|
|
JS_FreeCString(ctx, str);
|
|
} else {
|
|
printf("<imm string>");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
printf("<tag=%d>", tag);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* (labels removed in new format) */
|
|
|
|
/* Internal helper to dump JSCodeRegister (32-bit instruction format) */
|
|
static void dump_register_code(JSContext *ctx, JSCodeRegister *code, int indent) {
|
|
char pad[64];
|
|
int pad_len = indent * 2;
|
|
if (pad_len > 60) pad_len = 60;
|
|
memset(pad, ' ', pad_len);
|
|
pad[pad_len] = '\0';
|
|
|
|
/* Function header */
|
|
const char *name = "<anonymous>";
|
|
if (!JS_IsNull(code->name)) {
|
|
const char *n = JS_ToCString(ctx, code->name);
|
|
if (n) name = n;
|
|
}
|
|
printf("%sFunction: %s\n", pad, name);
|
|
printf("%s Arity: %d, Slots: %d, Close: %d\n", pad,
|
|
code->arity, code->nr_slots, code->nr_close_slots);
|
|
if (!JS_IsNull(code->name)) {
|
|
JS_FreeCString(ctx, name);
|
|
}
|
|
if (code->disruption_pc > 0)
|
|
printf("%s Disruption handler at: %d\n", pad, code->disruption_pc);
|
|
|
|
/* Constant pool */
|
|
if (code->cpool_count > 0) {
|
|
printf("%s\n%sConstant Pool (%d entries):\n", pad, pad, code->cpool_count);
|
|
for (uint32_t i = 0; i < code->cpool_count; i++) {
|
|
printf("%s [%d]: ", pad, i);
|
|
dump_cpool_value(ctx, code->cpool[i]);
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
/* Instructions */
|
|
printf("%s\n%sInstructions (%d):\n", pad, pad, code->instr_count);
|
|
for (uint32_t i = 0; i < code->instr_count; i++) {
|
|
MachInstr32 instr = code->instructions[i];
|
|
int op = MACH_GET_OP(instr);
|
|
int a = MACH_GET_A(instr);
|
|
int b = MACH_GET_B(instr);
|
|
int c = MACH_GET_C(instr);
|
|
const char *op_name = (op < MACH_OP_COUNT) ? mach_opcode_names[op] : "???";
|
|
if (!op_name) op_name = "???";
|
|
|
|
printf("%s %3d: %-14s ", pad, i, op_name);
|
|
|
|
switch (op) {
|
|
/* No operands */
|
|
case MACH_NOP:
|
|
case MACH_RETNIL:
|
|
break;
|
|
|
|
/* A only */
|
|
case MACH_LOADNULL:
|
|
case MACH_LOADTRUE:
|
|
case MACH_LOADFALSE:
|
|
printf("r%d", a);
|
|
break;
|
|
|
|
/* ABx: load constant */
|
|
case MACH_LOADK: {
|
|
int bx = MACH_GET_Bx(instr);
|
|
printf("r%d, #%d", a, bx);
|
|
if (bx >= 0 && (uint32_t)bx < code->cpool_count) {
|
|
printf(" ; ");
|
|
dump_cpool_value(ctx, code->cpool[bx]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* AsBx: load small int */
|
|
case MACH_LOADI:
|
|
printf("r%d, %d", a, MACH_GET_sBx(instr));
|
|
break;
|
|
|
|
/* A, B: move, unary ops */
|
|
case MACH_MOVE:
|
|
case MACH_NEG:
|
|
case MACH_LNOT:
|
|
case MACH_BNOT:
|
|
printf("r%d, r%d", a, b);
|
|
break;
|
|
|
|
/* A, B, C: arithmetic, comparison, bitwise */
|
|
case MACH_ADD: case MACH_SUB: case MACH_MUL: case MACH_DIV:
|
|
case MACH_MOD: case MACH_POW:
|
|
case MACH_EQ: case MACH_NEQ: case MACH_LT: case MACH_LE:
|
|
case MACH_GT: case MACH_GE:
|
|
case MACH_BAND: case MACH_BOR: case MACH_BXOR:
|
|
case MACH_SHL: case MACH_SHR: case MACH_USHR:
|
|
printf("r%d, r%d, r%d", a, b, c);
|
|
break;
|
|
|
|
case MACH_EQ_TOL: case MACH_NEQ_TOL:
|
|
printf("r%d, r%d, %d", a, b, c);
|
|
break;
|
|
|
|
/* Property access */
|
|
case MACH_GETFIELD:
|
|
printf("r%d, r%d, #%d", a, b, c);
|
|
if ((uint32_t)c < code->cpool_count) {
|
|
printf(" ; ");
|
|
dump_cpool_value(ctx, code->cpool[c]);
|
|
}
|
|
break;
|
|
case MACH_SETFIELD:
|
|
printf("r%d, #%d, r%d", a, b, c);
|
|
if ((uint32_t)b < code->cpool_count) {
|
|
printf(" ; ");
|
|
dump_cpool_value(ctx, code->cpool[b]);
|
|
}
|
|
break;
|
|
case MACH_GETINDEX:
|
|
case MACH_SETINDEX:
|
|
printf("r%d, r%d, r%d", a, b, c);
|
|
break;
|
|
|
|
/* ABx: name/intrinsic/env access */
|
|
case MACH_GETNAME:
|
|
case MACH_GETINTRINSIC:
|
|
case MACH_GETENV: {
|
|
int bx = MACH_GET_Bx(instr);
|
|
printf("r%d, #%d", a, bx);
|
|
if ((uint32_t)bx < code->cpool_count) {
|
|
printf(" ; ");
|
|
dump_cpool_value(ctx, code->cpool[bx]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Closure access */
|
|
case MACH_GETUP:
|
|
case MACH_SETUP:
|
|
printf("r%d, depth=%d, slot=%d", a, b, c);
|
|
break;
|
|
|
|
/* isJ: unconditional jump */
|
|
case MACH_JMP: {
|
|
int offset = MACH_GET_sJ(instr);
|
|
printf("%+d", offset);
|
|
printf(" ; -> %d", (int)i + 1 + offset);
|
|
break;
|
|
}
|
|
|
|
/* iAsBx: conditional jumps */
|
|
case MACH_JMPTRUE:
|
|
case MACH_JMPFALSE:
|
|
case MACH_JMPNULL: {
|
|
int offset = MACH_GET_sBx(instr);
|
|
printf("r%d, %+d", a, offset);
|
|
printf(" ; -> %d", (int)i + 1 + offset);
|
|
break;
|
|
}
|
|
|
|
/* Return / throw */
|
|
case MACH_RETURN:
|
|
case MACH_THROW:
|
|
printf("r%d", a);
|
|
break;
|
|
|
|
/* Object/array creation */
|
|
case MACH_NEWOBJECT:
|
|
printf("r%d", a);
|
|
break;
|
|
case MACH_NEWARRAY:
|
|
printf("r%d, %d", a, b);
|
|
break;
|
|
|
|
/* Push/Pop */
|
|
case MACH_PUSH:
|
|
printf("r%d, r%d", a, b);
|
|
break;
|
|
case MACH_POP:
|
|
printf("r%d, r%d", a, b);
|
|
break;
|
|
|
|
/* Closure */
|
|
case MACH_CLOSURE: {
|
|
int bx = MACH_GET_Bx(instr);
|
|
printf("r%d, func#%d", a, bx);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
printf("0x%08x", instr);
|
|
break;
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
/* Nested functions */
|
|
if (code->func_count > 0) {
|
|
printf("%s\n%sNested Functions (%d):\n", pad, pad, code->func_count);
|
|
for (uint32_t i = 0; i < code->func_count; i++) {
|
|
printf("%s [%d]:\n", pad, i);
|
|
if (code->functions[i]) {
|
|
dump_register_code(ctx, code->functions[i], indent + 2);
|
|
} else {
|
|
printf("%s <null>\n", pad);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
JSValue JS_RunMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue env) {
|
|
MachCode *mc = JS_DeserializeMachCode(data, size);
|
|
if (!mc)
|
|
return JS_RaiseDisrupt(ctx, "failed to deserialize MACH bytecode");
|
|
|
|
JSGCRef env_ref;
|
|
JS_PushGCRef(ctx, &env_ref);
|
|
env_ref.val = env;
|
|
|
|
JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env_ref.val);
|
|
JS_FreeMachCode(mc);
|
|
|
|
JSValue result = JS_CallRegisterVM(ctx, code, ctx->global_obj, 0, NULL, env_ref.val, JS_NULL);
|
|
JS_PopGCRef(ctx, &env_ref);
|
|
return result;
|
|
}
|
|
|
|
JSValue JS_RunMachMcode(JSContext *ctx, const char *json_str, size_t len, JSValue env) {
|
|
(void)len;
|
|
cJSON *mcode = cJSON_Parse(json_str);
|
|
if (!mcode)
|
|
return JS_RaiseDisrupt(ctx, "failed to parse mcode JSON");
|
|
|
|
MachCode *mc = mach_compile_mcode(mcode);
|
|
cJSON_Delete(mcode);
|
|
if (!mc)
|
|
return JS_RaiseDisrupt(ctx, "mcode compilation failed");
|
|
|
|
JSGCRef env_ref;
|
|
JS_PushGCRef(ctx, &env_ref);
|
|
env_ref.val = env;
|
|
|
|
JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env_ref.val);
|
|
JS_FreeMachCode(mc);
|
|
|
|
JSValue result = JS_CallRegisterVM(ctx, code, ctx->global_obj, 0, NULL, env_ref.val, JS_NULL);
|
|
JS_PopGCRef(ctx, &env_ref);
|
|
return result;
|
|
}
|
|
|
|
void JS_DumpMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue env) {
|
|
MachCode *mc = JS_DeserializeMachCode(data, size);
|
|
if (!mc) {
|
|
printf("Failed to deserialize MACH bytecode\n");
|
|
return;
|
|
}
|
|
|
|
JSGCRef env_ref;
|
|
JS_PushGCRef(ctx, &env_ref);
|
|
env_ref.val = env;
|
|
|
|
JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env_ref.val);
|
|
JS_FreeMachCode(mc);
|
|
|
|
dump_register_code(ctx, code, 0);
|
|
JS_PopGCRef(ctx, &env_ref);
|
|
}
|