Files
cell/qbe_emit.cm
2026-02-17 03:33:21 -06:00

1066 lines
28 KiB
Plaintext

// qbe_emit.cm — mcode IR → QBE IL compiler
// Takes mcode IR (from mcode.cm) and uses qbe.cm macros to produce
// a complete QBE IL program ready for the qbe compiler.
// qbe module is passed via env as 'qbe'
var qbe_emit = function(ir, qbe, export_name) {
var out = []
var data_out = []
var str_table = {}
var str_id = 0
var uid = 0
// ============================================================
// Output helpers
// ============================================================
var emit = function(s) {
push(out, s)
}
var fresh = function() {
uid = uid + 1
return "u" + text(uid)
}
var sanitize = function(lbl) {
var r = replace(lbl, ".", "_")
r = replace(r, "-", "_")
r = replace(r, " ", "_")
r = replace(r, "/", "_")
r = replace(r, "<", "")
r = replace(r, ">", "")
r = replace(r, "(", "")
r = replace(r, ")", "")
return r
}
// ============================================================
// String interning — emit data section entries
// ============================================================
var intern_str = function(val) {
if (str_table[val] != null) return str_table[val]
var label = "$d_str_" + text(str_id)
str_id = str_id + 1
var escaped = replace(val, "\\", "\\\\")
escaped = replace(escaped, "\"", "\\\"")
escaped = replace(escaped, "\n", "\\n")
escaped = replace(escaped, "\r", "\\r")
escaped = replace(escaped, "\t", "\\t")
var line = "data " + label + ' = ' + '{ b "' + escaped + '", b 0 }'
push(data_out, line)
str_table[val] = label
return label
}
// ============================================================
// Extract property name from mcode operand
// ============================================================
var prop_name = function(a) {
if (is_text(a)) return a
if (is_object(a)) {
if (a.name != null) return a.name
if (a.value != null) return a.value
}
return null
}
// ============================================================
// Compile one function's instructions
// ============================================================
var compile_fn = function(fn, fn_idx, is_main) {
var instrs = fn.instructions
var nr_slots = fn.nr_slots
var nr_args = fn.nr_args
var disruption_pc = fn.disruption_pc != null ? fn.disruption_pc : 0
var has_handler = disruption_pc > 0
var name = is_main ? (export_name ? export_name : "cell_main") : "cell_fn_" + text(fn_idx)
name = sanitize(name)
var i = 0
var instr = null
var op = null
var a1 = null
var a2 = null
var a3 = null
var a4 = null
var p = null
var pn = null
var sl = null
var lbl = null
var fop_id = 0
var nr_elems = 0
var ei = 0
var elem_slot = 0
var v = null
var lhs = null
var rhs = null
var obj = null
var chk = null
var pat_label = null
var flg_label = null
var in_handler = false
// Function signature: (ctx, frame_ptr) → JSValue
emit(`export function l $${name}(l %ctx, l %fp) {`)
emit("@entry")
// GC-safe slot access: every read/write goes through frame memory.
// %fp may become stale after GC-triggering calls — use refresh_fp().
var s_read = function(slot) {
var t = fresh()
emit(` %${t} =l add %fp, ${text(slot * 8)}`)
emit(` %${t}v =l loadl %${t}`)
return `%${t}v`
}
var s_write = function(slot, val) {
var t = fresh()
var sv = val
if (!starts_with(val, "%")) {
sv = `%${t}c`
emit(` ${sv} =l copy ${val}`)
}
emit(` %${t} =l add %fp, ${text(slot * 8)}`)
emit(` storel ${sv}, %${t}`)
}
var refresh_fp = function() {
emit(` %fp =l call $cell_rt_refresh_fp_checked(l %ctx)`)
var exc = fresh()
emit(` %${exc} =w ceql %fp, 0`)
if (has_handler && !in_handler) {
emit(` jnz %${exc}, @disruption_handler, @${exc}_ok`)
} else {
emit(` jnz %${exc}, @${exc}_exc, @${exc}_ok`)
emit(`@${exc}_exc`)
emit(` ret 15`)
}
emit(`@${exc}_ok`)
}
// Walk instructions
var last_was_term = false
i = 0
while (i < length(instrs)) {
instr = instrs[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_ur_ pseudo-labels from streamline
if (is_text(instr)) {
if (starts_with(instr, "_nop_ur_")) continue
lbl = sanitize(instr)
if (!last_was_term) {
emit(` jmp @${lbl}`)
}
emit("@" + lbl)
last_was_term = false
continue
}
// 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
// --- 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") {
p = fresh()
if (is_number(a2)) {
if (is_integer(a2)) {
s_write(a1, text(a2 * 2))
} else {
emit(` %${p} =l call $qbe_new_float64(l %ctx, d d_${text(a2)})`)
refresh_fp()
s_write(a1, `%${p}`)
}
} else if (is_text(a2)) {
sl = intern_str(a2)
emit(` %${p} =l call $qbe_new_string(l %ctx, l ${sl})`)
refresh_fp()
s_write(a1, `%${p}`)
} else if (is_object(a2)) {
if (a2.make == "intrinsic") {
sl = intern_str(a2.name)
emit(` %${p} =l call $cell_rt_get_intrinsic(l %ctx, l ${sl})`)
refresh_fp()
s_write(a1, `%${p}`)
} else if (a2.kind == "number") {
if (a2.number != null && is_integer(a2.number)) {
s_write(a1, text(a2.number * 2))
} else if (a2.number != null) {
emit(` %${p} =l call $qbe_new_float64(l %ctx, d d_${text(a2.number)})`)
refresh_fp()
s_write(a1, `%${p}`)
} else {
s_write(a1, text(qbe.js_null))
}
} else if (a2.kind == "text") {
sl = intern_str(a2.value)
emit(` %${p} =l call $qbe_new_string(l %ctx, l ${sl})`)
refresh_fp()
s_write(a1, `%${p}`)
} else if (a2.kind == "true") {
s_write(a1, text(qbe.js_true))
} else if (a2.kind == "false") {
s_write(a1, text(qbe.js_false))
} else if (a2.kind == "null") {
s_write(a1, text(qbe.js_null))
} else {
s_write(a1, text(qbe.js_null))
}
} else {
s_write(a1, text(qbe.js_null))
}
continue
}
// --- Movement ---
if (op == "move") {
v = s_read(a2)
s_write(a1, v)
continue
}
// --- Generic arithmetic (VM dispatches int/float) ---
if (op == "add") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p} =l call $cell_rt_add(l %ctx, l ${lhs}, l ${rhs})`)
refresh_fp()
s_write(a1, `%${p}`)
continue
}
if (op == "subtract") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.sub(p, "%ctx", lhs, rhs))
refresh_fp()
s_write(a1, `%${p}`)
continue
}
if (op == "multiply") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.mul(p, "%ctx", lhs, rhs))
refresh_fp()
s_write(a1, `%${p}`)
continue
}
if (op == "divide") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.div(p, "%ctx", lhs, rhs))
refresh_fp()
s_write(a1, `%${p}`)
continue
}
if (op == "modulo") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.mod(p, "%ctx", lhs, rhs))
refresh_fp()
s_write(a1, `%${p}`)
continue
}
if (op == "negate") {
v = s_read(a2)
p = fresh()
emit(qbe.neg(p, "%ctx", v))
refresh_fp()
s_write(a1, `%${p}`)
continue
}
if (op == "pow") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p} =l call $qbe_float_pow(l %ctx, l ${lhs}, l ${rhs})`)
refresh_fp()
s_write(a1, `%${p}`)
continue
}
// --- String concat ---
if (op == "concat") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.concat(p, "%ctx", lhs, rhs))
refresh_fp()
s_write(a1, `%${p}`)
continue
}
// --- Type checks — use qbe.cm macros (no GC, no refresh) ---
if (op == "is_int") {
v = s_read(a2)
p = fresh()
emit(qbe.is_int(p, v))
emit(qbe.new_bool(p + ".r", "%" + p))
s_write(a1, `%${p}.r`)
continue
}
if (op == "is_text") {
v = s_read(a2)
p = fresh()
emit(` %${p} =w call $JS_IsText(l ${v})`)
emit(qbe.new_bool(p + ".r", "%" + p))
s_write(a1, `%${p}.r`)
continue
}
if (op == "is_num") {
v = s_read(a2)
p = fresh()
emit(qbe.is_number(p, v))
emit(qbe.new_bool(p + ".r", "%" + p))
s_write(a1, `%${p}.r`)
continue
}
if (op == "is_bool") {
v = s_read(a2)
p = fresh()
emit(qbe.is_bool(p, v))
emit(qbe.new_bool(p + ".r", "%" + p))
s_write(a1, `%${p}.r`)
continue
}
if (op == "is_null") {
v = s_read(a2)
p = fresh()
emit(qbe.is_null(p, v))
emit(qbe.new_bool(p + ".r", "%" + p))
s_write(a1, `%${p}.r`)
continue
}
if (op == "is_identical") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.is_identical(p, lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "is_array") {
v = s_read(a2)
p = fresh()
emit(` %${p} =w call $JS_IsArray(l ${v})`)
emit(qbe.new_bool(p + ".r", "%" + p))
s_write(a1, `%${p}.r`)
continue
}
if (op == "is_func") {
v = s_read(a2)
p = fresh()
emit(` %${p} =w call $JS_IsFunction(l ${v})`)
emit(qbe.new_bool(p + ".r", "%" + p))
s_write(a1, `%${p}.r`)
continue
}
if (op == "is_record") {
v = s_read(a2)
p = fresh()
emit(` %${p} =w call $JS_IsRecord(l ${v})`)
emit(qbe.new_bool(p + ".r", "%" + p))
s_write(a1, `%${p}.r`)
continue
}
if (op == "is_stone") {
v = s_read(a2)
p = fresh()
emit(` %${p} =w call $JS_IsStone(l ${v})`)
emit(qbe.new_bool(p + ".r", "%" + p))
s_write(a1, `%${p}.r`)
continue
}
if (op == "is_proxy") {
v = s_read(a2)
p = fresh()
emit(` %${p} =w call $cell_rt_is_proxy(l %ctx, l ${v})`)
emit(qbe.new_bool(p + ".r", "%" + p))
s_write(a1, `%${p}.r`)
continue
}
// --- Comparisons (int path, no GC) ---
if (op == "eq_int") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.eq_int(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "ne_int") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.ne_int(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "lt_int") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.lt_int(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "gt_int") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.gt_int(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "le_int") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.le_int(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "ge_int") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.ge_int(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
// --- Comparisons (float/text/bool) ---
if (op == "eq_float") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.eq_float(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "ne_float") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.ne_float(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "lt_float") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.lt_float(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "le_float") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.le_float(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "gt_float") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.gt_float(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "ge_float") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.ge_float(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "eq_text") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.eq_text(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "ne_text") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.ne_text(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "lt_text" || op == "gt_text" || op == "le_text" || op == "ge_text") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p} =l call $cell_rt_${op}(l %ctx, l ${lhs}, l ${rhs})`)
s_write(a1, `%${p}`)
continue
}
if (op == "eq_bool") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.eq_bool(p, lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "ne_bool") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.ne_bool(p, lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "eq_tol" || op == "ne_tol") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p} =l call $cell_rt_${op}(l %ctx, l ${lhs}, l ${rhs})`)
s_write(a1, `%${p}`)
continue
}
// --- Boolean ops ---
if (op == "not") {
v = s_read(a2)
p = fresh()
emit(qbe.lnot(p, "%ctx", v))
s_write(a1, `%${p}`)
continue
}
if (op == "and") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p} =l call $cell_rt_and(l %ctx, l ${lhs}, l ${rhs})`)
s_write(a1, `%${p}`)
continue
}
if (op == "or") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p} =l call $cell_rt_or(l %ctx, l ${lhs}, l ${rhs})`)
s_write(a1, `%${p}`)
continue
}
// --- Bitwise ops — use qbe.cm macros (no GC) ---
if (op == "bitnot") {
v = s_read(a2)
p = fresh()
emit(qbe.bnot(p, "%ctx", v))
s_write(a1, `%${p}`)
continue
}
if (op == "bitand") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.band(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "bitor") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.bor(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "bitxor") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.bxor(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "shl") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.shl(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "shr") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.shr(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
if (op == "ushr") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(qbe.ushr(p, "%ctx", lhs, rhs))
s_write(a1, `%${p}`)
continue
}
// --- Property access — runtime calls [G] ---
if (op == "load_field") {
v = s_read(a2)
pn = null
if (is_text(a3)) pn = a3
else if (is_object(a3) && a3.name != null) pn = a3.name
else if (is_object(a3) && a3.value != null) pn = a3.value
p = fresh()
if (pn != null) {
sl = intern_str(pn)
emit(` %${p} =l call $cell_rt_load_field(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})`)
}
refresh_fp()
s_write(a1, `%${p}`)
continue
}
if (op == "load_index") {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p} =l call $cell_rt_load_index(l %ctx, l ${lhs}, l ${rhs})`)
refresh_fp()
s_write(a1, `%${p}`)
continue
}
if (op == "load_dynamic") {
v = s_read(a2)
pn = null
if (is_text(a3)) pn = a3
else if (is_object(a3) && a3.name != null) pn = a3.name
else if (is_object(a3) && a3.value != null) pn = a3.value
p = fresh()
if (pn != null) {
sl = intern_str(pn)
emit(` %${p} =l call $cell_rt_load_field(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})`)
}
refresh_fp()
s_write(a1, `%${p}`)
continue
}
if (op == "store_field") {
// IR: ["store_field", obj, val, prop] → C: (ctx, val, obj, name)
obj = s_read(a1)
v = s_read(a2)
pn = null
if (is_text(a3)) {
pn = a3
} else if (is_object(a3)) {
if (a3.name != null) pn = a3.name
else if (a3.value != null) pn = a3.value
}
if (pn != null) {
sl = intern_str(pn)
emit(` call $cell_rt_store_field(l %ctx, l ${v}, l ${obj}, l ${sl})`)
} else {
lhs = s_read(a3)
emit(` call $cell_rt_store_dynamic(l %ctx, l ${v}, l ${obj}, l ${lhs})`)
}
refresh_fp()
continue
}
if (op == "store_index") {
// IR: ["store_index", obj, val, idx] → C: (ctx, val, obj, idx)
obj = s_read(a1)
v = s_read(a2)
lhs = s_read(a3)
emit(` call $cell_rt_store_index(l %ctx, l ${v}, l ${obj}, l ${lhs})`)
refresh_fp()
continue
}
if (op == "store_dynamic") {
// IR: ["store_dynamic", obj, val, key] → C: (ctx, val, obj, key)
obj = s_read(a1)
v = s_read(a2)
pn = null
if (is_text(a3)) pn = a3
else if (is_object(a3) && a3.name != null) pn = a3.name
else if (is_object(a3) && a3.value != null) pn = a3.value
if (pn != null) {
sl = intern_str(pn)
emit(` call $cell_rt_store_field(l %ctx, l ${v}, l ${obj}, l ${sl})`)
} else {
lhs = s_read(a3)
emit(` call $cell_rt_store_dynamic(l %ctx, l ${v}, l ${obj}, l ${lhs})`)
}
refresh_fp()
continue
}
// --- Closure access (no GC) ---
if (op == "get") {
// mcode: get(dest, slot, depth) — a2=slot, a3=depth
p = fresh()
emit(` %${p} =l call $cell_rt_get_closure(l %ctx, l %fp, l ${text(a3)}, l ${text(a2)})`)
s_write(a1, `%${p}`)
continue
}
if (op == "put") {
// mcode: put(val, slot, depth) — a2=slot, a3=depth
v = s_read(a1)
emit(` call $cell_rt_put_closure(l %ctx, l %fp, l ${v}, l ${text(a3)}, l ${text(a2)})`)
continue
}
// --- Control flow ---
if (op == "jump") {
emit(` jmp @${sanitize(a1)}`)
last_was_term = true
continue
}
if (op == "jump_true") {
v = s_read(a1)
p = fresh()
emit(` %${p} =w call $JS_ToBool(l %ctx, l ${v})`)
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_f`)
emit(`@${p}_f`)
continue
}
if (op == "jump_false") {
v = s_read(a1)
p = fresh()
emit(` %${p} =w call $JS_ToBool(l %ctx, l ${v})`)
emit(` jnz %${p}, @${p}_t, @${sanitize(a2)}`)
emit(`@${p}_t`)
continue
}
if (op == "jump_null") {
v = s_read(a1)
p = fresh()
emit(` %${p} =w ceql ${v}, ${text(qbe.js_null)}`)
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_nn`)
emit(`@${p}_nn`)
continue
}
if (op == "jump_not_null") {
v = s_read(a1)
p = fresh()
emit(` %${p} =w cnel ${v}, ${text(qbe.js_null)}`)
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_n`)
emit(`@${p}_n`)
continue
}
// --- Function calls [G] ---
if (op == "frame") {
v = s_read(a2)
p = fresh()
emit(` %${p} =l call $cell_rt_frame(l %ctx, l ${v}, l ${text(a3)})`)
refresh_fp()
s_write(a1, `%${p}`)
continue
}
if (op == "setarg") {
v = s_read(a1)
lhs = s_read(a3)
emit(` call $cell_rt_setarg(l ${v}, l ${text(a2)}, l ${lhs})`)
continue
}
if (op == "invoke") {
v = s_read(a1)
p = fresh()
emit(` %${p} =l call $cell_rt_invoke(l %ctx, l ${v})`)
chk = fresh()
emit(` %${chk} =w ceql %${p}, 15`)
if (has_handler) {
emit(` jnz %${chk}, @disruption_handler, @${chk}_ok`)
} else {
emit(` jnz %${chk}, @${chk}_exc, @${chk}_ok`)
emit(`@${chk}_exc`)
emit(` ret 15`)
}
emit(`@${chk}_ok`)
refresh_fp()
s_write(a2, `%${p}`)
continue
}
if (op == "tail_invoke") {
v = s_read(a1)
p = fresh()
emit(` %${p} =l call $cell_rt_invoke(l %ctx, l ${v})`)
chk = fresh()
emit(` %${chk} =w ceql %${p}, 15`)
if (has_handler) {
emit(` jnz %${chk}, @disruption_handler, @${chk}_ok`)
} else {
emit(` jnz %${chk}, @${chk}_exc, @${chk}_ok`)
emit(`@${chk}_exc`)
emit(` ret 15`)
}
emit(`@${chk}_ok`)
refresh_fp()
s_write(a2, `%${p}`)
continue
}
if (op == "goframe") {
v = s_read(a2)
p = fresh()
emit(` %${p} =l call $cell_rt_goframe(l %ctx, l ${v}, l ${text(a3)})`)
refresh_fp()
s_write(a1, `%${p}`)
continue
}
if (op == "goinvoke") {
v = s_read(a1)
p = fresh()
emit(` %${p} =l call $cell_rt_goinvoke(l %ctx, l ${v})`)
chk = fresh()
emit(` %${chk} =w ceql %${p}, 15`)
if (has_handler) {
emit(` jnz %${chk}, @disruption_handler, @${chk}_ok`)
emit(`@${chk}_ok`)
refresh_fp()
emit(` ret %${p}`)
} else {
emit(` jnz %${chk}, @${chk}_exc, @${chk}_ok`)
emit(`@${chk}_exc`)
emit(` ret 15`)
emit(`@${chk}_ok`)
emit(` ret %${p}`)
}
last_was_term = true
continue
}
// --- Function object creation [G] ---
if (op == "function") {
p = fresh()
emit(` %${p} =l call $cell_rt_make_function(l %ctx, l ${text(a2)}, l %fp)`)
refresh_fp()
s_write(a1, `%${p}`)
continue
}
// --- Record/Array creation [G] ---
if (op == "record") {
p = fresh()
emit(` %${p} =l call $JS_NewObject(l %ctx)`)
refresh_fp()
s_write(a1, `%${p}`)
continue
}
if (op == "array") {
// a2 is a size hint; elements are pushed via separate push instructions
p = fresh()
emit(` %${p} =l call $JS_NewArray(l %ctx)`)
refresh_fp()
s_write(a1, `%${p}`)
continue
}
// --- Array push/pop [G] ---
if (op == "push") {
lhs = s_read(a1)
rhs = s_read(a2)
emit(` call $cell_rt_push(l %ctx, l ${lhs}, l ${rhs})`)
refresh_fp()
continue
}
if (op == "pop") {
v = s_read(a2)
p = fresh()
emit(` %${p} =l call $cell_rt_pop(l %ctx, l ${v})`)
refresh_fp()
s_write(a1, `%${p}`)
continue
}
// --- Length [G] ---
if (op == "length") {
v = s_read(a2)
p = fresh()
emit(` %${p} =l call $JS_CellLength(l %ctx, l ${v})`)
refresh_fp()
s_write(a1, `%${p}`)
continue
}
// --- Misc ---
if (op == "return") {
v = s_read(a1)
emit(` ret ${v}`)
last_was_term = true
continue
}
if (op == "disrupt") {
emit(` call $cell_rt_disrupt(l %ctx)`)
if (has_handler && !in_handler) {
emit(" jmp @disruption_handler")
} else {
emit(` ret 15`)
}
last_was_term = true
continue
}
if (op == "delete") {
v = s_read(a2)
pn = null
if (is_text(a3)) pn = a3
else if (is_object(a3) && a3.name != null) pn = a3.name
else if (is_object(a3) && a3.value != null) pn = a3.value
p = fresh()
if (pn != null) {
sl = intern_str(pn)
emit(` %${p} =l call $cell_rt_delete(l %ctx, l ${v}, l ${sl})`)
} else {
lhs = s_read(a3)
emit(` %${p} =l call $cell_rt_delete(l %ctx, l ${v}, l ${lhs})`)
}
refresh_fp()
s_write(a1, `%${p}`)
continue
}
// --- in [G] ---
if (op == "in") {
// IR: ["in", dest, key_slot, obj_slot]
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p} =l call $cell_rt_in(l %ctx, l ${lhs}, l ${rhs})`)
refresh_fp()
s_write(a1, `%${p}`)
continue
}
// --- regexp [G] ---
if (op == "regexp") {
// IR: ["regexp", dest_slot, pattern_string, flags_string]
pat_label = intern_str(a2)
flg_label = intern_str(a3)
p = fresh()
emit(` %${p} =l call $cell_rt_regexp(l %ctx, l ${pat_label}, l ${flg_label})`)
refresh_fp()
s_write(a1, `%${p}`)
continue
}
// --- Unknown opcode ---
emit(` # unknown: ${op}`)
}
// Emit @disrupt landing pad for arithmetic type-error branches
if (!last_was_term) {
emit(" jmp @disrupt")
}
emit("@disrupt")
emit(` call $cell_rt_disrupt(l %ctx)`)
emit(` ret 15`)
emit("}")
emit("")
}
// ============================================================
// Main: compile all functions then main
// ============================================================
var fi = 0
while (fi < length(ir.functions)) {
compile_fn(ir.functions[fi], fi, false)
fi = fi + 1
}
compile_fn(ir.main, -1, true)
// Assemble: data section first, then function bodies
var result = []
var di = 0
while (di < length(data_out)) {
push(result, data_out[di])
di = di + 1
}
if (length(data_out) > 0) push(result, "")
di = 0
while (di < length(out)) {
push(result, out[di])
di = di + 1
}
return text(result, "\n")
}
return qbe_emit