diff --git a/source/mach.c b/source/mach.c index ea08beda..fa74b598 100644 --- a/source/mach.c +++ b/source/mach.c @@ -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 */ diff --git a/source/qbe_helpers.c b/source/qbe_helpers.c index 9f96f2b9..ba90379a 100644 --- a/source/qbe_helpers.c +++ b/source/qbe_helpers.c @@ -12,12 +12,6 @@ #include #include -#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); diff --git a/source/quickjs-internal.h b/source/quickjs-internal.h index 73dcdad7..db5546d6 100644 --- a/source/quickjs-internal.h +++ b/source/quickjs-internal.h @@ -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 */ diff --git a/source/runtime.c b/source/runtime.c index 8bc865a4..9bc46d5d 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -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; }