1490 lines
49 KiB
C
1490 lines
49 KiB
C
/*
|
|
* QBE Helper Functions
|
|
*
|
|
* Thin C wrappers called from QBE-generated code for operations
|
|
* that are too complex to inline: float arithmetic, float comparison,
|
|
* string comparison, bitwise ops on floats, and boolean conversion.
|
|
*/
|
|
|
|
#include "quickjs-internal.h"
|
|
#include <math.h>
|
|
#include <pthread.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* Non-inline wrappers for static inline functions in quickjs.h */
|
|
JSValue qbe_new_float64(JSContext *ctx, double d) {
|
|
return __JS_NewFloat64(ctx, d);
|
|
}
|
|
|
|
JSValue qbe_new_string(JSContext *ctx, const char *str) {
|
|
return JS_NewString(ctx, str);
|
|
}
|
|
|
|
/* Comparison op IDs (must match qbe.cm float_cmp_op_id values) */
|
|
enum {
|
|
QBE_CMP_EQ = 0,
|
|
QBE_CMP_NE = 1,
|
|
QBE_CMP_LT = 2,
|
|
QBE_CMP_LE = 3,
|
|
QBE_CMP_GT = 4,
|
|
QBE_CMP_GE = 5
|
|
};
|
|
|
|
/* ============================================================
|
|
Float binary arithmetic
|
|
============================================================ */
|
|
|
|
static inline JSValue qbe_float_binop(JSContext *ctx, JSValue a, JSValue b,
|
|
double (*op)(double, double)) {
|
|
double da, db;
|
|
JS_ToFloat64(ctx, &da, a);
|
|
JS_ToFloat64(ctx, &db, b);
|
|
double r = op(da, db);
|
|
if (!isfinite(r))
|
|
return JS_NULL;
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
|
|
static double op_add(double a, double b) { return a + b; }
|
|
static double op_sub(double a, double b) { return a - b; }
|
|
static double op_mul(double a, double b) { return a * b; }
|
|
|
|
JSValue qbe_float_add(JSContext *ctx, JSValue a, JSValue b) {
|
|
return qbe_float_binop(ctx, a, b, op_add);
|
|
}
|
|
|
|
/* Generic add: concat if both text, float add if both numeric, else type error */
|
|
JSValue cell_rt_add(JSContext *ctx, JSValue a, JSValue b) {
|
|
if (JS_IsText(a) && JS_IsText(b))
|
|
return JS_ConcatString(ctx, a, b);
|
|
if (JS_IsNumber(a) && JS_IsNumber(b))
|
|
return qbe_float_binop(ctx, a, b, op_add);
|
|
JS_RaiseDisrupt(ctx, "cannot add incompatible types");
|
|
return JS_NULL;
|
|
}
|
|
|
|
JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b) {
|
|
return qbe_float_binop(ctx, a, b, op_sub);
|
|
}
|
|
|
|
JSValue qbe_float_mul(JSContext *ctx, JSValue a, JSValue b) {
|
|
return qbe_float_binop(ctx, a, b, op_mul);
|
|
}
|
|
|
|
JSValue qbe_float_div(JSContext *ctx, JSValue a, JSValue b) {
|
|
double da, db;
|
|
JS_ToFloat64(ctx, &da, a);
|
|
JS_ToFloat64(ctx, &db, b);
|
|
if (db == 0.0)
|
|
return JS_NULL;
|
|
double r = da / db;
|
|
if (!isfinite(r))
|
|
return JS_NULL;
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
|
|
JSValue qbe_float_mod(JSContext *ctx, JSValue a, JSValue b) {
|
|
double da, db;
|
|
JS_ToFloat64(ctx, &da, a);
|
|
JS_ToFloat64(ctx, &db, b);
|
|
if (db == 0.0)
|
|
return JS_NULL;
|
|
double r = fmod(da, db);
|
|
if (!isfinite(r))
|
|
return JS_NULL;
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
|
|
JSValue qbe_float_pow(JSContext *ctx, JSValue a, JSValue b) {
|
|
double da, db;
|
|
JS_ToFloat64(ctx, &da, a);
|
|
JS_ToFloat64(ctx, &db, b);
|
|
double r = pow(da, db);
|
|
if (!isfinite(r) && isfinite(da) && isfinite(db))
|
|
return JS_NULL;
|
|
return JS_NewFloat64(ctx, r);
|
|
}
|
|
|
|
/* ============================================================
|
|
Float unary ops
|
|
============================================================ */
|
|
|
|
JSValue qbe_float_neg(JSContext *ctx, JSValue v) {
|
|
double d;
|
|
JS_ToFloat64(ctx, &d, v);
|
|
return JS_NewFloat64(ctx, -d);
|
|
}
|
|
|
|
JSValue qbe_float_inc(JSContext *ctx, JSValue v) {
|
|
double d;
|
|
JS_ToFloat64(ctx, &d, v);
|
|
return JS_NewFloat64(ctx, d + 1);
|
|
}
|
|
|
|
JSValue qbe_float_dec(JSContext *ctx, JSValue v) {
|
|
double d;
|
|
JS_ToFloat64(ctx, &d, v);
|
|
return JS_NewFloat64(ctx, d - 1);
|
|
}
|
|
|
|
/* ============================================================
|
|
Float comparison — returns 0 or 1 for QBE branching
|
|
============================================================ */
|
|
|
|
int qbe_float_cmp(JSContext *ctx, int op, JSValue a, JSValue b) {
|
|
double da, db;
|
|
JS_ToFloat64(ctx, &da, a);
|
|
JS_ToFloat64(ctx, &db, b);
|
|
switch (op) {
|
|
case QBE_CMP_EQ: return da == db;
|
|
case QBE_CMP_NE: return da != db;
|
|
case QBE_CMP_LT: return da < db;
|
|
case QBE_CMP_LE: return da <= db;
|
|
case QBE_CMP_GT: return da > db;
|
|
case QBE_CMP_GE: return da >= db;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
/* ============================================================
|
|
Boolean conversion wrapper
|
|
============================================================ */
|
|
|
|
int qbe_to_bool(JSContext *ctx, JSValue v) {
|
|
return JS_ToBool(ctx, v);
|
|
}
|
|
|
|
/* ============================================================
|
|
Bitwise not on non-int (float -> int32 -> ~)
|
|
============================================================ */
|
|
|
|
JSValue qbe_bnot(JSContext *ctx, JSValue v) {
|
|
int32_t i;
|
|
JS_ToInt32(ctx, &i, v);
|
|
return JS_NewInt32(ctx, ~i);
|
|
}
|
|
|
|
/* ============================================================
|
|
Bitwise binary ops on floats (convert both to int32, apply, re-tag)
|
|
============================================================ */
|
|
|
|
JSValue qbe_bitwise_and(JSContext *ctx, JSValue a, JSValue b) {
|
|
int32_t ia, ib;
|
|
JS_ToInt32(ctx, &ia, a);
|
|
JS_ToInt32(ctx, &ib, b);
|
|
return JS_NewInt32(ctx, ia & ib);
|
|
}
|
|
|
|
JSValue qbe_bitwise_or(JSContext *ctx, JSValue a, JSValue b) {
|
|
int32_t ia, ib;
|
|
JS_ToInt32(ctx, &ia, a);
|
|
JS_ToInt32(ctx, &ib, b);
|
|
return JS_NewInt32(ctx, ia | ib);
|
|
}
|
|
|
|
JSValue qbe_bitwise_xor(JSContext *ctx, JSValue a, JSValue b) {
|
|
int32_t ia, ib;
|
|
JS_ToInt32(ctx, &ia, a);
|
|
JS_ToInt32(ctx, &ib, b);
|
|
return JS_NewInt32(ctx, ia ^ ib);
|
|
}
|
|
|
|
/* ============================================================
|
|
Shift ops on floats (convert to int32, shift, re-tag)
|
|
============================================================ */
|
|
|
|
JSValue qbe_shift_shl(JSContext *ctx, JSValue a, JSValue b) {
|
|
int32_t ia, ib;
|
|
JS_ToInt32(ctx, &ia, a);
|
|
JS_ToInt32(ctx, &ib, b);
|
|
return JS_NewInt32(ctx, ia << (ib & 31));
|
|
}
|
|
|
|
JSValue qbe_shift_sar(JSContext *ctx, JSValue a, JSValue b) {
|
|
int32_t ia, ib;
|
|
JS_ToInt32(ctx, &ia, a);
|
|
JS_ToInt32(ctx, &ib, b);
|
|
return JS_NewInt32(ctx, ia >> (ib & 31));
|
|
}
|
|
|
|
JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b) {
|
|
int32_t ia, ib;
|
|
JS_ToInt32(ctx, &ia, a);
|
|
JS_ToInt32(ctx, &ib, b);
|
|
return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31));
|
|
}
|
|
|
|
/* ============================================================
|
|
cell_rt_* — Runtime support for QBE-compiled code
|
|
============================================================ */
|
|
|
|
#include <dlfcn.h>
|
|
#include <stdio.h>
|
|
|
|
/* --- Property access --- */
|
|
|
|
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;
|
|
|
|
typedef struct AOTGCRefChunk AOTGCRefChunk;
|
|
|
|
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;
|
|
}
|
|
ctx->native_state = st;
|
|
return st;
|
|
}
|
|
|
|
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;
|
|
|
|
int *count_ptr = (int *)dlsym(dl_handle, "cell_lit_count");
|
|
const char **table_ptr = (const char **)dlsym(dl_handle, "cell_lit_table");
|
|
int count = count_ptr ? *count_ptr : 0;
|
|
if (count <= 0 || !table_ptr)
|
|
return 1;
|
|
|
|
st->lit_pool.vals = (JSGCRef *)calloc((size_t)count, sizeof(JSGCRef));
|
|
if (!st->lit_pool.vals) {
|
|
JS_RaiseOOM(ctx);
|
|
return 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, &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;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
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 (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 >= st->lit_pool.count) {
|
|
JS_RaiseDisrupt(ctx, "literal index out of range");
|
|
return JS_EXCEPTION;
|
|
}
|
|
return st->lit_pool.vals[lit_idx].val;
|
|
}
|
|
|
|
/* Convert a static C string to an interned JSValue key.
|
|
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;
|
|
|
|
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 (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(st->key_cache, (size_t)new_cap * sizeof(*new_cache));
|
|
if (!new_cache)
|
|
return JS_RaiseOOM(ctx);
|
|
st->key_cache = new_cache;
|
|
st->key_cache_cap = new_cap;
|
|
}
|
|
|
|
st->key_cache[st->key_cache_count].name = name;
|
|
st->key_cache[st->key_cache_count].key = key;
|
|
st->key_cache_count++;
|
|
return key;
|
|
}
|
|
|
|
static JSValue cell_rt_load_field_key(JSContext *ctx, JSValue obj, JSValue key) {
|
|
if (JS_IsFunction(obj)) {
|
|
JS_RaiseDisrupt(ctx, "cannot read property of function");
|
|
return JS_EXCEPTION;
|
|
}
|
|
return JS_GetProperty(ctx, obj, key);
|
|
}
|
|
|
|
JSValue cell_rt_load_field(JSContext *ctx, JSValue obj, const char *name) {
|
|
JSValue key = aot_key_from_cstr(ctx, name);
|
|
if (JS_IsException(key))
|
|
return JS_EXCEPTION;
|
|
return cell_rt_load_field_key(ctx, obj, key);
|
|
}
|
|
|
|
JSValue cell_rt_load_field_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) {
|
|
JSValue key = aot_lit_from_index(ctx, lit_idx);
|
|
if (JS_IsException(key))
|
|
return JS_EXCEPTION;
|
|
return cell_rt_load_field_key(ctx, obj, key);
|
|
}
|
|
|
|
/* Like cell_rt_load_field but without the function guard.
|
|
Used by load_dynamic when the key happens to be a static string. */
|
|
JSValue cell_rt_load_prop_str(JSContext *ctx, JSValue obj, const char *name) {
|
|
JSValue key = aot_key_from_cstr(ctx, name);
|
|
if (JS_IsException(key))
|
|
return JS_EXCEPTION;
|
|
return JS_GetProperty(ctx, obj, key);
|
|
}
|
|
|
|
static int cell_rt_store_field_key(JSContext *ctx, JSValue val, JSValue obj,
|
|
JSValue key) {
|
|
int ret = JS_SetProperty(ctx, obj, key, val);
|
|
return (ret < 0 || JS_HasException(ctx)) ? 0 : 1;
|
|
}
|
|
|
|
int cell_rt_store_field(JSContext *ctx, JSValue val, JSValue obj,
|
|
const char *name) {
|
|
JSValue key = aot_key_from_cstr(ctx, name);
|
|
if (JS_IsException(key))
|
|
return 0;
|
|
return cell_rt_store_field_key(ctx, val, obj, key);
|
|
}
|
|
|
|
int cell_rt_store_field_lit(JSContext *ctx, JSValue val, JSValue obj,
|
|
int64_t lit_idx) {
|
|
JSValue key = aot_lit_from_index(ctx, lit_idx);
|
|
if (JS_IsException(key))
|
|
return 0;
|
|
return cell_rt_store_field_key(ctx, val, obj, key);
|
|
}
|
|
|
|
JSValue cell_rt_access_lit(JSContext *ctx, int64_t lit_idx) {
|
|
return aot_lit_from_index(ctx, lit_idx);
|
|
}
|
|
|
|
JSValue cell_rt_load_dynamic(JSContext *ctx, JSValue obj, JSValue key) {
|
|
if (JS_IsInt(key))
|
|
return JS_GetPropertyNumber(ctx, obj, (uint32_t)JS_VALUE_GET_INT(key));
|
|
return JS_GetProperty(ctx, obj, key);
|
|
}
|
|
|
|
int cell_rt_store_dynamic(JSContext *ctx, JSValue val, JSValue obj,
|
|
JSValue key) {
|
|
int ret = 0;
|
|
JSValue nr = JS_NULL;
|
|
if (JS_IsInt(key)) {
|
|
nr = JS_SetPropertyNumber(ctx, obj, (uint32_t)JS_VALUE_GET_INT(key), val);
|
|
return JS_IsException(nr) ? 0 : 1;
|
|
} else if (JS_IsArray(obj) && !JS_IsInt(key)) {
|
|
JS_RaiseDisrupt(ctx, "array index must be a number");
|
|
return 0;
|
|
} else if (JS_IsBool(key) || JS_IsNull(key) || JS_IsArray(key) || JS_IsFunction(key)) {
|
|
JS_RaiseDisrupt(ctx, "object key must be text");
|
|
return 0;
|
|
} else {
|
|
ret = JS_SetProperty(ctx, obj, key, val);
|
|
return (ret < 0 || JS_HasException(ctx)) ? 0 : 1;
|
|
}
|
|
}
|
|
|
|
JSValue cell_rt_load_index(JSContext *ctx, JSValue arr, JSValue idx) {
|
|
if (JS_IsInt(idx))
|
|
return JS_GetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx));
|
|
return JS_GetProperty(ctx, arr, idx);
|
|
}
|
|
|
|
int cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr,
|
|
JSValue idx) {
|
|
int ret = 0;
|
|
JSValue nr = JS_NULL;
|
|
if (JS_IsInt(idx))
|
|
nr = JS_SetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx), val);
|
|
else
|
|
ret = JS_SetProperty(ctx, arr, idx, val);
|
|
if (JS_IsInt(idx))
|
|
return JS_IsException(nr) ? 0 : 1;
|
|
return (ret < 0 || JS_HasException(ctx)) ? 0 : 1;
|
|
}
|
|
|
|
/* --- Intrinsic/global lookup --- */
|
|
|
|
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(ctx, st);
|
|
|
|
/* Native module boundary: clear per-actor key cache so stale keys
|
|
cannot survive across context/module lifetimes. */
|
|
free(st->key_cache);
|
|
st->key_cache = NULL;
|
|
st->key_cache_count = 0;
|
|
st->key_cache_cap = 0;
|
|
|
|
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, &st->native_env_ref);
|
|
st->native_env_ref_inited = 1;
|
|
st->native_env_ref.val = env;
|
|
st->has_native_env = 1;
|
|
} else {
|
|
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 (st->has_native_env) {
|
|
JSValue v = JS_GetProperty(ctx, st->native_env_ref.val, key);
|
|
if (!JS_IsNull(v))
|
|
return v;
|
|
}
|
|
/* Linear scan of global object — avoids hash mismatch issues with
|
|
stoned records whose keys may be in cold storage */
|
|
JSValue gobj = ctx->global_obj;
|
|
if (JS_IsRecord(gobj)) {
|
|
JSRecord *rec = (JSRecord *)chase(gobj);
|
|
uint64_t mask = objhdr_cap56(rec->mist_hdr);
|
|
for (uint64_t i = 1; i <= mask; i++) {
|
|
if (js_key_equal(rec->slots[i].key, key))
|
|
return rec->slots[i].val;
|
|
}
|
|
}
|
|
JS_RaiseDisrupt(ctx, "name is not defined");
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) {
|
|
JSValue key = aot_key_from_cstr(ctx, name);
|
|
if (JS_IsException(key))
|
|
return JS_EXCEPTION;
|
|
return cell_rt_get_intrinsic_key(ctx, key);
|
|
}
|
|
|
|
JSValue cell_rt_get_intrinsic_lit(JSContext *ctx, int64_t lit_idx) {
|
|
JSValue key = aot_lit_from_index(ctx, lit_idx);
|
|
if (JS_IsException(key))
|
|
return JS_EXCEPTION;
|
|
return cell_rt_get_intrinsic_key(ctx, key);
|
|
}
|
|
|
|
/* --- Closure access ---
|
|
Walk the outer_frame chain on JSFunction (JS_FUNC_KIND_NATIVE).
|
|
The frame's function field links to the JSFunction, whose
|
|
u.native.outer_frame points to the enclosing frame.
|
|
GC traces outer_frame naturally — no registry needed. */
|
|
|
|
/* Get the outer frame's slots from a frame pointer.
|
|
The frame's function must be JS_FUNC_KIND_NATIVE. */
|
|
static JSValue *get_outer_frame_slots(JSValue *fp) {
|
|
/* fp points to frame->slots[0]; frame header is before it */
|
|
JSFrameRegister *frame = (JSFrameRegister *)((char *)fp - offsetof(JSFrameRegister, slots));
|
|
if (JS_IsNull(frame->function))
|
|
return NULL;
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
if (fn->kind != JS_FUNC_KIND_NATIVE)
|
|
return NULL;
|
|
JSValue outer = fn->u.cell.outer_frame;
|
|
if (JS_IsNull(outer))
|
|
return NULL;
|
|
JSFrameRegister *outer_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(outer);
|
|
return (JSValue *)outer_frame->slots;
|
|
}
|
|
|
|
JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth,
|
|
int64_t slot) {
|
|
(void)ctx;
|
|
JSValue *frame = (JSValue *)fp;
|
|
for (int64_t d = 0; d < depth; d++) {
|
|
frame = get_outer_frame_slots(frame);
|
|
if (!frame)
|
|
return JS_NULL;
|
|
}
|
|
return frame[slot];
|
|
}
|
|
|
|
void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
|
|
int64_t slot) {
|
|
(void)ctx;
|
|
JSValue *frame = (JSValue *)fp;
|
|
for (int64_t d = 0; d < depth; d++) {
|
|
frame = get_outer_frame_slots(frame);
|
|
if (!frame) return;
|
|
}
|
|
frame[slot] = val;
|
|
}
|
|
|
|
/* --- GC-managed AOT frame stack ---
|
|
Each native dispatch loop pushes a GC ref so the GC can find and
|
|
update the current frame pointer when it moves objects.
|
|
cell_rt_refresh_fp re-derives the slot pointer after any GC call. */
|
|
|
|
// 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];
|
|
uint8_t inited[AOT_GC_REF_CHUNK_SIZE];
|
|
} AOTGCRefChunk;
|
|
|
|
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, 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 <= st->gc_ref_chunk_count)
|
|
return 1;
|
|
AOTGCRefChunk **new_chunks =
|
|
(AOTGCRefChunk **)realloc(st->gc_ref_chunks,
|
|
(size_t)needed_chunks * sizeof(*new_chunks));
|
|
if (!new_chunks) {
|
|
JS_RaiseOOM(ctx);
|
|
return 0;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
st->gc_ref_chunk_count = needed_chunks;
|
|
return 1;
|
|
}
|
|
|
|
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 &st->gc_ref_chunks[chunk_index]->refs[slot_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 &st->gc_ref_chunks[chunk_index]->inited[slot_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;
|
|
}
|
|
}
|
|
|
|
JSValue *cell_rt_enter_frame(JSContext *ctx, int64_t nr_slots) {
|
|
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, st, st->aot_depth);
|
|
JSGCRef *ref = aot_gc_ref_at(st, st->aot_depth);
|
|
ref->val = JS_MKPTR(frame);
|
|
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) {
|
|
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, st, st->aot_depth);
|
|
JSGCRef *ref = aot_gc_ref_at(st, st->aot_depth);
|
|
ref->val = frame_val;
|
|
st->aot_depth++;
|
|
return 1;
|
|
}
|
|
|
|
JSValue *cell_rt_refresh_fp(JSContext *ctx) {
|
|
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(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",
|
|
st->aot_depth, (long long)val);
|
|
abort();
|
|
}
|
|
return (JSValue *)frame->slots;
|
|
}
|
|
|
|
/* 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 (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(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");
|
|
abort();
|
|
}
|
|
return (JSValue *)frame->slots;
|
|
}
|
|
|
|
void cell_rt_leave_frame(JSContext *ctx) {
|
|
NativeRTState *st = native_state(ctx);
|
|
if (!st) return;
|
|
if (st->aot_depth <= 0) {
|
|
fprintf(stderr, "[BUG] cell_rt_leave_frame underflow\n");
|
|
abort();
|
|
}
|
|
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);
|
|
|
|
/* ============================================================
|
|
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).
|
|
============================================================ */
|
|
|
|
/* 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) {
|
|
NativeRTState *st = native_state(ctx);
|
|
if (!st) return;
|
|
JSValue *slots = (JSValue *)fp;
|
|
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) {
|
|
NativeRTState *st = native_state(ctx);
|
|
if (!st) return;
|
|
JSValue *slots = (JSValue *)fp;
|
|
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 = 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); \
|
|
st->current_dl_handle = prev_dl_handle; \
|
|
return (v); \
|
|
} while (0)
|
|
|
|
/* Root func_obj across allocation — GC can move it */
|
|
JSGCRef func_ref;
|
|
JS_PushGCRef(ctx, &func_ref);
|
|
func_ref.val = func_obj;
|
|
|
|
/* Allocate initial frame */
|
|
JSValue *fp = cell_rt_enter_frame(ctx, nr_slots);
|
|
if (!fp) {
|
|
JS_PopGCRef(ctx, &func_ref);
|
|
RETURN_DISPATCH(JS_EXCEPTION);
|
|
}
|
|
|
|
/* Re-derive func_obj after potential GC */
|
|
func_obj = func_ref.val;
|
|
JS_PopGCRef(ctx, &func_ref);
|
|
|
|
/* Set up frame: this in slot 0, args in slots 1..N */
|
|
fp[0] = this_obj;
|
|
int copy = (argc < arity) ? argc : arity;
|
|
if (copy < 0) copy = argc; /* variadic: copy all */
|
|
for (int i = 0; i < copy && i < nr_slots - 1; i++)
|
|
fp[1 + i] = argv[i];
|
|
|
|
/* Link function to frame for closure access */
|
|
JSFrameRegister *frame = (JSFrameRegister *)((char *)fp - offsetof(JSFrameRegister, slots));
|
|
frame->function = func_obj;
|
|
|
|
int base_depth = st->aot_depth; /* remember entry depth for return detection */
|
|
|
|
for (;;) {
|
|
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);
|
|
|
|
/* 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)
|
|
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 (st->aot_depth <= 0) {
|
|
fprintf(stderr, "[BUG] native dispatch lost frame depth after fn call\n");
|
|
abort();
|
|
}
|
|
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 (st->pending_callee_frame != 0) {
|
|
/* Function signaled a call — dispatch it */
|
|
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;
|
|
JSFrameRegister *callee_fr = (JSFrameRegister *)JS_VALUE_GET_PTR(callee_ref.val);
|
|
int callee_argc = JS_VALUE_GET_INT(callee_fr->address);
|
|
if (callee_argc < 0)
|
|
callee_argc = 0;
|
|
JSValue callee_fn_val = callee_fr->function;
|
|
|
|
if (!JS_IsFunction(callee_fn_val)) {
|
|
JS_RaiseDisrupt(ctx, "not a function");
|
|
/* Resume caller with exception pending */
|
|
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr;
|
|
JS_PopGCRef(ctx, &callee_ref);
|
|
continue;
|
|
}
|
|
|
|
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)JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.native.fn_ptr;
|
|
|
|
if (pending_is_tail) {
|
|
/* Tail call: replace current frame with the prepared callee frame. */
|
|
JSValue saved_caller = frame->caller;
|
|
|
|
/* Pop old frame */
|
|
cell_rt_leave_frame(ctx);
|
|
|
|
callee_fr = (JSFrameRegister *)JS_VALUE_GET_PTR(callee_ref.val);
|
|
callee_fn_val = callee_fn_ref.val;
|
|
callee_fr->function = callee_fn_val;
|
|
callee_fr->caller = saved_caller;
|
|
callee_fr->address = JS_NewInt32(ctx, 0);
|
|
|
|
if (!cell_rt_push_existing_frame(ctx, callee_ref.val)) {
|
|
JS_PopGCRef(ctx, &callee_fn_ref);
|
|
JS_PopGCRef(ctx, &callee_ref);
|
|
RETURN_DISPATCH(JS_EXCEPTION);
|
|
}
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(aot_gc_ref_at(st, st->aot_depth - 1)->val);
|
|
fp = (JSValue *)frame->slots;
|
|
fn = callee_ptr;
|
|
} else {
|
|
/* Regular call: link caller and push prepared callee frame. */
|
|
int ret_info = JS_VALUE_GET_INT(frame->address);
|
|
int resume_seg = ret_info >> 16;
|
|
int ret_slot = ret_info & 0xFFFF;
|
|
|
|
/* Save return address in caller */
|
|
frame->address = JS_NewInt32(ctx, (resume_seg << 16) | ret_slot);
|
|
|
|
callee_fr = (JSFrameRegister *)JS_VALUE_GET_PTR(callee_ref.val);
|
|
callee_fn_val = callee_fn_ref.val;
|
|
callee_fr->function = callee_fn_val;
|
|
callee_fr->caller = JS_MKPTR(frame);
|
|
callee_fr->address = JS_NewInt32(ctx, 0);
|
|
|
|
if (!cell_rt_push_existing_frame(ctx, callee_ref.val)) {
|
|
/* Resume caller with exception pending */
|
|
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);
|
|
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr;
|
|
JS_PopGCRef(ctx, &callee_fn_ref);
|
|
JS_PopGCRef(ctx, &callee_ref);
|
|
continue;
|
|
}
|
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(aot_gc_ref_at(st, st->aot_depth - 1)->val);
|
|
fp = (JSValue *)frame->slots;
|
|
fn = callee_ptr;
|
|
}
|
|
} else {
|
|
/* Non-native callee (C function, register VM, etc.) —
|
|
call it via the standard path and store the result */
|
|
JSValue ret;
|
|
if (callee_fn->kind == JS_FUNC_KIND_C)
|
|
ret = js_call_c_function(ctx, callee_fn_val, callee_fr->slots[0],
|
|
callee_argc, &callee_fr->slots[1]);
|
|
else
|
|
ret = JS_CallInternal(ctx, callee_fn_val, callee_fr->slots[0],
|
|
callee_argc, &callee_fr->slots[1], 0);
|
|
|
|
/* Re-derive frame after call */
|
|
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 (JS_IsException(ret)) {
|
|
/* Non-native callee threw — resume caller with exception pending.
|
|
Tag the pending return slot with JS_EXCEPTION so generated code
|
|
can branch without an extra JS_HasException C call. */
|
|
if (!JS_HasException(ctx))
|
|
JS_Disrupt(ctx);
|
|
int ret_info = JS_VALUE_GET_INT(frame->address);
|
|
int ret_slot = ret_info & 0xFFFF;
|
|
if (ret_slot != 0xFFFF)
|
|
fp[ret_slot] = JS_EXCEPTION;
|
|
/* fn and fp still point to the calling native function's frame.
|
|
Just resume it — it will detect JS_EXCEPTION in the return slot. */
|
|
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr;
|
|
JS_PopGCRef(ctx, &callee_ref);
|
|
continue;
|
|
}
|
|
/* Clear stale exception */
|
|
if (JS_HasException(ctx))
|
|
JS_GetException(ctx);
|
|
|
|
if (pending_is_tail) {
|
|
/* Tail call to non-native: return its result up the chain */
|
|
/* Pop current frame and return to caller */
|
|
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 (st->aot_depth < base_depth) {
|
|
JS_PopGCRef(ctx, &callee_ref);
|
|
RETURN_DISPATCH(ret);
|
|
}
|
|
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);
|
|
int ret_slot = ret_info & 0xFFFF;
|
|
if (ret_slot != 0xFFFF)
|
|
fp[ret_slot] = ret;
|
|
/* Resume caller */
|
|
JSFunction *caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(caller_fn->u.cell.code)->u.native.fn_ptr;
|
|
} else {
|
|
/* Regular call: store result and resume current function */
|
|
int ret_info = JS_VALUE_GET_INT(frame->address);
|
|
int ret_slot = ret_info & 0xFFFF;
|
|
if (ret_slot != 0xFFFF)
|
|
fp[ret_slot] = ret;
|
|
/* fn stays the same — we resume the same function at next segment */
|
|
JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(cur_fn->u.cell.code)->u.native.fn_ptr;
|
|
}
|
|
}
|
|
JS_PopGCRef(ctx, &callee_fn_ref);
|
|
JS_PopGCRef(ctx, &callee_ref);
|
|
continue;
|
|
}
|
|
|
|
/* No pending call — function returned a value or exception */
|
|
if (result == JS_EXCEPTION) {
|
|
/* Exception: pop this frame and propagate to caller.
|
|
The caller's generated code has exception checks at resume points. */
|
|
if (!JS_HasException(ctx))
|
|
JS_Disrupt(ctx);
|
|
|
|
if (st->aot_depth <= base_depth) {
|
|
cell_rt_leave_frame(ctx);
|
|
RETURN_DISPATCH(JS_EXCEPTION);
|
|
}
|
|
cell_rt_leave_frame(ctx);
|
|
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(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);
|
|
int ret_slot = ret_info & 0xFFFF;
|
|
if (ret_slot != 0xFFFF)
|
|
fp[ret_slot] = JS_EXCEPTION;
|
|
|
|
JSFunction *exc_caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_caller_fn->u.cell.code)->u.native.fn_ptr;
|
|
continue;
|
|
}
|
|
|
|
/* Normal return — pop frame and store result in caller */
|
|
if (st->aot_depth <= base_depth) {
|
|
cell_rt_leave_frame(ctx);
|
|
RETURN_DISPATCH(result);
|
|
}
|
|
cell_rt_leave_frame(ctx);
|
|
if (st->aot_depth < base_depth) {
|
|
RETURN_DISPATCH(result);
|
|
}
|
|
|
|
/* Return to caller frame */
|
|
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);
|
|
int ret_slot = ret_info & 0xFFFF;
|
|
if (ret_slot != 0xFFFF)
|
|
fp[ret_slot] = result;
|
|
|
|
JSFunction *caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(caller_fn->u.cell.code)->u.native.fn_ptr;
|
|
continue;
|
|
}
|
|
|
|
#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) {
|
|
(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");
|
|
|
|
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 (st->aot_depth > 0)
|
|
outer_frame = aot_gc_ref_at(st, st->aot_depth - 1)->val;
|
|
|
|
return js_new_native_function_with_code(ctx, code_obj, (int)nr_args, outer_frame);
|
|
}
|
|
|
|
/* --- Frame-based function calling ---
|
|
Still used by QBE-generated code for building call frames
|
|
before signaling the dispatch loop. */
|
|
|
|
JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
|
|
if (!JS_IsFunction(fn)) {
|
|
JS_RaiseDisrupt(ctx, "not a function");
|
|
return JS_EXCEPTION;
|
|
}
|
|
int nr_slots = (int)nargs + 2;
|
|
JSFunction *f = JS_VALUE_GET_FUNCTION(fn);
|
|
if (f->kind == JS_FUNC_KIND_NATIVE && JS_VALUE_GET_CODE(f->u.cell.code)->u.native.nr_slots > nr_slots)
|
|
nr_slots = JS_VALUE_GET_CODE(f->u.cell.code)->u.native.nr_slots;
|
|
JSFrameRegister *new_frame = alloc_frame_register(ctx, nr_slots);
|
|
if (!new_frame) return JS_EXCEPTION;
|
|
new_frame->function = fn;
|
|
new_frame->address = JS_NewInt32(ctx, (int)nargs);
|
|
return JS_MKPTR(new_frame);
|
|
}
|
|
|
|
void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) {
|
|
if (frame_val == JS_EXCEPTION || frame_val == JS_NULL) return;
|
|
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
|
fr->slots[idx] = val;
|
|
}
|
|
|
|
/* cell_rt_invoke — still used for non-dispatch-loop paths (e.g. old code) */
|
|
JSValue cell_rt_invoke(JSContext *ctx, JSValue frame_val) {
|
|
if (frame_val == JS_EXCEPTION) return JS_EXCEPTION;
|
|
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
|
int c_argc = JS_VALUE_GET_INT(fr->address);
|
|
if (c_argc < 0) c_argc = 0;
|
|
JSValue fn_val = fr->function;
|
|
|
|
if (!JS_IsFunction(fn_val)) {
|
|
JS_RaiseDisrupt(ctx, "not a function");
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
|
|
JSValue result;
|
|
|
|
if (fn->kind == JS_FUNC_KIND_C) {
|
|
result = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
|
} else if (fn->kind == JS_FUNC_KIND_NATIVE) {
|
|
result = cell_native_dispatch(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
|
} else {
|
|
JSValue args[c_argc > 0 ? c_argc : 1];
|
|
for (int i = 0; i < c_argc; i++)
|
|
args[i] = fr->slots[i + 1];
|
|
result = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, args, 0);
|
|
}
|
|
|
|
if (JS_IsException(result))
|
|
return JS_EXCEPTION;
|
|
if (JS_HasException(ctx))
|
|
JS_GetException(ctx);
|
|
return result;
|
|
}
|
|
|
|
JSValue cell_rt_goframe(JSContext *ctx, JSValue fn, int64_t nargs) {
|
|
return cell_rt_frame(ctx, fn, nargs);
|
|
}
|
|
|
|
JSValue cell_rt_goinvoke(JSContext *ctx, JSValue frame_val) {
|
|
return cell_rt_invoke(ctx, frame_val);
|
|
}
|
|
|
|
/* --- Array push/pop --- */
|
|
|
|
JSValue cell_rt_push(JSContext *ctx, JSValue arr, JSValue val) {
|
|
JS_ArrayPush(ctx, &arr, val);
|
|
return arr;
|
|
}
|
|
|
|
JSValue cell_rt_pop(JSContext *ctx, JSValue arr) {
|
|
return JS_ArrayPop(ctx, arr);
|
|
}
|
|
|
|
/* --- Delete --- */
|
|
|
|
JSValue cell_rt_delete(JSContext *ctx, JSValue obj, JSValue key) {
|
|
int ret = JS_DeleteProperty(ctx, obj, key);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
return JS_NewBool(ctx, ret >= 0);
|
|
}
|
|
|
|
static JSValue cell_rt_delete_key(JSContext *ctx, JSValue obj, JSValue key) {
|
|
int ret = JS_DeleteProperty(ctx, obj, key);
|
|
if (ret < 0)
|
|
return JS_EXCEPTION;
|
|
return JS_NewBool(ctx, ret >= 0);
|
|
}
|
|
|
|
JSValue cell_rt_delete_str(JSContext *ctx, JSValue obj, const char *name) {
|
|
JSValue key = aot_key_from_cstr(ctx, name);
|
|
if (JS_IsException(key))
|
|
return JS_EXCEPTION;
|
|
return cell_rt_delete_key(ctx, obj, key);
|
|
}
|
|
|
|
JSValue cell_rt_delete_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) {
|
|
JSValue key = aot_lit_from_index(ctx, lit_idx);
|
|
if (JS_IsException(key))
|
|
return JS_EXCEPTION;
|
|
return cell_rt_delete_key(ctx, obj, key);
|
|
}
|
|
|
|
/* --- Typeof --- */
|
|
|
|
JSValue cell_rt_typeof(JSContext *ctx, JSValue val) {
|
|
if (JS_IsNull(val)) return JS_NewString(ctx, "null");
|
|
if (JS_IsInt(val) || JS_IsNumber(val)) return JS_NewString(ctx, "number");
|
|
if (JS_IsBool(val)) return JS_NewString(ctx, "logical");
|
|
if (JS_IsText(val)) return JS_NewString(ctx, "text");
|
|
if (JS_IsFunction(val)) return JS_NewString(ctx, "function");
|
|
if (JS_IsArray(val)) return JS_NewString(ctx, "array");
|
|
if (JS_IsRecord(val)) return JS_NewString(ctx, "object");
|
|
return JS_NewString(ctx, "unknown");
|
|
}
|
|
|
|
/* --- Text comparison stubs (called from QBE type-dispatch branches) --- */
|
|
|
|
JSValue cell_rt_lt_text(JSContext *ctx, JSValue a, JSValue b) {
|
|
const char *sa = JS_ToCString(ctx, a);
|
|
const char *sb = JS_ToCString(ctx, b);
|
|
int r = (sa && sb) ? strcmp(sa, sb) < 0 : 0;
|
|
return JS_NewBool(ctx, r);
|
|
}
|
|
|
|
JSValue cell_rt_gt_text(JSContext *ctx, JSValue a, JSValue b) {
|
|
const char *sa = JS_ToCString(ctx, a);
|
|
const char *sb = JS_ToCString(ctx, b);
|
|
int r = (sa && sb) ? strcmp(sa, sb) > 0 : 0;
|
|
return JS_NewBool(ctx, r);
|
|
}
|
|
|
|
JSValue cell_rt_le_text(JSContext *ctx, JSValue a, JSValue b) {
|
|
const char *sa = JS_ToCString(ctx, a);
|
|
const char *sb = JS_ToCString(ctx, b);
|
|
int r = (sa && sb) ? strcmp(sa, sb) <= 0 : 0;
|
|
return JS_NewBool(ctx, r);
|
|
}
|
|
|
|
JSValue cell_rt_ge_text(JSContext *ctx, JSValue a, JSValue b) {
|
|
const char *sa = JS_ToCString(ctx, a);
|
|
const char *sb = JS_ToCString(ctx, b);
|
|
int r = (sa && sb) ? strcmp(sa, sb) >= 0 : 0;
|
|
return JS_NewBool(ctx, r);
|
|
}
|
|
|
|
static int cell_rt_tol_eq_inner(JSContext *ctx, JSValue a, JSValue b,
|
|
JSValue tol) {
|
|
if (JS_IsNumber(a) && JS_IsNumber(b) && JS_IsNumber(tol)) {
|
|
double da, db, dt;
|
|
JS_ToFloat64(ctx, &da, a);
|
|
JS_ToFloat64(ctx, &db, b);
|
|
JS_ToFloat64(ctx, &dt, tol);
|
|
return fabs(da - db) <= dt;
|
|
}
|
|
if (JS_IsText(a) && JS_IsText(b) && JS_IsBool(tol) && JS_VALUE_GET_BOOL(tol)) {
|
|
return js_string_compare_value_nocase(ctx, a, b) == 0;
|
|
}
|
|
/* Fallback to standard equality */
|
|
if (a == b) return 1;
|
|
if (JS_IsText(a) && JS_IsText(b))
|
|
return js_string_compare_value(ctx, a, b, 1) == 0;
|
|
if (JS_IsNumber(a) && JS_IsNumber(b)) {
|
|
double da, db;
|
|
JS_ToFloat64(ctx, &da, a);
|
|
JS_ToFloat64(ctx, &db, b);
|
|
return da == db;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
JSValue cell_rt_eq_tol(JSContext *ctx, JSValue a, JSValue b, JSValue tol) {
|
|
return JS_NewBool(ctx, cell_rt_tol_eq_inner(ctx, a, b, tol));
|
|
}
|
|
|
|
JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b, JSValue tol) {
|
|
return JS_NewBool(ctx, !cell_rt_tol_eq_inner(ctx, a, b, tol));
|
|
}
|
|
|
|
/* --- Type check: is_proxy (function with arity 2) --- */
|
|
|
|
int cell_rt_is_proxy(JSContext *ctx, JSValue v) {
|
|
(void)ctx;
|
|
if (!JS_IsFunction(v)) return 0;
|
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(v);
|
|
return fn->length == 2;
|
|
}
|
|
|
|
/* --- Identity check (chases forwarding pointers) --- */
|
|
|
|
JSValue cell_rt_is_identical(JSContext *ctx, JSValue a, JSValue b) {
|
|
if (JS_IsPtr(a)) a = JS_MKPTR(chase(a));
|
|
if (JS_IsPtr(b)) b = JS_MKPTR(chase(b));
|
|
return JS_NewBool(ctx, a == b);
|
|
}
|
|
|
|
/* --- 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;
|
|
}
|
|
|
|
/* --- Exception checking ---
|
|
After potentially-throwing runtime calls, QBE-generated code needs to
|
|
check for pending exceptions and branch to the disruption handler. */
|
|
|
|
void cell_rt_clear_exception(JSContext *ctx) {
|
|
if (JS_HasException(ctx))
|
|
JS_GetException(ctx);
|
|
}
|
|
|
|
/* --- Disruption --- */
|
|
|
|
/* Disrupt: silently set exception flag like the bytecode VM does.
|
|
Does NOT call JS_ThrowTypeError — that would print to stderr
|
|
even when a disruption handler will catch it. */
|
|
void cell_rt_disrupt(JSContext *ctx) {
|
|
JS_RaiseDisrupt(ctx, "type error in native code");
|
|
}
|
|
|
|
/* --- 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, 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) {
|
|
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)) {
|
|
st->current_dl_handle = prev_handle;
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* Clear any stale exception left by a previous interpreted run */
|
|
if (JS_HasException(ctx))
|
|
JS_GetException(ctx);
|
|
|
|
JSValue result = cell_native_dispatch(ctx, func_obj, JS_NULL, 0, NULL);
|
|
st->current_dl_handle = prev_handle;
|
|
return result;
|
|
}
|
|
|
|
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_RaiseDisrupt(ctx, "cell_main not found in native module dylib");
|
|
|
|
/* Make env available for cell_rt_get_intrinsic lookups */
|
|
cell_rt_set_native_env(ctx, env);
|
|
|
|
/* Try to read nr_slots from the module (exported by emitter) */
|
|
int *slots_ptr = (int *)dlsym(dl_handle, "cell_main_nr_slots");
|
|
int nr_slots = slots_ptr ? *slots_ptr : 512;
|
|
|
|
return native_module_run(ctx, dl_handle, fn, nr_slots);
|
|
}
|
|
|
|
/* 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 env) {
|
|
cell_compiled_fn fn = NULL;
|
|
const char *used_name = NULL;
|
|
if (sym_name) {
|
|
fn = (cell_compiled_fn)dlsym(dl_handle, sym_name);
|
|
if (fn) used_name = sym_name;
|
|
}
|
|
if (!fn) {
|
|
fn = (cell_compiled_fn)dlsym(dl_handle, "cell_main");
|
|
used_name = "cell_main";
|
|
}
|
|
if (!fn)
|
|
return JS_RaiseDisrupt(ctx, "symbol not found in native module dylib");
|
|
|
|
/* Make env available for cell_rt_get_intrinsic lookups */
|
|
cell_rt_set_native_env(ctx, env);
|
|
|
|
/* Try to read nr_slots from the module */
|
|
char slots_sym[128];
|
|
snprintf(slots_sym, sizeof(slots_sym), "%s_nr_slots", used_name);
|
|
int *slots_ptr = (int *)dlsym(dl_handle, slots_sym);
|
|
int nr_slots = slots_ptr ? *slots_ptr : 512;
|
|
|
|
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);
|
|
return cell_rt_native_module_load(ctx, handle, JS_NULL);
|
|
}
|