qbe rt
This commit is contained in:
@@ -55,8 +55,10 @@ if (rc != 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// Append wrapper function
|
||||
var wrapper_cmd = `printf '\nexport function l $` + symbol + `(l %%ctx) {\n@entry\n %%frame =l alloc8 4096\n %%result =l call $cell_main(l %%ctx, l %%frame)\n ret %%result\n}\n' >> ` + tmp + `_fixed.ssa`
|
||||
// Append wrapper function — called as symbol(ctx) by os.dylib_symbol.
|
||||
// Delegates to cell_rt_module_entry which heap-allocates a frame
|
||||
// (so closures survive) and calls cell_main.
|
||||
var wrapper_cmd = `printf '\nexport function l $` + symbol + `(l %%ctx) {\n@entry\n %%result =l call $cell_rt_module_entry(l %%ctx)\n ret %%result\n}\n' >> ` + tmp + `_fixed.ssa`
|
||||
rc = os.system(wrapper_cmd)
|
||||
if (rc != 0) {
|
||||
print('wrapper append failed')
|
||||
|
||||
Binary file not shown.
BIN
parse.mach
BIN
parse.mach
Binary file not shown.
100
qbe_emit.cm
100
qbe_emit.cm
@@ -89,6 +89,9 @@ var qbe_emit = function(ir, qbe) {
|
||||
var pn = null
|
||||
var sl = null
|
||||
var fop_id = 0
|
||||
var nr_elems = 0
|
||||
var ei = 0
|
||||
var elem_slot = 0
|
||||
|
||||
// Function signature: (ctx, frame_ptr) → JSValue
|
||||
emit(`export function l $${name}(l %ctx, l %fp) {`)
|
||||
@@ -105,6 +108,11 @@ var qbe_emit = function(ir, qbe) {
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Write-back: store SSA var to frame slot so closures see updates
|
||||
var wb = function(slot) {
|
||||
emit(` storel ${s(slot)}, %p${text(slot)}`)
|
||||
}
|
||||
|
||||
// Walk instructions
|
||||
i = 0
|
||||
while (i < length(instrs)) {
|
||||
@@ -126,18 +134,22 @@ var qbe_emit = function(ir, qbe) {
|
||||
|
||||
if (op == "int") {
|
||||
emit(` ${s(a1)} =l copy ${text(a2 * 2)}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "null") {
|
||||
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "true") {
|
||||
emit(` ${s(a1)} =l copy ${text(qbe.js_true)}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "false") {
|
||||
emit(` ${s(a1)} =l copy ${text(qbe.js_false)}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "access") {
|
||||
@@ -177,6 +189,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
} else {
|
||||
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
|
||||
}
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -184,6 +197,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
|
||||
if (op == "move") {
|
||||
emit(` ${s(a1)} =l copy ${s(a2)}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -193,30 +207,35 @@ var qbe_emit = function(ir, qbe) {
|
||||
p = fresh()
|
||||
emit(qbe.add_int(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "sub_int") {
|
||||
p = fresh()
|
||||
emit(qbe.sub_int(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "mul_int") {
|
||||
p = fresh()
|
||||
emit(qbe.mul_int(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "div_int") {
|
||||
p = fresh()
|
||||
emit(qbe.div_int(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "mod_int") {
|
||||
p = fresh()
|
||||
emit(qbe.mod_int(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -226,30 +245,35 @@ var qbe_emit = function(ir, qbe) {
|
||||
p = fresh()
|
||||
emit(qbe.add_float(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "sub_float") {
|
||||
p = fresh()
|
||||
emit(qbe.sub_float(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "mul_float") {
|
||||
p = fresh()
|
||||
emit(qbe.mul_float(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "div_float") {
|
||||
p = fresh()
|
||||
emit(qbe.div_float(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "mod_float") {
|
||||
p = fresh()
|
||||
emit(qbe.mod_float(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -259,6 +283,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
p = fresh()
|
||||
emit(qbe.concat(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -269,6 +294,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
emit(qbe.is_int(p, s(a2)))
|
||||
emit(qbe.new_bool(p + ".r", "%" + p))
|
||||
emit(` ${s(a1)} =l copy %${p}.r`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "is_text") {
|
||||
@@ -276,6 +302,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
emit(qbe.is_imm_text(p, s(a2)))
|
||||
emit(qbe.new_bool(p + ".r", "%" + p))
|
||||
emit(` ${s(a1)} =l copy %${p}.r`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "is_num") {
|
||||
@@ -283,6 +310,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
emit(qbe.is_number(p, s(a2)))
|
||||
emit(qbe.new_bool(p + ".r", "%" + p))
|
||||
emit(` ${s(a1)} =l copy %${p}.r`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "is_bool") {
|
||||
@@ -290,6 +318,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
emit(qbe.is_bool(p, s(a2)))
|
||||
emit(qbe.new_bool(p + ".r", "%" + p))
|
||||
emit(` ${s(a1)} =l copy %${p}.r`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "is_null") {
|
||||
@@ -297,12 +326,14 @@ var qbe_emit = function(ir, qbe) {
|
||||
emit(qbe.is_null(p, s(a2)))
|
||||
emit(qbe.new_bool(p + ".r", "%" + p))
|
||||
emit(` ${s(a1)} =l copy %${p}.r`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "is_identical") {
|
||||
p = fresh()
|
||||
emit(qbe.is_identical(p, s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -312,36 +343,42 @@ var qbe_emit = function(ir, qbe) {
|
||||
p = fresh()
|
||||
emit(qbe.eq_int(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "ne_int") {
|
||||
p = fresh()
|
||||
emit(qbe.ne_int(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "lt_int") {
|
||||
p = fresh()
|
||||
emit(qbe.lt_int(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "gt_int") {
|
||||
p = fresh()
|
||||
emit(qbe.gt_int(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "le_int") {
|
||||
p = fresh()
|
||||
emit(qbe.le_int(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "ge_int") {
|
||||
p = fresh()
|
||||
emit(qbe.ge_int(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -351,12 +388,14 @@ var qbe_emit = function(ir, qbe) {
|
||||
p = fresh()
|
||||
emit(qbe.eq_float(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "ne_float") {
|
||||
p = fresh()
|
||||
emit(qbe.ne_float(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "lt_float" || op == "gt_float" || op == "le_float" || op == "ge_float") {
|
||||
@@ -368,39 +407,46 @@ var qbe_emit = function(ir, qbe) {
|
||||
else if (op == "ge_float") fop_id = 5
|
||||
emit(qbe.cmp_float != null ? cmp_float(p, "%ctx", s(a2), s(a3), fop_id) : ` %${p} =l call $qbe_float_cmp(l %ctx, w ${text(fop_id)}, l ${s(a2)}, l ${s(a3)})`)
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "eq_text") {
|
||||
p = fresh()
|
||||
emit(qbe.eq_text(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "ne_text") {
|
||||
p = fresh()
|
||||
emit(qbe.ne_text(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "lt_text" || op == "gt_text" || op == "le_text" || op == "ge_text") {
|
||||
p = fresh()
|
||||
emit(` ${s(a1)} =l call $cell_rt_${op}(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "eq_bool") {
|
||||
p = fresh()
|
||||
emit(qbe.eq_bool(p, s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "ne_bool") {
|
||||
p = fresh()
|
||||
emit(qbe.ne_bool(p, s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "eq_tol" || op == "ne_tol") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_${op}(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -410,14 +456,17 @@ var qbe_emit = function(ir, qbe) {
|
||||
p = fresh()
|
||||
emit(qbe.lnot(p, "%ctx", s(a2)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "and") {
|
||||
emit(` ${s(a1)} =l and ${s(a2)}, ${s(a3)}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "or") {
|
||||
emit(` ${s(a1)} =l or ${s(a2)}, ${s(a3)}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -427,42 +476,49 @@ var qbe_emit = function(ir, qbe) {
|
||||
p = fresh()
|
||||
emit(qbe.bnot(p, "%ctx", s(a2)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "bitand") {
|
||||
p = fresh()
|
||||
emit(qbe.band(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "bitor") {
|
||||
p = fresh()
|
||||
emit(qbe.bor(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "bitxor") {
|
||||
p = fresh()
|
||||
emit(qbe.bxor(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "shl") {
|
||||
p = fresh()
|
||||
emit(qbe.shl(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "shr") {
|
||||
p = fresh()
|
||||
emit(qbe.shr(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "ushr") {
|
||||
p = fresh()
|
||||
emit(qbe.ushr(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -476,32 +532,38 @@ var qbe_emit = function(ir, qbe) {
|
||||
} else {
|
||||
emit(` ${s(a1)} =l call $cell_rt_load_dynamic(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
}
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "load_index") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_load_index(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "load_dynamic") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_load_dynamic(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "store_field") {
|
||||
// IR: ["store_field", obj, val, prop] → C: (ctx, val, obj, name)
|
||||
pn = prop_name(a3)
|
||||
if (pn != null) {
|
||||
sl = intern_str(pn)
|
||||
emit(` call $cell_rt_store_field(l %ctx, l ${s(a1)}, l ${s(a2)}, l ${sl})`)
|
||||
emit(` call $cell_rt_store_field(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${sl})`)
|
||||
} else {
|
||||
emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a1)}, l ${s(a2)}, l ${s(a3)})`)
|
||||
emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (op == "store_index") {
|
||||
emit(` call $cell_rt_store_index(l %ctx, l ${s(a1)}, l ${s(a2)}, l ${s(a3)})`)
|
||||
// IR: ["store_index", obj, val, idx] → C: (ctx, val, obj, idx)
|
||||
emit(` call $cell_rt_store_index(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
|
||||
continue
|
||||
}
|
||||
if (op == "store_dynamic") {
|
||||
emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a1)}, l ${s(a2)}, l ${s(a3)})`)
|
||||
// IR: ["store_dynamic", obj, val, key] → C: (ctx, val, obj, key)
|
||||
emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -509,6 +571,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
|
||||
if (op == "get") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_get_closure(l %ctx, l %fp, l ${text(a2)}, l ${text(a3)})`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "put") {
|
||||
@@ -569,6 +632,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
|
||||
if (op == "frame") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_frame(l %ctx, l ${s(a2)}, l ${text(a3)})`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "setarg") {
|
||||
@@ -577,10 +641,12 @@ var qbe_emit = function(ir, qbe) {
|
||||
}
|
||||
if (op == "invoke") {
|
||||
emit(` ${s(a2)} =l call $cell_rt_invoke(l %ctx, l ${s(a1)})`)
|
||||
wb(a2)
|
||||
continue
|
||||
}
|
||||
if (op == "goframe") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_goframe(l %ctx, l ${s(a2)}, l ${text(a3)})`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "goinvoke") {
|
||||
@@ -591,7 +657,28 @@ var qbe_emit = function(ir, qbe) {
|
||||
// --- Function object creation ---
|
||||
|
||||
if (op == "function") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_make_function(l %ctx, l ${text(a2)})`)
|
||||
emit(` ${s(a1)} =l call $cell_rt_make_function(l %ctx, l ${text(a2)}, l %fp)`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
// --- Record/Array creation ---
|
||||
|
||||
if (op == "record") {
|
||||
emit(` ${s(a1)} =l call $JS_NewObject(l %ctx)`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "array") {
|
||||
nr_elems = a2 != null ? a2 : 0
|
||||
emit(` ${s(a1)} =l call $JS_NewArray(l %ctx)`)
|
||||
ei = 0
|
||||
while (ei < nr_elems) {
|
||||
elem_slot = instr[3 + ei]
|
||||
emit(` call $JS_SetPropertyUint32(l %ctx, l ${s(a1)}, l ${text(ei)}, l ${s(elem_slot)})`)
|
||||
ei = ei + 1
|
||||
}
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -603,6 +690,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
}
|
||||
if (op == "pop") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_pop(l %ctx, l ${s(a2)})`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -619,10 +707,12 @@ var qbe_emit = function(ir, qbe) {
|
||||
}
|
||||
if (op == "delete") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_delete(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "typeof") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_typeof(l %ctx, l ${s(a2)})`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
BIN
qbe_emit.mach
BIN
qbe_emit.mach
Binary file not shown.
119
qbe_rt.c
119
qbe_rt.c
@@ -1,16 +1,16 @@
|
||||
/*
|
||||
* qbe_rt.c - Runtime support for QBE-compiled ƿit modules
|
||||
* qbe_rt.c - Non-inline wrappers for QBE-compiled ƿit modules
|
||||
*
|
||||
* Provides non-inline versions of static-inline quickjs functions
|
||||
* (which QBE-generated code calls as external symbols) and stub
|
||||
* implementations of cell_rt_* helper functions.
|
||||
* (which QBE-generated code calls as external symbols).
|
||||
*
|
||||
* All cell_rt_* runtime functions are implemented in source/qbe_helpers.c
|
||||
* (compiled into the cell binary) and resolved via -undefined dynamic_lookup.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
typedef uint64_t JSValue;
|
||||
typedef struct JSContext JSContext;
|
||||
@@ -80,112 +80,3 @@ extern JSValue JS_NewStringLen(JSContext *ctx, const char *str, size_t len);
|
||||
JSValue JS_NewString(JSContext *ctx, const char *str) {
|
||||
return JS_NewStringLen(ctx, str, strlen(str));
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
cell_rt_* stubs — error/fallback paths for QBE-compiled code
|
||||
These are called from type-mismatch branches that should not
|
||||
be reached in pure numeric code.
|
||||
============================================================ */
|
||||
|
||||
extern JSValue JS_ThrowTypeError(JSContext *ctx, const char *fmt, ...);
|
||||
|
||||
void cell_rt_disrupt(JSContext *ctx) {
|
||||
JS_ThrowTypeError(ctx, "type error in native code");
|
||||
}
|
||||
|
||||
JSValue cell_rt_lt_text(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_gt_text(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_le_text(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_ge_text(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_eq_tol(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
|
||||
JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_load_field(JSContext *ctx, JSValue obj, const char *name) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_load_dynamic(JSContext *ctx, JSValue obj, JSValue key) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_load_index(JSContext *ctx, JSValue arr, JSValue idx) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
void cell_rt_store_field(JSContext *ctx, JSValue val, JSValue obj,
|
||||
const char *name) {}
|
||||
void cell_rt_store_dynamic(JSContext *ctx, JSValue val, JSValue obj,
|
||||
JSValue key) {}
|
||||
void cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr,
|
||||
JSValue idx) {}
|
||||
JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth,
|
||||
int64_t index) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
|
||||
int64_t index) {}
|
||||
JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
void cell_rt_setarg(JSValue frame, int64_t idx, JSValue val) {}
|
||||
JSValue cell_rt_invoke(JSContext *ctx, JSValue frame) { return JS_VAL_NULL; }
|
||||
JSValue cell_rt_goframe(JSContext *ctx, JSValue fn, int64_t nargs) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
void cell_rt_goinvoke(JSContext *ctx, JSValue frame) {}
|
||||
/*
|
||||
* cell_rt_make_function — create a callable JS function wrapping a
|
||||
* QBE-compiled cell_fn_N. Uses magic to store fn_idx, and dlsym to
|
||||
* look up the compiled symbol at call time.
|
||||
*/
|
||||
|
||||
typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
|
||||
|
||||
/* JS_CFUNC_generic_magic = 1 in the JSCFunctionEnum */
|
||||
#define QBE_JS_CFUNC_GENERIC_MAGIC 1
|
||||
|
||||
extern JSValue JS_NewCFunction2(JSContext *ctx, void *func,
|
||||
const char *name, int length,
|
||||
int cproto, int magic);
|
||||
|
||||
static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
|
||||
int argc, JSValue *argv, int magic) {
|
||||
char name[64];
|
||||
snprintf(name, sizeof(name), "cell_fn_%d", magic);
|
||||
|
||||
cell_compiled_fn fn = (cell_compiled_fn)dlsym(RTLD_DEFAULT, name);
|
||||
if (!fn)
|
||||
return JS_ThrowTypeError(ctx, "native function %s not found", name);
|
||||
|
||||
/* Allocate frame: slot 0 = this, slots 1..argc = args, rest zeroed */
|
||||
JSValue frame[512];
|
||||
memset(frame, 0, sizeof(frame));
|
||||
frame[0] = this_val;
|
||||
for (int i = 0; i < argc && i < 511; i++)
|
||||
frame[1 + i] = argv[i];
|
||||
|
||||
return fn(ctx, frame);
|
||||
}
|
||||
|
||||
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx) {
|
||||
return JS_NewCFunction2(ctx, (void *)cell_fn_trampoline, "native_fn",
|
||||
0, QBE_JS_CFUNC_GENERIC_MAGIC, (int)fn_idx);
|
||||
}
|
||||
void cell_rt_push(JSContext *ctx, JSValue arr, JSValue val) {}
|
||||
JSValue cell_rt_pop(JSContext *ctx, JSValue arr) { return JS_VAL_NULL; }
|
||||
JSValue cell_rt_delete(JSContext *ctx, JSValue obj, JSValue key) {
|
||||
return JS_VAL_NULL;
|
||||
}
|
||||
JSValue cell_rt_typeof(JSContext *ctx, JSValue val) { return JS_VAL_NULL; }
|
||||
|
||||
@@ -192,3 +192,261 @@ JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b) {
|
||||
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 --- */
|
||||
|
||||
JSValue cell_rt_load_field(JSContext *ctx, JSValue obj, const char *name) {
|
||||
return JS_GetPropertyStr(ctx, obj, name);
|
||||
}
|
||||
|
||||
void cell_rt_store_field(JSContext *ctx, JSValue val, JSValue obj,
|
||||
const char *name) {
|
||||
JS_SetPropertyStr(ctx, obj, name, val);
|
||||
}
|
||||
|
||||
JSValue cell_rt_load_dynamic(JSContext *ctx, JSValue obj, JSValue key) {
|
||||
if (JS_IsInt(key))
|
||||
return JS_GetPropertyUint32(ctx, obj, (uint32_t)JS_VALUE_GET_INT(key));
|
||||
return JS_GetProperty(ctx, obj, key);
|
||||
}
|
||||
|
||||
void cell_rt_store_dynamic(JSContext *ctx, JSValue val, JSValue obj,
|
||||
JSValue key) {
|
||||
if (JS_IsInt(key))
|
||||
JS_SetPropertyUint32(ctx, obj, (uint32_t)JS_VALUE_GET_INT(key), val);
|
||||
else
|
||||
JS_SetProperty(ctx, obj, key, val);
|
||||
}
|
||||
|
||||
JSValue cell_rt_load_index(JSContext *ctx, JSValue arr, JSValue idx) {
|
||||
if (JS_IsInt(idx))
|
||||
return JS_GetPropertyUint32(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx));
|
||||
return JS_GetProperty(ctx, arr, idx);
|
||||
}
|
||||
|
||||
void cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr,
|
||||
JSValue idx) {
|
||||
if (JS_IsInt(idx))
|
||||
JS_SetPropertyUint32(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx), val);
|
||||
else
|
||||
JS_SetProperty(ctx, arr, idx, val);
|
||||
}
|
||||
|
||||
/* --- Intrinsic/global lookup --- */
|
||||
|
||||
JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) {
|
||||
return JS_GetPropertyStr(ctx, ctx->global_obj, name);
|
||||
}
|
||||
|
||||
/* --- Closure access ---
|
||||
Slot 511 in each frame stores a pointer to the enclosing frame.
|
||||
Walking depth levels up the chain gives the target frame. */
|
||||
|
||||
#define QBE_FRAME_OUTER_SLOT 511
|
||||
|
||||
JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth,
|
||||
int64_t slot) {
|
||||
JSValue *frame = (JSValue *)fp;
|
||||
for (int64_t d = 0; d < depth; d++) {
|
||||
void *outer = (void *)(uintptr_t)frame[QBE_FRAME_OUTER_SLOT];
|
||||
if (!outer) return JS_NULL;
|
||||
frame = (JSValue *)outer;
|
||||
}
|
||||
return frame[slot];
|
||||
}
|
||||
|
||||
void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
|
||||
int64_t slot) {
|
||||
JSValue *frame = (JSValue *)fp;
|
||||
for (int64_t d = 0; d < depth; d++) {
|
||||
void *outer = (void *)(uintptr_t)frame[QBE_FRAME_OUTER_SLOT];
|
||||
if (!outer) return;
|
||||
frame = (JSValue *)outer;
|
||||
}
|
||||
frame[slot] = val;
|
||||
}
|
||||
|
||||
/* --- Function creation and calling --- */
|
||||
|
||||
typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
|
||||
|
||||
/* Table mapping fn_idx → outer_fp at creation time.
|
||||
Valid for single-threaded, non-recursive closure patterns. */
|
||||
#define MAX_QBE_FUNCTIONS 256
|
||||
static void *g_outer_fp[MAX_QBE_FUNCTIONS];
|
||||
|
||||
static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
|
||||
int argc, JSValue *argv, int magic) {
|
||||
char name[64];
|
||||
snprintf(name, sizeof(name), "cell_fn_%d", magic);
|
||||
|
||||
cell_compiled_fn fn = (cell_compiled_fn)dlsym(RTLD_DEFAULT, name);
|
||||
if (!fn)
|
||||
return JS_ThrowTypeError(ctx, "native function %s not found", name);
|
||||
|
||||
/* Allocate frame: slot 0 = this, slots 1..argc = args */
|
||||
JSValue frame[512];
|
||||
memset(frame, 0, sizeof(frame));
|
||||
frame[0] = this_val;
|
||||
for (int i = 0; i < argc && i < 510; i++)
|
||||
frame[1 + i] = argv[i];
|
||||
|
||||
/* Link to outer frame for closure access */
|
||||
if (magic >= 0 && magic < MAX_QBE_FUNCTIONS)
|
||||
frame[QBE_FRAME_OUTER_SLOT] = (JSValue)(uintptr_t)g_outer_fp[magic];
|
||||
|
||||
return fn(ctx, frame);
|
||||
}
|
||||
|
||||
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp) {
|
||||
if (fn_idx >= 0 && fn_idx < MAX_QBE_FUNCTIONS)
|
||||
g_outer_fp[fn_idx] = outer_fp;
|
||||
return JS_NewCFunction2(ctx, (JSCFunction *)cell_fn_trampoline, "native_fn",
|
||||
255, JS_CFUNC_generic_magic, (int)fn_idx);
|
||||
}
|
||||
|
||||
/* --- Frame-based function calling --- */
|
||||
|
||||
JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
|
||||
if (!JS_IsFunction(fn)) {
|
||||
JS_ThrowTypeError(ctx, "not a function");
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
int nr_slots = (int)nargs + 2;
|
||||
JSFrameRegister *new_frame = alloc_frame_register(ctx, nr_slots);
|
||||
if (!new_frame) return JS_EXCEPTION;
|
||||
new_frame->function = fn;
|
||||
return JS_MKPTR(new_frame);
|
||||
}
|
||||
|
||||
void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) {
|
||||
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
||||
fr->slots[idx] = val;
|
||||
}
|
||||
|
||||
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->hdr);
|
||||
int c_argc = (nr_slots >= 2) ? nr_slots - 2 : 0;
|
||||
|
||||
/* Push args onto value stack (GC-safe) */
|
||||
int vs_base = ctx->value_stack_top;
|
||||
for (int i = 0; i < c_argc; i++)
|
||||
ctx->value_stack[vs_base + i] = fr->slots[i + 1];
|
||||
ctx->value_stack_top = vs_base + c_argc;
|
||||
|
||||
JSValue result = JS_Call(ctx, fr->function, fr->slots[0],
|
||||
c_argc, &ctx->value_stack[vs_base]);
|
||||
ctx->value_stack_top = vs_base;
|
||||
if (JS_IsException(result))
|
||||
return JS_EXCEPTION;
|
||||
return result;
|
||||
}
|
||||
|
||||
JSValue cell_rt_goframe(JSContext *ctx, JSValue fn, int64_t nargs) {
|
||||
return cell_rt_frame(ctx, fn, nargs);
|
||||
}
|
||||
|
||||
void cell_rt_goinvoke(JSContext *ctx, JSValue frame_val) {
|
||||
cell_rt_invoke(ctx, frame_val);
|
||||
}
|
||||
|
||||
/* --- Array push/pop --- */
|
||||
|
||||
void cell_rt_push(JSContext *ctx, JSValue arr, JSValue val) {
|
||||
JS_ArrayPush(ctx, &arr, val);
|
||||
}
|
||||
|
||||
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);
|
||||
return JS_NewBool(ctx, ret >= 0);
|
||||
}
|
||||
|
||||
/* --- 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);
|
||||
}
|
||||
|
||||
JSValue cell_rt_eq_tol(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_NewBool(ctx, a == b);
|
||||
}
|
||||
|
||||
JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_NewBool(ctx, a != b);
|
||||
}
|
||||
|
||||
/* --- Disruption --- */
|
||||
|
||||
void cell_rt_disrupt(JSContext *ctx) {
|
||||
JS_ThrowTypeError(ctx, "type error in native code");
|
||||
}
|
||||
|
||||
/* --- Module entry point ---
|
||||
Called as symbol(ctx) by os.dylib_symbol. Looks up cell_main
|
||||
in the loaded dylib, builds a heap-allocated frame (so closures
|
||||
can reference it after the module returns), and runs the module body. */
|
||||
|
||||
JSValue cell_rt_module_entry(JSContext *ctx) {
|
||||
cell_compiled_fn fn = (cell_compiled_fn)dlsym(RTLD_DEFAULT, "cell_main");
|
||||
if (!fn)
|
||||
return JS_ThrowTypeError(ctx, "cell_main not found in loaded dylib");
|
||||
|
||||
/* Heap-allocate so closures created in cell_main can reference
|
||||
this frame after the module entry returns. */
|
||||
JSValue *frame = calloc(512, sizeof(JSValue));
|
||||
if (!frame)
|
||||
return JS_ThrowTypeError(ctx, "frame allocation failed");
|
||||
|
||||
return fn(ctx, frame);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user