// 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' // ============================================================ // QBE IL helper function generation // Generates helper functions that are defined once and called // from each operation site, reducing code duplication. // ============================================================ var emit_helpers = function(qbe) { var h = [] // --- Slot access IL fragments --- var sr = function(name, slot) { return ` %${name}.o =l shl ${slot}, 3 %${name}.p =l add %fp, %${name}.o %${name} =l loadl %${name}.p` } var sw = function(name, fp_var, slot, val) { return ` %${name}.o =l shl ${slot}, 3 %${name}.p =l add ${fp_var}, %${name}.o storel ${val}, %${name}.p` } // --- Allocating tail: refresh fp, write result, return fp --- var alloc_tail = function(result_var) { return ` %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx) jnz %fp2, @ok, @exc @ok ${sw("w", "%fp2", "%dest", result_var)} ret %fp2 @exc ret 0` } // --- Allocating tail without dest write --- var alloc_tail_nw = function() { return ` %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx) jnz %fp2, @ok, @exc @ok ret %fp2 @exc ret 0` } // ============================================================ // Category A: Pure QBE helpers (no C calls) // ============================================================ // move h[] = `export function $__move_ss(l %fp, l %dest, l %src) { @entry ${sr("a", "%src")} ${sw("w", "%fp", "%dest", "%a")} ret }` // int comparisons var int_ops = [ ["eq_int", "ceqw"], ["ne_int", "cnew"], ["lt_int", "csltw"], ["le_int", "cslew"], ["gt_int", "csgtw"], ["ge_int", "csgew"] ] var i = 0 while (i < length(int_ops)) { h[] = `export function $__${int_ops[i][0]}_ss(l %fp, l %dest, l %s1, l %s2) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %ia =l sar %a, 1 %ib =l sar %b, 1 %iaw =w copy %ia %ibw =w copy %ib %cr =w ${int_ops[i][1]} %iaw, %ibw %crext =l extuw %cr %sh =l shl %crext, 5 %r =l or %sh, 3 ${sw("w", "%fp", "%dest", "%r")} ret }` i = i + 1 } // bool comparisons h[] = `export function $__eq_bool_ss(l %fp, l %dest, l %s1, l %s2) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %cr =w ceql %a, %b %crext =l extuw %cr %sh =l shl %crext, 5 %r =l or %sh, 3 ${sw("w", "%fp", "%dest", "%r")} ret }` h[] = `export function $__ne_bool_ss(l %fp, l %dest, l %s1, l %s2) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %cr =w cnel %a, %b %crext =l extuw %cr %sh =l shl %crext, 5 %r =l or %sh, 3 ${sw("w", "%fp", "%dest", "%r")} ret }` // is_identical (same as eq_bool) h[] = `export function $__is_identical_ss(l %fp, l %dest, l %s1, l %s2) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %cr =w ceql %a, %b %crext =l extuw %cr %sh =l shl %crext, 5 %r =l or %sh, 3 ${sw("w", "%fp", "%dest", "%r")} ret }` // is_int: (val & 1) == 0 h[] = `export function $__is_int_ss(l %fp, l %dest, l %src) { @entry ${sr("a", "%src")} %t =l and %a, 1 %cr =w ceql %t, 0 %crext =l extuw %cr %sh =l shl %crext, 5 %r =l or %sh, 3 ${sw("w", "%fp", "%dest", "%r")} ret }` // is_null: (val & 31) == 7 h[] = `export function $__is_null_ss(l %fp, l %dest, l %src) { @entry ${sr("a", "%src")} %t =l and %a, 31 %cr =w ceql %t, 7 %crext =l extuw %cr %sh =l shl %crext, 5 %r =l or %sh, 3 ${sw("w", "%fp", "%dest", "%r")} ret }` // is_bool: (val & 31) == 3 h[] = `export function $__is_bool_ss(l %fp, l %dest, l %src) { @entry ${sr("a", "%src")} %t =l and %a, 31 %cr =w ceql %t, 3 %crext =l extuw %cr %sh =l shl %crext, 5 %r =l or %sh, 3 ${sw("w", "%fp", "%dest", "%r")} ret }` // is_num: (val & 1 == 0) || (val & 7 == 5) h[] = `export function $__is_num_ss(l %fp, l %dest, l %src) { @entry ${sr("a", "%src")} %t1 =l and %a, 1 %ii =w ceql %t1, 0 %t2 =l and %a, 7 %fi =w ceql %t2, 5 %cr =w or %ii, %fi %crext =l extuw %cr %sh =l shl %crext, 5 %r =l or %sh, 3 ${sw("w", "%fp", "%dest", "%r")} ret }` // ============================================================ // Category B: Non-allocating C call helpers // ============================================================ // Type checks via C (no ctx needed except is_proxy) var tc_ops = [ ["is_text", "JS_IsText", false], ["is_array", "JS_IsArray", false], ["is_func", "JS_IsFunction", false], ["is_record", "JS_IsRecord", false], ["is_stone", "JS_IsStone", false], ["is_proxy", "cell_rt_is_proxy", true] ] var tc_name = null var tc_cfn = null var tc_ctx = null i = 0 while (i < length(tc_ops)) { tc_name = tc_ops[i][0] tc_cfn = tc_ops[i][1] tc_ctx = tc_ops[i][2] if (tc_ctx) { h[] = `export function $__${tc_name}_ss(l %ctx, l %fp, l %dest, l %src) { @entry ${sr("a", "%src")} %cr =w call $${tc_cfn}(l %ctx, l %a) %crext =l extuw %cr %sh =l shl %crext, 5 %r =l or %sh, 3 ${sw("w", "%fp", "%dest", "%r")} ret }` } else { h[] = `export function $__${tc_name}_ss(l %fp, l %dest, l %src) { @entry ${sr("a", "%src")} %cr =w call $${tc_cfn}(l %a) %crext =l extuw %cr %sh =l shl %crext, 5 %r =l or %sh, 3 ${sw("w", "%fp", "%dest", "%r")} ret }` } i = i + 1 } // Float comparisons: call qbe_float_cmp(ctx, op_id, a, b) → w, tag var fc_ops = [ ["eq_float", 0], ["ne_float", 1], ["lt_float", 2], ["le_float", 3], ["gt_float", 4], ["ge_float", 5] ] i = 0 while (i < length(fc_ops)) { h[] = `export function $__${fc_ops[i][0]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %cr =w call $qbe_float_cmp(l %ctx, w ${fc_ops[i][1]}, l %a, l %b) %crext =l extuw %cr %sh =l shl %crext, 5 %r =l or %sh, 3 ${sw("w", "%fp", "%dest", "%r")} ret }` i = i + 1 } // Text comparisons: eq/ne via js_string_compare_value, others via cell_rt_* var txcmp_sv = [ ["eq_text", "ceqw", 1], ["ne_text", "cnew", 1] ] i = 0 while (i < length(txcmp_sv)) { h[] = `export function $__${txcmp_sv[i][0]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %scmp =w call $js_string_compare_value(l %ctx, l %a, l %b, w ${txcmp_sv[i][2]}) %cr =w ${txcmp_sv[i][1]} %scmp, 0 %crext =l extuw %cr %sh =l shl %crext, 5 %r =l or %sh, 3 ${sw("w", "%fp", "%dest", "%r")} ret }` i = i + 1 } // lt/le/gt/ge_text via cell_rt_* (return tagged JSValue directly) var txcmp_rt = ["lt_text", "gt_text", "le_text", "ge_text"] i = 0 while (i < length(txcmp_rt)) { h[] = `export function $__${txcmp_rt[i]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %r =l call $cell_rt_${txcmp_rt[i]}(l %ctx, l %a, l %b) ${sw("w", "%fp", "%dest", "%r")} ret }` i = i + 1 } // eq_tol, ne_tol (return tagged JSValue directly) — needs tolerance (3rd value) var tol_ops = ["eq_tol", "ne_tol"] i = 0 while (i < length(tol_ops)) { h[] = `export function $__${tol_ops[i]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2, l %s3) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} ${sr("c", "%s3")} %r =l call $cell_rt_${tol_ops[i]}(l %ctx, l %a, l %b, l %c) ${sw("w", "%fp", "%dest", "%r")} ret }` i = i + 1 } // not: JS_ToBool + negate + tag h[] = `export function $__not_ss(l %ctx, l %fp, l %dest, l %src) { @entry ${sr("a", "%src")} %bval =w call $JS_ToBool(l %ctx, l %a) %neg =w ceqw %bval, 0 %nex =l extuw %neg %sh =l shl %nex, 5 %r =l or %sh, 3 ${sw("w", "%fp", "%dest", "%r")} ret }` // and, or (return tagged JSValue directly) h[] = `export function $__and_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %r =l call $cell_rt_and(l %ctx, l %a, l %b) ${sw("w", "%fp", "%dest", "%r")} ret }` h[] = `export function $__or_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %r =l call $cell_rt_or(l %ctx, l %a, l %b) ${sw("w", "%fp", "%dest", "%r")} ret }` // Bitwise unary: bnot h[] = `export function $__bnot_ss(l %ctx, l %fp, l %dest, l %src) { @entry ${sr("a", "%src")} %r =l call $qbe_bnot(l %ctx, l %a) ${sw("w", "%fp", "%dest", "%r")} ret }` // Bitwise binary ops var bw_ops = [ ["band", "qbe_bitwise_and"], ["bor", "qbe_bitwise_or"], ["bxor", "qbe_bitwise_xor"], ["bshl", "qbe_shift_shl"], ["bshr", "qbe_shift_sar"], ["bushr", "qbe_shift_shr"] ] i = 0 while (i < length(bw_ops)) { h[] = `export function $__${bw_ops[i][0]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %r =l call $${bw_ops[i][1]}(l %ctx, l %a, l %b) ${sw("w", "%fp", "%dest", "%r")} ret }` i = i + 1 } // ============================================================ // Category C: Allocating helpers (return fp or 0) // ============================================================ // Allocating binary ops: read 2 slots, call C, refresh, write dest var ab_ops = [ ["add", "cell_rt_add"], ["sub", "qbe_float_sub"], ["mul", "qbe_float_mul"], ["div", "qbe_float_div"], ["mod", "qbe_float_mod"], ["pow", "qbe_float_pow"], ["concat", "JS_ConcatString"] ] i = 0 while (i < length(ab_ops)) { h[] = `export function l $__${ab_ops[i][0]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %r =l call $${ab_ops[i][1]}(l %ctx, l %a, l %b) ${alloc_tail("%r")} }` i = i + 1 } // Allocating unary: negate h[] = `export function l $__neg_ss(l %ctx, l %fp, l %dest, l %src) { @entry ${sr("a", "%src")} %r =l call $qbe_float_neg(l %ctx, l %a) ${alloc_tail("%r")} }` // Property access: load_field(ctx, fp, dest, obj_slot, name_ptr) h[] = `export function l $__load_field_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %name) { @entry ${sr("a", "%obj_slot")} %r =l call $cell_rt_load_field(l %ctx, l %a, l %name) ${alloc_tail("%r")} }` // load_dynamic(ctx, fp, dest, obj_slot, key_slot) h[] = `export function l $__load_dynamic_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %key_slot) { @entry ${sr("a", "%obj_slot")} ${sr("b", "%key_slot")} %r =l call $cell_rt_load_dynamic(l %ctx, l %a, l %b) ${alloc_tail("%r")} }` // load_index(ctx, fp, dest, arr_slot, idx_slot) h[] = `export function l $__load_index_ss(l %ctx, l %fp, l %dest, l %arr_slot, l %idx_slot) { @entry ${sr("a", "%arr_slot")} ${sr("b", "%idx_slot")} %r =l call $cell_rt_load_index(l %ctx, l %a, l %b) ${alloc_tail("%r")} }` // store_field(ctx, fp, obj_slot, val_slot, name_ptr) — no dest write h[] = `export function l $__store_field_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %name) { @entry ${sr("a", "%obj_slot")} ${sr("b", "%val_slot")} call $cell_rt_store_field(l %ctx, l %b, l %a, l %name) ${alloc_tail_nw()} }` // store_dynamic(ctx, fp, obj_slot, val_slot, key_slot) — no dest write h[] = `export function l $__store_dynamic_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %key_slot) { @entry ${sr("a", "%obj_slot")} ${sr("b", "%val_slot")} ${sr("c", "%key_slot")} call $cell_rt_store_dynamic(l %ctx, l %b, l %a, l %c) ${alloc_tail_nw()} }` // store_index(ctx, fp, obj_slot, val_slot, idx_slot) — no dest write h[] = `export function l $__store_index_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %idx_slot) { @entry ${sr("a", "%obj_slot")} ${sr("b", "%val_slot")} ${sr("c", "%idx_slot")} call $cell_rt_store_index(l %ctx, l %b, l %a, l %c) ${alloc_tail_nw()} }` // frame(ctx, fp, dest, fn_slot, nargs) h[] = `export function l $__frame_ss(l %ctx, l %fp, l %dest, l %fn_slot, l %nargs) { @entry ${sr("a", "%fn_slot")} %r =l call $cell_rt_frame(l %ctx, l %a, l %nargs) ${alloc_tail("%r")} }` // goframe(ctx, fp, dest, fn_slot, nargs) h[] = `export function l $__goframe_ss(l %ctx, l %fp, l %dest, l %fn_slot, l %nargs) { @entry ${sr("a", "%fn_slot")} %r =l call $cell_rt_goframe(l %ctx, l %a, l %nargs) ${alloc_tail("%r")} }` // invoke(ctx, fp, frame_slot, result_slot) — two checks: exc + refresh h[] = `export function l $__invoke_ss(l %ctx, l %fp, l %frame_slot, l %result_slot) { @entry ${sr("a", "%frame_slot")} %r =l call $cell_rt_invoke(l %ctx, l %a) %is_exc =w ceql %r, 15 jnz %is_exc, @exc, @ok1 @ok1 %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx) jnz %fp2, @ok2, @exc @ok2 ${sw("w", "%fp2", "%result_slot", "%r")} ret %fp2 @exc ret 0 }` // function(ctx, fp, dest, fn_idx, arity) h[] = `export function l $__function_ss(l %ctx, l %fp, l %dest, l %fn_idx, l %arity) { @entry %r =l call $cell_rt_make_function(l %ctx, l %fn_idx, l %fp, l %arity) ${alloc_tail("%r")} }` // new_record(ctx, fp, dest) h[] = `export function l $__new_record_ss(l %ctx, l %fp, l %dest) { @entry %r =l call $JS_NewObject(l %ctx) ${alloc_tail("%r")} }` // new_array(ctx, fp, dest) h[] = `export function l $__new_array_ss(l %ctx, l %fp, l %dest) { @entry %r =l call $JS_NewArray(l %ctx) ${alloc_tail("%r")} }` // new_string(ctx, fp, dest, str_ptr) h[] = `export function l $__new_string_ss(l %ctx, l %fp, l %dest, l %str_ptr) { @entry %r =l call $qbe_new_string(l %ctx, l %str_ptr) ${alloc_tail("%r")} }` // new_float64(ctx, fp, dest, val) — val is a double h[] = `export function l $__new_float64_ss(l %ctx, l %fp, l %dest, d %val) { @entry %r =l call $qbe_new_float64(l %ctx, d %val) ${alloc_tail("%r")} }` // get_intrinsic(ctx, fp, dest, name_ptr) h[] = `export function l $__get_intrinsic_ss(l %ctx, l %fp, l %dest, l %name_ptr) { @entry %r =l call $cell_rt_get_intrinsic(l %ctx, l %name_ptr) ${alloc_tail("%r")} }` // push(ctx, fp, arr_slot, val_slot) — write back arr in case GC moved it h[] = `export function l $__push_ss(l %ctx, l %fp, l %arr_slot, l %val_slot) { @entry ${sr("a", "%arr_slot")} ${sr("b", "%val_slot")} %r =l call $cell_rt_push(l %ctx, l %a, l %b) %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx) jnz %fp2, @ok, @exc @ok ${sw("w", "%fp2", "%arr_slot", "%r")} ret %fp2 @exc ret 0 }` // pop(ctx, fp, dest, arr_slot) h[] = `export function l $__pop_ss(l %ctx, l %fp, l %dest, l %arr_slot) { @entry ${sr("a", "%arr_slot")} %r =l call $cell_rt_pop(l %ctx, l %a) ${alloc_tail("%r")} }` // length(ctx, fp, dest, src) h[] = `export function l $__length_ss(l %ctx, l %fp, l %dest, l %src) { @entry ${sr("a", "%src")} %r =l call $JS_CellLength(l %ctx, l %a) ${alloc_tail("%r")} }` // delete_field(ctx, fp, dest, obj_slot, name_ptr) h[] = `export function l $__delete_field_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %name) { @entry ${sr("a", "%obj_slot")} %r =l call $cell_rt_delete_str(l %ctx, l %a, l %name) ${alloc_tail("%r")} }` // delete_dynamic(ctx, fp, dest, obj_slot, key_slot) h[] = `export function l $__delete_dynamic_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %key_slot) { @entry ${sr("a", "%obj_slot")} ${sr("b", "%key_slot")} %r =l call $cell_rt_delete(l %ctx, l %a, l %b) ${alloc_tail("%r")} }` // in(ctx, fp, dest, key_slot, obj_slot) h[] = `export function l $__in_ss(l %ctx, l %fp, l %dest, l %key_slot, l %obj_slot) { @entry ${sr("a", "%key_slot")} ${sr("b", "%obj_slot")} %r =l call $cell_rt_in(l %ctx, l %a, l %b) ${alloc_tail("%r")} }` // regexp(ctx, fp, dest, pat_ptr, flg_ptr) h[] = `export function l $__regexp_ss(l %ctx, l %fp, l %dest, l %pat, l %flg) { @entry %r =l call $cell_rt_regexp(l %ctx, l %pat, l %flg) ${alloc_tail("%r")} }` return h } 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 // ============================================================ // prop_name inlined at each call site — closures have bytecode bugs with // early returns so we extract the property name inline via pn variable // ============================================================ // 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 var tol = null var fn_arity = 0 var arity_tmp = null // 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 needs_exc_ret = false 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 { needs_exc_ret = true emit(` jnz %${exc}, @_exc_ret, @${exc}_ok`) } emit(`@${exc}_ok`) } // Exception check after allocating helper call (helper returns fp or 0) var emit_exc_check = function() { var lbl = fresh() if (has_handler && !in_handler) { emit(` jnz %fp, @${lbl}_ok, @disruption_handler`) } else { needs_exc_ret = true emit(` jnz %fp, @${lbl}_ok, @_exc_ret`) } emit(`@${lbl}_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 pseudo-labels from streamline if (is_text(instr)) { if (starts_with(instr, "_nop_ur_") || starts_with(instr, "_nop_tc_")) 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") { if (is_number(a2)) { if (is_integer(a2)) { s_write(a1, text(a2 * 2)) } else { emit(` %fp =l call $__new_float64_ss(l %ctx, l %fp, l ${text(a1)}, d d_${text(a2)})`) emit_exc_check() } } else if (is_text(a2)) { sl = intern_str(a2) emit(` %fp =l call $__new_string_ss(l %ctx, l %fp, l ${text(a1)}, l ${sl})`) emit_exc_check() } else if (is_object(a2)) { if (a2.make == "intrinsic") { sl = intern_str(a2.name) emit(` %fp =l call $__get_intrinsic_ss(l %ctx, l %fp, l ${text(a1)}, l ${sl})`) emit_exc_check() } 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(` %fp =l call $__new_float64_ss(l %ctx, l %fp, l ${text(a1)}, d d_${text(a2.number)})`) emit_exc_check() } else { s_write(a1, text(qbe.js_null)) } } else if (a2.kind == "text") { sl = intern_str(a2.value) emit(` %fp =l call $__new_string_ss(l %ctx, l %fp, l ${text(a1)}, l ${sl})`) emit_exc_check() } 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") { emit(` call $__move_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } // --- Generic arithmetic (VM dispatches int/float) --- if (op == "add") { emit(` %fp =l call $__add_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() continue } if (op == "subtract") { emit(` %fp =l call $__sub_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() continue } if (op == "multiply") { emit(` %fp =l call $__mul_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() continue } if (op == "divide") { emit(` %fp =l call $__div_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() continue } if (op == "modulo") { emit(` %fp =l call $__mod_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() continue } if (op == "negate") { emit(` %fp =l call $__neg_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) emit_exc_check() continue } if (op == "pow") { emit(` %fp =l call $__pow_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() continue } // --- String concat --- if (op == "concat") { emit(` %fp =l call $__concat_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() continue } // --- Type checks — use qbe.cm macros (no GC, no refresh) --- if (op == "is_int") { emit(` call $__is_int_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_text") { emit(` call $__is_text_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_num") { emit(` call $__is_num_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_bool") { emit(` call $__is_bool_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_null") { emit(` call $__is_null_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_identical") { emit(` call $__is_identical_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "is_array") { emit(` call $__is_array_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_func") { emit(` call $__is_func_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_record") { emit(` call $__is_record_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_stone") { emit(` call $__is_stone_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_proxy") { emit(` call $__is_proxy_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) continue } // --- Comparisons (int path, no GC) --- if (op == "eq_int") { emit(` call $__eq_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ne_int") { emit(` call $__ne_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "lt_int") { emit(` call $__lt_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "gt_int") { emit(` call $__gt_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "le_int") { emit(` call $__le_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ge_int") { emit(` call $__ge_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } // --- Comparisons (float/text/bool) --- if (op == "eq_float") { emit(` call $__eq_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ne_float") { emit(` call $__ne_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "lt_float") { emit(` call $__lt_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "le_float") { emit(` call $__le_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "gt_float") { emit(` call $__gt_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ge_float") { emit(` call $__ge_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "eq_text") { emit(` call $__eq_text_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ne_text") { emit(` call $__ne_text_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "lt_text" || op == "gt_text" || op == "le_text" || op == "ge_text") { emit(` call $__${op}_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "eq_bool") { emit(` call $__eq_bool_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ne_bool") { emit(` call $__ne_bool_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "eq_tol" || op == "ne_tol") { 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") { emit(` call $__not_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "and") { emit(` call $__and_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "or") { emit(` call $__or_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } // --- Bitwise ops — use qbe.cm macros (no GC) --- if (op == "bitnot") { emit(` call $__bnot_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "bitand") { emit(` call $__band_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "bitor") { emit(` call $__bor_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "bitxor") { emit(` call $__bxor_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "shl") { emit(` call $__bshl_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "shr") { emit(` call $__bshr_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ushr") { emit(` call $__bushr_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } // --- Property access — runtime calls [G] --- if (op == "load_field") { 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(` %fp =l call $__load_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) } else { 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") { 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") { 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(` %fp =l call $__load_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) } else { 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") { // IR: ["store_field", obj, val, prop] 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(` %fp =l call $__store_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) } else { 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") { // IR: ["store_index", obj, val, idx] emit(` %fp =l call $__store_index_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() continue } if (op == "store_dynamic") { // IR: ["store_dynamic", obj, val, key] 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(` %fp =l call $__store_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) } else { 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") { // 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") { 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 == "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") { emit(` %fp =l call $__invoke_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) emit_exc_check() continue } if (op == "tail_invoke") { emit(` %fp =l call $__invoke_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) emit_exc_check() continue } if (op == "goframe") { 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") { 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 { needs_exc_ret = true emit(` jnz %${chk}, @_exc_ret, @${chk}_ok`) emit(`@${chk}_ok`) emit(` ret %${p}`) } last_was_term = true continue } // --- Function object creation [G] --- if (op == "function") { fn_arity = 0 if (a2 >= 0 && a2 < length(ir.functions)) { fn_arity = ir.functions[a2].nr_args } emit(` %fp =l call $__function_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(fn_arity)})`) emit_exc_check() continue } // --- Record/Array creation [G] --- if (op == "record") { emit(` %fp =l call $__new_record_ss(l %ctx, l %fp, l ${text(a1)})`) emit_exc_check() continue } if (op == "array") { 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") { emit(` %fp =l call $__push_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) emit_exc_check() continue } if (op == "pop") { 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") { emit(` %fp =l call $__length_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) emit_exc_check() 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") { 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(` %fp =l call $__delete_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) } else { 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") { // 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") { // 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}, l ${flg_label})`) emit_exc_check() 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`) // Shared exception return (for functions without disruption handler) if (needs_exc_ret) { emit("@_exc_ret") emit(" ret 15") } emit("}") emit("") } // ============================================================ // Main: compile all functions then main // ============================================================ var fn_bodies = [] var fi = 0 while (fi < length(ir.functions)) { out = [] compile_fn(ir.functions[fi], fi, false) fn_bodies[] = text(out, "\n") fi = fi + 1 } out = [] compile_fn(ir.main, -1, true) fn_bodies[] = text(out, "\n") return { data: text(data_out, "\n"), functions: fn_bodies, helpers: emit_helpers(qbe) } } return qbe_emit