From 5ef3381fffd596810b527ca37563f00f3b5583af Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 17 Feb 2026 11:12:51 -0600 Subject: [PATCH] native aot suite passes --- qbe.cm | 7 +-- qbe_emit.cm | 19 ++++++-- source/qbe_helpers.c | 105 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 109 insertions(+), 22 deletions(-) diff --git a/qbe.cm b/qbe.cm index 516d1b8f..2424b566 100644 --- a/qbe.cm +++ b/qbe.cm @@ -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}) ` } diff --git a/qbe_emit.cm b/qbe_emit.cm index faa301c9..dfbdb9a7 100644 --- a/qbe_emit.cm +++ b/qbe_emit.cm @@ -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})`) diff --git a/source/qbe_helpers.c b/source/qbe_helpers.c index 8a48ce02..08e30425 100644 --- a/source/qbe_helpers.c +++ b/source/qbe_helpers.c @@ -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) {