Merge branch 'fix_native_suite'
This commit is contained in:
172
source/qbe_backend.c
Normal file
172
source/qbe_backend.c
Normal 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;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user