// 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