progress on aot
This commit is contained in:
@@ -85,6 +85,11 @@ static JSValue *mach_materialize_cpool(JSContext *ctx, MachCPoolEntry *entries,
|
||||
/* ---- 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: WARNING env not stone (code=%s file=%s)\n",
|
||||
code->name_cstr ? code->name_cstr : "<unknown>",
|
||||
code->filename_cstr ? code->filename_cstr : "<unknown>");
|
||||
}
|
||||
JSGCRef env_ref;
|
||||
JS_PushGCRef(ctx, &env_ref);
|
||||
env_ref.val = env;
|
||||
@@ -809,7 +814,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
DT(MACH_ADD), DT(MACH_SUB),
|
||||
DT(MACH_MUL), DT(MACH_DIV),
|
||||
DT(MACH_MOD), DT(MACH_POW),
|
||||
DT(MACH_NEG), DT(MACH_INC), DT(MACH_DEC),
|
||||
DT(MACH_NEG),
|
||||
DT(MACH_EQ), DT(MACH_NEQ),
|
||||
DT(MACH_LT), DT(MACH_LE),
|
||||
DT(MACH_GT), DT(MACH_GE),
|
||||
@@ -1218,38 +1223,6 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
VM_BREAK();
|
||||
}
|
||||
|
||||
VM_CASE(MACH_INC): {
|
||||
JSValue v = frame->slots[b];
|
||||
if (JS_IsInt(v)) {
|
||||
int32_t i = JS_VALUE_GET_INT(v);
|
||||
if (i == INT32_MAX)
|
||||
frame->slots[a] = JS_NewFloat64(ctx, (double)i + 1);
|
||||
else
|
||||
frame->slots[a] = JS_NewInt32(ctx, i + 1);
|
||||
} else {
|
||||
double d;
|
||||
JS_ToFloat64(ctx, &d, v);
|
||||
frame->slots[a] = JS_NewFloat64(ctx, d + 1);
|
||||
}
|
||||
VM_BREAK();
|
||||
}
|
||||
|
||||
VM_CASE(MACH_DEC): {
|
||||
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 - 1);
|
||||
else
|
||||
frame->slots[a] = JS_NewInt32(ctx, i - 1);
|
||||
} else {
|
||||
double d;
|
||||
JS_ToFloat64(ctx, &d, v);
|
||||
frame->slots[a] = JS_NewFloat64(ctx, d - 1);
|
||||
}
|
||||
VM_BREAK();
|
||||
}
|
||||
|
||||
VM_CASE(MACH_LNOT): {
|
||||
int bval = JS_ToBool(ctx, frame->slots[b]);
|
||||
frame->slots[a] = JS_NewBool(ctx, !bval);
|
||||
@@ -3331,8 +3304,6 @@ static void dump_register_code(JSContext *ctx, JSCodeRegister *code, int indent)
|
||||
/* A, B: move, unary ops */
|
||||
case MACH_MOVE:
|
||||
case MACH_NEG:
|
||||
case MACH_INC:
|
||||
case MACH_DEC:
|
||||
case MACH_LNOT:
|
||||
case MACH_BNOT:
|
||||
printf("r%d, r%d", a, b);
|
||||
|
||||
@@ -260,23 +260,56 @@ void cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr,
|
||||
|
||||
/* --- Intrinsic/global lookup --- */
|
||||
|
||||
/* Native module environment — set before executing a native module's cell_main.
|
||||
Contains runtime functions (starts_with, ends_with, etc.) and use(). */
|
||||
static JSGCRef g_native_env_ref;
|
||||
static int g_has_native_env = 0;
|
||||
|
||||
void cell_rt_set_native_env(JSContext *ctx, JSValue env) {
|
||||
if (!JS_IsNull(env) && !JS_IsStone(env)) {
|
||||
fprintf(stderr, "cell_rt_set_native_env: WARNING env not stone\n");
|
||||
}
|
||||
if (g_has_native_env)
|
||||
JS_DeleteGCRef(ctx, &g_native_env_ref);
|
||||
if (!JS_IsNull(env)) {
|
||||
JS_AddGCRef(ctx, &g_native_env_ref);
|
||||
g_native_env_ref.val = env;
|
||||
g_has_native_env = 1;
|
||||
} else {
|
||||
g_has_native_env = 0;
|
||||
}
|
||||
}
|
||||
|
||||
JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) {
|
||||
/* Check native env first (runtime-provided functions like starts_with) */
|
||||
if (g_has_native_env) {
|
||||
JSValue v = JS_GetPropertyStr(ctx, g_native_env_ref.val, name);
|
||||
if (!JS_IsNull(v)) return v;
|
||||
}
|
||||
return JS_GetPropertyStr(ctx, ctx->global_obj, name);
|
||||
}
|
||||
|
||||
/* --- Closure access ---
|
||||
Slot 511 in each frame stores a pointer to the enclosing frame.
|
||||
Walking depth levels up the chain gives the target frame. */
|
||||
Slot 511 in each frame stores the magic ID (registry index) of the
|
||||
function that owns this frame. cell_rt_get/put_closure re-derive
|
||||
the enclosing frame from the function's GC ref at call time, so
|
||||
pointers stay valid even if GC moves frames. */
|
||||
|
||||
#define QBE_FRAME_OUTER_SLOT 511
|
||||
|
||||
static JSValue *derive_outer_fp(int magic);
|
||||
|
||||
JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth,
|
||||
int64_t slot) {
|
||||
JSValue *frame = (JSValue *)fp;
|
||||
for (int64_t d = 0; d < depth; d++) {
|
||||
void *outer = (void *)(uintptr_t)frame[QBE_FRAME_OUTER_SLOT];
|
||||
if (!outer) return JS_NULL;
|
||||
frame = (JSValue *)outer;
|
||||
/* fp[511] stores the magic ID (registry index) of the function
|
||||
that owns this frame. derive_outer_fp re-derives the enclosing
|
||||
frame from the function's GC ref, so it's always current even
|
||||
if GC moved the frame. */
|
||||
int magic = (int)(int64_t)frame[QBE_FRAME_OUTER_SLOT];
|
||||
frame = derive_outer_fp(magic);
|
||||
if (!frame) return JS_NULL;
|
||||
}
|
||||
return frame[slot];
|
||||
}
|
||||
@@ -285,13 +318,46 @@ void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
|
||||
int64_t slot) {
|
||||
JSValue *frame = (JSValue *)fp;
|
||||
for (int64_t d = 0; d < depth; d++) {
|
||||
void *outer = (void *)(uintptr_t)frame[QBE_FRAME_OUTER_SLOT];
|
||||
if (!outer) return;
|
||||
frame = (JSValue *)outer;
|
||||
int magic = (int)(int64_t)frame[QBE_FRAME_OUTER_SLOT];
|
||||
frame = derive_outer_fp(magic);
|
||||
if (!frame) return;
|
||||
}
|
||||
frame[slot] = val;
|
||||
}
|
||||
|
||||
/* --- GC-managed AOT frame stack ---
|
||||
Each AOT function call pushes a GC ref so the GC can find and
|
||||
update frame pointers when it moves objects. cell_rt_refresh_fp
|
||||
re-derives the slot pointer after any GC-triggering call. */
|
||||
|
||||
#define MAX_AOT_DEPTH 256
|
||||
static JSGCRef g_aot_gc_refs[MAX_AOT_DEPTH];
|
||||
static int g_aot_depth = 0;
|
||||
|
||||
JSValue *cell_rt_enter_frame(JSContext *ctx, int64_t nr_slots) {
|
||||
if (g_aot_depth >= MAX_AOT_DEPTH)
|
||||
return NULL;
|
||||
JSFrameRegister *frame = alloc_frame_register(ctx, (int)nr_slots);
|
||||
if (!frame) return NULL;
|
||||
JSGCRef *ref = &g_aot_gc_refs[g_aot_depth];
|
||||
JS_AddGCRef(ctx, ref);
|
||||
ref->val = JS_MKPTR(frame);
|
||||
g_aot_depth++;
|
||||
return (JSValue *)frame->slots;
|
||||
}
|
||||
|
||||
JSValue *cell_rt_refresh_fp(JSContext *ctx) {
|
||||
(void)ctx;
|
||||
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(
|
||||
g_aot_gc_refs[g_aot_depth - 1].val);
|
||||
return (JSValue *)frame->slots;
|
||||
}
|
||||
|
||||
void cell_rt_leave_frame(JSContext *ctx) {
|
||||
g_aot_depth--;
|
||||
JS_DeleteGCRef(ctx, &g_aot_gc_refs[g_aot_depth]);
|
||||
}
|
||||
|
||||
/* --- Function creation and calling --- */
|
||||
|
||||
typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
|
||||
@@ -305,7 +371,8 @@ typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
|
||||
static struct {
|
||||
void *dl_handle;
|
||||
int fn_idx;
|
||||
void *outer_fp;
|
||||
JSGCRef frame_ref; /* independent GC ref for enclosing frame */
|
||||
int has_frame_ref;
|
||||
} g_native_fn_registry[MAX_NATIVE_FN];
|
||||
|
||||
static int g_native_fn_count = 0;
|
||||
@@ -313,6 +380,16 @@ static int g_native_fn_count = 0;
|
||||
/* Set before executing a native module's cell_main */
|
||||
static void *g_current_dl_handle = NULL;
|
||||
|
||||
/* Derive the outer frame's slots pointer from the closure's own GC ref.
|
||||
Each closure keeps an independent GC ref so the enclosing frame
|
||||
survives even after cell_rt_leave_frame pops the stack ref. */
|
||||
static JSValue *derive_outer_fp(int magic) {
|
||||
if (!g_native_fn_registry[magic].has_frame_ref) return NULL;
|
||||
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(
|
||||
g_native_fn_registry[magic].frame_ref.val);
|
||||
return (JSValue *)frame->slots;
|
||||
}
|
||||
|
||||
static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
|
||||
int argc, JSValue *argv, int magic) {
|
||||
if (magic < 0 || magic >= g_native_fn_count)
|
||||
@@ -328,27 +405,44 @@ static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
|
||||
if (!fn)
|
||||
return JS_ThrowTypeError(ctx, "native function %s not found in dylib", name);
|
||||
|
||||
/* Allocate frame: slot 0 = this, slots 1..argc = args */
|
||||
JSValue frame[512];
|
||||
memset(frame, 0, sizeof(frame));
|
||||
frame[0] = this_val;
|
||||
/* Allocate GC-managed frame: slot 0 = this, slots 1..argc = args */
|
||||
JSValue *fp = cell_rt_enter_frame(ctx, 512);
|
||||
if (!fp) return JS_EXCEPTION;
|
||||
fp[0] = this_val;
|
||||
for (int i = 0; i < argc && i < 510; i++)
|
||||
frame[1 + i] = argv[i];
|
||||
fp[1 + i] = argv[i];
|
||||
|
||||
/* Link to outer frame for closure access */
|
||||
frame[QBE_FRAME_OUTER_SLOT] = (JSValue)(uintptr_t)g_native_fn_registry[magic].outer_fp;
|
||||
/* Store the magic ID (registry index) so cell_rt_get/put_closure
|
||||
can re-derive the enclosing frame from the GC ref at call time,
|
||||
surviving GC moves */
|
||||
fp[QBE_FRAME_OUTER_SLOT] = (JSValue)(int64_t)magic;
|
||||
|
||||
return fn(ctx, frame);
|
||||
JSValue result = fn(ctx, fp);
|
||||
cell_rt_leave_frame(ctx);
|
||||
if (result == JS_EXCEPTION)
|
||||
return JS_EXCEPTION;
|
||||
return result;
|
||||
}
|
||||
|
||||
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp) {
|
||||
(void)outer_fp;
|
||||
if (g_native_fn_count >= MAX_NATIVE_FN)
|
||||
return JS_ThrowTypeError(ctx, "too many native functions (max %d)", MAX_NATIVE_FN);
|
||||
|
||||
int global_id = g_native_fn_count++;
|
||||
g_native_fn_registry[global_id].dl_handle = g_current_dl_handle;
|
||||
g_native_fn_registry[global_id].fn_idx = (int)fn_idx;
|
||||
g_native_fn_registry[global_id].outer_fp = outer_fp;
|
||||
|
||||
/* Create independent GC ref so the enclosing frame survives
|
||||
even after cell_rt_leave_frame pops the stack ref */
|
||||
if (g_aot_depth > 0) {
|
||||
JSGCRef *ref = &g_native_fn_registry[global_id].frame_ref;
|
||||
JS_AddGCRef(ctx, ref);
|
||||
ref->val = g_aot_gc_refs[g_aot_depth - 1].val;
|
||||
g_native_fn_registry[global_id].has_frame_ref = 1;
|
||||
} else {
|
||||
g_native_fn_registry[global_id].has_frame_ref = 0;
|
||||
}
|
||||
|
||||
return JS_NewCFunction2(ctx, (JSCFunction *)cell_fn_trampoline, "native_fn",
|
||||
255, JS_CFUNC_generic_magic, global_id);
|
||||
@@ -369,6 +463,7 @@ JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
|
||||
}
|
||||
|
||||
void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) {
|
||||
if (frame_val == JS_EXCEPTION) return;
|
||||
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
||||
fr->slots[idx] = val;
|
||||
}
|
||||
@@ -474,18 +569,54 @@ int cell_rt_is_proxy(JSContext *ctx, JSValue v) {
|
||||
return fn->length == 2;
|
||||
}
|
||||
|
||||
/* --- Short-circuit and/or (non-allocating) --- */
|
||||
|
||||
JSValue cell_rt_and(JSContext *ctx, JSValue left, JSValue right) {
|
||||
return JS_ToBool(ctx, left) ? right : left;
|
||||
}
|
||||
|
||||
JSValue cell_rt_or(JSContext *ctx, JSValue left, JSValue right) {
|
||||
return JS_ToBool(ctx, left) ? left : right;
|
||||
}
|
||||
|
||||
/* --- Disruption --- */
|
||||
|
||||
void cell_rt_disrupt(JSContext *ctx) {
|
||||
JS_ThrowTypeError(ctx, "type error in native code");
|
||||
}
|
||||
|
||||
/* --- set_var: set a variable in env_record or global --- */
|
||||
|
||||
void cell_rt_set_var(JSContext *ctx, JSValue val, const char *name) {
|
||||
JSValue key = JS_NewString(ctx, name);
|
||||
JS_SetProperty(ctx, ctx->global_obj, key, val);
|
||||
}
|
||||
|
||||
/* --- in: key in obj --- */
|
||||
|
||||
JSValue cell_rt_in(JSContext *ctx, JSValue key, JSValue obj) {
|
||||
int has = JS_HasProperty(ctx, obj, key);
|
||||
return JS_NewBool(ctx, has > 0);
|
||||
}
|
||||
|
||||
/* --- regexp: create regex from pattern and flags --- */
|
||||
|
||||
JSValue cell_rt_regexp(JSContext *ctx, const char *pattern, const char *flags) {
|
||||
JSValue argv[2];
|
||||
argv[0] = JS_NewString(ctx, pattern);
|
||||
argv[1] = JS_NewString(ctx, flags);
|
||||
JSValue re = js_regexp_constructor(ctx, JS_NULL, 2, argv);
|
||||
if (JS_IsException(re))
|
||||
return JS_EXCEPTION;
|
||||
return re;
|
||||
}
|
||||
|
||||
/* --- Module entry point ---
|
||||
Loads a native .cm module from a dylib handle.
|
||||
Looks up cell_main, builds a heap-allocated frame, sets
|
||||
g_current_dl_handle so closures register in the right module. */
|
||||
|
||||
JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle) {
|
||||
JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle, JSValue env) {
|
||||
cell_compiled_fn fn = (cell_compiled_fn)dlsym(dl_handle, "cell_main");
|
||||
if (!fn)
|
||||
return JS_ThrowTypeError(ctx, "cell_main not found in native module dylib");
|
||||
@@ -495,22 +626,27 @@ JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle) {
|
||||
void *prev_handle = g_current_dl_handle;
|
||||
g_current_dl_handle = dl_handle;
|
||||
|
||||
/* Heap-allocate so closures created in cell_main can reference
|
||||
this frame after the module entry returns. */
|
||||
JSValue *frame = calloc(512, sizeof(JSValue));
|
||||
if (!frame) {
|
||||
/* Make env available for cell_rt_get_intrinsic lookups */
|
||||
cell_rt_set_native_env(ctx, env);
|
||||
|
||||
/* GC-managed frame for module execution */
|
||||
JSValue *fp = cell_rt_enter_frame(ctx, 512);
|
||||
if (!fp) {
|
||||
g_current_dl_handle = prev_handle;
|
||||
return JS_ThrowTypeError(ctx, "frame allocation failed");
|
||||
}
|
||||
|
||||
JSValue result = fn(ctx, frame);
|
||||
JSValue result = fn(ctx, fp);
|
||||
cell_rt_leave_frame(ctx); /* safe — closures have independent GC refs */
|
||||
g_current_dl_handle = prev_handle;
|
||||
if (result == JS_EXCEPTION)
|
||||
return JS_EXCEPTION;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Load a native module from a dylib handle, trying a named symbol first.
|
||||
Falls back to cell_main if the named symbol is not found. */
|
||||
JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const char *sym_name) {
|
||||
JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const char *sym_name, JSValue env) {
|
||||
cell_compiled_fn fn = NULL;
|
||||
if (sym_name)
|
||||
fn = (cell_compiled_fn)dlsym(dl_handle, sym_name);
|
||||
@@ -522,19 +658,25 @@ JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const
|
||||
void *prev_handle = g_current_dl_handle;
|
||||
g_current_dl_handle = dl_handle;
|
||||
|
||||
JSValue *frame = calloc(512, sizeof(JSValue));
|
||||
if (!frame) {
|
||||
/* Make env available for cell_rt_get_intrinsic lookups */
|
||||
cell_rt_set_native_env(ctx, env);
|
||||
|
||||
JSValue *fp = cell_rt_enter_frame(ctx, 512);
|
||||
if (!fp) {
|
||||
g_current_dl_handle = prev_handle;
|
||||
return JS_ThrowTypeError(ctx, "frame allocation failed");
|
||||
}
|
||||
|
||||
JSValue result = fn(ctx, frame);
|
||||
JSValue result = fn(ctx, fp);
|
||||
cell_rt_leave_frame(ctx); /* safe — closures have independent GC refs */
|
||||
g_current_dl_handle = prev_handle;
|
||||
if (result == JS_EXCEPTION)
|
||||
return JS_EXCEPTION;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Backward-compat: uses RTLD_DEFAULT (works when dylib opened with RTLD_GLOBAL) */
|
||||
JSValue cell_rt_module_entry(JSContext *ctx) {
|
||||
void *handle = dlopen(NULL, RTLD_LAZY);
|
||||
return cell_rt_native_module_load(ctx, handle);
|
||||
return cell_rt_native_module_load(ctx, handle, JS_NULL);
|
||||
}
|
||||
|
||||
@@ -495,8 +495,8 @@ typedef enum MachOpcode {
|
||||
MACH_MOD, /* R(A) = R(B) % R(C) */
|
||||
MACH_POW, /* R(A) = R(B) ** R(C) */
|
||||
MACH_NEG, /* R(A) = -R(B) */
|
||||
MACH_INC, /* R(A) = R(B) + 1 */
|
||||
MACH_DEC, /* R(A) = R(B) - 1 */
|
||||
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)) */
|
||||
@@ -671,8 +671,8 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
|
||||
[MACH_MOD] = "mod",
|
||||
[MACH_POW] = "pow",
|
||||
[MACH_NEG] = "neg",
|
||||
[MACH_INC] = "inc",
|
||||
[MACH_DEC] = "dec",
|
||||
[MACH__DEAD_INC] = "dead_inc",
|
||||
[MACH__DEAD_DEC] = "dead_dec",
|
||||
[MACH_EQ] = "eq",
|
||||
[MACH_NEQ] = "neq",
|
||||
[MACH_LT] = "lt",
|
||||
|
||||
Reference in New Issue
Block a user