Merge branch 'native_boot'

This commit is contained in:
2026-02-23 11:20:41 -06:00
6 changed files with 32897 additions and 28618 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -123,4 +123,4 @@ log.console("")
log.console("Run 'cell help <command>' for more information.")
}
$stop()
$$stop()

View File

@@ -147,21 +147,38 @@ function compile_native_cached(name, source_path) {
return
}
var t0 = null
var t1 = null
os.print("bootstrap: compiling native: " + name + "\n")
t0 = os.now()
ast = analyze(src, source_path)
compiled = streamline_mod(mcode_mod(ast))
t1 = os.now()
os.print(" [" + name + "] pipeline (tok+parse+fold+mcode+streamline): " + text((t1 - t0) / 1000000) + "ms\n")
t0 = os.now()
il_parts = _qbe_emit_mod(compiled, _qbe_mod, null)
t1 = os.now()
os.print(" [" + name + "] qbe_emit: " + text((t1 - t0) / 1000000) + "ms\n")
helpers_il = (il_parts.helpers && length(il_parts.helpers) > 0)
? text(il_parts.helpers, "\n") : ""
all_fns = text(il_parts.functions, "\n")
full_il = il_parts.data + "\n\n" + helpers_il + "\n\n" + all_fns
t0 = os.now()
asm_text = os.qbe(full_il)
t1 = os.now()
os.print(" [" + name + "] os.qbe (QBE compile): " + text((t1 - t0) / 1000000) + "ms\n")
tmp = '/tmp/cell_boot_' + name
fd.slurpwrite(tmp + '.s', stone(blob(asm_text)))
t0 = os.now()
rc = os.system(_cc + ' -c ' + tmp + '.s -o ' + tmp + '.o')
t1 = os.now()
os.print(" [" + name + "] clang -c: " + text((t1 - t0) / 1000000) + "ms\n")
if (rc != 0) {
os.print("error: assembly failed for " + name + "\n")
disrupt
@@ -186,7 +203,10 @@ function compile_native_cached(name, source_path) {
link_cmd = link_cmd + ' -undefined dynamic_lookup'
link_cmd = link_cmd + ' ' + tmp + '.o ' + rt_o + ' -o ' + dylib_path
t0 = os.now()
rc = os.system(link_cmd)
t1 = os.now()
os.print(" [" + name + "] clang -shared (link): " + text((t1 - t0) / 1000000) + "ms\n")
if (rc != 0) {
os.print("error: linking failed for " + name + "\n")
disrupt

View File

@@ -1073,6 +1073,7 @@ var qbe_emit = function(ir, qbe, export_name) {
var cmp_id = 0
var depth = 0
var d = 0
var handler = null
// Pre-scan: count invoke/tail_invoke points to assign segment numbers.
// Must skip dead code (instructions after terminators) the same way
@@ -1385,51 +1386,25 @@ var qbe_emit = function(ir, qbe, export_name) {
return `%${np}_iw`
}
// Walk instructions
var last_was_term = false
i = 0
while (i < length(instrs)) {
instr = instrs[i]
instr_idx = i
// --- Opcode handlers (record dispatch) ---
var handlers = {}
// Emit @disruption_handler at the right flat index
// disruption_pc counts all entries (labels + instructions)
if (has_handler && i == disruption_pc) {
if (!last_was_term) {
emit(" jmp @disruption_handler")
handlers["int"] = function() {
s_write(a1, text(a2 * 2))
}
emit("@disruption_handler")
emit(" call $cell_rt_clear_exception(l %ctx)")
emit(` %fp =l call $cell_rt_refresh_fp(l %ctx)`)
last_was_term = false
in_handler = true
handlers["null"] = function() {
s_write(a1, text(qbe.js_null))
}
i = i + 1
// Labels are plain strings; skip nop pseudo-labels from streamline
if (is_text(instr)) {
if (starts_with(instr, "_nop_")) continue
lbl = sanitize(instr)
if (!last_was_term) {
emit(` jmp @${lbl}`)
handlers["true"] = function() {
s_write(a1, text(qbe.js_true))
}
emit("@" + lbl)
last_was_term = false
continue
handlers["false"] = function() {
s_write(a1, text(qbe.js_false))
}
// Skip dead code: non-label instructions after a terminator are unreachable
if (last_was_term) continue
op = instr[0]
a1 = instr[1]
a2 = instr[2]
a3 = instr[3]
last_was_term = false
handlers["access"] = function() {
// Peephole: inline `text(x)` intrinsic call sequence
// access text; frame; null this; setarg 0 this; setarg 1 x; invoke
if (op == "access" && is_object(a2) && a2.make == "intrinsic" && a2.name == "text") {
if (is_object(a2) && a2.make == "intrinsic" && a2.name == "text") {
if (instr_idx + 5 < length(instrs)) {
peek1 = instrs[instr_idx + 1]
peek2 = instrs[instr_idx + 2]
@@ -1454,31 +1429,12 @@ var qbe_emit = function(ir, qbe, export_name) {
refresh_fp()
s_write(text_dest_slot, `%${p}_r`)
i = instr_idx + 6
continue
return
}
}
}
}
// --- Constants ---
if (op == "int") {
s_write(a1, text(a2 * 2))
continue
}
if (op == "null") {
s_write(a1, text(qbe.js_null))
continue
}
if (op == "true") {
s_write(a1, text(qbe.js_true))
continue
}
if (op == "false") {
s_write(a1, text(qbe.js_false))
continue
}
if (op == "access") {
if (is_number(a2)) {
if (is_integer(a2)) {
s_write(a1, text(a2 * 2))
@@ -1520,20 +1476,12 @@ var qbe_emit = function(ir, qbe, export_name) {
} else {
s_write(a1, text(qbe.js_null))
}
continue
}
// --- Movement ---
if (op == "move") {
handlers.move = function() {
v = s_read(a2)
s_write(a1, v)
continue
}
// --- Generic arithmetic (VM dispatches int/float) ---
if (op == "add") {
handlers.add = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -1562,9 +1510,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
s_write(a1, `%${p}_r`)
emit(`@${p}_done`)
continue
}
if (op == "subtract") {
handlers.subtract = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -1593,9 +1540,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
s_write(a1, `%${p}_r`)
emit(`@${p}_done`)
continue
}
if (op == "multiply") {
handlers.multiply = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -1624,9 +1570,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
s_write(a1, `%${p}_r`)
emit(`@${p}_done`)
continue
}
if (op == "divide") {
handlers.divide = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -1635,9 +1580,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_rd =d div ${lhs_d}, ${rhs_d}`)
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
s_write(a1, `%${p}_r`)
continue
}
if (op == "modulo") {
handlers.modulo = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -1661,9 +1605,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(`@${p}_bad`)
s_write(a1, text(qbe.js_null))
emit(`@${p}_done`)
continue
}
if (op == "remainder") {
handlers.remainder = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -1682,9 +1625,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(`@${p}_bad`)
s_write(a1, text(qbe.js_null))
emit(`@${p}_done`)
continue
}
if (op == "max" || op == "min") {
var max_min_handler = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -1704,18 +1646,18 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(`@${p}_done_math`)
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
s_write(a1, `%${p}_r`)
continue
}
if (op == "abs") {
handlers.max = max_min_handler
handlers.min = max_min_handler
handlers.abs = function() {
lhs = s_read(a2)
p = fresh()
lhs_d = emit_num_to_double(lhs)
emit(` %${p}_rd =d call $fabs(d ${lhs_d})`)
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
s_write(a1, `%${p}_r`)
continue
}
if (op == "sign") {
handlers.sign = function() {
lhs = s_read(a2)
p = fresh()
lhs_d = emit_num_to_double(lhs)
@@ -1733,9 +1675,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(`@${p}_zero`)
s_write(a1, text(0))
emit(`@${p}_done`)
continue
}
if (op == "fraction") {
handlers.fraction = function() {
lhs = s_read(a2)
p = fresh()
lhs_d = emit_num_to_double(lhs)
@@ -1743,18 +1684,16 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_rd =d sub ${lhs_d}, %${p}_ti`)
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
s_write(a1, `%${p}_r`)
continue
}
if (op == "integer") {
handlers.integer = function() {
lhs = s_read(a2)
p = fresh()
lhs_d = emit_num_to_double(lhs)
emit(` %${p}_rd =d call $trunc(d ${lhs_d})`)
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
s_write(a1, `%${p}_r`)
continue
}
if (op == "floor" || op == "ceiling" || op == "round" || op == "trunc") {
var floor_ceil_round_trunc_handler = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -1829,19 +1768,20 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
s_write(a1, `%${p}_r`)
emit(`@${p}_done`)
continue
}
if (op == "negate") {
handlers.floor = floor_ceil_round_trunc_handler
handlers.ceiling = floor_ceil_round_trunc_handler
handlers.round = floor_ceil_round_trunc_handler
handlers.trunc = floor_ceil_round_trunc_handler
handlers.negate = function() {
lhs = s_read(a2)
p = fresh()
lhs_d = emit_num_to_double(lhs)
emit(` %${p}_rd =d neg ${lhs_d}`)
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
s_write(a1, `%${p}_r`)
continue
}
if (op == "pow") {
handlers.pow = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -1850,17 +1790,12 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_rd =d call $pow(d ${lhs_d}, d ${rhs_d})`)
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
s_write(a1, `%${p}_r`)
continue
}
// --- String concat ---
if (op == "concat") {
handlers.concat = function() {
emit(` %fp =l call $__concat_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
emit_exc_check()
continue
}
if (op == "stone_text") {
handlers.stone_text = function() {
v = s_read(a1)
p = fresh()
emit(` %${p}_ptag =l and ${v}, 7`)
@@ -1880,66 +1815,53 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_new_hdr =l or %${p}_hdr, 8`)
emit(` storel %${p}_new_hdr, %${p}_ptr`)
emit(`@${p}_done`)
continue
}
// --- Type checks — use qbe.cm macros (no GC, no refresh) ---
if (op == "is_int") {
handlers.is_int = function() {
v = s_read(a2)
p = fresh()
emit(` %${p}_tag =l and ${v}, 1`)
emit(` %${p}_w =w ceql %${p}_tag, 0`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_text") {
handlers.is_text = function() {
v = s_read(a2)
s_write(a1, emit_pack_bool_js(emit_is_text_w(v)))
continue
}
if (op == "is_num") {
handlers.is_num = function() {
v = s_read(a2)
s_write(a1, emit_pack_bool_js(emit_is_num_w(v)))
continue
}
if (op == "is_bool") {
handlers.is_bool = function() {
v = s_read(a2)
p = fresh()
emit(` %${p}_t5 =l and ${v}, 31`)
emit(` %${p}_w =w ceql %${p}_t5, 3`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_null") {
handlers.is_null = function() {
v = s_read(a2)
p = fresh()
emit(` %${p}_t5 =l and ${v}, 31`)
emit(` %${p}_w =w ceql %${p}_t5, 7`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_identical") {
handlers.is_identical = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p}_w =w ceql ${lhs}, ${rhs}`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_array") {
handlers.is_array = function() {
emit(` call $__is_array_ss(l %fp, l ${text(a1)}, l ${text(a2)})`)
continue
}
if (op == "is_func") {
handlers.is_func = function() {
emit(` call $__is_func_ss(l %fp, l ${text(a1)}, l ${text(a2)})`)
continue
}
if (op == "is_record") {
handlers.is_record = function() {
emit(` call $__is_record_ss(l %fp, l ${text(a1)}, l ${text(a2)})`)
continue
}
if (op == "is_stone") {
handlers.is_stone = function() {
v = s_read(a2)
p = fresh()
emit(` %${p}_ptag =l and ${v}, 7`)
@@ -1964,9 +1886,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_w =w copy 1`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_proxy") {
handlers.is_proxy = function() {
v = s_read(a2)
p = fresh()
emit(` %${p}_ptag =l and ${v}, 7`)
@@ -1996,9 +1917,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_w =w copy 0`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_blob") {
handlers.is_blob = function() {
v = s_read(a2)
p = fresh()
emit(` %${p}_ptag =l and ${v}, 7`)
@@ -2022,9 +1942,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_w =w copy 0`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_data") {
handlers.is_data = function() {
v = s_read(a2)
p = fresh()
emit(` %${p}_ptag =l and ${v}, 7`)
@@ -2053,9 +1972,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_w =w copy 0`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_fit") {
handlers.is_fit = function() {
v = s_read(a2)
p = fresh()
emit(` %${p}_tag =l and ${v}, 1`)
@@ -2082,9 +2000,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_w =w copy 0`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_char") {
handlers.is_char = function() {
v = s_read(a2)
p = fresh()
emit(` %${p}_imm =l and ${v}, 31`)
@@ -2122,10 +2039,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_w =w copy 0`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_digit" || op == "is_letter" ||
op == "is_lower" || op == "is_upper" || op == "is_ws") {
var is_char_class_handler = function() {
v = s_read(a2)
p = fresh()
emit(` %${p}_imm =l and ${v}, 31`)
@@ -2214,33 +2129,31 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_w =w copy 0`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_true") {
handlers.is_digit = is_char_class_handler
handlers.is_letter = is_char_class_handler
handlers.is_lower = is_char_class_handler
handlers.is_upper = is_char_class_handler
handlers.is_ws = is_char_class_handler
handlers.is_true = function() {
v = s_read(a2)
p = fresh()
emit(` %${p}_w =w ceql ${v}, ${text(qbe.js_true)}`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_false") {
handlers.is_false = function() {
v = s_read(a2)
p = fresh()
emit(` %${p}_w =w ceql ${v}, ${text(qbe.js_false)}`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_actor") {
handlers.is_actor = function() {
v = s_read(a2)
p = fresh()
emit(` %${p}_w =w call $cell_rt_is_actor(l %ctx, l ${v})`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
// --- Comparisons (int path, no GC) ---
if (op == "eq" || op == "ne" || op == "lt" || op == "le" || op == "gt" || op == "ge") {
var cmp_handler = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -2289,22 +2202,23 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(`@${p}_ok`)
s_write(a1, `%${p}_r`)
emit(`@${p}_done`)
continue
}
if (op == "eq_tol" || op == "ne_tol") {
handlers.eq = cmp_handler
handlers.ne = cmp_handler
handlers.lt = cmp_handler
handlers.le = cmp_handler
handlers.gt = cmp_handler
handlers.ge = cmp_handler
var eq_ne_tol_handler = function() {
a4 = instr[4]
emit(` call $__${op}_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)}, l ${text(a4)})`)
continue
}
// --- Boolean ops ---
if (op == "not") {
handlers.eq_tol = eq_ne_tol_handler
handlers.ne_tol = eq_ne_tol_handler
handlers.not = function() {
emit(` call $__not_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
continue
}
if (op == "and") {
handlers["and"] = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -2316,9 +2230,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(`@${p}_f`)
s_write(a1, lhs)
emit(`@${p}_done`)
continue
}
if (op == "or") {
handlers["or"] = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -2330,28 +2243,20 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(`@${p}_f`)
s_write(a1, rhs)
emit(`@${p}_done`)
continue
}
// --- Bitwise ops — use qbe.cm macros (no GC) ---
if (op == "bitnot") {
handlers.bitnot = function() {
emit(` call $__bnot_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
continue
}
if (op == "bitand") {
handlers.bitand = function() {
emit(` call $__band_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
continue
}
if (op == "bitor") {
handlers.bitor = function() {
emit(` call $__bor_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
continue
}
if (op == "bitxor") {
handlers.bitxor = function() {
emit(` call $__bxor_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
continue
}
if (op == "shl") {
handlers.shl = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -2376,9 +2281,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` ret 15`)
}
emit(`@${p}_done`)
continue
}
if (op == "shr") {
handlers.shr = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -2403,9 +2307,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` ret 15`)
}
emit(`@${p}_done`)
continue
}
if (op == "ushr") {
handlers.ushr = function() {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
@@ -2430,12 +2333,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` ret 15`)
}
emit(`@${p}_done`)
continue
}
// --- Property access — runtime calls [G] ---
if (op == "load_field") {
handlers.load_field = function() {
pn = null
if (is_text(a3)) {
pn = a3
@@ -2453,14 +2352,12 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %fp =l call $__load_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
}
emit_exc_check()
continue
}
if (op == "load_index") {
handlers.load_index = function() {
emit(` %fp =l call $__load_index_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
emit_exc_check()
continue
}
if (op == "load_dynamic") {
handlers.load_dynamic = function() {
pn = null
if (is_text(a3)) {
pn = a3
@@ -2478,9 +2375,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %fp =l call $__load_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
}
emit_exc_check()
continue
}
if (op == "store_field") {
handlers.store_field = function() {
// IR: ["store_field", obj, val, prop]
pn = null
if (is_text(a3)) {
@@ -2499,9 +2395,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %fp =l call $__store_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
}
emit_exc_check()
continue
}
if (op == "store_index") {
handlers.store_index = function() {
// IR: ["store_index", obj, val, idx]
lhs = s_read(a1)
rhs = s_read(a2)
@@ -2563,9 +2458,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %fp =l call $__store_index_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
emit_exc_check()
emit(`@${p}_done`)
continue
}
if (op == "store_dynamic") {
handlers.store_dynamic = function() {
// IR: ["store_dynamic", obj, val, key]
pn = null
if (is_text(a3)) {
@@ -2584,12 +2478,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %fp =l call $__store_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
}
emit_exc_check()
continue
}
// --- Closure access (no GC) ---
if (op == "get") {
handlers.get = function() {
// mcode: get(dest, slot, depth) — a2=slot, a3=depth
depth = a3
if (depth == 0) {
@@ -2613,9 +2503,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_val =l loadl %${p}_slotp`)
s_write(a1, `%${p}_val`)
}
continue
}
if (op == "put") {
handlers.put = function() {
// mcode: put(val, slot, depth) — a2=slot, a3=depth
v = s_read(a1)
depth = a3
@@ -2638,12 +2527,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_slotp =l add %${p}_fp, ${text(a2 * 8)}`)
emit(` storel ${v}, %${p}_slotp`)
}
continue
}
// --- Control flow ---
if (op == "jump") {
handlers.jump = function() {
j_lbl = sanitize(a1)
j_idx = label_pos[j_lbl]
if (j_idx != null && j_idx < instr_idx) {
@@ -2652,9 +2537,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` jmp @${j_lbl}`)
}
last_was_term = true
continue
}
if (op == "jump_true") {
handlers.jump_true = function() {
v = s_read(a1)
p = fresh()
jt_lbl = sanitize(a2)
@@ -2669,9 +2553,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` jmp @${jt_lbl}`)
}
emit(`@${p}_f`)
continue
}
if (op == "jump_false") {
handlers.jump_false = function() {
v = s_read(a1)
p = fresh()
jf_lbl = sanitize(a2)
@@ -2686,9 +2569,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` jmp @${jf_lbl}`)
}
emit(`@${p}_t`)
continue
}
if (op == "wary_true") {
handlers.wary_true = function() {
v = s_read(a1)
p = fresh()
wt_lbl = sanitize(a2)
@@ -2703,9 +2585,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` jmp @${wt_lbl}`)
}
emit(`@${p}_f`)
continue
}
if (op == "wary_false") {
handlers.wary_false = function() {
v = s_read(a1)
p = fresh()
wf_lbl = sanitize(a2)
@@ -2720,9 +2601,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` jmp @${wf_lbl}`)
}
emit(`@${p}_t`)
continue
}
if (op == "jump_null") {
handlers.jump_null = function() {
v = s_read(a1)
p = fresh()
jn_lbl = sanitize(a2)
@@ -2737,9 +2617,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` jnz %${p}, @${jn_lbl}, @${p}_n`)
}
emit(`@${p}_n`)
continue
}
if (op == "jump_empty") {
handlers.jump_empty = function() {
v = s_read(a1)
p = fresh()
je_lbl = sanitize(a2)
@@ -2754,9 +2633,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` jnz %${p}, @${je_lbl}, @${p}_n`)
}
emit(`@${p}_n`)
continue
}
if (op == "jump_not_null") {
handlers.jump_not_null = function() {
v = s_read(a1)
p = fresh()
jnn_lbl = sanitize(a2)
@@ -2771,22 +2649,16 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` jnz %${p}, @${jnn_lbl}, @${p}_n`)
}
emit(`@${p}_n`)
continue
}
// --- Function calls [G] ---
if (op == "frame") {
handlers.frame = function() {
emit(` %fp =l call $__frame_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
emit_exc_check()
continue
}
if (op == "apply") {
handlers.apply = function() {
emit(` %fp =l call $__apply_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
emit_exc_check()
continue
}
if (op == "setarg") {
handlers.setarg = function() {
v = s_read(a1)
lhs = s_read(a3)
p = fresh()
@@ -2795,9 +2667,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %${p}_fr =l and ${v}, -8`)
emit(` %${p}_slot =l add %${p}_fr, ${text(32 + a2 * 8)}`)
emit(` storel ${lhs}, %${p}_slot`)
continue
}
if (op == "invoke" || op == "tail_invoke") {
var invoke_handler = function() {
// Signal dispatcher to call frame in slot a1 and resume at @_segN.
seg_counter = seg_counter + 1
resume_val = seg_counter * 65536 + a2
@@ -2818,24 +2689,20 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` jnz %${p}_exc, @_exc_ret, @${p}_ok`)
}
emit(`@${p}_ok`)
continue
}
if (op == "goframe") {
handlers.invoke = invoke_handler
handlers.tail_invoke = invoke_handler
handlers.goframe = function() {
emit(` %fp =l call $__goframe_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
emit_exc_check()
continue
}
if (op == "goinvoke") {
handlers.goinvoke = function() {
// Tail call via dispatcher: no resume in this frame.
emit(` call $cell_rt_signal_tail_call(l %ctx, l %fp, l ${text(a1)})`)
emit(` ret ${text(qbe.js_null)}`)
last_was_term = true
continue
}
// --- Function object creation [G] ---
if (op == "function") {
handlers["function"] = function() {
fn_arity = 0
fn_nr_slots = 0
if (a2 >= 0 && a2 < length(ir.functions)) {
@@ -2844,25 +2711,16 @@ var qbe_emit = function(ir, qbe, export_name) {
}
emit(` %fp =l call $__function_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(fn_arity)}, l ${text(fn_nr_slots)})`)
emit_exc_check()
continue
}
// --- Record/Array creation [G] ---
if (op == "record") {
handlers.record = function() {
emit(` %fp =l call $__new_record_ss(l %ctx, l %fp, l ${text(a1)})`)
emit_exc_check()
continue
}
if (op == "array") {
handlers.array = function() {
emit(` %fp =l call $__new_array_ss(l %ctx, l %fp, l ${text(a1)})`)
emit_exc_check()
continue
}
// --- Array push/pop [G] ---
if (op == "push") {
handlers.push = function() {
lhs = s_read(a1)
rhs = s_read(a2)
p = fresh()
@@ -2909,31 +2767,21 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %fp =l call $__push_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
emit_exc_check()
emit(`@${p}_done`)
continue
}
if (op == "pop") {
handlers.pop = function() {
emit(` %fp =l call $__pop_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
emit_exc_check()
continue
}
// --- Length [G] ---
if (op == "length") {
handlers.length = function() {
emit(` %fp =l call $__length_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
emit_exc_check()
continue
}
// --- Misc ---
if (op == "return") {
handlers["return"] = function() {
v = s_read(a1)
emit(` ret ${v}`)
last_was_term = true
continue
}
if (op == "disrupt") {
handlers.disrupt = function() {
emit(` call $cell_rt_disrupt(l %ctx)`)
if (has_handler && !in_handler) {
emit(" jmp @disruption_handler")
@@ -2941,9 +2789,8 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` ret 15`)
}
last_was_term = true
continue
}
if (op == "delete") {
handlers["delete"] = function() {
pn = null
if (is_text(a3)) {
pn = a3
@@ -2961,32 +2808,69 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(` %fp =l call $__delete_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
}
emit_exc_check()
continue
}
// --- in [G] ---
if (op == "in") {
handlers["in"] = function() {
// IR: ["in", dest, key_slot, obj_slot]
emit(` %fp =l call $__in_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
emit_exc_check()
continue
}
// --- regexp [G] ---
if (op == "regexp") {
handlers.regexp = function() {
// IR: ["regexp", dest_slot, pattern_string, flags_string]
pat_label = intern_str(a2)
flg_label = intern_str(a3)
emit(` %fp =l call $__regexp_ss(l %ctx, l %fp, l ${text(a1)}, l ${pat_label.label}, l ${flg_label.label})`)
emit_exc_check()
}
// Walk instructions
var last_was_term = false
i = 0
while (i < length(instrs)) {
instr = instrs[i]
instr_idx = i
// Emit @disruption_handler at the right flat index
// disruption_pc counts all entries (labels + instructions)
if (has_handler && i == disruption_pc) {
if (!last_was_term) {
emit(" jmp @disruption_handler")
}
emit("@disruption_handler")
emit(" call $cell_rt_clear_exception(l %ctx)")
emit(` %fp =l call $cell_rt_refresh_fp(l %ctx)`)
last_was_term = false
in_handler = true
}
i = i + 1
// Labels are plain strings; skip nop pseudo-labels from streamline
if (is_text(instr)) {
if (starts_with(instr, "_nop_")) continue
lbl = sanitize(instr)
if (!last_was_term) {
emit(` jmp @${lbl}`)
}
emit("@" + lbl)
last_was_term = false
continue
}
// --- Unknown opcode ---
// Skip dead code: non-label instructions after a terminator are unreachable
if (last_was_term) continue
op = instr[0]
a1 = instr[1]
a2 = instr[2]
a3 = instr[3]
last_was_term = false
handler = handlers[op]
if (handler) {
handler()
} else {
emit(` # unknown: ${op}`)
}
}
// Emit @disrupt landing pad for arithmetic type-error branches
if (!last_was_term) {

View File

@@ -10,6 +10,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* QBE headers */
#include "all.h"
@@ -154,6 +155,8 @@ JSValue js_os_qbe(JSContext *js, JSValue self, int argc, JSValue *argv) {
}
/* Run the QBE pipeline */
struct timespec t0, t1;
clock_gettime(CLOCK_MONOTONIC, &t0);
parse(inf, "<ir>", qbe_dbgfile, qbe_data, qbe_func);
fclose(inf);
@@ -162,6 +165,11 @@ JSValue js_os_qbe(JSContext *js, JSValue self, int argc, JSValue *argv) {
fflush(qbe_outf);
fclose(qbe_outf);
qbe_outf = NULL;
clock_gettime(CLOCK_MONOTONIC, &t1);
double ms = (t1.tv_sec - t0.tv_sec) * 1000.0 +
(t1.tv_nsec - t0.tv_nsec) / 1e6;
fprintf(stderr, "[qbe_backend] os.qbe: %.1fms (%zu bytes IL -> %zu bytes asm)\n",
ms, ir_len, out_len);
JS_FreeCString(js, ir);