Merge branch 'master' into fix_aot
This commit is contained in:
@@ -124,7 +124,7 @@ CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item)
|
||||
CJSON_PUBLIC(const char*) cJSON_Version(void)
|
||||
{
|
||||
static char version[15];
|
||||
sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH);
|
||||
snprintf(version, sizeof(version), "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH);
|
||||
|
||||
return version;
|
||||
}
|
||||
@@ -606,22 +606,22 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out
|
||||
/* This checks for NaN and Infinity */
|
||||
if (isnan(d) || isinf(d))
|
||||
{
|
||||
length = sprintf((char*)number_buffer, "null");
|
||||
length = snprintf((char*)number_buffer, sizeof(number_buffer), "null");
|
||||
}
|
||||
else if(d == (double)item->valueint)
|
||||
{
|
||||
length = sprintf((char*)number_buffer, "%d", item->valueint);
|
||||
length = snprintf((char*)number_buffer, sizeof(number_buffer), "%d", item->valueint);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */
|
||||
length = sprintf((char*)number_buffer, "%1.15g", d);
|
||||
length = snprintf((char*)number_buffer, sizeof(number_buffer), "%1.15g", d);
|
||||
|
||||
/* Check whether the original double can be recovered */
|
||||
if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d))
|
||||
{
|
||||
/* If not, print with 17 decimal places of precision */
|
||||
length = sprintf((char*)number_buffer, "%1.17g", d);
|
||||
length = snprintf((char*)number_buffer, sizeof(number_buffer), "%1.17g", d);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1055,7 +1055,7 @@ static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffe
|
||||
break;
|
||||
default:
|
||||
/* escape and print as unicode codepoint */
|
||||
sprintf((char*)output_pointer, "u%04x", *input_pointer);
|
||||
snprintf((char*)output_pointer, 6, "u%04x", *input_pointer);
|
||||
output_pointer += 4;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -28,8 +28,10 @@ static int run_test_suite(size_t heap_size);
|
||||
|
||||
cell_rt *root_cell = NULL;
|
||||
static char *shop_path = NULL;
|
||||
volatile JSContext *g_crash_ctx = NULL;
|
||||
static char *core_path = NULL;
|
||||
static int native_mode = 0;
|
||||
static int warn_mode = 1;
|
||||
static JSRuntime *g_runtime = NULL;
|
||||
|
||||
// Compute blake2b hash of data and return hex string (caller must free)
|
||||
@@ -260,7 +262,7 @@ void actor_disrupt(cell_rt *crt)
|
||||
actor_free(crt);
|
||||
}
|
||||
|
||||
JSValue js_core_os_use(JSContext *js);
|
||||
JSValue js_core_internal_os_use(JSContext *js);
|
||||
JSValue js_core_json_use(JSContext *js);
|
||||
|
||||
void script_startup(cell_rt *prt)
|
||||
@@ -271,6 +273,7 @@ void script_startup(cell_rt *prt)
|
||||
JSContext *js = JS_NewContext(g_runtime);
|
||||
|
||||
JS_SetContextOpaque(js, prt);
|
||||
JS_SetGCScanExternal(js, actor_gc_scan);
|
||||
prt->context = js;
|
||||
|
||||
/* Set per-actor heap memory limit */
|
||||
@@ -316,7 +319,7 @@ void script_startup(cell_rt *prt)
|
||||
JS_AddGCRef(js, &boot_env_ref);
|
||||
boot_env_ref.val = JS_NewObject(js);
|
||||
JSValue btmp;
|
||||
btmp = js_core_os_use(js);
|
||||
btmp = js_core_internal_os_use(js);
|
||||
JS_SetPropertyStr(js, boot_env_ref.val, "os", btmp);
|
||||
if (core_path) {
|
||||
btmp = JS_NewString(js, core_path);
|
||||
@@ -346,7 +349,7 @@ void script_startup(cell_rt *prt)
|
||||
JS_AddGCRef(js, &env_ref);
|
||||
env_ref.val = JS_NewObject(js);
|
||||
JSValue tmp;
|
||||
tmp = js_core_os_use(js);
|
||||
tmp = js_core_internal_os_use(js);
|
||||
JS_SetPropertyStr(js, env_ref.val, "os", tmp);
|
||||
tmp = js_core_json_use(js);
|
||||
JS_SetPropertyStr(js, env_ref.val, "json", tmp);
|
||||
@@ -407,6 +410,13 @@ static void signal_handler(int sig)
|
||||
#endif
|
||||
if (!str) return;
|
||||
|
||||
/* Reset handler to default so a double-fault terminates immediately */
|
||||
signal(sig, SIG_DFL);
|
||||
|
||||
/* Try to print the JS stack (best-effort, signal-safe) */
|
||||
if (g_crash_ctx)
|
||||
JS_CrashPrintStack((JSContext *)g_crash_ctx);
|
||||
|
||||
exit_handler();
|
||||
}
|
||||
|
||||
@@ -523,6 +533,9 @@ int cell_init(int argc, char **argv)
|
||||
} else if (strcmp(argv[arg_start], "--native") == 0) {
|
||||
native_mode = 1;
|
||||
arg_start++;
|
||||
} else if (strcmp(argv[arg_start], "--no-warn") == 0) {
|
||||
warn_mode = 0;
|
||||
arg_start++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -562,6 +575,7 @@ int cell_init(int argc, char **argv)
|
||||
|
||||
cli_rt->context = ctx;
|
||||
JS_SetContextOpaque(ctx, cli_rt);
|
||||
JS_SetGCScanExternal(ctx, actor_gc_scan);
|
||||
|
||||
JS_AddGCRef(ctx, &cli_rt->idx_buffer_ref);
|
||||
JS_AddGCRef(ctx, &cli_rt->on_exception_ref);
|
||||
@@ -608,7 +622,7 @@ int cell_init(int argc, char **argv)
|
||||
JS_AddGCRef(ctx, &boot_env_ref);
|
||||
boot_env_ref.val = JS_NewObject(ctx);
|
||||
JSValue btmp;
|
||||
btmp = js_core_os_use(ctx);
|
||||
btmp = js_core_internal_os_use(ctx);
|
||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "os", btmp);
|
||||
btmp = JS_NewString(ctx, core_path);
|
||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "core_path", btmp);
|
||||
@@ -652,7 +666,7 @@ int cell_init(int argc, char **argv)
|
||||
JS_AddGCRef(ctx, &env_ref);
|
||||
env_ref.val = JS_NewObject(ctx);
|
||||
JSValue tmp;
|
||||
tmp = js_core_os_use(ctx);
|
||||
tmp = js_core_internal_os_use(ctx);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "os", tmp);
|
||||
tmp = JS_NewString(ctx, core_path);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
|
||||
@@ -661,11 +675,14 @@ int cell_init(int argc, char **argv)
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
|
||||
tmp = js_core_json_use(ctx);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
|
||||
if (native_mode) {
|
||||
if (native_mode || !warn_mode) {
|
||||
JSGCRef init_ref;
|
||||
JS_AddGCRef(ctx, &init_ref);
|
||||
init_ref.val = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, init_ref.val, "native_mode", JS_NewBool(ctx, 1));
|
||||
if (native_mode)
|
||||
JS_SetPropertyStr(ctx, init_ref.val, "native_mode", JS_NewBool(ctx, 1));
|
||||
if (!warn_mode)
|
||||
JS_SetPropertyStr(ctx, init_ref.val, "no_warn", JS_NewBool(ctx, 1));
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "init", init_ref.val);
|
||||
JS_DeleteGCRef(ctx, &init_ref);
|
||||
} else {
|
||||
@@ -682,7 +699,9 @@ int cell_init(int argc, char **argv)
|
||||
JS_DeleteGCRef(ctx, &args_ref);
|
||||
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
|
||||
|
||||
g_crash_ctx = ctx;
|
||||
JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env);
|
||||
g_crash_ctx = NULL;
|
||||
JS_DeleteGCRef(ctx, &env_ref);
|
||||
free(bin_data);
|
||||
|
||||
@@ -757,9 +776,9 @@ double cell_random() {
|
||||
return (double)buf / 9007199254740992.0;
|
||||
}
|
||||
|
||||
void cell_trace_sethook(cell_hook)
|
||||
void cell_trace_sethook(cell_hook hook)
|
||||
{
|
||||
|
||||
(void)hook;
|
||||
}
|
||||
|
||||
int uncaught_exception(JSContext *js, JSValue v)
|
||||
|
||||
@@ -10,6 +10,8 @@ extern "C" {
|
||||
|
||||
// blob fns
|
||||
JSValue js_core_blob_use(JSContext *js);
|
||||
JSValue js_new_blob_alloc(JSContext *js, size_t bytes, void **out);
|
||||
void js_blob_stone(JSValue blob, size_t actual_bytes);
|
||||
JSValue js_new_blob_stoned_copy(JSContext *js, void *data, size_t bytes);
|
||||
void *js_get_blob_data(JSContext *js, size_t *size, JSValue v); // bytes
|
||||
void *js_get_blob_data_bits(JSContext *js, size_t *bits, JSValue v); // bits
|
||||
|
||||
@@ -27,7 +27,7 @@ typedef struct letter {
|
||||
// #define ACTOR_TRACE
|
||||
|
||||
#define ACTOR_FAST_TIMER_NS (10ULL * 1000000) // 10ms per turn
|
||||
#define ACTOR_SLOW_TIMER_NS (5000ULL * 1000000) // 5s for slow actors
|
||||
#define ACTOR_SLOW_TIMER_NS (60000ULL * 1000000) // 60s for slow actors
|
||||
#define ACTOR_SLOW_STRIKES_MAX 3 // consecutive slow turns -> kill
|
||||
#define ACTOR_MEMORY_LIMIT (16ULL * 1024 * 1024) // 16MB heap cap
|
||||
|
||||
@@ -76,6 +76,10 @@ typedef struct cell_rt {
|
||||
cell_hook trace_hook;
|
||||
} cell_rt;
|
||||
|
||||
/* Set by actor_turn/CLI before entering the VM, cleared after.
|
||||
Read by signal_handler to print JS stack on crash. */
|
||||
extern volatile JSContext *g_crash_ctx;
|
||||
|
||||
cell_rt *create_actor(void *wota);
|
||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
|
||||
void actor_disrupt(cell_rt *actor);
|
||||
@@ -96,6 +100,8 @@ void exit_handler(void);
|
||||
void actor_loop();
|
||||
void actor_initialize(void);
|
||||
void actor_free(cell_rt *actor);
|
||||
void actor_gc_scan(JSContext *ctx, uint8_t *fb, uint8_t *fe,
|
||||
uint8_t *tb, uint8_t **tf, uint8_t *te);
|
||||
int scheduler_actor_count(void);
|
||||
void scheduler_enable_quiescence(void);
|
||||
|
||||
|
||||
687
source/mach.c
687
source/mach.c
@@ -25,7 +25,363 @@
|
||||
|
||||
#include "quickjs-internal.h"
|
||||
|
||||
/* ============================================================
|
||||
Mach VM instruction definitions (private to mach.c)
|
||||
============================================================ */
|
||||
|
||||
/* Encoding macros */
|
||||
#define MACH_ABC(op, a, b, c) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(b)<<16) | ((uint32_t)(c)<<24))
|
||||
#define MACH_ABx(op, a, bx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(bx)<<16))
|
||||
#define MACH_AsBx(op, a, sbx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(uint16_t)(sbx)<<16))
|
||||
#define MACH_sJ(op, sj) ((uint32_t)(op) | (((uint32_t)(sj) & 0xFFFFFF) << 8))
|
||||
|
||||
/* Decoding macros */
|
||||
#define MACH_GET_OP(i) ((i) & 0xFF)
|
||||
#define MACH_GET_A(i) (((i) >> 8) & 0xFF)
|
||||
#define MACH_GET_B(i) (((i) >> 16) & 0xFF)
|
||||
#define MACH_GET_C(i) (((i) >> 24) & 0xFF)
|
||||
#define MACH_GET_Bx(i) ((i) >> 16)
|
||||
#define MACH_GET_sBx(i) ((int16_t)((i) >> 16))
|
||||
#define MACH_GET_sJ(i) ((int32_t)((i) & 0xFFFFFF00) >> 8)
|
||||
|
||||
/* ============================================================
|
||||
GC Safepoint Classification for the MACH VM Dispatch Loop
|
||||
============================================================
|
||||
|
||||
Every opcode falls into one of three categories:
|
||||
|
||||
[P] Pure inline — never calls C, never allocates. No GC possible.
|
||||
No frame re-derivation needed after execution.
|
||||
|
||||
[N] Non-allocating C call — calls a C function that is guaranteed
|
||||
to never allocate (e.g. JS_ToBool, js_string_compare_value).
|
||||
No frame re-derivation needed.
|
||||
|
||||
[G] GC safepoint — calls C that may allocate, triggering GC.
|
||||
After the call, all heap pointers (including `frame`) MUST be
|
||||
re-derived via: frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
|
||||
The 18 C entry points that can allocate (GC safepoints):
|
||||
1. JS_GetProperty — key interning for string keys >7 chars
|
||||
2. JS_SetProperty — rec_resize when record grows
|
||||
3. JS_GetPropertyNumber — text substring extraction
|
||||
4. JS_SetPropertyNumber — array grow
|
||||
5. JS_NewObject — allocates record
|
||||
6. JS_NewArray / JS_NewArrayLen — allocates array
|
||||
7. js_new_register_function — allocates function object (closure)
|
||||
8. alloc_frame_register — allocates frame via js_mallocz
|
||||
9. js_call_c_function — arbitrary C code
|
||||
10. JS_CallInternal — arbitrary bytecode
|
||||
11. JS_Call — arbitrary call
|
||||
12. JS_ConcatString — allocates new string
|
||||
13. JS_ArrayPush — array grow
|
||||
14. JS_ArrayPop — reads, but frame refresh needed
|
||||
15. JS_DeleteProperty — mutates record
|
||||
16. JS_HasProperty — complex traversal
|
||||
17. js_regexp_constructor — allocates regex
|
||||
18. reg_vm_binop — polymorphic dispatch (legacy opcodes)
|
||||
|
||||
Opcode-level classification:
|
||||
[P] LOADK, LOADI, LOADNULL, LOADTRUE, LOADFALSE, MOVE, NOP
|
||||
[P] ADD_INT..MOD_INT, NEG_INT, ADD_FLOAT..MOD_FLOAT, NEG_FLOAT
|
||||
[P] EQ_INT..GE_INT, EQ_FLOAT..GE_FLOAT, EQ_BOOL, NE_BOOL
|
||||
[P] IS_IDENTICAL, IS_INT, IS_NUM, IS_TEXT, IS_BOOL, IS_NULL
|
||||
[P] IS_ARRAY, IS_FUNC, IS_RECORD, IS_STONE, IS_PROXY
|
||||
[P] NOT, AND, OR, BITNOT, BITAND, BITOR, BITXOR
|
||||
[P] JMP, JMPTRUE, JMPFALSE, JMPNULL, JMPNOTNULL
|
||||
[P] RETURN, RETNIL, SETARG, GETUP, SETUP, DISRUPT, THROW
|
||||
[P] LENGTH (array + imm-ASCII fast path only; text/blob fallback is [G])
|
||||
[N] EQ_TEXT..GE_TEXT (js_string_compare_value — no allocation)
|
||||
[N] LNOT (JS_ToBool — no allocation)
|
||||
[G] ADD..USHR, NEG, INC, DEC, EQ..GE (legacy: reg_vm_binop)
|
||||
[G] EQ_TOL, NEQ_TOL (tolerance comparison, may fall back)
|
||||
[G] CONCAT (JS_ConcatString)
|
||||
[G] GETFIELD, SETFIELD, GETINDEX, SETINDEX (property access)
|
||||
[G] LOAD_FIELD, STORE_FIELD, LOAD_INDEX, STORE_INDEX
|
||||
[G] LOAD_DYNAMIC, STORE_DYNAMIC
|
||||
[G] GETNAME, GETINTRINSIC, GETENV, SET_VAR
|
||||
[G] NEWOBJECT, NEWRECORD, NEWARRAY (object/array creation)
|
||||
[G] CLOSURE (js_new_register_function)
|
||||
[G] FRAME, GOFRAME (alloc_frame_register)
|
||||
[G] INVOKE, GOINVOKE (function calls)
|
||||
[G] PUSH (JS_ArrayPush), POP (JS_ArrayPop)
|
||||
[G] DELETE, DELETEINDEX (JS_DeleteProperty)
|
||||
[G] HASPROP, IN (JS_HasProperty)
|
||||
[G] REGEXP (js_regexp_constructor)
|
||||
============================================================ */
|
||||
|
||||
typedef enum MachOpcode {
|
||||
/* === Legacy opcodes (used by existing .mach files) === */
|
||||
|
||||
/* Constants & Loading */
|
||||
MACH_LOADK, /* R(A) = K(Bx) — load from constant pool (ABx) */
|
||||
MACH_LOADI, /* R(A) = (int16_t)sBx — load small integer (AsBx) */
|
||||
MACH_LOADNULL, /* R(A) = null (A only) */
|
||||
MACH_LOADTRUE, /* R(A) = true (A only) */
|
||||
MACH_LOADFALSE, /* R(A) = false (A only) */
|
||||
|
||||
/* Movement */
|
||||
MACH_MOVE, /* R(A) = R(B) */
|
||||
|
||||
/* Generic arithmetic (ABC) — used by legacy .mach */
|
||||
MACH_ADD, /* R(A) = R(B) + R(C) */
|
||||
MACH_SUB, /* R(A) = R(B) - R(C) */
|
||||
MACH_MUL, /* R(A) = R(B) * R(C) */
|
||||
MACH_DIV, /* R(A) = R(B) / R(C) */
|
||||
MACH_MOD, /* R(A) = R(B) % R(C) */
|
||||
MACH_POW, /* R(A) = R(B) ** R(C) */
|
||||
MACH_NEG, /* R(A) = -R(B) */
|
||||
MACH_REMAINDER, /* R(A) = remainder(R(B), R(C)) */
|
||||
MACH_MAX, /* R(A) = max(R(B), R(C)) */
|
||||
MACH_MIN, /* R(A) = min(R(B), R(C)) */
|
||||
MACH_ABS, /* R(A) = abs(R(B)) */
|
||||
MACH_SIGN, /* R(A) = sign(R(B)) */
|
||||
MACH_FRACTION, /* R(A) = fraction(R(B)) */
|
||||
MACH_INTEGER, /* R(A) = integer(R(B)) */
|
||||
MACH_FLOOR, /* R(A) = floor(R(B), R(C)) */
|
||||
MACH_CEILING, /* R(A) = ceiling(R(B), R(C)) */
|
||||
MACH_ROUND, /* R(A) = round(R(B), R(C)) */
|
||||
MACH_TRUNC, /* R(A) = trunc(R(B), R(C)) */
|
||||
MACH__DEAD_INC, /* reserved — was MACH_INC, never emitted */
|
||||
MACH__DEAD_DEC, /* reserved — was MACH_DEC, never emitted */
|
||||
|
||||
/* Generic comparison (ABC) — used by legacy .mach */
|
||||
MACH_EQ, /* R(A) = (R(B) == R(C)) */
|
||||
MACH_NEQ, /* R(A) = (R(B) != R(C)) */
|
||||
MACH_LT, /* R(A) = (R(B) < R(C)) */
|
||||
MACH_LE, /* R(A) = (R(B) <= R(C)) */
|
||||
MACH_GT, /* R(A) = (R(B) > R(C)) */
|
||||
MACH_GE, /* R(A) = (R(B) >= R(C)) */
|
||||
|
||||
/* Logical/Bitwise — used by legacy .mach */
|
||||
MACH_LNOT, /* R(A) = !R(B) */
|
||||
MACH_BNOT, /* R(A) = ~R(B) */
|
||||
MACH_BAND, /* R(A) = R(B) & R(C) */
|
||||
MACH_BOR, /* R(A) = R(B) | R(C) */
|
||||
MACH_BXOR, /* R(A) = R(B) ^ R(C) */
|
||||
MACH_SHL, /* R(A) = R(B) << R(C) */
|
||||
MACH_SHR, /* R(A) = R(B) >> R(C) */
|
||||
MACH_USHR, /* R(A) = R(B) >>> R(C) */
|
||||
|
||||
/* Property access — used by legacy .mach */
|
||||
MACH_GETFIELD, /* R(A) = R(B)[K(C)] — named property */
|
||||
MACH_SETFIELD, /* R(A)[K(B)] = R(C) — named property */
|
||||
MACH_GETINDEX, /* R(A) = R(B)[R(C)] — computed property */
|
||||
MACH_SETINDEX, /* R(A)[R(B)] = R(C) — computed property */
|
||||
|
||||
/* Unbound variable access (ABx) */
|
||||
MACH_GETNAME, /* R(A) = resolve(K(Bx)) — compiler placeholder, patched by link */
|
||||
MACH_GETINTRINSIC, /* R(A) = global[K(Bx)] — post-link, intrinsic/built-in */
|
||||
MACH_GETENV, /* R(A) = env[K(Bx)] — post-link, module environment */
|
||||
|
||||
/* Closure access (ABC) */
|
||||
MACH_GETUP, /* R(A) = outer_frame[B].slots[C] */
|
||||
MACH_SETUP, /* outer_frame[B].slots[C] = R(A) */
|
||||
|
||||
/* Control flow */
|
||||
MACH_JMP, /* pc += sJ — unconditional (isJ format) */
|
||||
MACH_JMPTRUE, /* if R(A): pc += sBx — (iAsBx format) */
|
||||
MACH_JMPFALSE, /* if !R(A): pc += sBx — (iAsBx format) */
|
||||
MACH_JMPNULL, /* if R(A)==null: pc += sBx */
|
||||
|
||||
/* Function calls — Lua-style consecutive registers (legacy .mach) */
|
||||
MACH_RETURN, /* Return R(A) */
|
||||
MACH_RETNIL, /* Return null */
|
||||
|
||||
/* Object/array creation — legacy .mach */
|
||||
MACH_NEWOBJECT, /* R(A) = {} */
|
||||
MACH_NEWARRAY, /* R(A) = new array, B = element count in R(A+1)..R(A+B) */
|
||||
MACH_CLOSURE, /* R(A) = closure(functions[Bx]) (ABx) */
|
||||
|
||||
MACH_THROW, /* disrupt — trigger disruption */
|
||||
|
||||
MACH_PUSH, /* push R(B) onto array R(A) */
|
||||
MACH_POP, /* R(A) = pop last element from array R(B) */
|
||||
|
||||
MACH_DELETE, /* R(A) = delete R(B)[K(C)] — named property delete */
|
||||
MACH_DELETEINDEX, /* R(A) = delete R(B)[R(C)] — computed property delete */
|
||||
MACH_HASPROP, /* R(A) = R(C) in R(B) — has property check */
|
||||
MACH_REGEXP, /* R(A) = regexp(K(B), K(C)) — regex literal */
|
||||
|
||||
MACH_EQ_TOL, /* R(A) = eq_tol(R(B), R(B+1), R(B+2)), C=3 */
|
||||
MACH_NEQ_TOL, /* R(A) = ne_tol(R(B), R(B+1), R(B+2)), C=3 */
|
||||
|
||||
MACH_NOP,
|
||||
|
||||
/* === New mcode-derived opcodes (1:1 mapping to mcode IR) === */
|
||||
|
||||
/* Text */
|
||||
MACH_CONCAT, /* R(A) = R(B) ++ R(C) — string concatenation */
|
||||
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_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",
|
||||
};
|
||||
|
||||
/* ---- Compile-time constant pool entry ---- */
|
||||
/* Stores raw data during compilation; converted to JSValues when loading into context */
|
||||
@@ -92,6 +448,37 @@ static int mach_check_call_arity(JSContext *ctx, JSFunction *fn, int argc) {
|
||||
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) {
|
||||
@@ -538,7 +925,6 @@ JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JSValue e
|
||||
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);
|
||||
@@ -648,10 +1034,6 @@ static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
|
||||
}
|
||||
}
|
||||
|
||||
/* String concat for ADD */
|
||||
if (op == MACH_ADD && mist_is_text(a) && mist_is_text(b))
|
||||
return JS_ConcatString(ctx, a, b);
|
||||
|
||||
/* 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) */
|
||||
@@ -710,7 +1092,10 @@ static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
/* Different types: EQ→false, NEQ→true, others→false */
|
||||
/* 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);
|
||||
}
|
||||
@@ -821,8 +1206,8 @@ void __asan_on_error(void) {
|
||||
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->u.cell.code)->u.reg.code) {
|
||||
JSCodeRegister *code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
|
||||
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)
|
||||
@@ -833,7 +1218,7 @@ void __asan_on_error(void) {
|
||||
fprintf(stderr, " %s (%s:%u)\n",
|
||||
func_name ? func_name : "<anonymous>",
|
||||
file ? file : "<unknown>", line);
|
||||
if (JS_IsNull(frame->caller)) break;
|
||||
if (!JS_IsPtr(frame->caller)) break;
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
||||
is_first = 0;
|
||||
}
|
||||
@@ -858,7 +1243,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
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->u.cell.code)->u.reg.code;
|
||||
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;
|
||||
@@ -913,6 +1298,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
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 */
|
||||
@@ -946,6 +1332,11 @@ vm_dispatch:
|
||||
#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,
|
||||
@@ -984,17 +1375,7 @@ vm_dispatch:
|
||||
DT(MACH_HASPROP), DT(MACH_REGEXP),
|
||||
DT(MACH_EQ_TOL), DT(MACH_NEQ_TOL),
|
||||
DT(MACH_NOP),
|
||||
DT(MACH_CONCAT),
|
||||
DT(MACH_EQ_INT), DT(MACH_NE_INT),
|
||||
DT(MACH_LT_INT), DT(MACH_LE_INT),
|
||||
DT(MACH_GT_INT), DT(MACH_GE_INT),
|
||||
DT(MACH_EQ_FLOAT), DT(MACH_NE_FLOAT),
|
||||
DT(MACH_LT_FLOAT), DT(MACH_LE_FLOAT),
|
||||
DT(MACH_GT_FLOAT), DT(MACH_GE_FLOAT),
|
||||
DT(MACH_EQ_TEXT), DT(MACH_NE_TEXT),
|
||||
DT(MACH_LT_TEXT), DT(MACH_LE_TEXT),
|
||||
DT(MACH_GT_TEXT), DT(MACH_GE_TEXT),
|
||||
DT(MACH_EQ_BOOL), DT(MACH_NE_BOOL),
|
||||
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),
|
||||
@@ -1016,6 +1397,7 @@ vm_dispatch:
|
||||
DT(MACH_IS_RECORD), DT(MACH_IS_STONE),
|
||||
DT(MACH_LENGTH), DT(MACH_IS_PROXY),
|
||||
};
|
||||
#pragma GCC diagnostic pop
|
||||
#undef DT
|
||||
#define VM_DECODE() do { \
|
||||
ctx->reg_current_frame = frame_ref.val; \
|
||||
@@ -1057,28 +1439,34 @@ vm_dispatch:
|
||||
|
||||
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();
|
||||
|
||||
@@ -1617,6 +2005,7 @@ vm_dispatch:
|
||||
}
|
||||
target = next;
|
||||
}
|
||||
stone_mutable_text(target->slots[c]);
|
||||
frame->slots[a] = target->slots[c];
|
||||
VM_BREAK();
|
||||
}
|
||||
@@ -1630,6 +2019,18 @@ vm_dispatch:
|
||||
fn = JS_VALUE_GET_FUNCTION(target->function);
|
||||
target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
|
||||
}
|
||||
{
|
||||
uint64_t tcap = objhdr_cap56(target->header);
|
||||
if ((unsigned)c >= tcap) {
|
||||
fprintf(stderr, "MACH_SETUP OOB: slot=%d >= target_cap=%llu depth=%d "
|
||||
"cur_fn=%s (%s) pc=%u\n",
|
||||
c, (unsigned long long)tcap, depth,
|
||||
code->name_cstr ? code->name_cstr : "?",
|
||||
code->filename_cstr ? code->filename_cstr : "?", pc - 1);
|
||||
fflush(stderr);
|
||||
VM_BREAK();
|
||||
}
|
||||
}
|
||||
target->slots[c] = frame->slots[a];
|
||||
VM_BREAK();
|
||||
}
|
||||
@@ -1714,17 +2115,18 @@ vm_dispatch:
|
||||
}
|
||||
|
||||
VM_CASE(MACH_RETURN):
|
||||
stone_mutable_text(frame->slots[a]);
|
||||
result = frame->slots[a];
|
||||
if (JS_IsNull(frame->caller)) goto done;
|
||||
if (!JS_IsPtr(frame->caller)) goto done;
|
||||
{
|
||||
#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(callee_fn->u.cell.code)->u.reg.code) {
|
||||
if (JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code->name_cstr) callee_name = JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code->name_cstr;
|
||||
if (JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code->filename_cstr) callee_file = JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code->filename_cstr;
|
||||
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
|
||||
@@ -1734,11 +2136,12 @@ vm_dispatch:
|
||||
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->u.cell.code)->u.reg.code;
|
||||
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);
|
||||
@@ -1759,7 +2162,7 @@ vm_dispatch:
|
||||
|
||||
VM_CASE(MACH_RETNIL):
|
||||
result = JS_NULL;
|
||||
if (JS_IsNull(frame->caller)) goto done;
|
||||
if (!JS_IsPtr(frame->caller)) goto done;
|
||||
{
|
||||
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
||||
frame->caller = JS_NULL;
|
||||
@@ -1767,11 +2170,14 @@ vm_dispatch:
|
||||
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->u.cell.code)->u.reg.code;
|
||||
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;
|
||||
if (ret_slot != 0xFFFF) {
|
||||
|
||||
frame->slots[ret_slot] = result;
|
||||
}
|
||||
}
|
||||
VM_BREAK();
|
||||
|
||||
@@ -1793,6 +2199,7 @@ vm_dispatch:
|
||||
|
||||
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 */
|
||||
@@ -1876,81 +2283,46 @@ vm_dispatch:
|
||||
|
||||
/* === New mcode-derived opcodes === */
|
||||
|
||||
/* Text concatenation */
|
||||
/* Text concatenation — with in-place append fast path for s = s + x */
|
||||
VM_CASE(MACH_CONCAT): {
|
||||
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();
|
||||
}
|
||||
|
||||
/* Typed integer comparisons */
|
||||
VM_CASE(MACH_EQ_INT):
|
||||
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) == JS_VALUE_GET_INT(frame->slots[c]));
|
||||
VM_BREAK();
|
||||
VM_CASE(MACH_NE_INT):
|
||||
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) != JS_VALUE_GET_INT(frame->slots[c]));
|
||||
VM_BREAK();
|
||||
VM_CASE(MACH_LT_INT):
|
||||
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) < JS_VALUE_GET_INT(frame->slots[c]));
|
||||
VM_BREAK();
|
||||
VM_CASE(MACH_LE_INT):
|
||||
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) <= JS_VALUE_GET_INT(frame->slots[c]));
|
||||
VM_BREAK();
|
||||
VM_CASE(MACH_GT_INT):
|
||||
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) > JS_VALUE_GET_INT(frame->slots[c]));
|
||||
VM_BREAK();
|
||||
VM_CASE(MACH_GE_INT):
|
||||
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[b]) >= JS_VALUE_GET_INT(frame->slots[c]));
|
||||
VM_BREAK();
|
||||
|
||||
/* Typed float comparisons */
|
||||
VM_CASE(MACH_EQ_FLOAT): VM_CASE(MACH_NE_FLOAT):
|
||||
VM_CASE(MACH_LT_FLOAT): VM_CASE(MACH_LE_FLOAT):
|
||||
VM_CASE(MACH_GT_FLOAT): VM_CASE(MACH_GE_FLOAT): {
|
||||
double da, db;
|
||||
JS_ToFloat64(ctx, &da, frame->slots[b]);
|
||||
JS_ToFloat64(ctx, &db, frame->slots[c]);
|
||||
int r;
|
||||
switch (op) {
|
||||
case MACH_EQ_FLOAT: r = (da == db); break;
|
||||
case MACH_NE_FLOAT: r = (da != db); break;
|
||||
case MACH_LT_FLOAT: r = (da < db); break;
|
||||
case MACH_LE_FLOAT: r = (da <= db); break;
|
||||
case MACH_GT_FLOAT: r = (da > db); break;
|
||||
case MACH_GE_FLOAT: r = (da >= db); break;
|
||||
default: r = 0; break;
|
||||
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;
|
||||
}
|
||||
frame->slots[a] = JS_NewBool(ctx, r);
|
||||
VM_BREAK();
|
||||
}
|
||||
|
||||
/* Typed text comparisons */
|
||||
VM_CASE(MACH_EQ_TEXT): VM_CASE(MACH_NE_TEXT):
|
||||
VM_CASE(MACH_LT_TEXT): VM_CASE(MACH_LE_TEXT):
|
||||
VM_CASE(MACH_GT_TEXT): VM_CASE(MACH_GE_TEXT): {
|
||||
int cmp = js_string_compare_value(ctx, frame->slots[b], frame->slots[c], FALSE);
|
||||
int r;
|
||||
switch (op) {
|
||||
case MACH_EQ_TEXT: r = (cmp == 0); break;
|
||||
case MACH_NE_TEXT: r = (cmp != 0); break;
|
||||
case MACH_LT_TEXT: r = (cmp < 0); break;
|
||||
case MACH_LE_TEXT: r = (cmp <= 0); break;
|
||||
case MACH_GT_TEXT: r = (cmp > 0); break;
|
||||
case MACH_GE_TEXT: r = (cmp >= 0); break;
|
||||
default: r = 0; break;
|
||||
}
|
||||
frame->slots[a] = JS_NewBool(ctx, r);
|
||||
VM_BREAK();
|
||||
}
|
||||
|
||||
/* Typed bool comparisons */
|
||||
VM_CASE(MACH_EQ_BOOL):
|
||||
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_BOOL(frame->slots[b]) == JS_VALUE_GET_BOOL(frame->slots[c]));
|
||||
VM_BREAK();
|
||||
VM_CASE(MACH_NE_BOOL):
|
||||
frame->slots[a] = JS_NewBool(ctx, JS_VALUE_GET_BOOL(frame->slots[b]) != JS_VALUE_GET_BOOL(frame->slots[c]));
|
||||
/* Stone mutable text — compiler-emitted at escape points */
|
||||
VM_CASE(MACH_STONE_TEXT):
|
||||
stone_mutable_text(frame->slots[a]);
|
||||
VM_BREAK();
|
||||
|
||||
/* Identity check */
|
||||
@@ -2151,11 +2523,24 @@ vm_dispatch:
|
||||
/* A=frame_slot, B=func_reg, C=argc */
|
||||
JSValue func_val = frame->slots[b];
|
||||
if (!mist_is_function(func_val)) {
|
||||
JS_RaiseDisrupt(ctx, "not a function");
|
||||
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;
|
||||
}
|
||||
int nr = c + 2; /* argc + this + func overhead */
|
||||
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);
|
||||
@@ -2164,6 +2549,7 @@ vm_dispatch:
|
||||
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();
|
||||
}
|
||||
@@ -2176,36 +2562,19 @@ vm_dispatch:
|
||||
VM_CASE(MACH_INVOKE): {
|
||||
/* A=frame_slot, B=result_slot */
|
||||
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
|
||||
int nr = (int)objhdr_cap56(fr->header);
|
||||
int c_argc = (nr >= 2) ? nr - 2 : 0;
|
||||
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: switch frames inline (fast path) */
|
||||
/* Register function: FRAME already allocated nr_slots — just switch */
|
||||
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
|
||||
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots);
|
||||
if (!new_frame) {
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
goto disrupt;
|
||||
}
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
|
||||
fn_val = fr->function;
|
||||
fn = JS_VALUE_GET_FUNCTION(fn_val);
|
||||
fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
|
||||
new_frame->function = fn_val;
|
||||
/* Copy this + args from call frame to new frame */
|
||||
int copy_count = (c_argc < fn_code->arity) ? c_argc : fn_code->arity;
|
||||
new_frame->slots[0] = fr->slots[0]; /* this */
|
||||
for (int i = 0; i < copy_count; i++)
|
||||
new_frame->slots[1 + i] = fr->slots[1 + i];
|
||||
/* Save return info */
|
||||
frame->address = JS_NewInt32(ctx, (pc << 16) | b);
|
||||
new_frame->caller = JS_MKPTR(frame);
|
||||
frame = new_frame;
|
||||
fr->caller = JS_MKPTR(frame);
|
||||
frame = fr;
|
||||
frame_ref.val = JS_MKPTR(frame);
|
||||
code = fn_code;
|
||||
env = fn->u.cell.env_record;
|
||||
@@ -2249,15 +2618,14 @@ vm_dispatch:
|
||||
VM_CASE(MACH_GOINVOKE): {
|
||||
/* Tail call: replace current frame with callee */
|
||||
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
|
||||
int nr = (int)objhdr_cap56(fr->header);
|
||||
int c_argc = (nr >= 2) ? nr - 2 : 0;
|
||||
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->u.cell.code)->u.reg.code;
|
||||
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
|
||||
int current_slots = (int)objhdr_cap56(frame->header);
|
||||
|
||||
if (fn_code->nr_slots <= current_slots) {
|
||||
@@ -2275,25 +2643,10 @@ vm_dispatch:
|
||||
env = fn->u.cell.env_record;
|
||||
pc = code->entry_point;
|
||||
} else {
|
||||
/* SLOW PATH: callee needs more slots, must allocate */
|
||||
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots);
|
||||
if (!new_frame) {
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
goto disrupt;
|
||||
}
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
|
||||
fn_val = fr->function;
|
||||
fn = JS_VALUE_GET_FUNCTION(fn_val);
|
||||
fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
|
||||
new_frame->function = fn_val;
|
||||
int copy_count = (c_argc < fn_code->arity) ? c_argc : fn_code->arity;
|
||||
new_frame->slots[0] = fr->slots[0]; /* this */
|
||||
for (int i = 0; i < copy_count; i++)
|
||||
new_frame->slots[1 + i] = fr->slots[1 + i];
|
||||
new_frame->caller = frame->caller;
|
||||
/* SLOW PATH: GOFRAME already allocated nr_slots — use fr directly */
|
||||
fr->caller = frame->caller;
|
||||
frame->caller = JS_NULL;
|
||||
frame = new_frame;
|
||||
frame = fr;
|
||||
frame_ref.val = JS_MKPTR(frame);
|
||||
code = fn_code;
|
||||
env = fn->u.cell.env_record;
|
||||
@@ -2317,14 +2670,14 @@ vm_dispatch:
|
||||
if (JS_IsException(ret)) goto disrupt;
|
||||
/* Tail-return: act like MACH_RETURN with the result */
|
||||
result = ret;
|
||||
if (JS_IsNull(frame->caller)) goto done;
|
||||
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(ret_fn->u.cell.code)->u.reg.code;
|
||||
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;
|
||||
@@ -2377,7 +2730,7 @@ vm_dispatch:
|
||||
uint32_t frame_pc = pc;
|
||||
for (;;) {
|
||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
|
||||
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;
|
||||
@@ -2387,7 +2740,7 @@ vm_dispatch:
|
||||
ctx->current_exception = JS_NULL;
|
||||
break;
|
||||
}
|
||||
if (JS_IsNull(frame->caller)) {
|
||||
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 */
|
||||
@@ -2423,6 +2776,10 @@ done:
|
||||
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;
|
||||
}
|
||||
@@ -2543,10 +2900,10 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
||||
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, "ERROR: function '%s' has %d slots (max 255). "
|
||||
fprintf(stderr, "FATAL: function '%s' has %d slots (max 255). "
|
||||
"Ensure the streamline optimizer ran before mach compilation.\n",
|
||||
fn_name, s.nr_slots);
|
||||
return NULL;
|
||||
abort();
|
||||
}
|
||||
int dis_raw = (int)cJSON_GetNumberValue(
|
||||
cJSON_GetObjectItemCaseSensitive(fobj, "disruption_pc"));
|
||||
@@ -2613,6 +2970,7 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
||||
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); }
|
||||
@@ -2632,30 +2990,13 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
||||
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); }
|
||||
/* Typed integer comparisons */
|
||||
else if (strcmp(op, "eq_int") == 0) { ABC3(MACH_EQ_INT); }
|
||||
else if (strcmp(op, "ne_int") == 0) { ABC3(MACH_NE_INT); }
|
||||
else if (strcmp(op, "lt_int") == 0) { ABC3(MACH_LT_INT); }
|
||||
else if (strcmp(op, "le_int") == 0) { ABC3(MACH_LE_INT); }
|
||||
else if (strcmp(op, "gt_int") == 0) { ABC3(MACH_GT_INT); }
|
||||
else if (strcmp(op, "ge_int") == 0) { ABC3(MACH_GE_INT); }
|
||||
/* Typed float comparisons */
|
||||
else if (strcmp(op, "eq_float") == 0) { ABC3(MACH_EQ_FLOAT); }
|
||||
else if (strcmp(op, "ne_float") == 0) { ABC3(MACH_NE_FLOAT); }
|
||||
else if (strcmp(op, "lt_float") == 0) { ABC3(MACH_LT_FLOAT); }
|
||||
else if (strcmp(op, "le_float") == 0) { ABC3(MACH_LE_FLOAT); }
|
||||
else if (strcmp(op, "gt_float") == 0) { ABC3(MACH_GT_FLOAT); }
|
||||
else if (strcmp(op, "ge_float") == 0) { ABC3(MACH_GE_FLOAT); }
|
||||
/* Typed text comparisons */
|
||||
else if (strcmp(op, "eq_text") == 0) { ABC3(MACH_EQ_TEXT); }
|
||||
else if (strcmp(op, "ne_text") == 0) { ABC3(MACH_NE_TEXT); }
|
||||
else if (strcmp(op, "lt_text") == 0) { ABC3(MACH_LT_TEXT); }
|
||||
else if (strcmp(op, "le_text") == 0) { ABC3(MACH_LE_TEXT); }
|
||||
else if (strcmp(op, "gt_text") == 0) { ABC3(MACH_GT_TEXT); }
|
||||
else if (strcmp(op, "ge_text") == 0) { ABC3(MACH_GE_TEXT); }
|
||||
/* Typed bool comparisons */
|
||||
else if (strcmp(op, "eq_bool") == 0) { ABC3(MACH_EQ_BOOL); }
|
||||
else if (strcmp(op, "ne_bool") == 0) { ABC3(MACH_NE_BOOL); }
|
||||
/* 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) {
|
||||
|
||||
@@ -802,11 +802,12 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
||||
NativeRTState *st = native_state(ctx);
|
||||
if (!st) return JS_EXCEPTION;
|
||||
JSFunction *f = JS_VALUE_GET_FUNCTION(func_obj);
|
||||
cell_compiled_fn fn = (cell_compiled_fn)JS_VALUE_GET_CODE(f->u.cell.code)->u.native.fn_ptr;
|
||||
int nr_slots = JS_VALUE_GET_CODE(f->u.cell.code)->u.native.nr_slots;
|
||||
JSCode *f_code = JS_VALUE_GET_CODE(FN_READ_CODE(f));
|
||||
cell_compiled_fn fn = (cell_compiled_fn)f_code->u.native.fn_ptr;
|
||||
int nr_slots = f_code->u.native.nr_slots;
|
||||
int arity = f->length;
|
||||
void *prev_dl_handle = st->current_dl_handle;
|
||||
st->current_dl_handle = JS_VALUE_GET_CODE(f->u.cell.code)->u.native.dl_handle;
|
||||
st->current_dl_handle = f_code->u.native.dl_handle;
|
||||
|
||||
#define RETURN_DISPATCH(v) \
|
||||
do { \
|
||||
@@ -863,7 +864,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
||||
if (JS_IsFunction(frame->function)) {
|
||||
JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
if (cur_fn->kind == JS_FUNC_KIND_NATIVE)
|
||||
st->current_dl_handle = JS_VALUE_GET_CODE(cur_fn->u.cell.code)->u.native.dl_handle;
|
||||
st->current_dl_handle = JS_VALUE_GET_CODE(FN_READ_CODE(cur_fn))->u.native.dl_handle;
|
||||
}
|
||||
|
||||
JSValue result = fn(ctx, fp);
|
||||
@@ -896,7 +897,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
||||
JS_RaiseDisrupt(ctx, "not a function");
|
||||
/* Resume caller with exception pending */
|
||||
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr;
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
|
||||
cell_sync_dl_from_native_fn(st, exc_fn);
|
||||
JS_PopGCRef(ctx, &callee_ref);
|
||||
continue;
|
||||
@@ -905,7 +906,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
||||
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(callee_fn_val);
|
||||
if (!cell_check_call_arity(ctx, callee_fn, callee_argc)) {
|
||||
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr;
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
|
||||
cell_sync_dl_from_native_fn(st, exc_fn);
|
||||
JS_PopGCRef(ctx, &callee_ref);
|
||||
continue;
|
||||
@@ -913,7 +914,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
||||
|
||||
if (callee_fn->kind == JS_FUNC_KIND_NATIVE) {
|
||||
/* Native-to-native call — no C stack growth */
|
||||
cell_compiled_fn callee_ptr = (cell_compiled_fn)JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.native.fn_ptr;
|
||||
cell_compiled_fn callee_ptr = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.native.fn_ptr;
|
||||
|
||||
if (pending_is_tail) {
|
||||
/* Tail call: replace current frame with the prepared callee frame. */
|
||||
@@ -955,7 +956,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
||||
fp = (JSValue *)frame->slots;
|
||||
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr;
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
|
||||
cell_sync_dl_from_native_fn(st, exc_fn);
|
||||
JS_PopGCRef(ctx, &callee_ref);
|
||||
continue;
|
||||
@@ -994,7 +995,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
||||
/* fn and fp still point to the calling native function's frame.
|
||||
Just resume it — it will detect JS_EXCEPTION in the return slot. */
|
||||
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr;
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
|
||||
cell_sync_dl_from_native_fn(st, exc_fn);
|
||||
JS_PopGCRef(ctx, &callee_ref);
|
||||
continue;
|
||||
@@ -1026,7 +1027,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
||||
fp[ret_slot] = ret;
|
||||
/* Resume caller */
|
||||
JSFunction *caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(caller_fn->u.cell.code)->u.native.fn_ptr;
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(caller_fn))->u.native.fn_ptr;
|
||||
cell_sync_dl_from_native_fn(st, caller_fn);
|
||||
} else {
|
||||
/* Regular call: store result and resume current function */
|
||||
@@ -1036,7 +1037,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
||||
fp[ret_slot] = ret;
|
||||
/* fn stays the same — we resume the same function at next segment */
|
||||
JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(cur_fn->u.cell.code)->u.native.fn_ptr;
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(cur_fn))->u.native.fn_ptr;
|
||||
cell_sync_dl_from_native_fn(st, cur_fn);
|
||||
}
|
||||
}
|
||||
@@ -1070,7 +1071,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
||||
fp[ret_slot] = JS_EXCEPTION;
|
||||
|
||||
JSFunction *exc_caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_caller_fn->u.cell.code)->u.native.fn_ptr;
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_caller_fn))->u.native.fn_ptr;
|
||||
cell_sync_dl_from_native_fn(st, exc_caller_fn);
|
||||
continue;
|
||||
}
|
||||
@@ -1095,7 +1096,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
||||
fp[ret_slot] = result;
|
||||
|
||||
JSFunction *caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(caller_fn->u.cell.code)->u.native.fn_ptr;
|
||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(caller_fn))->u.native.fn_ptr;
|
||||
cell_sync_dl_from_native_fn(st, caller_fn);
|
||||
continue;
|
||||
}
|
||||
@@ -1181,8 +1182,8 @@ JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
|
||||
}
|
||||
int nr_slots = (int)nargs + 2;
|
||||
JSFunction *f = JS_VALUE_GET_FUNCTION(fn);
|
||||
if (f->kind == JS_FUNC_KIND_NATIVE && JS_VALUE_GET_CODE(f->u.cell.code)->u.native.nr_slots > nr_slots)
|
||||
nr_slots = JS_VALUE_GET_CODE(f->u.cell.code)->u.native.nr_slots;
|
||||
if (f->kind == JS_FUNC_KIND_NATIVE && JS_VALUE_GET_CODE(FN_READ_CODE(f))->u.native.nr_slots > nr_slots)
|
||||
nr_slots = JS_VALUE_GET_CODE(FN_READ_CODE(f))->u.native.nr_slots;
|
||||
JSFrameRegister *new_frame = alloc_frame_register(ctx, nr_slots);
|
||||
if (!new_frame) return JS_EXCEPTION;
|
||||
new_frame->function = fn;
|
||||
|
||||
@@ -126,8 +126,7 @@ JSC_CCALL(actor_removetimer,
|
||||
cell_rt *actor = JS_GetContextOpaque(js);
|
||||
uint32_t timer_id;
|
||||
JS_ToUint32(js, &timer_id, argv[0]);
|
||||
JSValue removed = actor_remove_timer(actor, timer_id);
|
||||
JS_FreeValue(js, removed);
|
||||
(void)actor_remove_timer(actor, timer_id);
|
||||
)
|
||||
|
||||
/* Log callback bridge: called from JS_Log, calls ƿit log(channel, [msg, stack])
|
||||
|
||||
@@ -55,6 +55,9 @@
|
||||
#include "nota.h"
|
||||
#include "wota.h"
|
||||
|
||||
void *js_malloc (JSContext *ctx, size_t size);
|
||||
void *js_mallocz (JSContext *ctx, size_t size);
|
||||
|
||||
#if !defined(_WIN32)
|
||||
/* define it if printf uses the RNDN rounding mode instead of RNDNA */
|
||||
#define CONFIG_PRINTF_RNDN
|
||||
@@ -92,6 +95,10 @@
|
||||
// #define DUMP_OBJECTS /* dump objects in JS_FreeContext */
|
||||
// #define DUMP_READ_OBJECT
|
||||
// #define DUMP_ROPE_REBALANCE
|
||||
/* dump GC ref stack push/pop with C backtrace on mismatch.
|
||||
When HAVE_ASAN is also set, uses __sanitizer_print_stack_trace()
|
||||
for symbolized output; otherwise falls back to execinfo backtrace(). */
|
||||
// #define DUMP_GC_REFS
|
||||
|
||||
/* test the GC by forcing it before each object allocation */
|
||||
// #define FORCE_GC_AT_MALLOC
|
||||
@@ -111,6 +118,7 @@
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ASAN
|
||||
#include <sanitizer/asan_interface.h>
|
||||
static struct JSContext *__asan_js_ctx;
|
||||
#endif
|
||||
|
||||
@@ -384,406 +392,6 @@ typedef uint32_t MachInstr32;
|
||||
|
||||
typedef struct { uint16_t line; uint16_t col; } MachLineEntry;
|
||||
|
||||
/* Encoding macros */
|
||||
#define MACH_ABC(op, a, b, c) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(b)<<16) | ((uint32_t)(c)<<24))
|
||||
#define MACH_ABx(op, a, bx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(bx)<<16))
|
||||
#define MACH_AsBx(op, a, sbx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(uint16_t)(sbx)<<16))
|
||||
#define MACH_sJ(op, sj) ((uint32_t)(op) | (((uint32_t)(sj) & 0xFFFFFF) << 8))
|
||||
|
||||
/* Decoding macros */
|
||||
#define MACH_GET_OP(i) ((i) & 0xFF)
|
||||
#define MACH_GET_A(i) (((i) >> 8) & 0xFF)
|
||||
#define MACH_GET_B(i) (((i) >> 16) & 0xFF)
|
||||
#define MACH_GET_C(i) (((i) >> 24) & 0xFF)
|
||||
#define MACH_GET_Bx(i) ((i) >> 16)
|
||||
#define MACH_GET_sBx(i) ((int16_t)((i) >> 16))
|
||||
#define MACH_GET_sJ(i) ((int32_t)((i) & 0xFFFFFF00) >> 8)
|
||||
|
||||
/* ============================================================
|
||||
GC Safepoint Classification for the MACH VM Dispatch Loop
|
||||
============================================================
|
||||
|
||||
Every opcode falls into one of three categories:
|
||||
|
||||
[P] Pure inline — never calls C, never allocates. No GC possible.
|
||||
No frame re-derivation needed after execution.
|
||||
|
||||
[N] Non-allocating C call — calls a C function that is guaranteed
|
||||
to never allocate (e.g. JS_ToBool, js_string_compare_value).
|
||||
No frame re-derivation needed.
|
||||
|
||||
[G] GC safepoint — calls C that may allocate, triggering GC.
|
||||
After the call, all heap pointers (including `frame`) MUST be
|
||||
re-derived via: frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
|
||||
The 18 C entry points that can allocate (GC safepoints):
|
||||
1. JS_GetProperty — key interning for string keys >7 chars
|
||||
2. JS_SetProperty — rec_resize when record grows
|
||||
3. JS_GetPropertyNumber — text substring extraction
|
||||
4. JS_SetPropertyNumber — array grow
|
||||
5. JS_NewObject — allocates record
|
||||
6. JS_NewArray / JS_NewArrayLen — allocates array
|
||||
7. js_new_register_function — allocates function object (closure)
|
||||
8. alloc_frame_register — allocates frame via js_mallocz
|
||||
9. js_call_c_function — arbitrary C code
|
||||
10. JS_CallInternal — arbitrary bytecode
|
||||
11. JS_Call — arbitrary call
|
||||
12. JS_ConcatString — allocates new string
|
||||
13. JS_ArrayPush — array grow
|
||||
14. JS_ArrayPop — reads, but frame refresh needed
|
||||
15. JS_DeleteProperty — mutates record
|
||||
16. JS_HasProperty — complex traversal
|
||||
17. js_regexp_constructor — allocates regex
|
||||
18. reg_vm_binop — polymorphic dispatch (legacy opcodes)
|
||||
|
||||
Opcode-level classification:
|
||||
[P] LOADK, LOADI, LOADNULL, LOADTRUE, LOADFALSE, MOVE, NOP
|
||||
[P] ADD_INT..MOD_INT, NEG_INT, ADD_FLOAT..MOD_FLOAT, NEG_FLOAT
|
||||
[P] EQ_INT..GE_INT, EQ_FLOAT..GE_FLOAT, EQ_BOOL, NE_BOOL
|
||||
[P] IS_IDENTICAL, IS_INT, IS_NUM, IS_TEXT, IS_BOOL, IS_NULL
|
||||
[P] IS_ARRAY, IS_FUNC, IS_RECORD, IS_STONE, IS_PROXY
|
||||
[P] NOT, AND, OR, BITNOT, BITAND, BITOR, BITXOR
|
||||
[P] JMP, JMPTRUE, JMPFALSE, JMPNULL, JMPNOTNULL
|
||||
[P] RETURN, RETNIL, SETARG, GETUP, SETUP, DISRUPT, THROW
|
||||
[P] LENGTH (array + imm-ASCII fast path only; text/blob fallback is [G])
|
||||
[N] EQ_TEXT..GE_TEXT (js_string_compare_value — no allocation)
|
||||
[N] LNOT (JS_ToBool — no allocation)
|
||||
[G] ADD..USHR, NEG, INC, DEC, EQ..GE (legacy: reg_vm_binop)
|
||||
[G] EQ_TOL, NEQ_TOL (tolerance comparison, may fall back)
|
||||
[G] CONCAT (JS_ConcatString)
|
||||
[G] GETFIELD, SETFIELD, GETINDEX, SETINDEX (property access)
|
||||
[G] LOAD_FIELD, STORE_FIELD, LOAD_INDEX, STORE_INDEX
|
||||
[G] LOAD_DYNAMIC, STORE_DYNAMIC
|
||||
[G] GETNAME, GETINTRINSIC, GETENV, SET_VAR
|
||||
[G] NEWOBJECT, NEWRECORD, NEWARRAY (object/array creation)
|
||||
[G] CLOSURE (js_new_register_function)
|
||||
[G] FRAME, GOFRAME (alloc_frame_register)
|
||||
[G] INVOKE, GOINVOKE (function calls)
|
||||
[G] PUSH (JS_ArrayPush), POP (JS_ArrayPop)
|
||||
[G] DELETE, DELETEINDEX (JS_DeleteProperty)
|
||||
[G] HASPROP, IN (JS_HasProperty)
|
||||
[G] REGEXP (js_regexp_constructor)
|
||||
============================================================ */
|
||||
|
||||
typedef enum MachOpcode {
|
||||
/* === Legacy opcodes (used by existing .mach files) === */
|
||||
|
||||
/* Constants & Loading */
|
||||
MACH_LOADK, /* R(A) = K(Bx) — load from constant pool (ABx) */
|
||||
MACH_LOADI, /* R(A) = (int16_t)sBx — load small integer (AsBx) */
|
||||
MACH_LOADNULL, /* R(A) = null (A only) */
|
||||
MACH_LOADTRUE, /* R(A) = true (A only) */
|
||||
MACH_LOADFALSE, /* R(A) = false (A only) */
|
||||
|
||||
/* Movement */
|
||||
MACH_MOVE, /* R(A) = R(B) */
|
||||
|
||||
/* Generic arithmetic (ABC) — used by legacy .mach */
|
||||
MACH_ADD, /* R(A) = R(B) + R(C) */
|
||||
MACH_SUB, /* R(A) = R(B) - R(C) */
|
||||
MACH_MUL, /* R(A) = R(B) * R(C) */
|
||||
MACH_DIV, /* R(A) = R(B) / R(C) */
|
||||
MACH_MOD, /* R(A) = R(B) % R(C) */
|
||||
MACH_POW, /* R(A) = R(B) ** R(C) */
|
||||
MACH_NEG, /* R(A) = -R(B) */
|
||||
MACH_REMAINDER, /* R(A) = remainder(R(B), R(C)) */
|
||||
MACH_MAX, /* R(A) = max(R(B), R(C)) */
|
||||
MACH_MIN, /* R(A) = min(R(B), R(C)) */
|
||||
MACH_ABS, /* R(A) = abs(R(B)) */
|
||||
MACH_SIGN, /* R(A) = sign(R(B)) */
|
||||
MACH_FRACTION, /* R(A) = fraction(R(B)) */
|
||||
MACH_INTEGER, /* R(A) = integer(R(B)) */
|
||||
MACH_FLOOR, /* R(A) = floor(R(B), R(C)) */
|
||||
MACH_CEILING, /* R(A) = ceiling(R(B), R(C)) */
|
||||
MACH_ROUND, /* R(A) = round(R(B), R(C)) */
|
||||
MACH_TRUNC, /* R(A) = trunc(R(B), R(C)) */
|
||||
MACH__DEAD_INC, /* reserved — was MACH_INC, never emitted */
|
||||
MACH__DEAD_DEC, /* reserved — was MACH_DEC, never emitted */
|
||||
|
||||
/* Generic comparison (ABC) — used by legacy .mach */
|
||||
MACH_EQ, /* R(A) = (R(B) == R(C)) */
|
||||
MACH_NEQ, /* R(A) = (R(B) != R(C)) */
|
||||
MACH_LT, /* R(A) = (R(B) < R(C)) */
|
||||
MACH_LE, /* R(A) = (R(B) <= R(C)) */
|
||||
MACH_GT, /* R(A) = (R(B) > R(C)) */
|
||||
MACH_GE, /* R(A) = (R(B) >= R(C)) */
|
||||
|
||||
/* Logical/Bitwise — used by legacy .mach */
|
||||
MACH_LNOT, /* R(A) = !R(B) */
|
||||
MACH_BNOT, /* R(A) = ~R(B) */
|
||||
MACH_BAND, /* R(A) = R(B) & R(C) */
|
||||
MACH_BOR, /* R(A) = R(B) | R(C) */
|
||||
MACH_BXOR, /* R(A) = R(B) ^ R(C) */
|
||||
MACH_SHL, /* R(A) = R(B) << R(C) */
|
||||
MACH_SHR, /* R(A) = R(B) >> R(C) */
|
||||
MACH_USHR, /* R(A) = R(B) >>> R(C) */
|
||||
|
||||
/* Property access — used by legacy .mach */
|
||||
MACH_GETFIELD, /* R(A) = R(B)[K(C)] — named property */
|
||||
MACH_SETFIELD, /* R(A)[K(B)] = R(C) — named property */
|
||||
MACH_GETINDEX, /* R(A) = R(B)[R(C)] — computed property */
|
||||
MACH_SETINDEX, /* R(A)[R(B)] = R(C) — computed property */
|
||||
|
||||
/* Unbound variable access (ABx) */
|
||||
MACH_GETNAME, /* R(A) = resolve(K(Bx)) — compiler placeholder, patched by link */
|
||||
MACH_GETINTRINSIC, /* R(A) = global[K(Bx)] — post-link, intrinsic/built-in */
|
||||
MACH_GETENV, /* R(A) = env[K(Bx)] — post-link, module environment */
|
||||
|
||||
/* Closure access (ABC) */
|
||||
MACH_GETUP, /* R(A) = outer_frame[B].slots[C] */
|
||||
MACH_SETUP, /* outer_frame[B].slots[C] = R(A) */
|
||||
|
||||
/* Control flow */
|
||||
MACH_JMP, /* pc += sJ — unconditional (isJ format) */
|
||||
MACH_JMPTRUE, /* if R(A): pc += sBx — (iAsBx format) */
|
||||
MACH_JMPFALSE, /* if !R(A): pc += sBx — (iAsBx format) */
|
||||
MACH_JMPNULL, /* if R(A)==null: pc += sBx */
|
||||
|
||||
/* Function calls — Lua-style consecutive registers (legacy .mach) */
|
||||
MACH_RETURN, /* Return R(A) */
|
||||
MACH_RETNIL, /* Return null */
|
||||
|
||||
/* Object/array creation — legacy .mach */
|
||||
MACH_NEWOBJECT, /* R(A) = {} */
|
||||
MACH_NEWARRAY, /* R(A) = new array, B = element count in R(A+1)..R(A+B) */
|
||||
MACH_CLOSURE, /* R(A) = closure(functions[Bx]) (ABx) */
|
||||
|
||||
MACH_THROW, /* disrupt — trigger disruption */
|
||||
|
||||
MACH_PUSH, /* push R(B) onto array R(A) */
|
||||
MACH_POP, /* R(A) = pop last element from array R(B) */
|
||||
|
||||
MACH_DELETE, /* R(A) = delete R(B)[K(C)] — named property delete */
|
||||
MACH_DELETEINDEX, /* R(A) = delete R(B)[R(C)] — computed property delete */
|
||||
MACH_HASPROP, /* R(A) = R(C) in R(B) — has property check */
|
||||
MACH_REGEXP, /* R(A) = regexp(K(B), K(C)) — regex literal */
|
||||
|
||||
MACH_EQ_TOL, /* R(A) = eq_tol(R(B), R(B+1), R(B+2)), C=3 */
|
||||
MACH_NEQ_TOL, /* R(A) = ne_tol(R(B), R(B+1), R(B+2)), C=3 */
|
||||
|
||||
MACH_NOP,
|
||||
|
||||
/* === New mcode-derived opcodes (1:1 mapping to mcode IR) === */
|
||||
|
||||
/* Text */
|
||||
MACH_CONCAT, /* R(A) = R(B) ++ R(C) — string concatenation */
|
||||
|
||||
/* Typed integer comparisons (ABC) */
|
||||
MACH_EQ_INT, /* R(A) = (R(B) == R(C)) — int */
|
||||
MACH_NE_INT, /* R(A) = (R(B) != R(C)) — int */
|
||||
MACH_LT_INT, /* R(A) = (R(B) < R(C)) — int */
|
||||
MACH_LE_INT, /* R(A) = (R(B) <= R(C)) — int */
|
||||
MACH_GT_INT, /* R(A) = (R(B) > R(C)) — int */
|
||||
MACH_GE_INT, /* R(A) = (R(B) >= R(C)) — int */
|
||||
|
||||
/* Typed float comparisons (ABC) */
|
||||
MACH_EQ_FLOAT, /* R(A) = (R(B) == R(C)) — float */
|
||||
MACH_NE_FLOAT, /* R(A) = (R(B) != R(C)) — float */
|
||||
MACH_LT_FLOAT, /* R(A) = (R(B) < R(C)) — float */
|
||||
MACH_LE_FLOAT, /* R(A) = (R(B) <= R(C)) — float */
|
||||
MACH_GT_FLOAT, /* R(A) = (R(B) > R(C)) — float */
|
||||
MACH_GE_FLOAT, /* R(A) = (R(B) >= R(C)) — float */
|
||||
|
||||
/* Typed text comparisons (ABC) */
|
||||
MACH_EQ_TEXT, /* R(A) = (R(B) == R(C)) — text */
|
||||
MACH_NE_TEXT, /* R(A) = (R(B) != R(C)) — text */
|
||||
MACH_LT_TEXT, /* R(A) = (R(B) < R(C)) — text */
|
||||
MACH_LE_TEXT, /* R(A) = (R(B) <= R(C)) — text */
|
||||
MACH_GT_TEXT, /* R(A) = (R(B) > R(C)) — text */
|
||||
MACH_GE_TEXT, /* R(A) = (R(B) >= R(C)) — text */
|
||||
|
||||
/* Typed bool comparisons (ABC) */
|
||||
MACH_EQ_BOOL, /* R(A) = (R(B) == R(C)) — bool */
|
||||
MACH_NE_BOOL, /* R(A) = (R(B) != R(C)) — bool */
|
||||
|
||||
/* Special comparisons */
|
||||
MACH_IS_IDENTICAL, /* R(A) = (R(B) === R(C)) — identity check (ABC) */
|
||||
|
||||
/* Type checks (AB) */
|
||||
MACH_IS_INT, /* R(A) = is_int(R(B)) */
|
||||
MACH_IS_NUM, /* R(A) = is_num(R(B)) */
|
||||
MACH_IS_TEXT, /* R(A) = is_text(R(B)) */
|
||||
MACH_IS_BOOL, /* R(A) = is_bool(R(B)) */
|
||||
MACH_IS_NULL, /* R(A) = is_null(R(B)) */
|
||||
|
||||
/* Logical (mcode-style) */
|
||||
MACH_NOT, /* R(A) = !R(B) — boolean not (AB) */
|
||||
MACH_AND, /* R(A) = R(B) && R(C) (ABC) */
|
||||
MACH_OR, /* R(A) = R(B) || R(C) (ABC) */
|
||||
|
||||
/* Bitwise (mcode names) */
|
||||
MACH_BITNOT, /* R(A) = ~R(B) (AB) */
|
||||
MACH_BITAND, /* R(A) = R(B) & R(C) (ABC) */
|
||||
MACH_BITOR, /* R(A) = R(B) | R(C) (ABC) */
|
||||
MACH_BITXOR, /* R(A) = R(B) ^ R(C) (ABC) */
|
||||
|
||||
/* Property access (mcode names) */
|
||||
MACH_LOAD_FIELD, /* R(A) = R(B).K(C) — named property (ABC) */
|
||||
MACH_STORE_FIELD, /* R(A).K(B) = R(C) — named property (ABC) */
|
||||
MACH_LOAD_INDEX, /* R(A) = R(B)[R(C)] — integer index (ABC) */
|
||||
MACH_STORE_INDEX, /* R(A)[R(B)] = R(C) — integer index (ABC) */
|
||||
MACH_LOAD_DYNAMIC, /* R(A) = R(B)[R(C)] — dynamic key (ABC) */
|
||||
MACH_STORE_DYNAMIC, /* R(A)[R(B)] = R(C) — dynamic key (ABC) */
|
||||
|
||||
/* Object/Array creation (mcode names) */
|
||||
MACH_NEWRECORD, /* R(A) = {} — new empty record (A only) */
|
||||
|
||||
/* Decomposed function calls (mcode-style) */
|
||||
MACH_FRAME, /* R(A) = frame(R(B), C) — alloc call frame (ABC) */
|
||||
MACH_SETARG, /* frame R(A)[B] = R(C) — set arg in frame (ABC) */
|
||||
MACH_INVOKE, /* R(B) = invoke(R(A)) — call frame, result in R(B) (AB) */
|
||||
MACH_GOFRAME, /* R(A) = goframe(R(B), C) — async frame (ABC) */
|
||||
MACH_GOINVOKE, /* goinvoke(R(A)) — async invoke, no result (A only) */
|
||||
|
||||
/* Control flow */
|
||||
MACH_JMPNOTNULL, /* if R(A)!=null: pc += sBx (iAsBx) */
|
||||
|
||||
/* Error handling */
|
||||
MACH_DISRUPT, /* trigger disruption (A only) */
|
||||
|
||||
/* Misc */
|
||||
MACH_IN, /* R(A) = (R(B) in R(C)) — has property (ABC) */
|
||||
|
||||
/* Extended type checks (AB) */
|
||||
MACH_IS_ARRAY, /* R(A) = is_array(R(B)) */
|
||||
MACH_IS_FUNC, /* R(A) = is_function(R(B)) */
|
||||
MACH_IS_RECORD, /* R(A) = is_object(R(B)) */
|
||||
MACH_IS_STONE, /* R(A) = is_stone(R(B)) */
|
||||
MACH_LENGTH, /* R(A) = length(R(B)) — array/text/blob length */
|
||||
MACH_IS_PROXY, /* R(A) = is_function(R(B)) && R(B).length == 2 */
|
||||
|
||||
MACH_OP_COUNT
|
||||
} MachOpcode;
|
||||
|
||||
static const char *mach_opcode_names[MACH_OP_COUNT] = {
|
||||
/* Legacy */
|
||||
[MACH_LOADK] = "loadk",
|
||||
[MACH_LOADI] = "loadi",
|
||||
[MACH_LOADNULL] = "loadnull",
|
||||
[MACH_LOADTRUE] = "loadtrue",
|
||||
[MACH_LOADFALSE] = "loadfalse",
|
||||
[MACH_MOVE] = "move",
|
||||
[MACH_ADD] = "add",
|
||||
[MACH_SUB] = "sub",
|
||||
[MACH_MUL] = "mul",
|
||||
[MACH_DIV] = "div",
|
||||
[MACH_MOD] = "mod",
|
||||
[MACH_POW] = "pow",
|
||||
[MACH_NEG] = "neg",
|
||||
[MACH_REMAINDER] = "remainder",
|
||||
[MACH_MAX] = "max",
|
||||
[MACH_MIN] = "min",
|
||||
[MACH_ABS] = "abs",
|
||||
[MACH_SIGN] = "sign",
|
||||
[MACH_FRACTION] = "fraction",
|
||||
[MACH_INTEGER] = "integer",
|
||||
[MACH_FLOOR] = "floor",
|
||||
[MACH_CEILING] = "ceiling",
|
||||
[MACH_ROUND] = "round",
|
||||
[MACH_TRUNC] = "trunc",
|
||||
[MACH__DEAD_INC] = "dead_inc",
|
||||
[MACH__DEAD_DEC] = "dead_dec",
|
||||
[MACH_EQ] = "eq",
|
||||
[MACH_NEQ] = "neq",
|
||||
[MACH_LT] = "lt",
|
||||
[MACH_LE] = "le",
|
||||
[MACH_GT] = "gt",
|
||||
[MACH_GE] = "ge",
|
||||
[MACH_LNOT] = "lnot",
|
||||
[MACH_BNOT] = "bnot",
|
||||
[MACH_BAND] = "band",
|
||||
[MACH_BOR] = "bor",
|
||||
[MACH_BXOR] = "bxor",
|
||||
[MACH_SHL] = "shl",
|
||||
[MACH_SHR] = "shr",
|
||||
[MACH_USHR] = "ushr",
|
||||
[MACH_GETFIELD] = "getfield",
|
||||
[MACH_SETFIELD] = "setfield",
|
||||
[MACH_GETINDEX] = "getindex",
|
||||
[MACH_SETINDEX] = "setindex",
|
||||
[MACH_GETNAME] = "getname",
|
||||
[MACH_GETINTRINSIC] = "getintrinsic",
|
||||
[MACH_GETENV] = "getenv",
|
||||
[MACH_GETUP] = "getup",
|
||||
[MACH_SETUP] = "setup",
|
||||
[MACH_JMP] = "jmp",
|
||||
[MACH_JMPTRUE] = "jmptrue",
|
||||
[MACH_JMPFALSE] = "jmpfalse",
|
||||
[MACH_JMPNULL] = "jmpnull",
|
||||
[MACH_RETURN] = "return",
|
||||
[MACH_RETNIL] = "retnil",
|
||||
[MACH_NEWOBJECT] = "newobject",
|
||||
[MACH_NEWARRAY] = "newarray",
|
||||
[MACH_CLOSURE] = "closure",
|
||||
[MACH_THROW] = "throw",
|
||||
[MACH_PUSH] = "push",
|
||||
[MACH_POP] = "pop",
|
||||
[MACH_DELETE] = "delete",
|
||||
[MACH_DELETEINDEX] = "deleteindex",
|
||||
[MACH_HASPROP] = "hasprop",
|
||||
[MACH_REGEXP] = "regexp",
|
||||
[MACH_EQ_TOL] = "eq_tol",
|
||||
[MACH_NEQ_TOL] = "neq_tol",
|
||||
[MACH_NOP] = "nop",
|
||||
/* Mcode-derived */
|
||||
[MACH_CONCAT] = "concat",
|
||||
[MACH_EQ_INT] = "eq_int",
|
||||
[MACH_NE_INT] = "ne_int",
|
||||
[MACH_LT_INT] = "lt_int",
|
||||
[MACH_LE_INT] = "le_int",
|
||||
[MACH_GT_INT] = "gt_int",
|
||||
[MACH_GE_INT] = "ge_int",
|
||||
[MACH_EQ_FLOAT] = "eq_float",
|
||||
[MACH_NE_FLOAT] = "ne_float",
|
||||
[MACH_LT_FLOAT] = "lt_float",
|
||||
[MACH_LE_FLOAT] = "le_float",
|
||||
[MACH_GT_FLOAT] = "gt_float",
|
||||
[MACH_GE_FLOAT] = "ge_float",
|
||||
[MACH_EQ_TEXT] = "eq_text",
|
||||
[MACH_NE_TEXT] = "ne_text",
|
||||
[MACH_LT_TEXT] = "lt_text",
|
||||
[MACH_LE_TEXT] = "le_text",
|
||||
[MACH_GT_TEXT] = "gt_text",
|
||||
[MACH_GE_TEXT] = "ge_text",
|
||||
[MACH_EQ_BOOL] = "eq_bool",
|
||||
[MACH_NE_BOOL] = "ne_bool",
|
||||
[MACH_IS_IDENTICAL] = "is_identical",
|
||||
[MACH_IS_INT] = "is_int",
|
||||
[MACH_IS_NUM] = "is_num",
|
||||
[MACH_IS_TEXT] = "is_text",
|
||||
[MACH_IS_BOOL] = "is_bool",
|
||||
[MACH_IS_NULL] = "is_null",
|
||||
[MACH_NOT] = "not",
|
||||
[MACH_AND] = "and",
|
||||
[MACH_OR] = "or",
|
||||
[MACH_BITNOT] = "bitnot",
|
||||
[MACH_BITAND] = "bitand",
|
||||
[MACH_BITOR] = "bitor",
|
||||
[MACH_BITXOR] = "bitxor",
|
||||
[MACH_LOAD_FIELD] = "load_field",
|
||||
[MACH_STORE_FIELD] = "store_field",
|
||||
[MACH_LOAD_INDEX] = "load_index",
|
||||
[MACH_STORE_INDEX] = "store_index",
|
||||
[MACH_LOAD_DYNAMIC] = "load_dynamic",
|
||||
[MACH_STORE_DYNAMIC] = "store_dynamic",
|
||||
[MACH_NEWRECORD] = "newrecord",
|
||||
[MACH_FRAME] = "frame",
|
||||
[MACH_SETARG] = "setarg",
|
||||
[MACH_INVOKE] = "invoke",
|
||||
[MACH_GOFRAME] = "goframe",
|
||||
[MACH_GOINVOKE] = "goinvoke",
|
||||
[MACH_JMPNOTNULL] = "jmpnotnull",
|
||||
[MACH_DISRUPT] = "disrupt",
|
||||
[MACH_IN] = "in",
|
||||
/* Extended type checks */
|
||||
[MACH_IS_ARRAY] = "is_array",
|
||||
[MACH_IS_FUNC] = "is_func",
|
||||
[MACH_IS_RECORD] = "is_record",
|
||||
[MACH_IS_STONE] = "is_stone",
|
||||
[MACH_LENGTH] = "length",
|
||||
[MACH_IS_PROXY] = "is_proxy",
|
||||
};
|
||||
|
||||
/* Compiled register-based code (off-heap, never GC'd).
|
||||
Created by JS_CompileMach from AST JSON. */
|
||||
typedef struct JSCodeRegister {
|
||||
@@ -871,6 +479,17 @@ static inline void mach_resolve_forward(JSValue *slot) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Stone a mutable (unstoned) heap text in-place. Used at escape points
|
||||
in the VM to enforce the invariant that an unstoned text is uniquely
|
||||
referenced by exactly one slot. */
|
||||
static inline void stone_mutable_text(JSValue v) {
|
||||
if (JS_IsPtr(v)) {
|
||||
objhdr_t *oh = (objhdr_t *)JS_VALUE_GET_PTR(v);
|
||||
if (objhdr_type(*oh) == OBJ_TEXT && !(*oh & OBJHDR_S_MASK))
|
||||
*oh = objhdr_set_s(*oh, true);
|
||||
}
|
||||
}
|
||||
|
||||
/* Inline type checks — use these in the VM dispatch loop to avoid
|
||||
function call overhead. The public API (JS_IsArray etc. in quickjs.h)
|
||||
remains non-inline for external callers; those wrappers live in runtime.c. */
|
||||
@@ -1140,6 +759,11 @@ struct JSContext {
|
||||
JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags);
|
||||
void *user_opaque;
|
||||
|
||||
/* GC callback to scan external C-side roots (actor letters, timers) */
|
||||
void (*gc_scan_external)(JSContext *ctx,
|
||||
uint8_t *from_base, uint8_t *from_end,
|
||||
uint8_t *to_base, uint8_t **to_free, uint8_t *to_end);
|
||||
|
||||
js_hook trace_hook;
|
||||
int trace_type;
|
||||
void *trace_data;
|
||||
@@ -1383,6 +1007,8 @@ typedef struct JSFunction {
|
||||
} u;
|
||||
} JSFunction;
|
||||
|
||||
#define FN_READ_CODE(fn) ((fn)->u.cell.code)
|
||||
|
||||
/* ============================================================
|
||||
Context-Neutral Module Format (Phase 2+)
|
||||
Struct definitions are in quickjs.h
|
||||
@@ -1453,7 +1079,6 @@ JSValue js_cell_character (JSContext *ctx, JSValue this_val, int argc, JSValue *
|
||||
static JSValue js_cell_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
|
||||
static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
|
||||
static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
|
||||
static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
|
||||
JSValue JS_GetStack(JSContext *ctx);
|
||||
JSValue JS_RaiseOOM (JSContext *ctx);
|
||||
|
||||
@@ -1594,9 +1219,12 @@ int js_string_compare_value_nocase (JSContext *ctx, JSValue op1, JSValue op2);
|
||||
JSValue js_regexp_constructor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
|
||||
int JS_HasProperty (JSContext *ctx, JSValue obj, JSValue prop);
|
||||
int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key);
|
||||
JSValue JS_GetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key);
|
||||
int JS_SetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key, JSValue val);
|
||||
void *js_realloc_rt (void *ptr, size_t size);
|
||||
char *js_strdup_rt (const char *str);
|
||||
JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2);
|
||||
JSValue JS_ConcatStringGrow (JSContext *ctx, JSValue op1, JSValue op2);
|
||||
JSText *pretext_init (JSContext *ctx, int capacity);
|
||||
JSText *pretext_putc (JSContext *ctx, JSText *s, uint32_t c);
|
||||
JSText *pretext_concat_value (JSContext *ctx, JSText *s, JSValue v);
|
||||
|
||||
@@ -330,6 +330,12 @@ JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size);
|
||||
void JS_FreeContext (JSContext *s);
|
||||
void *JS_GetContextOpaque (JSContext *ctx);
|
||||
void JS_SetContextOpaque (JSContext *ctx, void *opaque);
|
||||
|
||||
typedef void (*JS_GCScanFn)(JSContext *ctx,
|
||||
uint8_t *from_base, uint8_t *from_end,
|
||||
uint8_t *to_base, uint8_t **to_free, uint8_t *to_end);
|
||||
void JS_SetGCScanExternal(JSContext *ctx, JS_GCScanFn fn);
|
||||
|
||||
void JS_SetActorSym (JSContext *ctx, JSValue sym);
|
||||
JSValue JS_GetActorSym (JSContext *ctx);
|
||||
JSRuntime *JS_GetRuntime (JSContext *ctx);
|
||||
@@ -1013,9 +1019,6 @@ void *js_debugger_val_address (JSContext *js, JSValue val);
|
||||
/* ============================================================
|
||||
12. Memory Allocation
|
||||
============================================================ */
|
||||
void *js_malloc (JSContext *ctx, size_t size);
|
||||
void *js_mallocz (JSContext *ctx, size_t size);
|
||||
|
||||
/* Runtime-level memory functions */
|
||||
void *js_malloc_rt (size_t size);
|
||||
void *js_mallocz_rt (size_t size);
|
||||
@@ -1023,7 +1026,7 @@ void js_free_rt (void *ptr);
|
||||
|
||||
/* ============================================================
|
||||
13. Compilation and Bytecode
|
||||
============================================================ */
|
||||
============================================================ */
|
||||
|
||||
struct cJSON;
|
||||
typedef struct MachCode MachCode;
|
||||
@@ -1057,6 +1060,11 @@ MachCode *mach_compile_mcode(struct cJSON *mcode_json);
|
||||
Does NOT clear reg_current_frame — caller is responsible if needed. */
|
||||
JSValue JS_GetStack (JSContext *ctx);
|
||||
|
||||
/* Crash-safe stack printer: walks JS frames and writes to stderr.
|
||||
Uses only write() (async-signal-safe). No GC allocations.
|
||||
Safe to call from signal handlers. */
|
||||
void JS_CrashPrintStack(JSContext *ctx);
|
||||
|
||||
#undef js_unlikely
|
||||
#undef inline
|
||||
|
||||
|
||||
1537
source/runtime.c
1537
source/runtime.c
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "stb_ds.h"
|
||||
#include "cell.h"
|
||||
#include "quickjs-internal.h"
|
||||
#include "cell_internal.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -330,6 +331,35 @@ void actor_free(cell_rt *actor)
|
||||
}
|
||||
lockless_shdel(actors, actor->id);
|
||||
|
||||
// Purge any pending timers referencing this actor from the timer heap
|
||||
// to prevent the timer thread from accessing freed memory.
|
||||
{
|
||||
pthread_mutex_lock(&engine.lock);
|
||||
int n = arrlen(timer_heap);
|
||||
int w = 0;
|
||||
for (int r = 0; r < n; r++) {
|
||||
if (timer_heap[r].actor != actor)
|
||||
timer_heap[w++] = timer_heap[r];
|
||||
}
|
||||
arrsetlen(timer_heap, w);
|
||||
for (int i = w / 2 - 1; i >= 0; i--) {
|
||||
int j = i;
|
||||
while (1) {
|
||||
int left = 2 * j + 1, right = 2 * j + 2, smallest = j;
|
||||
if (left < w && timer_heap[left].execute_at_ns < timer_heap[smallest].execute_at_ns)
|
||||
smallest = left;
|
||||
if (right < w && timer_heap[right].execute_at_ns < timer_heap[smallest].execute_at_ns)
|
||||
smallest = right;
|
||||
if (smallest == j) break;
|
||||
timer_node tmp = timer_heap[j];
|
||||
timer_heap[j] = timer_heap[smallest];
|
||||
timer_heap[smallest] = tmp;
|
||||
j = smallest;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
}
|
||||
|
||||
// Do not go forward with actor destruction until the actor is completely free
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
pthread_mutex_lock(actor->mutex);
|
||||
@@ -671,7 +701,9 @@ const char *send_message(const char *id, void *msg)
|
||||
void actor_turn(cell_rt *actor)
|
||||
{
|
||||
pthread_mutex_lock(actor->mutex);
|
||||
#ifdef ACTOR_TRACE
|
||||
int prev_state = actor->state;
|
||||
#endif
|
||||
actor->state = ACTOR_RUNNING;
|
||||
#ifdef ACTOR_TRACE
|
||||
fprintf(stderr, "[ACTOR_TRACE] %s: %d -> RUNNING\n",
|
||||
@@ -685,6 +717,7 @@ void actor_turn(cell_rt *actor)
|
||||
|
||||
if (actor->vm_suspended) {
|
||||
/* RESUME path: continue suspended turn under kill timer only */
|
||||
g_crash_ctx = actor->context;
|
||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||
JS_SetPauseFlag(actor->context, 0);
|
||||
actor->turn_start_ns = cell_ns();
|
||||
@@ -752,6 +785,8 @@ void actor_turn(cell_rt *actor)
|
||||
pthread_cond_signal(&engine.timer_cond);
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
g_crash_ctx = actor->context;
|
||||
|
||||
if (l.type == LETTER_BLOB) {
|
||||
size_t size = blob_length(l.blob_data) / 8;
|
||||
JSValue arg = js_new_blob_stoned_copy(actor->context,
|
||||
@@ -785,6 +820,7 @@ void actor_turn(cell_rt *actor)
|
||||
actor->slow_strikes = 0; /* completed within fast timer */
|
||||
|
||||
ENDTURN:
|
||||
g_crash_ctx = NULL;
|
||||
/* Invalidate any outstanding pause/kill timers for this turn */
|
||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||
actor->state = ACTOR_IDLE;
|
||||
@@ -811,6 +847,7 @@ ENDTURN:
|
||||
return;
|
||||
|
||||
ENDTURN_SLOW:
|
||||
g_crash_ctx = NULL;
|
||||
#ifdef ACTOR_TRACE
|
||||
fprintf(stderr, "[ACTOR_TRACE] %s: suspended mid-turn -> SLOW\n",
|
||||
actor->name ? actor->name : actor->id);
|
||||
@@ -821,6 +858,36 @@ ENDTURN_SLOW:
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
}
|
||||
|
||||
/* GC callback: scan actor's letters and timers so the copying GC
|
||||
can relocate JSValues stored in C-side data structures. */
|
||||
void actor_gc_scan(JSContext *ctx,
|
||||
uint8_t *from_base, uint8_t *from_end,
|
||||
uint8_t *to_base, uint8_t **to_free, uint8_t *to_end)
|
||||
{
|
||||
cell_rt *actor = JS_GetContextOpaque(ctx);
|
||||
if (!actor) return;
|
||||
|
||||
/* Lock msg_mutex to synchronize with the timer thread, which reads
|
||||
timers and writes letters under the same lock. */
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
|
||||
for (int i = 0; i < arrlen(actor->letters); i++) {
|
||||
if (actor->letters[i].type == LETTER_CALLBACK) {
|
||||
actor->letters[i].callback = gc_copy_value(ctx,
|
||||
actor->letters[i].callback,
|
||||
from_base, from_end, to_base, to_free, to_end);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
actor->timers[i].value = gc_copy_value(ctx,
|
||||
actor->timers[i].value,
|
||||
from_base, from_end, to_base, to_free, to_end);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
}
|
||||
|
||||
void actor_clock(cell_rt *actor, JSValue fn)
|
||||
{
|
||||
letter l;
|
||||
|
||||
@@ -54,6 +54,7 @@ static int rangematch(const char *, char, int, const char **);
|
||||
|
||||
int wildmatch(const char *pattern, const char *string, int flags)
|
||||
{
|
||||
const char *patternstart = pattern;
|
||||
const char *stringstart;
|
||||
const char *newp;
|
||||
const char *slash;
|
||||
@@ -87,7 +88,7 @@ int wildmatch(const char *pattern, const char *string, int flags)
|
||||
c = *pattern;
|
||||
wild = check_flag(flags, WM_WILDSTAR) && c == '*';
|
||||
if (wild) {
|
||||
prev = pattern[-2];
|
||||
prev = (pattern >= patternstart + 2) ? pattern[-2] : EOS;
|
||||
/* Collapse multiple stars and slash-** patterns,
|
||||
* e.g. "** / *** / **** / **" (without spaces)
|
||||
* is treated as a single ** wildstar.
|
||||
|
||||
Reference in New Issue
Block a user