Merge branch 'fix_native_suite'

This commit is contained in:
2026-02-17 12:35:20 -06:00
163 changed files with 77441 additions and 506 deletions

172
source/qbe_backend.c Normal file
View File

@@ -0,0 +1,172 @@
/*
* QBE Backend — in-process QBE IR → assembly compilation.
*
* Wraps QBE as a library: feeds IR text via fmemopen(), captures
* assembly output via open_memstream(), returns it as a JS string.
* No subprocess, no temp files for IR, no external qbe binary needed.
*/
#include "cell.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* QBE headers */
#include "all.h"
#include "config.h"
/* QBE globals (declared extern in all.h) */
Target T;
char debug['Z'+1] = {0};
extern Target T_amd64_sysv;
extern Target T_amd64_apple;
extern Target T_amd64_win;
extern Target T_arm64;
extern Target T_arm64_apple;
extern Target T_rv64;
/* Captured output stream — set before calling parse() */
static FILE *qbe_outf;
static void qbe_data(Dat *d) {
emitdat(d, qbe_outf);
if (d->type == DEnd) {
fputs("/* end data */\n\n", qbe_outf);
freeall();
}
}
static void qbe_func(Fn *fn) {
uint n;
T.abi0(fn);
fillcfg(fn);
filluse(fn);
promote(fn);
filluse(fn);
ssa(fn);
filluse(fn);
ssacheck(fn);
fillalias(fn);
loadopt(fn);
filluse(fn);
fillalias(fn);
coalesce(fn);
filluse(fn);
filldom(fn);
ssacheck(fn);
gvn(fn);
fillcfg(fn);
simplcfg(fn);
filluse(fn);
filldom(fn);
gcm(fn);
filluse(fn);
ssacheck(fn);
if (T.cansel) {
ifconvert(fn);
fillcfg(fn);
filluse(fn);
filldom(fn);
ssacheck(fn);
}
T.abi1(fn);
simpl(fn);
fillcfg(fn);
filluse(fn);
T.isel(fn);
fillcfg(fn);
filllive(fn);
fillloop(fn);
fillcost(fn);
spill(fn);
rega(fn);
fillcfg(fn);
simpljmp(fn);
fillcfg(fn);
assert(fn->rpo[0] == fn->start);
for (n = 0;; n++)
if (n == fn->nblk - 1) {
fn->rpo[n]->link = 0;
break;
} else
fn->rpo[n]->link = fn->rpo[n+1];
T.emitfn(fn, qbe_outf);
fprintf(qbe_outf, "/* end function %s */\n\n", fn->name);
freeall();
}
static void qbe_dbgfile(char *fn) {
emitdbgfile(fn, qbe_outf);
}
/*
* js_os_qbe(ctx, self, argc, argv)
*
* Takes a single string argument (QBE IR text).
* Returns the compiled assembly as a string.
*/
JSValue js_os_qbe(JSContext *js, JSValue self, int argc, JSValue *argv) {
if (argc < 1)
return JS_ThrowTypeError(js, "os.qbe requires an IR string argument");
const char *ir = JS_ToCString(js, argv[0]);
if (!ir)
return JS_EXCEPTION;
size_t ir_len = strlen(ir);
/* Select target for host platform */
#if defined(__APPLE__) && defined(__aarch64__)
T = T_arm64_apple;
#elif defined(__APPLE__) && defined(__x86_64__)
T = T_amd64_apple;
#elif defined(_WIN32) && defined(__x86_64__)
T = T_amd64_win;
#elif defined(__x86_64__)
T = T_amd64_sysv;
#elif defined(__aarch64__)
T = T_arm64;
#elif defined(__riscv) && __riscv_xlen == 64
T = T_rv64;
#else
T = Deftgt;
#endif
memset(debug, 0, sizeof(debug));
/* Open IR string as input FILE */
FILE *inf = fmemopen((void *)ir, ir_len, "r");
if (!inf) {
JS_FreeCString(js, ir);
return JS_ThrowInternalError(js, "os.qbe: fmemopen failed");
}
/* Open output memory stream */
char *out_buf = NULL;
size_t out_len = 0;
qbe_outf = open_memstream(&out_buf, &out_len);
if (!qbe_outf) {
fclose(inf);
JS_FreeCString(js, ir);
return JS_ThrowInternalError(js, "os.qbe: open_memstream failed");
}
/* Run the QBE pipeline */
parse(inf, "<ir>", qbe_dbgfile, qbe_data, qbe_func);
fclose(inf);
/* Finalize (emit assembler directives) */
T.emitfin(qbe_outf);
fflush(qbe_outf);
fclose(qbe_outf);
qbe_outf = NULL;
JS_FreeCString(js, ir);
/* Return assembly text */
JSValue result = JS_NewStringLen(js, out_buf, out_len);
free(out_buf);
return result;
}

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) {