aot pass all tests

This commit is contained in:
2026-02-18 16:53:33 -06:00
parent 469b7ac478
commit c33c35de87
6 changed files with 583 additions and 149 deletions

View File

@@ -9,6 +9,14 @@
#include "quickjs-internal.h"
#include <math.h>
#include <pthread.h>
#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) {
@@ -278,8 +286,8 @@ void cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr,
/* 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;
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) {
if (!JS_IsNull(env) && !JS_IsStone(env)) {
@@ -370,18 +378,58 @@ void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
update the current frame pointer when it moves objects.
cell_rt_refresh_fp re-derives the slot pointer after any GC call. */
#define MAX_AOT_DEPTH 8192
static JSGCRef g_aot_gc_refs[MAX_AOT_DEPTH];
static int g_aot_depth = 0;
// Keep GC roots for native frames in stable heap chunks (no fixed depth cap).
#define AOT_GC_REF_CHUNK_SIZE 1024
typedef struct AOTGCRefChunk {
JSGCRef refs[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;
int cell_rt_native_active(void) {
return g_aot_depth > 0;
}
static int ensure_aot_gc_ref_slot(JSContext *ctx, 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)
return 1;
AOTGCRefChunk **new_chunks =
(AOTGCRefChunk **)realloc(g_aot_gc_ref_chunks,
(size_t)needed_chunks * sizeof(*new_chunks));
if (!new_chunks) {
JS_ThrowOutOfMemory(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]) {
JS_ThrowOutOfMemory(ctx);
return 0;
}
}
g_aot_gc_ref_chunk_count = needed_chunks;
return 1;
}
static inline JSGCRef *aot_gc_ref_at(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];
}
JSValue *cell_rt_enter_frame(JSContext *ctx, int64_t nr_slots) {
if (g_aot_depth >= MAX_AOT_DEPTH) {
JS_ThrowTypeError(ctx, "native call stack overflow (depth %d)", g_aot_depth);
if (!ensure_aot_gc_ref_slot(ctx, g_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];
JSGCRef *ref = aot_gc_ref_at(g_aot_depth);
JS_AddGCRef(ctx, ref);
ref->val = JS_MKPTR(frame);
g_aot_depth++;
@@ -394,7 +442,7 @@ JSValue *cell_rt_refresh_fp(JSContext *ctx) {
fprintf(stderr, "[BUG] cell_rt_refresh_fp: g_aot_depth=%d\n", g_aot_depth);
abort();
}
JSValue val = g_aot_gc_refs[g_aot_depth - 1].val;
JSValue val = aot_gc_ref_at(g_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",
@@ -412,7 +460,7 @@ JSValue *cell_rt_refresh_fp_checked(JSContext *ctx) {
fprintf(stderr, "[BUG] cell_rt_refresh_fp_checked: g_aot_depth=%d\n", g_aot_depth);
abort();
}
JSValue val = g_aot_gc_refs[g_aot_depth - 1].val;
JSValue val = aot_gc_ref_at(g_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");
@@ -422,8 +470,12 @@ JSValue *cell_rt_refresh_fp_checked(JSContext *ctx) {
}
void cell_rt_leave_frame(JSContext *ctx) {
if (g_aot_depth <= 0) {
fprintf(stderr, "[BUG] cell_rt_leave_frame underflow\n");
abort();
}
g_aot_depth--;
JS_DeleteGCRef(ctx, &g_aot_gc_refs[g_aot_depth]);
JS_DeleteGCRef(ctx, aot_gc_ref_at(g_aot_depth));
}
/* --- Function creation and calling --- */
@@ -432,7 +484,7 @@ 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 */
static void *g_current_dl_handle = NULL;
static CELL_THREAD_LOCAL void *g_current_dl_handle = NULL;
/* ============================================================
Dispatch loop — the core of native function execution.
@@ -442,8 +494,20 @@ static void *g_current_dl_handle = NULL;
/* Pending call state — set by cell_rt_signal_call / cell_rt_signal_tail_call,
read by the dispatch loop. */
static JSValue g_pending_callee_frame = 0; /* JSFrameRegister ptr */
static int g_pending_is_tail = 0;
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
pauses by clearing pause_flag and continuing the current turn. */
int cell_rt_check_backedge(JSContext *ctx) {
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
if (pf >= 1) {
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
}
return 0;
}
void cell_rt_signal_call(JSContext *ctx, void *fp, int64_t frame_slot) {
(void)ctx;
@@ -467,6 +531,15 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
cell_compiled_fn fn = (cell_compiled_fn)f->u.native.fn_ptr;
int nr_slots = f->u.native.nr_slots;
int arity = f->length;
void *prev_dl_handle = g_current_dl_handle;
g_current_dl_handle = f->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; \
return (v); \
} while (0)
/* Root func_obj across allocation — GC can move it */
JSGCRef func_ref;
@@ -477,7 +550,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
JSValue *fp = cell_rt_enter_frame(ctx, nr_slots);
if (!fp) {
JS_PopGCRef(ctx, &func_ref);
return JS_EXCEPTION;
RETURN_DISPATCH(JS_EXCEPTION);
}
/* Re-derive func_obj after potential GC */
@@ -499,11 +572,25 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
for (;;) {
g_pending_callee_frame = 0;
g_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);
/* Keep closure creation bound to the currently executing native module. */
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 = cur_fn->u.native.dl_handle;
}
JSValue result = fn(ctx, fp);
/* Re-derive frame after potential GC */
JSValue frame_val = g_aot_gc_refs[g_aot_depth - 1].val;
if (g_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;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
fp = (JSValue *)frame->slots;
@@ -511,7 +598,12 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
/* Function signaled a call — dispatch it */
JSValue callee_frame_val = g_pending_callee_frame;
g_pending_callee_frame = 0;
JSFrameRegister *callee_fr = (JSFrameRegister *)JS_VALUE_GET_PTR(callee_frame_val);
int pending_is_tail = g_pending_is_tail;
g_pending_is_tail = 0;
JSGCRef callee_ref;
JS_PushGCRef(ctx, &callee_ref);
callee_ref.val = callee_frame_val;
JSFrameRegister *callee_fr = (JSFrameRegister *)JS_VALUE_GET_PTR(callee_ref.val);
int callee_argc = (int)objhdr_cap56(callee_fr->header);
callee_argc = (callee_argc >= 2) ? callee_argc - 2 : 0;
JSValue callee_fn_val = callee_fr->function;
@@ -521,95 +613,86 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
/* Resume caller with exception pending */
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)exc_fn->u.native.fn_ptr;
JS_PopGCRef(ctx, &callee_ref);
continue;
}
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(callee_fn_val);
JSGCRef callee_fn_ref;
JS_PushGCRef(ctx, &callee_fn_ref);
callee_fn_ref.val = callee_fn_val;
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(callee_fn_ref.val);
if (callee_fn->kind == JS_FUNC_KIND_NATIVE) {
/* Native-to-native call — no C stack growth */
cell_compiled_fn callee_ptr = (cell_compiled_fn)callee_fn->u.native.fn_ptr;
int callee_slots = callee_fn->u.native.nr_slots;
if (g_pending_is_tail) {
/* Tail call: reuse or replace current frame */
if (callee_slots <= (int)objhdr_cap56(frame->header)) {
/* Reuse current frame */
int cc = (callee_argc < callee_fn->length) ? callee_argc : callee_fn->length;
if (cc < 0) cc = callee_argc;
frame->slots[0] = callee_fr->slots[0]; /* this */
for (int i = 0; i < cc && i < callee_slots - 1; i++)
frame->slots[1 + i] = callee_fr->slots[1 + i];
/* Null out remaining slots */
int cur_slots = (int)objhdr_cap56(frame->header);
for (int i = 1 + cc; i < cur_slots; i++)
frame->slots[i] = JS_NULL;
frame->function = callee_fn_val;
frame->address = JS_NewInt32(ctx, 0);
fn = callee_ptr;
/* fp stays the same (same frame) */
} else {
/* Need bigger frame — save callee info, pop+push */
JSValue saved_caller = frame->caller;
JSValue callee_this = callee_fr->slots[0];
int cc = (callee_argc < callee_fn->length) ? callee_argc : callee_fn->length;
if (cc < 0) cc = callee_argc;
JSValue callee_args[cc > 0 ? cc : 1];
for (int i = 0; i < cc; i++)
callee_args[i] = callee_fr->slots[1 + i];
if (pending_is_tail) {
/* Tail call: replace frame instead of mutating in place.
In-place reuse breaks closures that captured the caller frame. */
JSValue saved_caller = frame->caller;
int cc = (callee_argc < callee_fn->length) ? callee_argc : callee_fn->length;
if (cc < 0) cc = callee_argc;
/* Pop old frame */
cell_rt_leave_frame(ctx);
/* Pop old frame */
cell_rt_leave_frame(ctx);
/* Push new right-sized frame */
JSValue *new_fp = cell_rt_enter_frame(ctx, callee_slots);
if (!new_fp)
return JS_EXCEPTION;
JSFrameRegister *new_frame = (JSFrameRegister *)((char *)new_fp - offsetof(JSFrameRegister, slots));
new_frame->function = callee_fn_val;
new_frame->caller = saved_caller;
new_frame->slots[0] = callee_this;
for (int i = 0; i < cc && i < callee_slots - 1; i++)
new_frame->slots[1 + i] = callee_args[i];
frame = new_frame;
fp = new_fp;
fn = callee_ptr;
/* Push new right-sized frame */
JSValue *new_fp = cell_rt_enter_frame(ctx, callee_slots);
if (!new_fp) {
JS_PopGCRef(ctx, &callee_fn_ref);
JS_PopGCRef(ctx, &callee_ref);
RETURN_DISPATCH(JS_EXCEPTION);
}
callee_fr = (JSFrameRegister *)JS_VALUE_GET_PTR(callee_ref.val);
JSFrameRegister *new_frame = (JSFrameRegister *)((char *)new_fp - offsetof(JSFrameRegister, slots));
callee_fn_val = callee_fn_ref.val;
new_frame->function = callee_fn_val;
new_frame->caller = saved_caller;
new_frame->slots[0] = callee_fr->slots[0];
for (int i = 0; i < cc && i < callee_slots - 1; i++)
new_frame->slots[1 + i] = callee_fr->slots[1 + i];
frame = new_frame;
fp = new_fp;
fn = callee_ptr;
} else {
/* Regular call: push new frame, link caller */
int ret_info = JS_VALUE_GET_INT(frame->address);
int resume_seg = ret_info >> 16;
int ret_slot = ret_info & 0xFFFF;
/* Save callee info before allocation */
JSValue callee_this = callee_fr->slots[0];
int cc = (callee_argc < callee_fn->length) ? callee_argc : callee_fn->length;
if (cc < 0) cc = callee_argc;
JSValue callee_args[cc > 0 ? cc : 1];
for (int i = 0; i < cc; i++)
callee_args[i] = callee_fr->slots[1 + i];
JSValue *new_fp = cell_rt_enter_frame(ctx, callee_slots);
if (!new_fp) {
/* Resume caller with exception pending */
frame_val = g_aot_gc_refs[g_aot_depth - 1].val;
frame_val = aot_gc_ref_at(g_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);
fn = (cell_compiled_fn)exc_fn->u.native.fn_ptr;
JS_PopGCRef(ctx, &callee_fn_ref);
JS_PopGCRef(ctx, &callee_ref);
continue;
}
callee_fr = (JSFrameRegister *)JS_VALUE_GET_PTR(callee_ref.val);
/* Re-derive caller frame after alloc */
frame_val = g_aot_gc_refs[g_aot_depth - 2].val;
if (g_aot_depth <= 1) {
fprintf(stderr, "[BUG] native dispatch bad depth while linking caller: %d\n", g_aot_depth);
abort();
}
frame_val = aot_gc_ref_at(g_aot_depth - 2)->val;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
JSFrameRegister *new_frame = (JSFrameRegister *)((char *)new_fp - offsetof(JSFrameRegister, slots));
callee_fn_val = callee_fn_ref.val;
new_frame->function = callee_fn_val;
new_frame->caller = JS_MKPTR(frame);
new_frame->slots[0] = callee_this;
new_frame->slots[0] = callee_fr->slots[0];
for (int i = 0; i < cc && i < callee_slots - 1; i++)
new_frame->slots[1 + i] = callee_args[i];
new_frame->slots[1 + i] = callee_fr->slots[1 + i];
/* Save return address in caller */
frame->address = JS_NewInt32(ctx, (resume_seg << 16) | ret_slot);
@@ -630,7 +713,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
callee_argc, &callee_fr->slots[1], 0);
/* Re-derive frame after call */
frame_val = g_aot_gc_refs[g_aot_depth - 1].val;
frame_val = aot_gc_ref_at(g_aot_depth - 1)->val;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
fp = (JSValue *)frame->slots;
@@ -643,28 +726,29 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
Just resume it — it will detect the exception. */
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)exc_fn->u.native.fn_ptr;
JS_PopGCRef(ctx, &callee_ref);
continue;
}
/* Clear stale exception */
if (JS_HasException(ctx))
JS_GetException(ctx);
if (g_pending_is_tail) {
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) {
cell_rt_leave_frame(ctx);
return ret;
JS_PopGCRef(ctx, &callee_ref);
RETURN_DISPATCH(ret);
}
/* Pop current frame, return to caller frame */
JSValue caller_val = frame->caller;
cell_rt_leave_frame(ctx);
if (JS_IsNull(caller_val) || g_aot_depth < base_depth) {
return ret;
if (g_aot_depth < base_depth) {
JS_PopGCRef(ctx, &callee_ref);
RETURN_DISPATCH(ret);
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(caller_val);
/* Update GC ref to point to caller */
g_aot_gc_refs[g_aot_depth - 1].val = caller_val;
frame_val = aot_gc_ref_at(g_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);
int ret_slot = ret_info & 0xFFFF;
@@ -684,6 +768,8 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
fn = (cell_compiled_fn)cur_fn->u.native.fn_ptr;
}
}
JS_PopGCRef(ctx, &callee_fn_ref);
JS_PopGCRef(ctx, &callee_ref);
continue;
}
@@ -696,19 +782,16 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
if (g_aot_depth <= base_depth) {
cell_rt_leave_frame(ctx);
return JS_EXCEPTION;
RETURN_DISPATCH(JS_EXCEPTION);
}
JSValue exc_caller_val = frame->caller;
cell_rt_leave_frame(ctx);
if (JS_IsNull(exc_caller_val) || g_aot_depth < base_depth) {
return JS_EXCEPTION;
if (g_aot_depth < base_depth) {
RETURN_DISPATCH(JS_EXCEPTION);
}
/* Resume caller — it will check JS_HasException and branch to handler */
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(exc_caller_val);
g_aot_gc_refs[g_aot_depth - 1].val = exc_caller_val;
frame_val = aot_gc_ref_at(g_aot_depth - 1)->val;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
fp = (JSValue *)frame->slots;
JSFunction *exc_caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
@@ -719,19 +802,16 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
/* Normal return — pop frame and store result in caller */
if (g_aot_depth <= base_depth) {
cell_rt_leave_frame(ctx);
return result;
RETURN_DISPATCH(result);
}
JSValue caller_val = frame->caller;
cell_rt_leave_frame(ctx);
if (JS_IsNull(caller_val) || g_aot_depth < base_depth) {
return result;
if (g_aot_depth < base_depth) {
RETURN_DISPATCH(result);
}
/* Return to caller frame */
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(caller_val);
g_aot_gc_refs[g_aot_depth - 1].val = caller_val;
frame_val = aot_gc_ref_at(g_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);
int ret_slot = ret_info & 0xFFFF;
@@ -742,6 +822,8 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
fn = (cell_compiled_fn)caller_fn->u.native.fn_ptr;
continue;
}
#undef RETURN_DISPATCH
}
/* Create a native function object from a compiled fn_idx.
@@ -761,7 +843,7 @@ JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp,
/* Get the current frame as outer_frame for closures */
JSValue outer_frame = JS_NULL;
if (g_aot_depth > 0)
outer_frame = g_aot_gc_refs[g_aot_depth - 1].val;
outer_frame = aot_gc_ref_at(g_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);