This commit is contained in:
2026-02-19 00:47:34 -06:00
parent 19132c1517
commit 3f206d80dd
4 changed files with 299 additions and 222 deletions

View File

@@ -536,13 +536,10 @@ JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JSValue e
return out;
}
/* Create a native (QBE-compiled) function */
JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle,
uint16_t nr_slots, int arity, JSValue outer_frame) {
JSValue js_new_native_function_with_code(JSContext *ctx, JSValue code_obj, int arity, JSValue outer_frame) {
JSGCRef frame_ref;
JSGCRef fn_ref;
JSFunction *fn;
JSValue code_obj;
JS_PushGCRef(ctx, &frame_ref);
frame_ref.val = outer_frame;
JS_AddGCRef(ctx, &fn_ref);
@@ -560,12 +557,6 @@ JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle,
fn->kind = JS_FUNC_KIND_NATIVE;
fn->length = arity;
fn->name = JS_NULL;
code_obj = js_new_native_code(ctx, fn_ptr, dl_handle, nr_slots, arity);
if (JS_IsException(code_obj)) {
JS_DeleteGCRef(ctx, &fn_ref);
JS_PopGCRef(ctx, &frame_ref);
return JS_EXCEPTION;
}
fn = JS_VALUE_GET_FUNCTION(fn_ref.val);
fn->u.cell.code = code_obj;
fn->u.cell.env_record = JS_NULL;
@@ -577,6 +568,15 @@ JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle,
return out;
}
/* Create a native (QBE-compiled) function */
JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle,
uint16_t nr_slots, int arity, JSValue outer_frame) {
JSValue code_obj = js_new_native_code(ctx, fn_ptr, dl_handle, nr_slots, arity);
if (JS_IsException(code_obj))
return JS_EXCEPTION;
return js_new_native_function_with_code(ctx, code_obj, arity, outer_frame);
}
/* Binary operations helper */
static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
/* Fast path for integers */

View File

