native aot suite passes

This commit is contained in:
2026-02-17 11:12:51 -06:00
parent 2d054fcf21
commit 5ef3381fff
3 changed files with 109 additions and 22 deletions

7
qbe.cm
View File

@@ -519,12 +519,9 @@ var ne_bool = function(p, a, b) {
`
}
// --- Type guard: is_identical ---
// --- Type guard: is_identical (chases forwarding pointers via C helper) ---
var is_identical = function(p, a, b) {
return ` %${p}.cr =w ceql ${a}, ${b}
%${p}.crext =l extuw %${p}.cr
%${p}.sh =l shl %${p}.crext, 5
%${p} =l or %${p}.sh, 3
return ` %${p} =l call $cell_rt_is_identical(l %ctx, l ${a}, l ${b})
`
}

View File

@@ -102,6 +102,9 @@ var qbe_emit = function(ir, qbe, export_name) {
var pat_label = null
var flg_label = null
var in_handler = false
var tol = null
var fn_arity = 0
var arity_tmp = null
// Function signature: (ctx, frame_ptr) → JSValue
emit(`export function l $${name}(l %ctx, l %fp) {`)
@@ -572,8 +575,10 @@ var qbe_emit = function(ir, qbe, export_name) {
if (op == "eq_tol" || op == "ne_tol") {
lhs = s_read(a2)
rhs = s_read(a3)
a4 = instr[4]
tol = s_read(a4)
p = fresh()
emit(` %${p} =l call $cell_rt_${op}(l %ctx, l ${lhs}, l ${rhs})`)
emit(` %${p} =l call $cell_rt_${op}(l %ctx, l ${lhs}, l ${rhs}, l ${tol})`)
s_write(a1, `%${p}`)
continue
}
@@ -700,7 +705,7 @@ var qbe_emit = function(ir, qbe, export_name) {
p = fresh()
if (pn != null) {
sl = intern_str(pn)
emit(` %${p} =l call $cell_rt_load_field(l %ctx, l ${v}, l ${sl})`)
emit(` %${p} =l call $cell_rt_load_prop_str(l %ctx, l ${v}, l ${sl})`)
} else {
lhs = s_read(a3)
emit(` %${p} =l call $cell_rt_load_dynamic(l %ctx, l ${v}, l ${lhs})`)
@@ -899,8 +904,14 @@ var qbe_emit = function(ir, qbe, export_name) {
// --- Function object creation [G] ---
if (op == "function") {
fn_arity = 0
if (a2 >= 0 && a2 < length(ir.functions)) {
fn_arity = ir.functions[a2].nr_args
}
p = fresh()
emit(` %${p} =l call $cell_rt_make_function(l %ctx, l ${text(a2)}, l %fp)`)
arity_tmp = fresh()
emit(` %${arity_tmp} =l copy ${text(fn_arity)}`)
emit(` %${p} =l call $cell_rt_make_function(l %ctx, l ${text(a2)}, l %fp, l %${arity_tmp})`)
refresh_fp()
s_write(a1, `%${p}`)
continue
@@ -980,7 +991,7 @@ var qbe_emit = function(ir, qbe, export_name) {
p = fresh()
if (pn != null) {
sl = intern_str(pn)
emit(` %${p} =l call $cell_rt_delete(l %ctx, l ${v}, l ${sl})`)
emit(` %${p} =l call $cell_rt_delete_str(l %ctx, l ${v}, l ${sl})`)
} else {
lhs = s_read(a3)
emit(` %${p} =l call $cell_rt_delete(l %ctx, l ${v}, l ${lhs})`)

View File

@@ -222,6 +222,16 @@ JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b) {
/* --- Property access --- */
JSValue cell_rt_load_field(JSContext *ctx, JSValue obj, const char *name) {
if (JS_IsFunction(obj)) {
JS_ThrowTypeError(ctx, "cannot read property of function");
return JS_EXCEPTION;
}
return JS_GetPropertyStr(ctx, obj, name);
}
/* 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) {
return JS_GetPropertyStr(ctx, obj, name);
}
@@ -238,10 +248,15 @@ JSValue cell_rt_load_dynamic(JSContext *ctx, JSValue obj, JSValue key) {
void cell_rt_store_dynamic(JSContext *ctx, JSValue val, JSValue obj,
JSValue key) {
if (JS_IsInt(key))
if (JS_IsInt(key)) {
JS_SetPropertyNumber(ctx, obj, (uint32_t)JS_VALUE_GET_INT(key), val);
else
} else if (JS_IsArray(obj) && !JS_IsInt(key)) {
JS_ThrowTypeError(ctx, "array index must be a number");
} else if (JS_IsBool(key) || JS_IsNull(key) || JS_IsArray(key) || JS_IsFunction(key)) {
JS_ThrowTypeError(ctx, "object key must be text");
} else {
JS_SetProperty(ctx, obj, key, val);
}
}
JSValue cell_rt_load_index(JSContext *ctx, JSValue arr, JSValue idx) {
@@ -466,7 +481,8 @@ static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
return result;
}
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp) {
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp,
int64_t nr_args) {
(void)outer_fp;
if (g_native_fn_count >= MAX_NATIVE_FN)
return JS_ThrowTypeError(ctx, "too many native functions (max %d)", MAX_NATIVE_FN);
@@ -487,7 +503,7 @@ JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp) {
}
return JS_NewCFunction2(ctx, (JSCFunction *)cell_fn_trampoline, "native_fn",
255, JS_CFUNC_generic_magic, global_id);
(int)nr_args, JS_CFUNC_generic_magic, global_id);
}
/* --- Frame-based function calling --- */
@@ -515,15 +531,35 @@ JSValue cell_rt_invoke(JSContext *ctx, JSValue frame_val) {
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
int nr_slots = (int)objhdr_cap56(fr->header);
int c_argc = (nr_slots >= 2) ? nr_slots - 2 : 0;
JSValue fn_val = fr->function;
/* Copy args to C stack */
JSValue args[c_argc > 0 ? c_argc : 1];
for (int i = 0; i < c_argc; i++)
args[i] = fr->slots[i + 1];
if (!JS_IsFunction(fn_val)) {
JS_ThrowTypeError(ctx, "not a function");
return JS_EXCEPTION;
}
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
JSValue result;
if (fn->kind == JS_FUNC_KIND_C) {
/* Match MACH_INVOKE: C functions go directly to js_call_c_function,
bypassing JS_Call's arity check. Extra args are silently available. */
result = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
} else {
/* Register/bytecode functions — use JS_CallInternal (no arity gate) */
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);
}
JSValue result = JS_Call(ctx, fr->function, fr->slots[0], c_argc, args);
if (JS_IsException(result))
return JS_EXCEPTION;
/* Clear any stale exception left by functions that returned a valid
value despite internal error (e.g., sign("text") returns null
but JS_ToFloat64 leaves an exception flag) */
if (JS_HasException(ctx))
JS_GetException(ctx);
return result;
}
@@ -549,6 +585,16 @@ JSValue cell_rt_pop(JSContext *ctx, JSValue arr) {
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);
}
JSValue cell_rt_delete_str(JSContext *ctx, JSValue obj, const char *name) {
JSValue key = JS_NewString(ctx, name);
int ret = JS_DeleteProperty(ctx, obj, key);
if (ret < 0)
return JS_EXCEPTION;
return JS_NewBool(ctx, ret >= 0);
}
@@ -595,12 +641,37 @@ JSValue cell_rt_ge_text(JSContext *ctx, JSValue a, JSValue b) {
return JS_NewBool(ctx, r);
}
JSValue cell_rt_eq_tol(JSContext *ctx, JSValue a, JSValue b) {
return JS_NewBool(ctx, a == b);
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_ne_tol(JSContext *ctx, JSValue a, JSValue b) {
return JS_NewBool(ctx, a != b);
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) --- */
@@ -612,6 +683,14 @@ int cell_rt_is_proxy(JSContext *ctx, JSValue 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) {