@@ -12,12 +12,6 @@
#include <stdlib.h>
#include <string.h>
#if defined(__GNUC__) || defined(__clang__)
#define CELL_THREAD_LOCAL __thread
#else
#define CELL_THREAD_LOCAL _Thread_local
#endif
/* Non-inline wrappers for static inline functions in quickjs.h */
JSValue qbe_new_float64(JSContext *ctx, double d) {
return __JS_NewFloat64(ctx, d);
@@ -230,36 +224,72 @@ JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b) {
/* --- Property access --- */
/* Current module handle for active native dispatch. */
static CELL_THREAD_LOCAL void *g_current_dl_handle = NULL;
typedef struct {
void *dl_handle;
JSContext *ctx;
JSGCRef *vals;
int count;
} AOTLiteralPool;
typedef struct {
const char *name;
JSValue key;
} AOTKeyCacheEntry;
typedef struct {
void *dl_handle;
int64_t fn_idx;
JSValue code;
} AOTCodeCacheEntry;
static CELL_THREAD_LOCAL AOTLiteralPool g_aot_lit_pool = {0};
typedef struct AOTGCRefChunk AOTGCRefChunk;
static void aot_clear_lit_pool(void) {
if (g_aot_lit_pool.vals) {
if (g_aot_lit_pool.ctx) {
for (int i = 0; i < g_aot_lit_pool.count; i++)
JS_DeleteGCRef(g_aot_lit_pool.ctx, &g_aot_lit_pool.vals[i]);
}
free(g_aot_lit_pool.vals);
typedef struct {
void *current_dl_handle;
AOTLiteralPool lit_pool;
AOTKeyCacheEntry *key_cache;
int key_cache_count;
int key_cache_cap;
AOTCodeCacheEntry *code_cache;
int code_cache_count;
int code_cache_cap;
JSGCRef native_env_ref;
int has_native_env;
int native_env_ref_inited;
AOTGCRefChunk **gc_ref_chunks;
int gc_ref_chunk_count;
int aot_depth;
JSValue pending_callee_frame;
int pending_is_tail;
} NativeRTState;
static NativeRTState *native_state(JSContext *ctx) {
NativeRTState *st = (NativeRTState *)ctx->native_state;
if (st) return st;
st = js_mallocz_rt(sizeof(*st));
if (!st) {
JS_RaiseOOM(ctx);
return NULL;
}
g_aot_lit_pool.dl_handle = NULL;
g_aot_lit_pool.ctx = NULL;
g_aot_lit_pool.vals = NULL;
g_aot_lit_pool.count = 0;
ctx->native_state = st;
return st;
}
static int aot_load_lit_pool(JSContext *ctx, void *dl_handle) {
aot_clear_lit_pool();
g_aot_lit_pool.dl_handle = dl_handle;
g_aot_lit_pool.ctx = ctx;
static void aot_clear_lit_pool(JSContext *ctx, NativeRTState *st) {
if (!st) return;
if (st->lit_pool.vals) {
for (int i = 0; i < st->lit_pool.count; i++)
JS_DeleteGCRef(ctx, &st->lit_pool.vals[i]);
free(st->lit_pool.vals);
}
st->lit_pool.dl_handle = NULL;
st->lit_pool.ctx = NULL;
st->lit_pool.vals = NULL;
st->lit_pool.count = 0;
}
static int aot_load_lit_pool(JSContext *ctx, NativeRTState *st, void *dl_handle) {
aot_clear_lit_pool(ctx, st);
st->lit_pool.dl_handle = dl_handle;
st->lit_pool.ctx = ctx;
if (!dl_handle)
return 1;
@@ -269,20 +299,20 @@ static int aot_load_lit_pool(JSContext *ctx, void *dl_handle) {
if (count <= 0 || !table_ptr)
return 1;
g_aot_lit_pool.vals = (JSGCRef *)calloc((size_t)count, sizeof(JSGCRef));
if (!g_aot_lit_pool.vals) {
st->lit_pool.vals = (JSGCRef *)calloc((size_t)count, sizeof(JSGCRef));
if (!st->lit_pool.vals) {
JS_RaiseOOM(ctx);
return 0;
}
g_aot_lit_pool.count = 0;
st->lit_pool.count = 0;
for (int i = 0; i < count; i++) {
const char *cstr = table_ptr[i] ? table_ptr[i] : "";
JS_AddGCRef(ctx, &g_aot_lit_pool.vals[i]);
g_aot_lit_pool.count = i + 1;
g_aot_lit_pool.vals[i].val = js_key_new(ctx, cstr);
if (JS_IsException(g_aot_lit_pool.vals[i].val)) {
aot_clear_lit_pool();
JS_AddGCRef(ctx, &st->lit_pool.vals[i]);
st->lit_pool.count = i + 1;
st->lit_pool.vals[i].val = js_key_new(ctx, cstr);
if (JS_IsException(st->lit_pool.vals[i].val)) {
aot_clear_lit_pool(ctx, st);
return 0;
}
}
@@ -290,70 +320,56 @@ static int aot_load_lit_pool(JSContext *ctx, void *dl_handle) {
}
static JSValue aot_lit_from_index(JSContext *ctx, int64_t lit_idx) {
NativeRTState *st = native_state(ctx);
if (!st) return JS_EXCEPTION;
if (lit_idx < 0) {
JS_RaiseDisrupt(ctx, "literal index out of range");
return JS_EXCEPTION;
}
if (g_aot_lit_pool.dl_handle != g_current_dl_handle || g_aot_lit_pool.ctx != ctx) {
if (!aot_load_lit_pool(ctx, g_current_dl_handle))
if (st->lit_pool.dl_handle != st->current_dl_handle || st->lit_pool.ctx != ctx) {
if (!aot_load_lit_pool(ctx, st, st->current_dl_handle))
return JS_EXCEPTION;
}
if (lit_idx >= g_aot_lit_pool.count) {
if (lit_idx >= st->lit_pool.count) {
JS_RaiseDisrupt(ctx, "literal index out of range");
return JS_EXCEPTION;
}
return g_aot_lit_pool.vals[lit_idx].val;
return st->lit_pool.vals[lit_idx].val;
}
typedef struct {
const char *name;
JSValue key;
} AOTKeyCacheEntry;
static CELL_THREAD_LOCAL JSContext *g_aot_key_cache_ctx = NULL;
static CELL_THREAD_LOCAL AOTKeyCacheEntry *g_aot_key_cache = NULL;
static CELL_THREAD_LOCAL int g_aot_key_cache_count = 0;
static CELL_THREAD_LOCAL int g_aot_key_cache_cap = 0;
/* Convert a static C string to an interned JSValue key.
Uses a small thread-local cache keyed by C-string pointer to avoid
Uses a small per-actor cache keyed by C-string pointer to avoid
repeated UTF-8 decoding in hot property paths. */
static JSValue aot_key_from_cstr(JSContext *ctx, const char *name) {
NativeRTState *st = native_state(ctx);
if (!st) return JS_EXCEPTION;
if (!name)
return JS_NULL;
if (g_aot_key_cache_ctx != ctx) {
free(g_aot_key_cache);
g_aot_key_cache = NULL;
g_aot_key_cache_count = 0;
g_aot_key_cache_cap = 0;
g_aot_key_cache_ctx = ctx;
}
for (int i = 0; i < g_aot_key_cache_count; i++) {
if (g_aot_key_cache[i].name == name)
return g_aot_key_cache[i].key;
for (int i = 0; i < st->key_cache_count; i++) {
if (st->key_cache[i].name == name)
return st->key_cache[i].key;
}
JSValue key = js_key_new(ctx, name);
if (JS_IsNull(key))
return JS_RaiseDisrupt(ctx, "invalid property key");
if (g_aot_key_cache_count >= g_aot_key_cache_cap) {
int new_cap = g_aot_key_cache_cap ? (g_aot_key_cache_cap * 2) : 64;
if (st->key_cache_count >= st->key_cache_cap) {
int new_cap = st->key_cache_cap ? (st->key_cache_cap * 2) : 64;
AOTKeyCacheEntry *new_cache =
(AOTKeyCacheEntry *)realloc(g_aot_key_cache, (size_t)new_cap * sizeof(*new_cache));
(AOTKeyCacheEntry *)realloc(st->key_cache, (size_t)new_cap * sizeof(*new_cache));
if (!new_cache)
return JS_RaiseOOM(ctx);
g_aot_key_cache = new_cache;
g_aot_key_cache_cap = new_cap;
st->key_cache = new_cache;
st->key_cache_cap = new_cap;
}
g_aot_key_cache[g_aot_key_cache_count].name = name;
g_aot_key_cache[g_aot_key_cache_count].key = key;
g_aot_key_cache_count++;
st->key_cache[st->key_cache_count].name = name;
st->key_cache[st->key_cache_count].key = key;
st->key_cache_count++;
return key;
}
@@ -460,42 +476,44 @@ int 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 CELL_THREAD_LOCAL JSGCRef g_native_env_ref;
static CELL_THREAD_LOCAL int g_has_native_env = 0;
void cell_rt_set_native_env(JSContext *ctx, JSValue env) {
NativeRTState *st = native_state(ctx);
if (!st) return;
if (!JS_IsNull(env) && !JS_IsStone(env)) {
fprintf(stderr, "cell_rt_set_native_env: ERROR env not stone\n");
abort();
}
/* Drop module literal pool roots before switching native env/module. */
aot_clear_lit_pool();
aot_clear_lit_pool(ctx, st);
/* Native module boundary: clear per-thread key cache so stale keys
/* Native module boundary: clear per-actor key cache so stale keys
cannot survive across context/module lifetimes. */
free(g_aot_key_cache);
g_aot_key_cache = NULL;
g_aot_key_cache_count = 0;
g_aot_key_cache_cap = 0;
g_aot_key_cache_ctx = ctx;
free(st->key_cache);
st->key_cache = NULL;
st->key_cache_count = 0;
st->key_cache_cap = 0;
if (g_has_native_env)
JS_DeleteGCRef(ctx, &g_native_env_ref);
if (st->has_native_env && st->native_env_ref_inited) {
JS_DeleteGCRef(ctx, &st->native_env_ref);
st->native_env_ref_inited = 0;
}
if (!JS_IsNull(env)) {
JS_AddGCRef(ctx, &g_native_env_ref);
g_native_env_ref.val = env;
g_has_native_env = 1;
JS_AddGCRef(ctx, &st->native_env_ref);
st->native_env_ref_inited = 1;
st->native_env_ref.val = env;
st->has_native_env = 1;
} else {
g_has_native_env = 0;
st->has_native_env = 0;
st->native_env_ref.val = JS_NULL;
}
}
static JSValue cell_rt_get_intrinsic_key(JSContext *ctx, JSValue key) {
NativeRTState *st = native_state(ctx);
if (!st) return JS_EXCEPTION;
/* Check native env first (runtime-provided functions like log) */
if (g_has_native_env) {
JSValue v = JS_GetProperty(ctx, g_native_env_ref.val, key);
if (st->has_native_env) {
JSValue v = JS_GetProperty(ctx, st->native_env_ref.val, key);
if (!JS_IsNull(v))
return v;
}
@@ -586,75 +604,51 @@ typedef struct AOTGCRefChunk {
uint8_t inited[AOT_GC_REF_CHUNK_SIZE];
} AOTGCRefChunk;
static CELL_THREAD_LOCAL AOTGCRefChunk **g_aot_gc_ref_chunks = NULL;
static CELL_THREAD_LOCAL int g_aot_gc_ref_chunk_count = 0;
static CELL_THREAD_LOCAL int g_aot_depth = 0;
static CELL_THREAD_LOCAL JSContext *g_aot_gc_ref_ctx = NULL;
int cell_rt_native_active(void) {
return g_aot_depth > 0;
int cell_rt_native_active(JSContext *ctx) {
NativeRTState *st = (NativeRTState *)ctx->native_state;
return st ? (st->aot_depth > 0) : 0;
}
static int ensure_aot_gc_ref_slot(JSContext *ctx, int depth_index) {
static int ensure_aot_gc_ref_slot(JSContext *ctx, NativeRTState *st, int depth_index) {
if (depth_index < 0)
return 0;
int needed_chunks = (depth_index / AOT_GC_REF_CHUNK_SIZE) + 1;
if (needed_chunks <= g_aot_gc_ref_chunk_count)
if (needed_chunks <= st->gc_ref_chunk_count)
return 1;
AOTGCRefChunk **new_chunks =
(AOTGCRefChunk **)realloc(g_aot_gc_ref_chunks,
(AOTGCRefChunk **)realloc(st->gc_ref_chunks,
(size_t)needed_chunks * sizeof(*new_chunks));
if (!new_chunks) {
JS_RaiseOOM(ctx);
return 0;
}
g_aot_gc_ref_chunks = new_chunks;
for (int i = g_aot_gc_ref_chunk_count; i < needed_chunks; i++) {
g_aot_gc_ref_chunks[i] = (AOTGCRefChunk *)calloc(1, sizeof(AOTGCRefChunk));
if (!g_aot_gc_ref_chunks[i]) {
st->gc_ref_chunks = new_chunks;
for (int i = st->gc_ref_chunk_count; i < needed_chunks; i++) {
st->gc_ref_chunks[i] = (AOTGCRefChunk *)calloc(1, sizeof(AOTGCRefChunk));
if (!st->gc_ref_chunks[i]) {
JS_RaiseOOM(ctx);
return 0;
}
}
g_aot_gc_ref_chunk_count = needed_chunks;
st->gc_ref_chunk_count = needed_chunks;
return 1;
}
static inline JSGCRef *aot_gc_ref_at(int depth_index) {
static inline JSGCRef *aot_gc_ref_at(NativeRTState *st, int depth_index) {
int chunk_index = depth_index / AOT_GC_REF_CHUNK_SIZE;
int slot_index = depth_index % AOT_GC_REF_CHUNK_SIZE;
return &g_aot_gc_ref_chunks[chunk_index]->refs[slot_index];
return &st->gc_ref_chunks[chunk_index]->refs[slot_index];
}
static inline uint8_t *aot_gc_ref_inited_at(int depth_index) {
static inline uint8_t *aot_gc_ref_inited_at(NativeRTState *st, int depth_index) {
int chunk_index = depth_index / AOT_GC_REF_CHUNK_SIZE;
int slot_index = depth_index % AOT_GC_REF_CHUNK_SIZE;
return &g_aot_gc_ref_chunks[chunk_index]->inited[slot_index];
return &st->gc_ref_chunks[chunk_index]->inited[slot_index];
}
/* GC refs are owned by a specific JSContext. If context changes on this thread,
unregister previous refs and reset per-slot initialization state. */
static void aot_gc_ref_reset_ctx(JSContext *ctx) {
if (g_aot_gc_ref_ctx == ctx)
return;
if (g_aot_gc_ref_ctx) {
for (int ci = 0; ci < g_aot_gc_ref_chunk_count; ci++) {
AOTGCRefChunk *chunk = g_aot_gc_ref_chunks[ci];
for (int si = 0; si < AOT_GC_REF_CHUNK_SIZE; si++) {
if (chunk->inited[si]) {
JS_DeleteGCRef(g_aot_gc_ref_ctx, &chunk->refs[si]);
chunk->inited[si] = 0;
chunk->refs[si].val = JS_NULL;
}
}
}
}
g_aot_gc_ref_ctx = ctx;
}
static inline void aot_gc_ref_activate(JSContext *ctx, int depth_index) {
JSGCRef *ref = aot_gc_ref_at(depth_index);
uint8_t *inited = aot_gc_ref_inited_at(depth_index);
static inline void aot_gc_ref_activate(JSContext *ctx, NativeRTState *st, int depth_index) {
JSGCRef *ref = aot_gc_ref_at(st, depth_index);
uint8_t *inited = aot_gc_ref_inited_at(st, depth_index);
if (!*inited) {
JS_AddGCRef(ctx, ref);
*inited = 1;
@@ -662,42 +656,45 @@ static inline void aot_gc_ref_activate(JSContext *ctx, int depth_index) {
}
JSValue *cell_rt_enter_frame(JSContext *ctx, int64_t nr_slots) {
aot_gc_ref_reset_ctx(ctx);
if (!ensure_aot_gc_ref_slot(ctx, g_aot_depth)) {
NativeRTState *st = native_state(ctx);
if (!st) return NULL;
if (!ensure_aot_gc_ref_slot(ctx, st, st->aot_depth)) {
return NULL;
}
JSFrameRegister *frame = alloc_frame_register(ctx, (int)nr_slots);
if (!frame) return NULL;
aot_gc_ref_activate(ctx, g_aot_depth);
JSGCRef *ref = aot_gc_ref_at(g_aot_depth);
aot_gc_ref_activate(ctx, st, st->aot_depth);
JSGCRef *ref = aot_gc_ref_at(st, st->aot_depth);
ref->val = JS_MKPTR(frame);
g_aot_depth++;
st->aot_depth++;
return (JSValue *)frame->slots;
}
/* Push an already-allocated frame onto the active AOT frame stack. */
static int cell_rt_push_existing_frame(JSContext *ctx, JSValue frame_val) {
aot_gc_ref_reset_ctx(ctx);
if (!ensure_aot_gc_ref_slot(ctx, g_aot_depth))
NativeRTState *st = native_state(ctx);
if (!st) return 0;
if (!ensure_aot_gc_ref_slot(ctx, st, st->aot_depth))
return 0;
aot_gc_ref_activate(ctx, g_aot_depth);
JSGCRef *ref = aot_gc_ref_at(g_aot_depth);
aot_gc_ref_activate(ctx, st, st->aot_depth);
JSGCRef *ref = aot_gc_ref_at(st, st->aot_depth);
ref->val = frame_val;
g_aot_depth++;
st->aot_depth++;
return 1;
}
JSValue *cell_rt_refresh_fp(JSContext *ctx) {
(void)ctx;
if (g_aot_depth <= 0) {
fprintf(stderr, "[BUG] cell_rt_refresh_fp: g_aot_depth=%d\n", g_aot_depth);
NativeRTState *st = native_state(ctx);
if (!st) return NULL;
if (st->aot_depth <= 0) {
fprintf(stderr, "[BUG] cell_rt_refresh_fp: aot_depth=%d\n", st->aot_depth);
abort();
}
JSValue val = aot_gc_ref_at(g_aot_depth - 1)->val;
JSValue val = aot_gc_ref_at(st, st->aot_depth - 1)->val;
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(val);
if (!frame) {
fprintf(stderr, "[BUG] cell_rt_refresh_fp: frame is NULL at depth=%d val=%lld\n",
g_aot_depth, (long long)val);
st->aot_depth, (long long)val);
abort();
}
return (JSValue *)frame->slots;
@@ -705,13 +702,15 @@ JSValue *cell_rt_refresh_fp(JSContext *ctx) {
/* Combined refresh + exception check in a single call. */
JSValue *cell_rt_refresh_fp_checked(JSContext *ctx) {
NativeRTState *st = native_state(ctx);
if (!st) return NULL;
if (JS_HasException(ctx))
return NULL;
if (g_aot_depth <= 0) {
fprintf(stderr, "[BUG] cell_rt_refresh_fp_checked: g_aot_depth=%d\n", g_aot_depth);
if (st->aot_depth <= 0) {
fprintf(stderr, "[BUG] cell_rt_refresh_fp_checked: aot_depth=%d\n", st->aot_depth);
abort();
}
JSValue val = aot_gc_ref_at(g_aot_depth - 1)->val;
JSValue val = aot_gc_ref_at(st, st->aot_depth - 1)->val;
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(val);
if (!frame) {
fprintf(stderr, "[BUG] cell_rt_refresh_fp_checked: frame is NULL\n");
@@ -721,34 +720,26 @@ JSValue *cell_rt_refresh_fp_checked(JSContext *ctx) {
}
void cell_rt_leave_frame(JSContext *ctx) {
(void)ctx;
if (g_aot_depth <= 0) {
NativeRTState *st = native_state(ctx);
if (!st) return;
if (st->aot_depth <= 0) {
fprintf(stderr, "[BUG] cell_rt_leave_frame underflow\n");
abort();
}
g_aot_depth--;
aot_gc_ref_at(g_aot_depth)->val = JS_NULL;
st->aot_depth--;
aot_gc_ref_at(st, st->aot_depth)->val = JS_NULL;
}
/* --- Function creation and calling --- */
typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
/* Set before executing a native module's cell_main —
used by cell_rt_make_function to resolve fn_ptr via dlsym */
/* g_current_dl_handle is defined near property/literal helpers. */
/* ============================================================
Dispatch loop — the core of native function execution.
Each compiled cell_fn_N returns to this loop when it needs
to call another function (instead of recursing via C stack).
============================================================ */
/* Pending call state — set by cell_rt_signal_call / cell_rt_signal_tail_call,
read by the dispatch loop. */
static CELL_THREAD_LOCAL JSValue g_pending_callee_frame = 0; /* JSFrameRegister ptr */
static CELL_THREAD_LOCAL int g_pending_is_tail = 0;
/* Poll pause state on taken backward jumps (AOT backedges).
MACH can suspend/resume a register VM frame at pc granularity; native AOT
does not currently have an equivalent resume point, so we acknowledge timer
@@ -762,34 +753,38 @@ int cell_rt_check_backedge(JSContext *ctx) {
}
void cell_rt_signal_call(JSContext *ctx, void *fp, int64_t frame_slot) {
(void)ctx;
NativeRTState *st = native_state(ctx);
if (!st) return;
JSValue *slots = (JSValue *)fp;
g_pending_callee_frame = slots[frame_slot];
g_pending_is_tail = 0;
st->pending_callee_frame = slots[frame_slot];
st->pending_is_tail = 0;
}
void cell_rt_signal_tail_call(JSContext *ctx, void *fp, int64_t frame_slot) {
(void)ctx;
NativeRTState *st = native_state(ctx);
if (!st) return;
JSValue *slots = (JSValue *)fp;
g_pending_callee_frame = slots[frame_slot];
g_pending_is_tail = 1;
st->pending_callee_frame = slots[frame_slot];
st->pending_is_tail = 1;
}
/* Entry point called from JS_CallInternal / JS_Call / MACH_INVOKE
for JS_FUNC_KIND_NATIVE functions. */
JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
JSValue this_obj, int argc, JSValue *argv) {
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;
int arity = f->length;
void *prev_dl_handle = g_current_dl_handle;
g_current_dl_handle = JS_VALUE_GET_CODE(f->u.cell.code)->u.native.dl_handle;
void *prev_dl_handle = st->current_dl_handle;
st->current_dl_handle = JS_VALUE_GET_CODE(f->u.cell.code)->u.native.dl_handle;
#define RETURN_DISPATCH(v) \
do { \
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed); \
g_current_dl_handle = prev_dl_handle; \
st->current_dl_handle = prev_dl_handle; \
return (v); \
} while (0)
@@ -820,11 +815,11 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
JSFrameRegister *frame = (JSFrameRegister *)((char *)fp - offsetof(JSFrameRegister, slots));
frame->function = func_obj;
int base_depth = g_aot_depth; /* remember entry depth for return detection */
int base_depth = st->aot_depth; /* remember entry depth for return detection */
for (;;) {
g_pending_callee_frame = 0;
g_pending_is_tail = 0;
st->pending_callee_frame = 0;
st->pending_is_tail = 0;
if (atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed) >= 1)
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
@@ -832,26 +827,26 @@ 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)
g_current_dl_handle = JS_VALUE_GET_CODE(cur_fn->u.cell.code)->u.native.dl_handle;
st->current_dl_handle = JS_VALUE_GET_CODE(cur_fn->u.cell.code)->u.native.dl_handle;
}
JSValue result = fn(ctx, fp);
/* Re-derive frame after potential GC */
if (g_aot_depth <= 0) {
if (st->aot_depth <= 0) {
fprintf(stderr, "[BUG] native dispatch lost frame depth after fn call\n");
abort();
}
JSValue frame_val = aot_gc_ref_at(g_aot_depth - 1)->val;
JSValue frame_val = aot_gc_ref_at(st, st->aot_depth - 1)->val;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
fp = (JSValue *)frame->slots;
if (g_pending_callee_frame != 0) {
if (st->pending_callee_frame != 0) {
/* Function signaled a call — dispatch it */
JSValue callee_frame_val = g_pending_callee_frame;
g_pending_callee_frame = 0;
int pending_is_tail = g_pending_is_tail;
g_pending_is_tail = 0;
JSValue callee_frame_val = st->pending_callee_frame;
st->pending_callee_frame = 0;
int pending_is_tail = st->pending_is_tail;
st->pending_is_tail = 0;
JSGCRef callee_ref;
JS_PushGCRef(ctx, &callee_ref);
callee_ref.val = callee_frame_val;
@@ -897,7 +892,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
JS_PopGCRef(ctx, &callee_ref);
RETURN_DISPATCH(JS_EXCEPTION);
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(aot_gc_ref_at(g_aot_depth - 1)->val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(aot_gc_ref_at(st, st->aot_depth - 1)->val);
fp = (JSValue *)frame->slots;
fn = callee_ptr;
} else {
@@ -917,7 +912,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
if (!cell_rt_push_existing_frame(ctx, callee_ref.val)) {
/* Resume caller with exception pending */
frame_val = aot_gc_ref_at(g_aot_depth - 1)->val;
frame_val = aot_gc_ref_at(st, st->aot_depth - 1)->val;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
fp = (JSValue *)frame->slots;
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
@@ -926,7 +921,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
JS_PopGCRef(ctx, &callee_ref);
continue;
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(aot_gc_ref_at(g_aot_depth - 1)->val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(aot_gc_ref_at(st, st->aot_depth - 1)->val);
fp = (JSValue *)frame->slots;
fn = callee_ptr;
}
@@ -942,7 +937,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
callee_argc, &callee_fr->slots[1], 0);
/* Re-derive frame after call */
frame_val = aot_gc_ref_at(g_aot_depth - 1)->val;
frame_val = aot_gc_ref_at(st, st->aot_depth - 1)->val;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
fp = (JSValue *)frame->slots;
@@ -970,18 +965,18 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
if (pending_is_tail) {
/* Tail call to non-native: return its result up the chain */
/* Pop current frame and return to caller */
if (g_aot_depth <= base_depth) {
if (st->aot_depth <= base_depth) {
cell_rt_leave_frame(ctx);
JS_PopGCRef(ctx, &callee_ref);
RETURN_DISPATCH(ret);
}
/* Pop current frame, return to caller frame */
cell_rt_leave_frame(ctx);
if (g_aot_depth < base_depth) {
if (st->aot_depth < base_depth) {
JS_PopGCRef(ctx, &callee_ref);
RETURN_DISPATCH(ret);
}
frame_val = aot_gc_ref_at(g_aot_depth - 1)->val;
frame_val = aot_gc_ref_at(st, st->aot_depth - 1)->val;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
fp = (JSValue *)frame->slots;
int ret_info = JS_VALUE_GET_INT(frame->address);
@@ -1014,17 +1009,17 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
if (!JS_HasException(ctx))
JS_Disrupt(ctx);
if (g_aot_depth <= base_depth) {
if (st->aot_depth <= base_depth) {
cell_rt_leave_frame(ctx);
RETURN_DISPATCH(JS_EXCEPTION);
}
cell_rt_leave_frame(ctx);
if (g_aot_depth < base_depth) {
if (st->aot_depth < base_depth) {
RETURN_DISPATCH(JS_EXCEPTION);
}
/* Resume caller and tag the return slot with JS_EXCEPTION. */
frame_val = aot_gc_ref_at(g_aot_depth - 1)->val;
frame_val = aot_gc_ref_at(st, st->aot_depth - 1)->val;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
fp = (JSValue *)frame->slots;
int ret_info = JS_VALUE_GET_INT(frame->address);
@@ -1038,17 +1033,17 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
}
/* Normal return — pop frame and store result in caller */
if (g_aot_depth <= base_depth) {
if (st->aot_depth <= base_depth) {
cell_rt_leave_frame(ctx);
RETURN_DISPATCH(result);
}
cell_rt_leave_frame(ctx);
if (g_aot_depth < base_depth) {
if (st->aot_depth < base_depth) {
RETURN_DISPATCH(result);
}
/* Return to caller frame */
frame_val = aot_gc_ref_at(g_aot_depth - 1)->val;
frame_val = aot_gc_ref_at(st, st->aot_depth - 1)->val;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
fp = (JSValue *)frame->slots;
int ret_info = JS_VALUE_GET_INT(frame->address);
@@ -1064,27 +1059,71 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
#undef RETURN_DISPATCH
}
static JSValue aot_get_or_create_native_code(JSContext *ctx, NativeRTState *st,
void *dl_handle, int64_t fn_idx,
int arity, uint16_t nr_slots) {
for (int i = 0; i < st->code_cache_count; i++) {
AOTCodeCacheEntry *e = &st->code_cache[i];
if (e->dl_handle == dl_handle && e->fn_idx == fn_idx)
return e->code;
}
char name[64];
snprintf(name, sizeof(name), "cell_fn_%lld", (long long)fn_idx);
void *fn_ptr = dlsym(dl_handle, name);
if (!fn_ptr)
return JS_RaiseDisrupt(ctx, "native function %s not found in dylib", name);
JSCode *code = ct_alloc(ctx, sizeof(JSCode), 8);
if (!code)
return JS_EXCEPTION;
memset(code, 0, sizeof(*code));
code->header = objhdr_make(0, OBJ_CODE, 0, 0, 0, 0);
code->kind = JS_CODE_KIND_NATIVE;
code->arity = (int16_t)arity;
code->u.native.fn_ptr = fn_ptr;
code->u.native.dl_handle = dl_handle;
code->u.native.nr_slots = nr_slots;
JSValue code_obj = JS_MKPTR(code);
if (st->code_cache_count >= st->code_cache_cap) {
int new_cap = st->code_cache_cap ? (st->code_cache_cap * 2) : 128;
AOTCodeCacheEntry *new_cache =
(AOTCodeCacheEntry *)realloc(st->code_cache, (size_t)new_cap * sizeof(*new_cache));
if (!new_cache)
return JS_RaiseOOM(ctx);
st->code_cache = new_cache;
st->code_cache_cap = new_cap;
}
st->code_cache[st->code_cache_count].dl_handle = dl_handle;
st->code_cache[st->code_cache_count].fn_idx = fn_idx;
st->code_cache[st->code_cache_count].code = code_obj;
st->code_cache_count++;
return code_obj;
}
/* Create a native function object from a compiled fn_idx.
Called from QBE-generated code during function creation. */
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp,
int64_t nr_args, int64_t nr_slots) {
if (!g_current_dl_handle)
(void)outer_fp;
NativeRTState *st = native_state(ctx);
if (!st) return JS_EXCEPTION;
if (!st->current_dl_handle)
return JS_RaiseDisrupt(ctx, "no native module loaded");
/* Resolve fn_ptr via dlsym at creation time — cached in the function object */
char name[64];
snprintf(name, sizeof(name), "cell_fn_%lld", (long long)fn_idx);
void *fn_ptr = dlsym(g_current_dl_handle, name);
if (!fn_ptr)
return JS_RaiseDisrupt(ctx, "native function %s not found in dylib", name);
JSValue code_obj = aot_get_or_create_native_code(
ctx, st, st->current_dl_handle, fn_idx, (int)nr_args, (uint16_t)nr_slots);
if (JS_IsException(code_obj))
return JS_EXCEPTION;
/* Get the current frame as outer_frame for closures */
JSValue outer_frame = JS_NULL;
if (g_aot_depth > 0)
outer_frame = aot_gc_ref_at(g_aot_depth - 1)->val;
if (st->aot_depth > 0)
outer_frame = aot_gc_ref_at(st, st->aot_depth - 1)->val;
return js_new_native_function(ctx, fn_ptr, g_current_dl_handle,
(uint16_t)nr_slots, (int)nr_args, outer_frame);
return js_new_native_function_with_code(ctx, code_obj, (int)nr_args, outer_frame);
}
/* --- Frame-based function calling ---
@@ -1338,22 +1377,24 @@ JSValue cell_rt_regexp(JSContext *ctx, const char *pattern, const char *flags) {
/* --- 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. */
Looks up cell_main, builds a heap-allocated frame, and
records active module handle in per-actor native state. */
/* Helper: run a native module's entry point through the dispatch loop.
Creates a temporary JS_FUNC_KIND_NATIVE function so that the full
dispatch loop (tail calls, closures, etc.) works for module-level code. */
static JSValue native_module_run(JSContext *ctx, void *dl_handle,
cell_compiled_fn entry, int nr_slots) {
void *prev_handle = g_current_dl_handle;
g_current_dl_handle = dl_handle;
NativeRTState *st = native_state(ctx);
if (!st) return JS_EXCEPTION;
void *prev_handle = st->current_dl_handle;
st->current_dl_handle = dl_handle;
/* Create a native function object for the entry point */
JSValue func_obj = js_new_native_function(ctx, (void *)entry, dl_handle,
(uint16_t)nr_slots, 0, JS_NULL);
if (JS_IsException(func_obj)) {
g_current_dl_handle = prev_handle;
st->current_dl_handle = prev_handle;
return JS_EXCEPTION;
}
@@ -1362,7 +1403,7 @@ static JSValue native_module_run(JSContext *ctx, void *dl_handle,
JS_GetException(ctx);
JSValue result = cell_native_dispatch(ctx, func_obj, JS_NULL, 0, NULL);
g_current_dl_handle = prev_handle;
st->current_dl_handle = prev_handle;
return result;
}
@@ -1409,6 +1450,38 @@ JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const
return native_module_run(ctx, dl_handle, fn, nr_slots);
}
void cell_rt_free_native_state(JSContext *ctx) {
NativeRTState *st = (NativeRTState *)ctx->native_state;
if (!st) return;
aot_clear_lit_pool(ctx, st);
if (st->has_native_env && st->native_env_ref_inited) {
JS_DeleteGCRef(ctx, &st->native_env_ref);
st->native_env_ref_inited = 0;
st->native_env_ref.val = JS_NULL;
}
for (int ci = 0; ci < st->gc_ref_chunk_count; ci++) {
AOTGCRefChunk *chunk = st->gc_ref_chunks[ci];
if (!chunk) continue;
for (int si = 0; si < AOT_GC_REF_CHUNK_SIZE; si++) {
if (chunk->inited[si]) {
JS_DeleteGCRef(ctx, &chunk->refs[si]);
chunk->inited[si] = 0;
chunk->refs[si].val = JS_NULL;
}
}
free(chunk);
}
free(st->gc_ref_chunks);
free(st->key_cache);
free(st->code_cache);
js_free_rt(st);
ctx->native_state = NULL;
}
/* 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);

View File

@@ -1121,6 +1121,7 @@ struct JSContext {
JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */
JSLocalRef *top_local_ref; /* for JS_LOCAL macro - GC updates C locals through pointers */
CCallRoot *c_call_root; /* stack of auto-rooted C call argv arrays */
void *native_state; /* qbe_helpers.c per-actor native runtime state */
int class_count; /* size of class_array and class_proto */
JSClass *class_array;
@@ -1562,7 +1563,7 @@ static inline void set_value (JSContext *ctx, JSValue *pval, JSValue new_val) {
*pval = new_val;
}
int cell_rt_native_active(void);
int cell_rt_native_active(JSContext *ctx);
static inline __exception int js_poll_interrupts (JSContext *ctx) {
if (unlikely (atomic_load_explicit (&ctx->pause_flag, memory_order_relaxed) >= 2)) {
@@ -1659,7 +1660,9 @@ JSValue js_key_from_string (JSContext *ctx, JSValue val);
/* mach.c exports */
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle, uint16_t nr_slots, int arity, JSValue outer_frame);
JSValue js_new_native_function_with_code(JSContext *ctx, JSValue code_obj, int arity, JSValue outer_frame);
JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count);
void cell_rt_free_native_state(JSContext *ctx);
#endif /* QUICKJS_INTERNAL_H */

View File

@@ -2102,6 +2102,7 @@ void JS_FreeContext (JSContext *ctx) {
JSRuntime *rt = ctx->rt;
int i;
cell_rt_free_native_state(ctx);
JS_DeleteGCRef(ctx, &ctx->suspended_frame_ref);
for (i = 0; i < ctx->class_count; i++) {
@@ -5332,7 +5333,7 @@ JSValue js_regexp_toString (JSContext *ctx, JSValue this_val, int argc, JSValue
int lre_check_timeout (void *opaque) {
JSContext *ctx = opaque;
if (cell_rt_native_active ()) {
if (cell_rt_native_active (ctx)) {
atomic_store_explicit (&ctx->pause_flag, 0, memory_order_relaxed);
return 0;
}