// 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 = []
  var lb = "{"

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

  // ============================================================
  // Category A: Pure QBE helpers (no C calls)
  // ============================================================

  // is_record: pointer + header type check (OBJ_RECORD=3), chase forwards
  h[] = `export function $__is_record_ss(l %fp, l %dest, l %src) ${lb}
@entry
${sr("a", "%src")}
  %ptag =l and %a, 7
  %is_ptr =w ceql %ptag, 1
  jnz %is_ptr, @ptr, @no
@ptr
  %ptr =l and %a, -8
  %hdr =l loadl %ptr
@chase
  %ht =l and %hdr, 7
  %is_fwd =w ceql %ht, 7
  jnz %is_fwd, @follow, @chk
@follow
  %ptr =l shr %hdr, 3
  %hdr =l loadl %ptr
  jmp @chase
@chk
  %cr =w ceql %ht, 3
  jmp @pack
@no
  %cr =w copy 0
@pack
  %crext =l extuw %cr
  %sh =l shl %crext, 5
  %r =l or %sh, 3
${sw("w", "%fp", "%dest", "%r")}
  ret
}`

  // is_array: inline pointer+header check (OBJ_ARRAY=0), chase forwards
  h[] = `export function $__is_array_ss(l %fp, l %dest, l %src) ${lb}
@entry
${sr("a", "%src")}
  %ptag =l and %a, 7
  %is_ptr =w ceql %ptag, 1
  jnz %is_ptr, @ptr, @no
@ptr
  %ptr =l and %a, -8
  %hdr =l loadl %ptr
@chase
  %ht =l and %hdr, 7
  %is_fwd =w ceql %ht, 7
  jnz %is_fwd, @follow, @chk
@follow
  %ptr =l shr %hdr, 3
  %hdr =l loadl %ptr
  jmp @chase
@chk
  %cr =w ceql %ht, 0
  jmp @pack
@no
  %cr =w copy 0
@pack
  %crext =l extuw %cr
  %sh =l shl %crext, 5
  %r =l or %sh, 3
${sw("w", "%fp", "%dest", "%r")}
  ret
}`

  // is_func: inline pointer+header check (OBJ_FUNCTION=4), chase forwards
  h[] = `export function $__is_func_ss(l %fp, l %dest, l %src) ${lb}
@entry
${sr("a", "%src")}
  %ptag =l and %a, 7
  %is_ptr =w ceql %ptag, 1
  jnz %is_ptr, @ptr, @no
@ptr
  %ptr =l and %a, -8
  %hdr =l loadl %ptr
@chase
  %ht =l and %hdr, 7
  %is_fwd =w ceql %ht, 7
  jnz %is_fwd, @follow, @chk
@follow
  %ptr =l shr %hdr, 3
  %hdr =l loadl %ptr
  jmp @chase
@chk
  %cr =w ceql %ht, 4
  jmp @pack
@no
  %cr =w copy 0
@pack
  %crext =l extuw %cr
  %sh =l shl %crext, 5
  %r =l or %sh, 3
${sw("w", "%fp", "%dest", "%r")}
  ret
}`

  // eq_tol, ne_tol (return tagged JSValue directly) — needs tolerance (3rd value)
  var tol_ops = ["eq_tol", "ne_tol"]
  var 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) ${lb}
@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: inline truthiness (no JS_ToBool call)
  h[] = `export function $__not_ss(l %ctx, l %fp, l %dest, l %src) ${lb}
@entry
${sr("a", "%src")}
  %t5 =l and %a, 31
  %is_bool =w ceql %t5, 3
  jnz %is_bool, @bool, @chk_null
@bool
  %truthy =w cnel %a, 3
  jmp @truthy_done
@chk_null
  %is_null =w ceql %t5, 7
  jnz %is_null, @falsey, @chk_int
@chk_int
  %t1 =l and %a, 1
  %is_int =w ceql %t1, 0
  jnz %is_int, @int_path, @chk_imm_text
@int_path
  %truthy =w cnel %a, 0
  jmp @truthy_done
@chk_imm_text
  %is_imm_text =w ceql %t5, 11
  jnz %is_imm_text, @imm_text, @chk_ptr
@imm_text
  %truthy =w cnel %a, 11
  jmp @truthy_done
@chk_ptr
  %ptag =l and %a, 7
  %is_ptr =w ceql %ptag, 1
  jnz %is_ptr, @ptr_path, @chk_sfloat
@chk_sfloat
  %is_sfloat =w ceql %ptag, 5
  jnz %is_sfloat, @sfloat_path, @other_imm
@sfloat_path
  %sexp =l shr %a, 55
  %sexp =l and %sexp, 255
  %truthy =w cnel %sexp, 0
  jmp @truthy_done
@other_imm
  %truthy =w copy 1
  jmp @truthy_done
@ptr_path
  %ptr =l and %a, -8
  %hdr =l loadl %ptr
@chase
  %ht =l and %hdr, 7
  %is_fwd =w ceql %ht, 7
  jnz %is_fwd, @follow, @chk_text_ptr
@follow
  %ptr =l shr %hdr, 3
  %hdr =l loadl %ptr
  jmp @chase
@chk_text_ptr
  %is_text_ptr =w ceql %ht, 2
  jnz %is_text_ptr, @text_ptr, @ptr_truthy
@text_ptr
  %len =l shr %hdr, 8
  %truthy =w cnel %len, 0
  jmp @truthy_done
@ptr_truthy
  %truthy =w copy 1
  jmp @truthy_done
@falsey
  %truthy =w copy 0
@truthy_done
  %neg =w ceqw %truthy, 0
  %nex =l extuw %neg
  %sh =l shl %nex, 5
  %r =l or %sh, 3
${sw("w", "%fp", "%dest", "%r")}
  ret
}`

  // Bitwise unary/binary ops: mirror MACH mcode op behavior via JS_ToInt32 coercion.
  h[] = `export function $__bnot_ss(l %ctx, l %fp, l %dest, l %src) ${lb}
@entry
${sr("a", "%src")}
  %r =l call $qbe_bnot(l %ctx, l %a)
${sw("w", "%fp", "%dest", "%r")}
  ret
}`

  h[] = `export function $__band_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb}
@entry
${sr("a", "%s1")}
${sr("b", "%s2")}
  %r =l call $qbe_bitwise_and(l %ctx, l %a, l %b)
${sw("w", "%fp", "%dest", "%r")}
  ret
}`

  h[] = `export function $__bor_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb}
@entry
${sr("a", "%s1")}
${sr("b", "%s2")}
  %r =l call $qbe_bitwise_or(l %ctx, l %a, l %b)
${sw("w", "%fp", "%dest", "%r")}
  ret
}`

  h[] = `export function $__bxor_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb}
@entry
${sr("a", "%s1")}
${sr("b", "%s2")}
  %r =l call $qbe_bitwise_xor(l %ctx, l %a, l %b)
${sw("w", "%fp", "%dest", "%r")}
  ret
}`

  // ============================================================
  // Category C: Allocating helpers (return fp or 0)
  // ============================================================

  // concat allocates; runtime helper mirrors MACH self-assign fast path
  h[] = `export function l $__concat_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb}
@entry
${sr("a", "%s1")}
${sr("b", "%s2")}
  %same =w ceql %dest, %s1
  %r =l call $cell_rt_concat(l %ctx, l %a, l %b, w %same)
  %is_exc =w ceql %r, 15
  jnz %is_exc, @exc, @refresh
@refresh
  %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx)
  jnz %fp2, @ok, @exc
@ok
${sw("w", "%fp2", "%dest", "%r")}
  ret %fp2
@exc
  ret 0
}`

  // access_lit(ctx, fp, dest, lit_idx)
  h[] = `export function l $__access_lit_ss(l %ctx, l %fp, l %dest, l %lit_idx) ${lb}
@entry
  %r =l call $cell_rt_access_lit(l %ctx, l %lit_idx)
  %is_exc =w ceql %r, 15
  jnz %is_exc, @exc, @ok
@ok
${sw("w", "%fp", "%dest", "%r")}
  ret %fp
@exc
  ret 0
}`

  // Property access: load_field(ctx, fp, dest, obj_slot, lit_idx)
  h[] = `export function l $__load_field_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %lit_idx) ${lb}
@entry
${sr("a", "%obj_slot")}
  %r =l call $cell_rt_load_field_lit(l %ctx, l %a, l %lit_idx)
  %is_exc =w ceql %r, 15
  jnz %is_exc, @exc, @ok
@ok
${sw("w", "%fp", "%dest", "%r")}
  ret %fp
@exc
  ret 0
}`

  // 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) ${lb}
@entry
${sr("a", "%obj_slot")}
${sr("b", "%key_slot")}
  %ptag =l and %a, 7
  %is_ptr =w ceql %ptag, 1
  jnz %is_ptr, @arr_ptr, @fallback
@arr_ptr
  %arr_ptr =l and %a, -8
  %arr_hdr =l loadl %arr_ptr
@arr_chase
  %arr_ty =l and %arr_hdr, 7
  %arr_is_fwd =w ceql %arr_ty, 7
  jnz %arr_is_fwd, @arr_follow, @arr_chk
@arr_follow
  %arr_ptr =l shr %arr_hdr, 3
  %arr_hdr =l loadl %arr_ptr
  jmp @arr_chase
@arr_chk
  %arr_is_array =w ceql %arr_ty, 0
  jnz %arr_is_array, @arr_index, @fallback
@arr_index
  %idx_tag =l and %b, 1
  %idx_is_int =w ceql %idx_tag, 0
  jnz %idx_is_int, @idx_ok, @ret_null
@idx_ok
  %idx_l =l sar %b, 1
  %idx_w =w copy %idx_l
  %idx_neg =w csltw %idx_w, 0
  jnz %idx_neg, @ret_null, @arr_len
@arr_len
  %len_p =l add %arr_ptr, 8
  %len_l =l loadl %len_p
  %len_w =w copy %len_l
  %in =w csltw %idx_w, %len_w
  jnz %in, @load, @ret_null
@load
  %idx_off_l =l extsw %idx_w
  %idx_off_l =l shl %idx_off_l, 3
  %vals_p =l add %arr_ptr, 16
  %elem_p =l add %vals_p, %idx_off_l
  %r =l loadl %elem_p
${sw("w", "%fp", "%dest", "%r")}
  ret %fp
@ret_null
${sw("w", "%fp", "%dest", text(qbe.js_null))}
  ret %fp
@fallback
  %r =l call $cell_rt_load_dynamic(l %ctx, l %a, l %b)
  %is_exc =w ceql %r, 15
  jnz %is_exc, @exc, @ok
@ok
${sw("w", "%fp", "%dest", "%r")}
  ret %fp
@exc
  ret 0
}`

  // 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) ${lb}
@entry
${sr("a", "%arr_slot")}
${sr("b", "%idx_slot")}
  %idx_tag =l and %b, 1
  %idx_is_int =w ceql %idx_tag, 0
  jnz %idx_is_int, @idx_ok, @ret_null
@idx_ok
  %idx_l =l sar %b, 1
  %idx_w =w copy %idx_l
  %idx_neg =w csltw %idx_w, 0
  jnz %idx_neg, @ret_null, @arr_init
@arr_init
  %ptag =l and %a, 7
  %is_ptr =w ceql %ptag, 1
  jnz %is_ptr, @arr_ptr_ok, @ret_null
@arr_ptr_ok
  %arr_ptr =l and %a, -8
  %arr_hdr =l loadl %arr_ptr
@arr_chase
  %arr_ty =l and %arr_hdr, 7
  %arr_is_fwd =w ceql %arr_ty, 7
  jnz %arr_is_fwd, @arr_follow, @arr_chk
@arr_follow
  %arr_ptr =l shr %arr_hdr, 3
  %arr_hdr =l loadl %arr_ptr
  jmp @arr_chase
@arr_chk
  %arr_is_array =w ceql %arr_ty, 0
  jnz %arr_is_array, @arr_len, @ret_null
@arr_len
  %len_p =l add %arr_ptr, 8
  %len_l =l loadl %len_p
  %len_w =w copy %len_l
  %in =w csltw %idx_w, %len_w
  jnz %in, @load, @ret_null
@load
  %idx_off_l =l extsw %idx_w
  %idx_off_l =l shl %idx_off_l, 3
  %vals_p =l add %arr_ptr, 16
  %elem_p =l add %vals_p, %idx_off_l
  %r =l loadl %elem_p
${sw("w", "%fp", "%dest", "%r")}
  ret %fp
@ret_null
${sw("w", "%fp", "%dest", text(qbe.js_null))}
  ret %fp
}`

  // store_field(ctx, fp, obj_slot, val_slot, lit_idx) — no dest write
  h[] = `export function l $__store_field_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %lit_idx) ${lb}
@entry
${sr("a", "%obj_slot")}
${sr("b", "%val_slot")}
  %ok =w call $cell_rt_store_field_lit(l %ctx, l %b, l %a, l %lit_idx)
  jnz %ok, @ok, @exc
@ok
  ret %fp
@exc
  ret 0
}`

  // 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) ${lb}
@entry
${sr("a", "%obj_slot")}
${sr("b", "%val_slot")}
${sr("c", "%key_slot")}
  %ptag =l and %a, 7
  %is_ptr =w ceql %ptag, 1
  jnz %is_ptr, @arr_ptr, @fallback
@arr_ptr
  %arr_ptr =l and %a, -8
  %arr_hdr =l loadl %arr_ptr
@arr_chase
  %arr_ty =l and %arr_hdr, 7
  %arr_is_fwd =w ceql %arr_ty, 7
  jnz %arr_is_fwd, @arr_follow, @arr_chk
@arr_follow
  %arr_ptr =l shr %arr_hdr, 3
  %arr_hdr =l loadl %arr_ptr
  jmp @arr_chase
@arr_chk
  %arr_is_array =w ceql %arr_ty, 0
  jnz %arr_is_array, @arr_key_chk, @fallback
@arr_key_chk
  %idx_tag =l and %c, 1
  %idx_is_int =w ceql %idx_tag, 0
  jnz %idx_is_int, @arr_store, @bad
@arr_store
  %fp2 =l call $__store_index_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %key_slot)
  ret %fp2
@fallback
  %ok =w call $cell_rt_store_dynamic(l %ctx, l %b, l %a, l %c)
  jnz %ok, @ok, @exc
@ok
  ret %fp
@bad
  call $cell_rt_disrupt(l %ctx)
@exc
  ret 0
}`

  // 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) ${lb}
@entry
${sr("a", "%obj_slot")}
${sr("b", "%val_slot")}
${sr("c", "%idx_slot")}
  %idx_tag =l and %c, 1
  %idx_is_int =w ceql %idx_tag, 0
  jnz %idx_is_int, @idx_ok, @bad
@idx_ok
  %idx_l =l sar %c, 1
  %idx_w =w copy %idx_l
  %idx_neg =w csltw %idx_w, 0
  jnz %idx_neg, @bad, @arr_init
@arr_init
  %ptag =l and %a, 7
  %is_ptr =w ceql %ptag, 1
  jnz %is_ptr, @arr_ptr_ok, @bad
@arr_ptr_ok
  %arr_val =l copy %a
  %arr_ptr =l and %arr_val, -8
  %arr_hdr =l loadl %arr_ptr
@arr_chase
  %arr_ty =l and %arr_hdr, 7
  %arr_is_fwd =w ceql %arr_ty, 7
  jnz %arr_is_fwd, @arr_follow, @arr_chk
@arr_follow
  %arr_ptr =l shr %arr_hdr, 3
  %arr_hdr =l loadl %arr_ptr
  jmp @arr_chase
@arr_chk
  %arr_is_array =w ceql %arr_ty, 0
  jnz %arr_is_array, @stone_chk, @bad
@stone_chk
  %arr_stone =l and %arr_hdr, 8
  %arr_is_stone =w cnel %arr_stone, 0
  jnz %arr_is_stone, @bad, @lens
@lens
  %len_p =l add %arr_ptr, 8
  %len_l =l loadl %len_p
  %len_w =w copy %len_l
  %cap_l =l shr %arr_hdr, 8
  %cap_w =w copy %cap_l
  %need_grow =w csgew %idx_w, %cap_w
  jnz %need_grow, @grow_init, @set_item
@grow_init
  %new_cap_w =w copy %cap_w
  %cap_zero =w ceqw %new_cap_w, 0
  jnz %cap_zero, @grow_cap0, @grow_check
@grow_cap0
  %new_cap_w =w copy 2
  jmp @grow_check
@grow_loop
  %new_cap_w =w shl %new_cap_w, 1
  %new_cap_neg =w csltw %new_cap_w, 0
  jnz %new_cap_neg, @bad, @grow_check
@grow_check
  %need_more =w cslew %new_cap_w, %idx_w
  jnz %need_more, @grow_loop, @grow_alloc
@grow_alloc
  %new_arr =l call $JS_NewArrayCap(l %ctx, w %new_cap_w)
  %new_exc =w ceql %new_arr, 15
  jnz %new_exc, @exc, @grow_refresh
@grow_refresh
  %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx)
  jnz %fp2, @grow_reload, @exc
@grow_reload
  %fp =l copy %fp2
${sr("ga", "%obj_slot")}
${sr("gb", "%val_slot")}
${sr("gc", "%idx_slot")}
  %a =l copy %ga
  %b =l copy %gb
  %c =l copy %gc
  %arr_val =l copy %a
  %arr_ptr =l and %arr_val, -8
  %arr_hdr =l loadl %arr_ptr
@grow_arr_chase
  %arr_ty =l and %arr_hdr, 7
  %arr_is_fwd =w ceql %arr_ty, 7
  jnz %arr_is_fwd, @grow_arr_follow, @grow_arr_ok
@grow_arr_follow
  %arr_ptr =l shr %arr_hdr, 3
  %arr_hdr =l loadl %arr_ptr
  jmp @grow_arr_chase
@grow_arr_ok
  %grow_arr_is_array =w ceql %arr_ty, 0
  jnz %grow_arr_is_array, @grow_arr_type_ok, @bad
@grow_arr_type_ok
  %old_cap_l =l shr %arr_hdr, 8
  %old_len_p =l add %arr_ptr, 8
  %old_len_l =l loadl %old_len_p
  %old_len_w =w copy %old_len_l
  %new_ptr =l and %new_arr, -8
  %old_vals =l add %arr_ptr, 16
  %new_vals =l add %new_ptr, 16
  %i_w =w copy 0
@copy_cond
  %copy_more =w csltw %i_w, %old_len_w
  jnz %copy_more, @copy_body, @copy_done
@copy_body
  %i_l =l extsw %i_w
  %i_off =l shl %i_l, 3
  %old_ep =l add %old_vals, %i_off
  %new_ep =l add %new_vals, %i_off
  %ev =l loadl %old_ep
  %ev_is_self =w ceql %ev, %arr_val
  jnz %ev_is_self, @copy_self, @copy_store
@copy_self
  storel %new_arr, %new_ep
  jmp @copy_next
@copy_store
  storel %ev, %new_ep
@copy_next
  %i_w =w add %i_w, 1
  jmp @copy_cond
@copy_done
  storel %old_len_l, %old_len_p
  %old_size =l shl %old_cap_l, 3
  %old_size =l add %old_size, 16
  %fwd =l shl %new_ptr, 3
  %fwd =l or %fwd, 7
  storel %fwd, %arr_ptr
  %arr_size_p =l add %arr_ptr, 8
  storel %old_size, %arr_size_p
  %obj_slot_o =l shl %obj_slot, 3
  %obj_slot_p =l add %fp2, %obj_slot_o
  storel %new_arr, %obj_slot_p
  %arr_val =l copy %new_arr
  %arr_ptr =l copy %new_ptr
  %arr_hdr =l loadl %arr_ptr
  %len_p =l add %arr_ptr, 8
  storel %old_len_l, %len_p
  %len_l =l copy %old_len_l
  %len_w =w copy %old_len_w
@set_item
  %need_len =w csgew %idx_w, %len_w
  jnz %need_len, @bump_len, @store_item
@bump_len
  %next_len_w =w add %idx_w, 1
  %next_len_l =l extsw %next_len_w
  storel %next_len_l, %len_p
@store_item
  %idx2_l =l extsw %idx_w
  %idx2_off =l shl %idx2_l, 3
  %vals_p =l add %arr_ptr, 16
  %item_p =l add %vals_p, %idx2_off
  storel %b, %item_p
  ret %fp
@bad
  call $cell_rt_disrupt(l %ctx)
@exc
  ret 0
}`

  // frame(ctx, fp, dest, fn_slot, nargs)
  h[] = `export function l $__frame_ss(l %ctx, l %fp, l %dest, l %fn_slot, l %nargs) ${lb}
@entry
${sr("a", "%fn_slot")}
  %r =l call $cell_rt_frame(l %ctx, l %a, l %nargs)
${alloc_tail("%r")}
}`

  // apply(ctx, fp, dest, fn_slot, arg_slot)
  h[] = `export function l $__apply_ss(l %ctx, l %fp, l %dest, l %fn_slot, l %arg_slot) ${lb}
@entry
${sr("a", "%fn_slot")}
${sr("b", "%arg_slot")}
  %r =l call $cell_rt_apply(l %ctx, l %a, l %b)
${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) ${lb}
@entry
${sr("a", "%fn_slot")}
  %r =l call $cell_rt_goframe(l %ctx, l %a, l %nargs)
${alloc_tail("%r")}
}`

  // function(ctx, fp, dest, fn_idx, arity, nr_slots)
  h[] = `export function l $__function_ss(l %ctx, l %fp, l %dest, l %fn_idx, l %arity, l %nr_slots) ${lb}
@entry
  %r =l call $cell_rt_make_function(l %ctx, l %fn_idx, l %fp, l %arity, l %nr_slots)
${alloc_tail("%r")}
}`

  // new_record(ctx, fp, dest)
  h[] = `export function l $__new_record_ss(l %ctx, l %fp, l %dest) ${lb}
@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) ${lb}
@entry
  %r =l call $JS_NewArray(l %ctx)
${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) ${lb}
@entry
  %r =l call $qbe_new_float64(l %ctx, d %val)
${sw("w", "%fp", "%dest", "%r")}
  ret %fp
}`

  // access_env(ctx, fp, dest, lit_idx) — read env from frame->function->env_record
  h[] = `export function l $__access_env_ss(l %ctx, l %fp, l %dest, l %lit_idx) ${lb}
@entry
  %fn_p =l sub %fp, 24
  %fn =l loadl %fn_p
  %fn_ptr =l and %fn, -8
  %env_p =l add %fn_ptr, 32
  %env =l loadl %env_p
  %r =l call $cell_rt_access_env(l %ctx, l %env, l %lit_idx)
${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) ${lb}
@entry
${sr("a", "%arr_slot")}
${sr("b", "%val_slot")}
  %ptag =l and %a, 7
  %is_ptr =w ceql %ptag, 1
  jnz %is_ptr, @arr_init, @bad
@arr_init
  %arr_val =l copy %a
  %arr_ptr =l and %arr_val, -8
  %arr_hdr =l loadl %arr_ptr
@arr_chase
  %arr_ty =l and %arr_hdr, 7
  %arr_is_fwd =w ceql %arr_ty, 7
  jnz %arr_is_fwd, @arr_follow, @arr_ok
@arr_follow
  %arr_ptr =l shr %arr_hdr, 3
  %arr_hdr =l loadl %arr_ptr
  jmp @arr_chase
@arr_ok
  %arr_is_array =w ceql %arr_ty, 0
  jnz %arr_is_array, @arr_type_ok, @bad
@arr_type_ok
  %arr_stone =l and %arr_hdr, 8
  %arr_is_stone =w cnel %arr_stone, 0
  jnz %arr_is_stone, @bad, @lens
@lens
  %len_p =l add %arr_ptr, 8
  %len_l =l loadl %len_p
  %len_w =w copy %len_l
  %cap_l =l shr %arr_hdr, 8
  %cap_w =w copy %cap_l
  %need_grow =w csgew %len_w, %cap_w
  jnz %need_grow, @grow, @store_push
@grow
  %new_cap_w =w copy %cap_w
  %cap_zero =w ceqw %new_cap_w, 0
  jnz %cap_zero, @grow_cap0, @grow_dbl
@grow_cap0
  %new_cap_w =w copy 2
  jmp @grow_alloc
@grow_dbl
  %new_cap_w =w shl %new_cap_w, 1
  %new_cap_neg =w csltw %new_cap_w, 0
  jnz %new_cap_neg, @bad, @grow_alloc
@grow_alloc
  %new_arr =l call $JS_NewArrayCap(l %ctx, w %new_cap_w)
  %new_exc =w ceql %new_arr, 15
  jnz %new_exc, @exc, @grow_refresh
@grow_refresh
  %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx)
  jnz %fp2, @grow_reload, @exc
@grow_reload
  %fp =l copy %fp2
${sr("ga", "%arr_slot")}
${sr("gb", "%val_slot")}
  %a =l copy %ga
  %b =l copy %gb
  %arr_val =l copy %a
  %arr_ptr =l and %arr_val, -8
  %arr_hdr =l loadl %arr_ptr
@grow_arr_chase
  %arr_ty =l and %arr_hdr, 7
  %arr_is_fwd =w ceql %arr_ty, 7
  jnz %arr_is_fwd, @grow_arr_follow, @grow_arr_ok
@grow_arr_follow
  %arr_ptr =l shr %arr_hdr, 3
  %arr_hdr =l loadl %arr_ptr
  jmp @grow_arr_chase
@grow_arr_ok
  %grow_arr_is_array =w ceql %arr_ty, 0
  jnz %grow_arr_is_array, @grow_arr_type_ok, @bad
@grow_arr_type_ok
  %old_cap_l =l shr %arr_hdr, 8
  %old_len_p =l add %arr_ptr, 8
  %old_len_l =l loadl %old_len_p
  %old_len_w =w copy %old_len_l
  %new_ptr =l and %new_arr, -8
  %old_vals =l add %arr_ptr, 16
  %new_vals =l add %new_ptr, 16
  %i_w =w copy 0
@copy_cond
  %copy_more =w csltw %i_w, %old_len_w
  jnz %copy_more, @copy_body, @copy_done
@copy_body
  %i_l =l extsw %i_w
  %i_off =l shl %i_l, 3
  %old_ep =l add %old_vals, %i_off
  %new_ep =l add %new_vals, %i_off
  %ev =l loadl %old_ep
  %ev_is_self =w ceql %ev, %arr_val
  jnz %ev_is_self, @copy_self, @copy_store
@copy_self
  storel %new_arr, %new_ep
  jmp @copy_next
@copy_store
  storel %ev, %new_ep
@copy_next
  %i_w =w add %i_w, 1
  jmp @copy_cond
@copy_done
  storel %old_len_l, %old_len_p
  %old_size =l shl %old_cap_l, 3
  %old_size =l add %old_size, 16
  %fwd =l shl %new_ptr, 3
  %fwd =l or %fwd, 7
  storel %fwd, %arr_ptr
  %arr_size_p =l add %arr_ptr, 8
  storel %old_size, %arr_size_p
  %arr_slot_o =l shl %arr_slot, 3
  %arr_slot_p =l add %fp2, %arr_slot_o
  storel %new_arr, %arr_slot_p
  %arr_val =l copy %new_arr
  %arr_ptr =l copy %new_ptr
  %arr_hdr =l loadl %arr_ptr
  %len_p =l add %arr_ptr, 8
  storel %old_len_l, %len_p
  %len_l =l copy %old_len_l
  %len_w =w copy %old_len_w
@store_push
  %idx_l =l extsw %len_w
  %idx_off =l shl %idx_l, 3
  %vals_p =l add %arr_ptr, 16
  %item_p =l add %vals_p, %idx_off
  storel %b, %item_p
  %next_len_w =w add %len_w, 1
  %next_len_l =l extsw %next_len_w
  storel %next_len_l, %len_p
  ret %fp
@bad
  call $cell_rt_disrupt(l %ctx)
@exc
  ret 0
}`

  // pop(ctx, fp, dest, arr_slot)
  h[] = `export function l $__pop_ss(l %ctx, l %fp, l %dest, l %arr_slot) ${lb}
@entry
${sr("a", "%arr_slot")}
  %ptag =l and %a, 7
  %is_ptr =w ceql %ptag, 1
  jnz %is_ptr, @arr_init, @bad
@arr_init
  %arr_ptr =l and %a, -8
  %arr_hdr =l loadl %arr_ptr
@arr_chase
  %arr_ty =l and %arr_hdr, 7
  %arr_is_fwd =w ceql %arr_ty, 7
  jnz %arr_is_fwd, @arr_follow, @arr_ok
@arr_follow
  %arr_ptr =l shr %arr_hdr, 3
  %arr_hdr =l loadl %arr_ptr
  jmp @arr_chase
@arr_ok
  %arr_is_array =w ceql %arr_ty, 0
  jnz %arr_is_array, @arr_type_ok, @bad
@arr_type_ok
  %arr_stone =l and %arr_hdr, 8
  %arr_is_stone =w cnel %arr_stone, 0
  jnz %arr_is_stone, @bad, @len_chk
@len_chk
  %len_p =l add %arr_ptr, 8
  %len_l =l loadl %len_p
  %len_w =w copy %len_l
  %empty =w ceqw %len_w, 0
  jnz %empty, @ret_null, @do_pop
@do_pop
  %last_w =w sub %len_w, 1
  %last_l =l extsw %last_w
  %last_off =l shl %last_l, 3
  %vals_p =l add %arr_ptr, 16
  %item_p =l add %vals_p, %last_off
  %r =l loadl %item_p
  storel ${text(qbe.js_null)}, %item_p
  %new_len_l =l extsw %last_w
  storel %new_len_l, %len_p
${sw("w", "%fp", "%dest", "%r")}
  ret %fp
@ret_null
${sw("w", "%fp", "%dest", text(qbe.js_null))}
  ret %fp
@bad
  call $cell_rt_disrupt(l %ctx)
  ret 0
}`

  // length(ctx, fp, dest, src)
  h[] = `export function l $__length_ss(l %ctx, l %fp, l %dest, l %src) ${lb}
@entry
${sr("a", "%src")}
  %r =l call $JS_CellLength(l %ctx, l %a)
${alloc_tail("%r")}
}`

  // delete_field(ctx, fp, dest, obj_slot, lit_idx)
  h[] = `export function l $__delete_field_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %lit_idx) ${lb}
@entry
${sr("a", "%obj_slot")}
  %r =l call $cell_rt_delete_lit(l %ctx, l %a, l %lit_idx)
${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) ${lb}
@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) ${lb}
@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) ${lb}
@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_entries = []
  var str_id = 0
  var uid = 0
  var lit_data = null
  var si = 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)
    var entry = { label: label, idx: length(str_entries) }
    push(str_entries, entry)
    str_table[val] = entry
    return entry
  }

  // ============================================================
  // 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 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 v = null
    var lhs = null
    var rhs = null
    var pat_label = null
    var flg_label = null
    var in_handler = false
    var fn_arity = 0
    var fn_nr_slots = 0
    var invoke_count = 0
    var si = 0
    var scan = null
    var scan_op = null
    var label_pos = {}
    var instr_idx = 0
    var has_invokes = false
    var seg_counter = 0
    var ri = 0
    var resume_val = 0
    var j_lbl = null
    var j_idx = null
    var jt_lbl = null
    var jt_idx = null
    var jt_backedge = false
    var jf_lbl = null
    var jf_idx = null
    var jf_backedge = false
    var jnn_lbl = null
    var jnn_idx = null
    var jnn_backedge = false
    var wt_lbl = null
    var wt_idx = null
    var wt_backedge = false
    var wf_lbl = null
    var wf_idx = null
    var wf_backedge = false
    var jn_lbl = null
    var jn_idx = null
    var jn_backedge = false
    var je_lbl = null
    var je_idx = null
    var je_backedge = false
    var truthy = null
    var lhs_d = null
    var rhs_d = null
    var peek1 = null
    var peek2 = null
    var peek3 = null
    var peek4 = null
    var peek5 = null
    var text_frame_slot = 0
    var text_this_slot = 0
    var text_arg_slot = 0
    var text_dest_slot = 0
    var cmp_id = 0
    var depth = 0
    var d = 0

    // Pre-scan: count invoke/tail_invoke points to assign segment numbers.
    // Must skip dead code (instructions after terminators) the same way
    // the main emission loop does, otherwise we create jump table entries
    // for segments that never get emitted.
    var scan_dead = false
    si = 0
    while (si < length(instrs)) {
      scan = instrs[si]
      si = si + 1
      if (is_text(scan)) {
        // Skip optimizer nop pseudo-labels entirely.
        if (starts_with(scan, "_nop_")) continue
        label_pos[sanitize(scan)] = si - 1
        // Real labels reset dead code state.
        scan_dead = false
        continue
      }
      if (scan_dead) continue
      if (!is_array(scan)) continue
      scan_op = scan[0]

      // Keep invoke segment counting consistent with main-loop peephole:
      // inline text intrinsic call sequence does not emit an invoke.
      if (scan_op == "access" && is_object(scan[2]) && scan[2].make == "intrinsic" && scan[2].name == "text") {
        if (si + 4 < length(instrs)) {
          peek1 = instrs[si]
          peek2 = instrs[si + 1]
          peek3 = instrs[si + 2]
          peek4 = instrs[si + 3]
          peek5 = instrs[si + 4]
          if (is_array(peek1) && peek1[0] == "frame" && peek1[2] == scan[1] && peek1[3] == 1 &&
              is_array(peek2) && peek2[0] == "null" &&
              is_array(peek3) && peek3[0] == "setarg" &&
              is_array(peek4) && peek4[0] == "setarg" &&
              is_array(peek5) && peek5[0] == "invoke") {
            text_frame_slot = peek1[1]
            text_this_slot = peek2[1]
            if (peek3[1] == text_frame_slot && peek3[2] == 0 && peek3[3] == text_this_slot &&
                peek4[1] == text_frame_slot && peek4[2] == 1 &&
                peek5[1] == text_frame_slot && peek5[2] == text_this_slot) {
              si = si + 5
              continue
            }
          }
        }
      }

      if (scan_op == "invoke" || scan_op == "tail_invoke") {
        invoke_count = invoke_count + 1
      }
      // Track terminators — same set as in the main loop
      if (scan_op == "return" || scan_op == "jump" || scan_op == "goinvoke" || scan_op == "disrupt") {
        scan_dead = true
      }
    }
    has_invokes = invoke_count > 0

    // Function signature: (ctx, frame_ptr) → JSValue
    emit(`export function l $${name}(l %ctx, l %fp) {`)
    emit("@entry")

    // Resume dispatch: if this function has invoke points, read the segment
    // number from frame->address and jump to the right resume point.
    // frame->address is at fp - 8 (last field before slots[]).
    if (has_invokes) {
      emit("  %addr_ptr =l sub %fp, 8")
      emit("  %addr_raw =l loadl %addr_ptr")
      // address is stored as JS_NewInt32 tagged value: n << 1
      emit("  %addr =l sar %addr_raw, 1")
      emit("  %resume =l shr %addr, 16")
      emit(`  jnz %resume, @_rcheck1, @_seg0`)
      ri = 1
      while (ri <= invoke_count) {
        emit(`@_rcheck${text(ri)}`)
        emit(`  %_rc${text(ri)} =w ceql %resume, ${text(ri)}`)
        if (ri < invoke_count) {
          emit(`  jnz %_rc${text(ri)}, @_seg${text(ri)}, @_rcheck${text(ri + 1)}`)
        } else {
          // Last check — if no match, fall through to seg0
          emit(`  jnz %_rc${text(ri)}, @_seg${text(ri)}, @_seg0`)
        }
        ri = ri + 1
      }
      emit("@_seg0")
    }

    // 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`)
    }

    // Poll pause/interrupt state on taken backward jumps.
    var emit_backedge_branch = function(target_label) {
      emit(`  jmp @${target_label}`)
    }

    // Inline JS_ToBool equivalent for hot branch paths.
    // Returns a `%name` holding w 0/1 truthiness.
    var emit_truthy_w = function(val) {
      var tp = fresh()
      emit(`  %${tp}_t5 =l and ${val}, 31`)
      emit(`  %${tp}_is_bool =w ceql %${tp}_t5, 3`)
      emit(`  jnz %${tp}_is_bool, @${tp}_bool, @${tp}_chk_null`)
      emit(`@${tp}_bool`)
      emit(`  %${tp}_truthy =w cnel ${val}, 3`)
      emit(`  jmp @${tp}_done`)
      emit(`@${tp}_chk_null`)
      emit(`  %${tp}_is_null =w ceql %${tp}_t5, 7`)
      emit(`  jnz %${tp}_is_null, @${tp}_falsey, @${tp}_chk_int`)
      emit(`@${tp}_chk_int`)
      emit(`  %${tp}_t1 =l and ${val}, 1`)
      emit(`  %${tp}_is_int =w ceql %${tp}_t1, 0`)
      emit(`  jnz %${tp}_is_int, @${tp}_int_path, @${tp}_chk_imm_text`)
      emit(`@${tp}_int_path`)
      emit(`  %${tp}_truthy =w cnel ${val}, 0`)
      emit(`  jmp @${tp}_done`)
      emit(`@${tp}_chk_imm_text`)
      emit(`  %${tp}_is_imm_text =w ceql %${tp}_t5, 11`)
      emit(`  jnz %${tp}_is_imm_text, @${tp}_imm_text, @${tp}_chk_ptr`)
      emit(`@${tp}_imm_text`)
      emit(`  %${tp}_truthy =w cnel ${val}, 11`)
      emit(`  jmp @${tp}_done`)
      emit(`@${tp}_chk_ptr`)
      emit(`  %${tp}_ptag =l and ${val}, 7`)
      emit(`  %${tp}_is_ptr =w ceql %${tp}_ptag, 1`)
      emit(`  jnz %${tp}_is_ptr, @${tp}_ptr_path, @${tp}_chk_sfloat`)
      emit(`@${tp}_chk_sfloat`)
      emit(`  %${tp}_is_sfloat =w ceql %${tp}_ptag, 5`)
      emit(`  jnz %${tp}_is_sfloat, @${tp}_sfloat_path, @${tp}_other_imm`)
      emit(`@${tp}_sfloat_path`)
      emit(`  %${tp}_sexp =l shr ${val}, 55`)
      emit(`  %${tp}_sexp =l and %${tp}_sexp, 255`)
      emit(`  %${tp}_truthy =w cnel %${tp}_sexp, 0`)
      emit(`  jmp @${tp}_done`)
      emit(`@${tp}_other_imm`)
      emit(`  %${tp}_truthy =w copy 1`)
      emit(`  jmp @${tp}_done`)
      emit(`@${tp}_ptr_path`)
      emit(`  %${tp}_ptr =l and ${val}, -8`)
      emit(`  %${tp}_hdr =l loadl %${tp}_ptr`)
      emit(`@${tp}_chase`)
      emit(`  %${tp}_ht =l and %${tp}_hdr, 7`)
      emit(`  %${tp}_is_fwd =w ceql %${tp}_ht, 7`)
      emit(`  jnz %${tp}_is_fwd, @${tp}_follow, @${tp}_chk_text_ptr`)
      emit(`@${tp}_follow`)
      emit(`  %${tp}_ptr =l shr %${tp}_hdr, 3`)
      emit(`  %${tp}_hdr =l loadl %${tp}_ptr`)
      emit(`  jmp @${tp}_chase`)
      emit(`@${tp}_chk_text_ptr`)
      emit(`  %${tp}_is_text_ptr =w ceql %${tp}_ht, 2`)
      emit(`  jnz %${tp}_is_text_ptr, @${tp}_text_ptr, @${tp}_ptr_truthy`)
      emit(`@${tp}_text_ptr`)
      emit(`  %${tp}_len =l shr %${tp}_hdr, 8`)
      emit(`  %${tp}_truthy =w cnel %${tp}_len, 0`)
      emit(`  jmp @${tp}_done`)
      emit(`@${tp}_ptr_truthy`)
      emit(`  %${tp}_truthy =w copy 1`)
      emit(`  jmp @${tp}_done`)
      emit(`@${tp}_falsey`)
      emit(`  %${tp}_truthy =w copy 0`)
      emit(`@${tp}_done`)
      return `%${tp}_truthy`
    }

    // Returns w 0/1 for JS text (immediate or heap), following forwards.
    var emit_is_text_w = function(val) {
      var tp = fresh()
      emit(`  %${tp}_imm =l and ${val}, 31`)
      emit(`  %${tp}_is_imm =w ceql %${tp}_imm, 11`)
      emit(`  jnz %${tp}_is_imm, @${tp}_yes, @${tp}_chk_ptr`)
      emit(`@${tp}_chk_ptr`)
      emit(`  %${tp}_ptag =l and ${val}, 7`)
      emit(`  %${tp}_is_ptr =w ceql %${tp}_ptag, 1`)
      emit(`  jnz %${tp}_is_ptr, @${tp}_ptr, @${tp}_no`)
      emit(`@${tp}_ptr`)
      emit(`  %${tp}_ptr =l and ${val}, -8`)
      emit(`  %${tp}_hdr =l loadl %${tp}_ptr`)
      emit(`@${tp}_chase`)
      emit(`  %${tp}_ht =l and %${tp}_hdr, 7`)
      emit(`  %${tp}_is_fwd =w ceql %${tp}_ht, 7`)
      emit(`  jnz %${tp}_is_fwd, @${tp}_follow, @${tp}_chk`)
      emit(`@${tp}_follow`)
      emit(`  %${tp}_ptr =l shr %${tp}_hdr, 3`)
      emit(`  %${tp}_hdr =l loadl %${tp}_ptr`)
      emit(`  jmp @${tp}_chase`)
      emit(`@${tp}_chk`)
      emit(`  %${tp}_is_text =w ceql %${tp}_ht, 2`)
      emit(`  jmp @${tp}_done`)
      emit(`@${tp}_yes`)
      emit(`  %${tp}_is_text =w copy 1`)
      emit(`  jmp @${tp}_done`)
      emit(`@${tp}_no`)
      emit(`  %${tp}_is_text =w copy 0`)
      emit(`@${tp}_done`)
      return `%${tp}_is_text`
    }

    // Returns w 0/1 for JS numbers (int or short-float).
    var emit_is_num_w = function(val) {
      var np = fresh()
      emit(`  %${np}_t1 =l and ${val}, 1`)
      emit(`  %${np}_ii =w ceql %${np}_t1, 0`)
      emit(`  %${np}_t2 =l and ${val}, 7`)
      emit(`  %${np}_fi =w ceql %${np}_t2, 5`)
      emit(`  %${np}_is_num =w or %${np}_ii, %${np}_fi`)
      return `%${np}_is_num`
    }

    // Pack w 0/1 into tagged JS bool (JS_FALSE/JS_TRUE).
    var emit_pack_bool_js = function(wv) {
      var bp = fresh()
      emit(`  %${bp}_ext =l extuw ${wv}`)
      emit(`  %${bp}_sh =l shl %${bp}_ext, 5`)
      emit(`  %${bp}_js =l or %${bp}_sh, 3`)
      return `%${bp}_js`
    }

    // Convert a known numeric JSValue (int or short-float) to QBE double.
    // Type checks happen earlier in mcode/streamline.
    var emit_num_to_double = function(val) {
      var np = fresh()
      emit(`  %${np}_tag =l and ${val}, 1`)
      emit(`  %${np}_is_int =w ceql %${np}_tag, 0`)
      emit(`  jnz %${np}_is_int, @${np}_int, @${np}_float`)
      emit(`@${np}_int`)
      emit(`  %${np}_isl =l sar ${val}, 1`)
      emit(`  %${np}_iw =w copy %${np}_isl`)
      emit(`  %${np}_d =d swtof %${np}_iw`)
      emit(`  jmp @${np}_done`)
      emit(`@${np}_float`)
      emit(`  %${np}_sexp =l shr ${val}, 55`)
      emit(`  %${np}_sexp =l and %${np}_sexp, 255`)
      emit(`  %${np}_is_zero =w ceql %${np}_sexp, 0`)
      emit(`  jnz %${np}_is_zero, @${np}_fzero, @${np}_fdecode`)
      emit(`@${np}_fzero`)
      emit(`  %${np}_d =d copy d_0.0`)
      emit(`  jmp @${np}_done`)
      emit(`@${np}_fdecode`)
      emit(`  %${np}_sign =l shr ${val}, 63`)
      emit(`  %${np}_mant =l shr ${val}, 3`)
      emit(`  %${np}_mant =l and %${np}_mant, 4503599627370495`)
      emit(`  %${np}_dexp =l sub %${np}_sexp, 127`)
      emit(`  %${np}_dexp =l add %${np}_dexp, 1023`)
      emit(`  %${np}_s63 =l shl %${np}_sign, 63`)
      emit(`  %${np}_e52 =l shl %${np}_dexp, 52`)
      emit(`  %${np}_bits =l or %${np}_s63, %${np}_e52`)
      emit(`  %${np}_bits =l or %${np}_bits, %${np}_mant`)
      emit(`  %${np}_d =d cast %${np}_bits`)
      emit(`@${np}_done`)
      return `%${np}_d`
    }

    // Convert a known numeric JSValue (int or short-float) to int32 in w.
    var emit_num_to_int32_w = function(val) {
      var np = fresh()
      emit(`  %${np}_tag =l and ${val}, 1`)
      emit(`  %${np}_is_int =w ceql %${np}_tag, 0`)
      emit(`  jnz %${np}_is_int, @${np}_int, @${np}_float`)
      emit(`@${np}_int`)
      emit(`  %${np}_isl =l sar ${val}, 1`)
      emit(`  %${np}_iw =w copy %${np}_isl`)
      emit(`  jmp @${np}_done`)
      emit(`@${np}_float`)
      emit(`  %${np}_fd =d copy ${emit_num_to_double(val)}`)
      emit(`  %${np}_iw =w dtosi %${np}_fd`)
      emit(`@${np}_done`)
      return `%${np}_iw`
    }

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

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

      // 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 (instr_idx + 5 < length(instrs)) {
          peek1 = instrs[instr_idx + 1]
          peek2 = instrs[instr_idx + 2]
          peek3 = instrs[instr_idx + 3]
          peek4 = instrs[instr_idx + 4]
          peek5 = instrs[instr_idx + 5]
          if (is_array(peek1) && peek1[0] == "frame" && peek1[2] == a1 && peek1[3] == 1 &&
              is_array(peek2) && peek2[0] == "null" &&
              is_array(peek3) && peek3[0] == "setarg" &&
              is_array(peek4) && peek4[0] == "setarg" &&
              is_array(peek5) && peek5[0] == "invoke") {
            text_frame_slot = peek1[1]
            text_this_slot = peek2[1]
            if (peek3[1] == text_frame_slot && peek3[2] == 0 && peek3[3] == text_this_slot &&
                peek4[1] == text_frame_slot && peek4[2] == 1 &&
                peek5[1] == text_frame_slot && peek5[2] == text_this_slot) {
              text_arg_slot = peek4[3]
              text_dest_slot = peek5[2]
              v = s_read(text_arg_slot)
              p = fresh()
              emit(`  %${p}_r =l call $JS_CellText(l %ctx, l ${v})`)
              refresh_fp()
              s_write(text_dest_slot, `%${p}_r`)
              i = instr_idx + 6
              continue
            }
          }
        }
      }

      // --- 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 $__access_lit_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(sl.idx)})`)
          emit_exc_check()
        } else if (is_object(a2)) {
          if (a2.make == "intrinsic") {
            sl = intern_str(a2.name)
            emit(`  %fp =l call $__access_env_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(sl.idx)})`)
            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 $__access_lit_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(sl.idx)})`)
            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") {
        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}_a_tag =l and ${lhs}, 1`)
        emit(`  %${p}_b_tag =l and ${rhs}, 1`)
        emit(`  %${p}_a_int =w ceql %${p}_a_tag, 0`)
        emit(`  %${p}_b_int =w ceql %${p}_b_tag, 0`)
        emit(`  %${p}_both_int =w and %${p}_a_int, %${p}_b_int`)
        emit(`  jnz %${p}_both_int, @${p}_int, @${p}_slow`)
        emit(`@${p}_int`)
        emit(`  %${p}_ai =l sar ${lhs}, 1`)
        emit(`  %${p}_bi =l sar ${rhs}, 1`)
        emit(`  %${p}_sum =l add %${p}_ai, %${p}_bi`)
        emit(`  %${p}_sumw =w copy %${p}_sum`)
        emit(`  %${p}_sumext =l extsw %${p}_sumw`)
        emit(`  %${p}_sum_ok =w ceql %${p}_sumext, %${p}_sum`)
        emit(`  jnz %${p}_sum_ok, @${p}_int_store, @${p}_slow`)
        emit(`@${p}_int_store`)
        emit(`  %${p}_tag =l shl %${p}_sum, 1`)
        s_write(a1, `%${p}_tag`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_slow`)
        lhs_d = emit_num_to_double(lhs)
        rhs_d = emit_num_to_double(rhs)
        emit(`  %${p}_rd =d add ${lhs_d}, ${rhs_d}`)
        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") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        emit(`  %${p}_a_tag =l and ${lhs}, 1`)
        emit(`  %${p}_b_tag =l and ${rhs}, 1`)
        emit(`  %${p}_a_int =w ceql %${p}_a_tag, 0`)
        emit(`  %${p}_b_int =w ceql %${p}_b_tag, 0`)
        emit(`  %${p}_both_int =w and %${p}_a_int, %${p}_b_int`)
        emit(`  jnz %${p}_both_int, @${p}_int, @${p}_slow`)
        emit(`@${p}_int`)
        emit(`  %${p}_ai =l sar ${lhs}, 1`)
        emit(`  %${p}_bi =l sar ${rhs}, 1`)
        emit(`  %${p}_diff =l sub %${p}_ai, %${p}_bi`)
        emit(`  %${p}_diffw =w copy %${p}_diff`)
        emit(`  %${p}_diffext =l extsw %${p}_diffw`)
        emit(`  %${p}_diff_ok =w ceql %${p}_diffext, %${p}_diff`)
        emit(`  jnz %${p}_diff_ok, @${p}_int_store, @${p}_slow`)
        emit(`@${p}_int_store`)
        emit(`  %${p}_tag =l shl %${p}_diff, 1`)
        s_write(a1, `%${p}_tag`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_slow`)
        lhs_d = emit_num_to_double(lhs)
        rhs_d = emit_num_to_double(rhs)
        emit(`  %${p}_rd =d sub ${lhs_d}, ${rhs_d}`)
        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") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        emit(`  %${p}_a_tag =l and ${lhs}, 1`)
        emit(`  %${p}_b_tag =l and ${rhs}, 1`)
        emit(`  %${p}_a_int =w ceql %${p}_a_tag, 0`)
        emit(`  %${p}_b_int =w ceql %${p}_b_tag, 0`)
        emit(`  %${p}_both_int =w and %${p}_a_int, %${p}_b_int`)
        emit(`  jnz %${p}_both_int, @${p}_int, @${p}_slow`)
        emit(`@${p}_int`)
        emit(`  %${p}_ai =l sar ${lhs}, 1`)
        emit(`  %${p}_bi =l sar ${rhs}, 1`)
        emit(`  %${p}_prod =l mul %${p}_ai, %${p}_bi`)
        emit(`  %${p}_prodw =w copy %${p}_prod`)
        emit(`  %${p}_prodext =l extsw %${p}_prodw`)
        emit(`  %${p}_prod_ok =w ceql %${p}_prodext, %${p}_prod`)
        emit(`  jnz %${p}_prod_ok, @${p}_int_store, @${p}_slow`)
        emit(`@${p}_int_store`)
        emit(`  %${p}_tag =l shl %${p}_prod, 1`)
        s_write(a1, `%${p}_tag`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_slow`)
        lhs_d = emit_num_to_double(lhs)
        rhs_d = emit_num_to_double(rhs)
        emit(`  %${p}_rd =d mul ${lhs_d}, ${rhs_d}`)
        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") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        lhs_d = emit_num_to_double(lhs)
        rhs_d = emit_num_to_double(rhs)
        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") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        lhs_d = emit_num_to_double(lhs)
        rhs_d = emit_num_to_double(rhs)
        emit(`  %${p}_lhs_nan =w cned ${lhs_d}, ${lhs_d}`)
        emit(`  %${p}_rhs_nan =w cned ${rhs_d}, ${rhs_d}`)
        emit(`  %${p}_has_nan =w or %${p}_lhs_nan, %${p}_rhs_nan`)
        emit(`  jnz %${p}_has_nan, @${p}_bad, @${p}_chk0`)
        emit(`@${p}_chk0`)
        emit(`  %${p}_rhs0 =w ceqd ${rhs_d}, d_0.0`)
        emit(`  jnz %${p}_rhs0, @${p}_bad, @${p}_calc`)
        emit(`@${p}_calc`)
        emit(`  %${p}_q =d div ${lhs_d}, ${rhs_d}`)
        emit(`  %${p}_qf =d call $floor(d %${p}_q)`)
        emit(`  %${p}_m =d mul ${rhs_d}, %${p}_qf`)
        emit(`  %${p}_rd =d sub ${lhs_d}, %${p}_m`)
        emit(`  %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
        s_write(a1, `%${p}_r`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_bad`)
        s_write(a1, text(qbe.js_null))
        emit(`@${p}_done`)
        continue
      }
      if (op == "remainder") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        lhs_d = emit_num_to_double(lhs)
        rhs_d = emit_num_to_double(rhs)
        emit(`  %${p}_rhs0 =w ceqd ${rhs_d}, d_0.0`)
        emit(`  jnz %${p}_rhs0, @${p}_bad, @${p}_calc`)
        emit(`@${p}_calc`)
        emit(`  %${p}_q =d div ${lhs_d}, ${rhs_d}`)
        emit(`  %${p}_qt =d call $trunc(d %${p}_q)`)
        emit(`  %${p}_m =d mul ${rhs_d}, %${p}_qt`)
        emit(`  %${p}_rd =d sub ${lhs_d}, %${p}_m`)
        emit(`  %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
        s_write(a1, `%${p}_r`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_bad`)
        s_write(a1, text(qbe.js_null))
        emit(`@${p}_done`)
        continue
      }
      if (op == "max" || op == "min") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        lhs_d = emit_num_to_double(lhs)
        rhs_d = emit_num_to_double(rhs)
        if (op == "max") {
          emit(`  %${p}_take_l =w cgtd ${lhs_d}, ${rhs_d}`)
        } else {
          emit(`  %${p}_take_l =w cltd ${lhs_d}, ${rhs_d}`)
        }
        emit(`  jnz %${p}_take_l, @${p}_lhs, @${p}_rhs`)
        emit(`@${p}_lhs`)
        emit(`  %${p}_rd =d copy ${lhs_d}`)
        emit(`  jmp @${p}_done_math`)
        emit(`@${p}_rhs`)
        emit(`  %${p}_rd =d copy ${rhs_d}`)
        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") {
        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") {
        lhs = s_read(a2)
        p = fresh()
        lhs_d = emit_num_to_double(lhs)
        emit(`  %${p}_lt0 =w cltd ${lhs_d}, d_0.0`)
        emit(`  jnz %${p}_lt0, @${p}_neg, @${p}_chk_pos`)
        emit(`@${p}_chk_pos`)
        emit(`  %${p}_gt0 =w cgtd ${lhs_d}, d_0.0`)
        emit(`  jnz %${p}_gt0, @${p}_pos, @${p}_zero`)
        emit(`@${p}_neg`)
        s_write(a1, text(-2))
        emit(`  jmp @${p}_done`)
        emit(`@${p}_pos`)
        s_write(a1, text(2))
        emit(`  jmp @${p}_done`)
        emit(`@${p}_zero`)
        s_write(a1, text(0))
        emit(`@${p}_done`)
        continue
      }
      if (op == "fraction") {
        lhs = s_read(a2)
        p = fresh()
        lhs_d = emit_num_to_double(lhs)
        emit(`  %${p}_ti =d call $trunc(d ${lhs_d})`)
        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") {
        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") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        lhs_d = emit_num_to_double(lhs)
        emit(`  %${p}_lhs_num =w copy ${emit_is_num_w(lhs)}`)
        emit(`  jnz %${p}_lhs_num, @${p}_place, @${p}_bad`)
        emit(`@${p}_place`)
        emit(`  %${p}_t1 =l and ${rhs}, 1`)
        emit(`  %${p}_is_int =w ceql %${p}_t1, 0`)
        emit(`  jnz %${p}_is_int, @${p}_pi_int, @${p}_pi_not_int`)
        emit(`@${p}_pi_int`)
        emit(`  %${p}_pil =l sar ${rhs}, 1`)
        emit(`  %${p}_piw =w copy %${p}_pil`)
        emit(`  jmp @${p}_pi_done`)
        emit(`@${p}_pi_not_int`)
        emit(`  %${p}_t5 =l and ${rhs}, 31`)
        emit(`  %${p}_is_null =w ceql %${p}_t5, 7`)
        emit(`  jnz %${p}_is_null, @${p}_pi_zero, @${p}_pi_chk_bool`)
        emit(`@${p}_pi_zero`)
        emit(`  %${p}_piw =w copy 0`)
        emit(`  jmp @${p}_pi_done`)
        emit(`@${p}_pi_chk_bool`)
        emit(`  %${p}_is_bool =w ceql %${p}_t5, 3`)
        emit(`  jnz %${p}_is_bool, @${p}_pi_bool, @${p}_pi_chk_float`)
        emit(`@${p}_pi_bool`)
        emit(`  %${p}_bl =l shr ${rhs}, 5`)
        emit(`  %${p}_bw =w copy %${p}_bl`)
        emit(`  %${p}_piw =w and %${p}_bw, 1`)
        emit(`  jmp @${p}_pi_done`)
        emit(`@${p}_pi_chk_float`)
        emit(`  %${p}_t3 =l and ${rhs}, 7`)
        emit(`  %${p}_is_float =w ceql %${p}_t3, 5`)
        emit(`  jnz %${p}_is_float, @${p}_pi_float, @${p}_bad`)
        emit(`@${p}_pi_float`)
        rhs_d = emit_num_to_double(rhs)
        emit(`  %${p}_piw =w dtosi ${rhs_d}`)
        emit(`@${p}_pi_done`)
        emit(`  %${p}_is_zero =w ceqw %${p}_piw, 0`)
        emit(`  jnz %${p}_is_zero, @${p}_direct, @${p}_scaled`)
        emit(`@${p}_direct`)
        if (op == "floor") {
          emit(`  %${p}_rd =d call $floor(d ${lhs_d})`)
        } else if (op == "ceiling") {
          emit(`  %${p}_rd =d call $ceil(d ${lhs_d})`)
        } else if (op == "round") {
          emit(`  %${p}_rd =d call $round(d ${lhs_d})`)
        } else {
          emit(`  %${p}_rd =d call $trunc(d ${lhs_d})`)
        }
        emit(`  jmp @${p}_store`)
        emit(`@${p}_scaled`)
        emit(`  %${p}_pl =l extsw %${p}_piw`)
        emit(`  %${p}_pd =d sltof %${p}_pl`)
        emit(`  %${p}_negpd =d neg %${p}_pd`)
        emit(`  %${p}_mult =d call $pow(d d_10.0, d %${p}_negpd)`)
        emit(`  %${p}_sd =d mul ${lhs_d}, %${p}_mult`)
        if (op == "floor") {
          emit(`  %${p}_sr =d call $floor(d %${p}_sd)`)
        } else if (op == "ceiling") {
          emit(`  %${p}_sr =d call $ceil(d %${p}_sd)`)
        } else if (op == "round") {
          emit(`  %${p}_sr =d call $round(d %${p}_sd)`)
        } else {
          emit(`  %${p}_sr =d call $trunc(d %${p}_sd)`)
        }
        emit(`  %${p}_rd =d div %${p}_sr, %${p}_mult`)
        emit(`  jmp @${p}_store`)
        emit(`@${p}_bad`)
        s_write(a1, text(qbe.js_null))
        emit(`  jmp @${p}_done`)
        emit(`@${p}_store`)
        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") {
        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") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        lhs_d = emit_num_to_double(lhs)
        rhs_d = emit_num_to_double(rhs)
        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") {
        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") {
        v = s_read(a1)
        p = fresh()
        emit(`  %${p}_ptag =l and ${v}, 7`)
        emit(`  %${p}_is_ptr =w ceql %${p}_ptag, 1`)
        emit(`  jnz %${p}_is_ptr, @${p}_ptr, @${p}_done`)
        emit(`@${p}_ptr`)
        emit(`  %${p}_ptr =l and ${v}, -8`)
        emit(`  %${p}_hdr =l loadl %${p}_ptr`)
        emit(`  %${p}_ht =l and %${p}_hdr, 7`)
        emit(`  %${p}_is_text =w ceql %${p}_ht, 2`)
        emit(`  jnz %${p}_is_text, @${p}_stone_chk, @${p}_done`)
        emit(`@${p}_stone_chk`)
        emit(`  %${p}_s =l and %${p}_hdr, 8`)
        emit(`  %${p}_is_stone =w cnel %${p}_s, 0`)
        emit(`  jnz %${p}_is_stone, @${p}_done, @${p}_set`)
        emit(`@${p}_set`)
        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") {
        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") {
        v = s_read(a2)
        s_write(a1, emit_pack_bool_js(emit_is_text_w(v)))
        continue
      }
      if (op == "is_num") {
        v = s_read(a2)
        s_write(a1, emit_pack_bool_js(emit_is_num_w(v)))
        continue
      }
      if (op == "is_bool") {
        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") {
        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") {
        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") {
        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") {
        v = s_read(a2)
        p = fresh()
        emit(`  %${p}_ptag =l and ${v}, 7`)
        emit(`  %${p}_is_ptr =w ceql %${p}_ptag, 1`)
        emit(`  jnz %${p}_is_ptr, @${p}_ptr, @${p}_yes`)
        emit(`@${p}_ptr`)
        emit(`  %${p}_ptr =l and ${v}, -8`)
        emit(`  %${p}_hdr =l loadl %${p}_ptr`)
        emit(`@${p}_chase`)
        emit(`  %${p}_ht =l and %${p}_hdr, 7`)
        emit(`  %${p}_is_fwd =w ceql %${p}_ht, 7`)
        emit(`  jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`)
        emit(`@${p}_follow`)
        emit(`  %${p}_ptr =l shr %${p}_hdr, 3`)
        emit(`  %${p}_hdr =l loadl %${p}_ptr`)
        emit(`  jmp @${p}_chase`)
        emit(`@${p}_chk`)
        emit(`  %${p}_s =l and %${p}_hdr, 8`)
        emit(`  %${p}_w =w cnel %${p}_s, 0`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_yes`)
        emit(`  %${p}_w =w copy 1`)
        emit(`@${p}_done`)
        s_write(a1, emit_pack_bool_js(`%${p}_w`))
        continue
      }
      if (op == "is_proxy") {
        v = s_read(a2)
        p = fresh()
        emit(`  %${p}_ptag =l and ${v}, 7`)
        emit(`  %${p}_is_ptr =w ceql %${p}_ptag, 1`)
        emit(`  jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`)
        emit(`@${p}_ptr`)
        emit(`  %${p}_ptr =l and ${v}, -8`)
        emit(`  %${p}_hdr =l loadl %${p}_ptr`)
        emit(`@${p}_chase`)
        emit(`  %${p}_ht =l and %${p}_hdr, 7`)
        emit(`  %${p}_is_fwd =w ceql %${p}_ht, 7`)
        emit(`  jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`)
        emit(`@${p}_follow`)
        emit(`  %${p}_ptr =l shr %${p}_hdr, 3`)
        emit(`  %${p}_hdr =l loadl %${p}_ptr`)
        emit(`  jmp @${p}_chase`)
        emit(`@${p}_chk`)
        emit(`  %${p}_is_fn =w ceql %${p}_ht, 4`)
        emit(`  jnz %${p}_is_fn, @${p}_len_chk, @${p}_no`)
        emit(`@${p}_len_chk`)
        emit(`  %${p}_len_p =l add %${p}_ptr, 16`)
        emit(`  %${p}_len_q =l loadl %${p}_len_p`)
        emit(`  %${p}_len =l and %${p}_len_q, 65535`)
        emit(`  %${p}_w =w ceql %${p}_len, 2`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_no`)
        emit(`  %${p}_w =w copy 0`)
        emit(`@${p}_done`)
        s_write(a1, emit_pack_bool_js(`%${p}_w`))
        continue
      }
      if (op == "is_blob") {
        v = s_read(a2)
        p = fresh()
        emit(`  %${p}_ptag =l and ${v}, 7`)
        emit(`  %${p}_is_ptr =w ceql %${p}_ptag, 1`)
        emit(`  jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`)
        emit(`@${p}_ptr`)
        emit(`  %${p}_ptr =l and ${v}, -8`)
        emit(`  %${p}_hdr =l loadl %${p}_ptr`)
        emit(`@${p}_chase`)
        emit(`  %${p}_ht =l and %${p}_hdr, 7`)
        emit(`  %${p}_is_fwd =w ceql %${p}_ht, 7`)
        emit(`  jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`)
        emit(`@${p}_follow`)
        emit(`  %${p}_ptr =l shr %${p}_hdr, 3`)
        emit(`  %${p}_hdr =l loadl %${p}_ptr`)
        emit(`  jmp @${p}_chase`)
        emit(`@${p}_chk`)
        emit(`  %${p}_w =w ceql %${p}_ht, 1`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_no`)
        emit(`  %${p}_w =w copy 0`)
        emit(`@${p}_done`)
        s_write(a1, emit_pack_bool_js(`%${p}_w`))
        continue
      }
      if (op == "is_data") {
        v = s_read(a2)
        p = fresh()
        emit(`  %${p}_ptag =l and ${v}, 7`)
        emit(`  %${p}_is_ptr =w ceql %${p}_ptag, 1`)
        emit(`  jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`)
        emit(`@${p}_ptr`)
        emit(`  %${p}_ptr =l and ${v}, -8`)
        emit(`  %${p}_hdr =l loadl %${p}_ptr`)
        emit(`@${p}_chase`)
        emit(`  %${p}_ht =l and %${p}_hdr, 7`)
        emit(`  %${p}_is_fwd =w ceql %${p}_ht, 7`)
        emit(`  jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`)
        emit(`@${p}_follow`)
        emit(`  %${p}_ptr =l shr %${p}_hdr, 3`)
        emit(`  %${p}_hdr =l loadl %${p}_ptr`)
        emit(`  jmp @${p}_chase`)
        emit(`@${p}_chk`)
        emit(`  %${p}_is_arr =w ceql %${p}_ht, 0`)
        emit(`  %${p}_is_fn =w ceql %${p}_ht, 4`)
        emit(`  %${p}_is_blob =w ceql %${p}_ht, 1`)
        emit(`  %${p}_bad =w or %${p}_is_arr, %${p}_is_fn`)
        emit(`  %${p}_bad =w or %${p}_bad, %${p}_is_blob`)
        emit(`  %${p}_w =w ceqw %${p}_bad, 0`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_no`)
        emit(`  %${p}_w =w copy 0`)
        emit(`@${p}_done`)
        s_write(a1, emit_pack_bool_js(`%${p}_w`))
        continue
      }
      if (op == "is_fit") {
        v = s_read(a2)
        p = fresh()
        emit(`  %${p}_tag =l and ${v}, 1`)
        emit(`  %${p}_is_int =w ceql %${p}_tag, 0`)
        emit(`  jnz %${p}_is_int, @${p}_yes, @${p}_sfloat_chk`)
        emit(`@${p}_sfloat_chk`)
        emit(`  %${p}_ptag =l and ${v}, 7`)
        emit(`  %${p}_is_sfloat =w ceql %${p}_ptag, 5`)
        emit(`  jnz %${p}_is_sfloat, @${p}_sfloat, @${p}_no`)
        emit(`@${p}_sfloat`)
        lhs_d = emit_num_to_double(v)
        emit(`  %${p}_ti =d call $trunc(d ${lhs_d})`)
        emit(`  %${p}_eqi =w ceqd ${lhs_d}, %${p}_ti`)
        emit(`  %${p}_ad =d call $fabs(d ${lhs_d})`)
        emit(`  %${p}_ltlim =w cltd %${p}_ad, d_9007199254740992.0`)
        emit(`  %${p}_eqlim =w ceqd %${p}_ad, d_9007199254740992.0`)
        emit(`  %${p}_lim =w or %${p}_ltlim, %${p}_eqlim`)
        emit(`  %${p}_w =w and %${p}_eqi, %${p}_lim`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_yes`)
        emit(`  %${p}_w =w copy 1`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_no`)
        emit(`  %${p}_w =w copy 0`)
        emit(`@${p}_done`)
        s_write(a1, emit_pack_bool_js(`%${p}_w`))
        continue
      }
      if (op == "is_char") {
        v = s_read(a2)
        p = fresh()
        emit(`  %${p}_imm =l and ${v}, 31`)
        emit(`  %${p}_is_imm =w ceql %${p}_imm, 11`)
        emit(`  jnz %${p}_is_imm, @${p}_imm_path, @${p}_ptr_chk`)
        emit(`@${p}_imm_path`)
        emit(`  %${p}_ilen =l shr ${v}, 5`)
        emit(`  %${p}_ilen =l and %${p}_ilen, 7`)
        emit(`  %${p}_w =w ceql %${p}_ilen, 1`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_ptr_chk`)
        emit(`  %${p}_ptag =l and ${v}, 7`)
        emit(`  %${p}_is_ptr =w ceql %${p}_ptag, 1`)
        emit(`  jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`)
        emit(`@${p}_ptr`)
        emit(`  %${p}_ptr =l and ${v}, -8`)
        emit(`  %${p}_hdr =l loadl %${p}_ptr`)
        emit(`@${p}_chase`)
        emit(`  %${p}_ht =l and %${p}_hdr, 7`)
        emit(`  %${p}_is_fwd =w ceql %${p}_ht, 7`)
        emit(`  jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`)
        emit(`@${p}_follow`)
        emit(`  %${p}_ptr =l shr %${p}_hdr, 3`)
        emit(`  %${p}_hdr =l loadl %${p}_ptr`)
        emit(`  jmp @${p}_chase`)
        emit(`@${p}_chk`)
        emit(`  %${p}_is_text =w ceql %${p}_ht, 2`)
        emit(`  jnz %${p}_is_text, @${p}_len_chk, @${p}_no`)
        emit(`@${p}_len_chk`)
        emit(`  %${p}_len_p =l add %${p}_ptr, 8`)
        emit(`  %${p}_len_l =l loadl %${p}_len_p`)
        emit(`  %${p}_w =w ceql %${p}_len_l, 1`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_no`)
        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") {
        v = s_read(a2)
        p = fresh()
        emit(`  %${p}_imm =l and ${v}, 31`)
        emit(`  %${p}_is_imm =w ceql %${p}_imm, 11`)
        emit(`  jnz %${p}_is_imm, @${p}_imm_len, @${p}_ptr_chk`)
        emit(`@${p}_imm_len`)
        emit(`  %${p}_ilen =l shr ${v}, 5`)
        emit(`  %${p}_ilen =l and %${p}_ilen, 7`)
        emit(`  %${p}_imm_one =w ceql %${p}_ilen, 1`)
        emit(`  jnz %${p}_imm_one, @${p}_imm_char, @${p}_no`)
        emit(`@${p}_imm_char`)
        emit(`  %${p}_ch_l =l shr ${v}, 8`)
        emit(`  %${p}_ch_l =l and %${p}_ch_l, 255`)
        emit(`  %${p}_ch_w =w copy %${p}_ch_l`)
        emit(`  jmp @${p}_pred`)
        emit(`@${p}_ptr_chk`)
        emit(`  %${p}_ptag =l and ${v}, 7`)
        emit(`  %${p}_is_ptr =w ceql %${p}_ptag, 1`)
        emit(`  jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`)
        emit(`@${p}_ptr`)
        emit(`  %${p}_ptr =l and ${v}, -8`)
        emit(`  %${p}_hdr =l loadl %${p}_ptr`)
        emit(`@${p}_chase`)
        emit(`  %${p}_ht =l and %${p}_hdr, 7`)
        emit(`  %${p}_is_fwd =w ceql %${p}_ht, 7`)
        emit(`  jnz %${p}_is_fwd, @${p}_follow, @${p}_text_chk`)
        emit(`@${p}_follow`)
        emit(`  %${p}_ptr =l shr %${p}_hdr, 3`)
        emit(`  %${p}_hdr =l loadl %${p}_ptr`)
        emit(`  jmp @${p}_chase`)
        emit(`@${p}_text_chk`)
        emit(`  %${p}_is_text =w ceql %${p}_ht, 2`)
        emit(`  jnz %${p}_is_text, @${p}_text_len, @${p}_no`)
        emit(`@${p}_text_len`)
        emit(`  %${p}_len_p =l add %${p}_ptr, 8`)
        emit(`  %${p}_len_l =l loadl %${p}_len_p`)
        emit(`  %${p}_text_one =w ceql %${p}_len_l, 1`)
        emit(`  jnz %${p}_text_one, @${p}_text_char, @${p}_no`)
        emit(`@${p}_text_char`)
        emit(`  %${p}_pack_p =l add %${p}_ptr, 24`)
        emit(`  %${p}_pack =l loadl %${p}_pack_p`)
        emit(`  %${p}_ch_l =l shr %${p}_pack, 32`)
        emit(`  %${p}_ch_l =l and %${p}_ch_l, 255`)
        emit(`  %${p}_ch_w =w copy %${p}_ch_l`)
        emit(`@${p}_pred`)
        if (op == "is_digit") {
          emit(`  %${p}_lt_0 =w csltw %${p}_ch_w, 48`)
          emit(`  %${p}_ge_0 =w ceqw %${p}_lt_0, 0`)
          emit(`  %${p}_lt_9 =w csltw %${p}_ch_w, 58`)
          emit(`  %${p}_w =w and %${p}_ge_0, %${p}_lt_9`)
        } else if (op == "is_lower") {
          emit(`  %${p}_lt_a =w csltw %${p}_ch_w, 97`)
          emit(`  %${p}_ge_a =w ceqw %${p}_lt_a, 0`)
          emit(`  %${p}_lt_z =w csltw %${p}_ch_w, 123`)
          emit(`  %${p}_w =w and %${p}_ge_a, %${p}_lt_z`)
        } else if (op == "is_upper") {
          emit(`  %${p}_lt_A =w csltw %${p}_ch_w, 65`)
          emit(`  %${p}_ge_A =w ceqw %${p}_lt_A, 0`)
          emit(`  %${p}_lt_Z =w csltw %${p}_ch_w, 91`)
          emit(`  %${p}_w =w and %${p}_ge_A, %${p}_lt_Z`)
        } else if (op == "is_letter") {
          emit(`  %${p}_lt_A =w csltw %${p}_ch_w, 65`)
          emit(`  %${p}_ge_A =w ceqw %${p}_lt_A, 0`)
          emit(`  %${p}_lt_Z =w csltw %${p}_ch_w, 91`)
          emit(`  %${p}_is_upper =w and %${p}_ge_A, %${p}_lt_Z`)
          emit(`  %${p}_lt_a =w csltw %${p}_ch_w, 97`)
          emit(`  %${p}_ge_a =w ceqw %${p}_lt_a, 0`)
          emit(`  %${p}_lt_z =w csltw %${p}_ch_w, 123`)
          emit(`  %${p}_is_lower =w and %${p}_ge_a, %${p}_lt_z`)
          emit(`  %${p}_w =w or %${p}_is_upper, %${p}_is_lower`)
        } else {
          emit(`  %${p}_is_sp =w ceqw %${p}_ch_w, 32`)
          emit(`  %${p}_is_tb =w ceqw %${p}_ch_w, 9`)
          emit(`  %${p}_is_nl =w ceqw %${p}_ch_w, 10`)
          emit(`  %${p}_is_cr =w ceqw %${p}_ch_w, 13`)
          emit(`  %${p}_is_ff =w ceqw %${p}_ch_w, 12`)
          emit(`  %${p}_is_vt =w ceqw %${p}_ch_w, 11`)
          emit(`  %${p}_w =w or %${p}_is_sp, %${p}_is_tb`)
          emit(`  %${p}_w =w or %${p}_w, %${p}_is_nl`)
          emit(`  %${p}_w =w or %${p}_w, %${p}_is_cr`)
          emit(`  %${p}_w =w or %${p}_w, %${p}_is_ff`)
          emit(`  %${p}_w =w or %${p}_w, %${p}_is_vt`)
        }
        emit(`  jmp @${p}_done`)
        emit(`@${p}_no`)
        emit(`  %${p}_w =w copy 0`)
        emit(`@${p}_done`)
        s_write(a1, emit_pack_bool_js(`%${p}_w`))
        continue
      }
      if (op == "is_true") {
        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") {
        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") {
        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") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        emit(`  %${p}_a_tag =l and ${lhs}, 1`)
        emit(`  %${p}_b_tag =l and ${rhs}, 1`)
        emit(`  %${p}_a_int =w ceql %${p}_a_tag, 0`)
        emit(`  %${p}_b_int =w ceql %${p}_b_tag, 0`)
        emit(`  %${p}_both_int =w and %${p}_a_int, %${p}_b_int`)
        emit(`  jnz %${p}_both_int, @${p}_int, @${p}_slow`)
        emit(`@${p}_int`)
        emit(`  %${p}_ai =l sar ${lhs}, 1`)
        emit(`  %${p}_bi =l sar ${rhs}, 1`)
        emit(`  %${p}_aiw =w copy %${p}_ai`)
        emit(`  %${p}_biw =w copy %${p}_bi`)
        if (op == "eq") {
          emit(`  %${p}_w =w ceqw %${p}_aiw, %${p}_biw`)
        } else if (op == "ne") {
          emit(`  %${p}_w =w cnew %${p}_aiw, %${p}_biw`)
        } else if (op == "lt") {
          emit(`  %${p}_w =w csltw %${p}_aiw, %${p}_biw`)
        } else if (op == "le") {
          emit(`  %${p}_w =w cslew %${p}_aiw, %${p}_biw`)
        } else if (op == "gt") {
          emit(`  %${p}_w =w csgtw %${p}_aiw, %${p}_biw`)
        } else {
          emit(`  %${p}_w =w csgew %${p}_aiw, %${p}_biw`)
        }
        s_write(a1, emit_pack_bool_js(`%${p}_w`))
        emit(`  jmp @${p}_done`)
        emit(`@${p}_slow`)
        cmp_id = 0
        if (op == "eq") cmp_id = 0
        else if (op == "ne") cmp_id = 1
        else if (op == "lt") cmp_id = 2
        else if (op == "le") cmp_id = 3
        else if (op == "gt") cmp_id = 4
        else cmp_id = 5
        emit(`  %${p}_r =l call $cell_rt_cmp(l %ctx, w ${text(cmp_id)}, l ${lhs}, l ${rhs})`)
        emit(`  %${p}_exc =w ceql %${p}_r, ${text(qbe.js_exception)}`)
        if (has_handler && !in_handler) {
          emit(`  jnz %${p}_exc, @disruption_handler, @${p}_ok`)
        } else {
          needs_exc_ret = true
          emit(`  jnz %${p}_exc, @_exc_ret, @${p}_ok`)
        }
        emit(`@${p}_ok`)
        s_write(a1, `%${p}_r`)
        emit(`@${p}_done`)
        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") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        truthy = emit_truthy_w(lhs)
        emit(`  jnz ${truthy}, @${p}_t, @${p}_f`)
        emit(`@${p}_t`)
        s_write(a1, rhs)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_f`)
        s_write(a1, lhs)
        emit(`@${p}_done`)
        continue
      }
      if (op == "or") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        truthy = emit_truthy_w(lhs)
        emit(`  jnz ${truthy}, @${p}_t, @${p}_f`)
        emit(`@${p}_t`)
        s_write(a1, lhs)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_f`)
        s_write(a1, rhs)
        emit(`@${p}_done`)
        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") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        emit(`  %${p}_a_num =w copy ${emit_is_num_w(lhs)}`)
        emit(`  %${p}_b_num =w copy ${emit_is_num_w(rhs)}`)
        emit(`  %${p}_both_num =w and %${p}_a_num, %${p}_b_num`)
        emit(`  jnz %${p}_both_num, @${p}_ok, @${p}_bad`)
        emit(`@${p}_ok`)
        emit(`  %${p}_aiw =w copy ${emit_num_to_int32_w(lhs)}`)
        emit(`  %${p}_biw =w copy ${emit_num_to_int32_w(rhs)}`)
        emit(`  %${p}_sh =w and %${p}_biw, 31`)
        emit(`  %${p}_rw =w shl %${p}_aiw, %${p}_sh`)
        emit(`  %${p}_rl =l extsw %${p}_rw`)
        emit(`  %${p}_r =l shl %${p}_rl, 1`)
        s_write(a1, `%${p}_r`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_bad`)
        emit(`  call $cell_rt_disrupt(l %ctx)`)
        if (has_handler && !in_handler) {
          emit(`  jmp @disruption_handler`)
        } else {
          emit(`  ret 15`)
        }
        emit(`@${p}_done`)
        continue
      }
      if (op == "shr") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        emit(`  %${p}_a_num =w copy ${emit_is_num_w(lhs)}`)
        emit(`  %${p}_b_num =w copy ${emit_is_num_w(rhs)}`)
        emit(`  %${p}_both_num =w and %${p}_a_num, %${p}_b_num`)
        emit(`  jnz %${p}_both_num, @${p}_ok, @${p}_bad`)
        emit(`@${p}_ok`)
        emit(`  %${p}_aiw =w copy ${emit_num_to_int32_w(lhs)}`)
        emit(`  %${p}_biw =w copy ${emit_num_to_int32_w(rhs)}`)
        emit(`  %${p}_sh =w and %${p}_biw, 31`)
        emit(`  %${p}_rw =w sar %${p}_aiw, %${p}_sh`)
        emit(`  %${p}_rl =l extsw %${p}_rw`)
        emit(`  %${p}_r =l shl %${p}_rl, 1`)
        s_write(a1, `%${p}_r`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_bad`)
        emit(`  call $cell_rt_disrupt(l %ctx)`)
        if (has_handler && !in_handler) {
          emit(`  jmp @disruption_handler`)
        } else {
          emit(`  ret 15`)
        }
        emit(`@${p}_done`)
        continue
      }
      if (op == "ushr") {
        lhs = s_read(a2)
        rhs = s_read(a3)
        p = fresh()
        emit(`  %${p}_a_num =w copy ${emit_is_num_w(lhs)}`)
        emit(`  %${p}_b_num =w copy ${emit_is_num_w(rhs)}`)
        emit(`  %${p}_both_num =w and %${p}_a_num, %${p}_b_num`)
        emit(`  jnz %${p}_both_num, @${p}_ok, @${p}_bad`)
        emit(`@${p}_ok`)
        emit(`  %${p}_aiw =w copy ${emit_num_to_int32_w(lhs)}`)
        emit(`  %${p}_biw =w copy ${emit_num_to_int32_w(rhs)}`)
        emit(`  %${p}_sh =w and %${p}_biw, 31`)
        emit(`  %${p}_rw =w shr %${p}_aiw, %${p}_sh`)
        emit(`  %${p}_rl =l extsw %${p}_rw`)
        emit(`  %${p}_r =l shl %${p}_rl, 1`)
        s_write(a1, `%${p}_r`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_bad`)
        emit(`  call $cell_rt_disrupt(l %ctx)`)
        if (has_handler && !in_handler) {
          emit(`  jmp @disruption_handler`)
        } else {
          emit(`  ret 15`)
        }
        emit(`@${p}_done`)
        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 ${text(sl.idx)})`)
        } 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 ${text(sl.idx)})`)
        } 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 ${text(sl.idx)})`)
        } 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]
        lhs = s_read(a1)
        rhs = s_read(a2)
        v = s_read(a3)
        p = fresh()
        emit(`  %${p}_idx_tag =l and ${v}, 1`)
        emit(`  %${p}_idx_is_int =w ceql %${p}_idx_tag, 0`)
        emit(`  jnz %${p}_idx_is_int, @${p}_idx_ok, @${p}_slow`)
        emit(`@${p}_idx_ok`)
        emit(`  %${p}_idx_l =l sar ${v}, 1`)
        emit(`  %${p}_idx_w =w copy %${p}_idx_l`)
        emit(`  %${p}_idx_neg =w csltw %${p}_idx_w, 0`)
        emit(`  jnz %${p}_idx_neg, @${p}_slow, @${p}_arr_ptr_chk`)
        emit(`@${p}_arr_ptr_chk`)
        emit(`  %${p}_ptag =l and ${lhs}, 7`)
        emit(`  %${p}_is_ptr =w ceql %${p}_ptag, 1`)
        emit(`  jnz %${p}_is_ptr, @${p}_arr_ptr, @${p}_slow`)
        emit(`@${p}_arr_ptr`)
        emit(`  %${p}_arr_ptr =l and ${lhs}, -8`)
        emit(`  %${p}_arr_hdr =l loadl %${p}_arr_ptr`)
        emit(`@${p}_arr_chase`)
        emit(`  %${p}_arr_ty =l and %${p}_arr_hdr, 7`)
        emit(`  %${p}_arr_is_fwd =w ceql %${p}_arr_ty, 7`)
        emit(`  jnz %${p}_arr_is_fwd, @${p}_arr_follow, @${p}_arr_chk`)
        emit(`@${p}_arr_follow`)
        emit(`  %${p}_arr_ptr =l shr %${p}_arr_hdr, 3`)
        emit(`  %${p}_arr_hdr =l loadl %${p}_arr_ptr`)
        emit(`  jmp @${p}_arr_chase`)
        emit(`@${p}_arr_chk`)
        emit(`  %${p}_arr_is_array =w ceql %${p}_arr_ty, 0`)
        emit(`  jnz %${p}_arr_is_array, @${p}_arr_stone_chk, @${p}_slow`)
        emit(`@${p}_arr_stone_chk`)
        emit(`  %${p}_arr_stone =l and %${p}_arr_hdr, 8`)
        emit(`  %${p}_arr_is_stone =w cnel %${p}_arr_stone, 0`)
        emit(`  jnz %${p}_arr_is_stone, @${p}_slow, @${p}_cap_chk`)
        emit(`@${p}_cap_chk`)
        emit(`  %${p}_cap_l =l shr %${p}_arr_hdr, 8`)
        emit(`  %${p}_cap_w =w copy %${p}_cap_l`)
        emit(`  %${p}_in_cap =w csltw %${p}_idx_w, %${p}_cap_w`)
        emit(`  jnz %${p}_in_cap, @${p}_len_chk, @${p}_slow`)
        emit(`@${p}_len_chk`)
        emit(`  %${p}_len_p =l add %${p}_arr_ptr, 8`)
        emit(`  %${p}_len_l =l loadl %${p}_len_p`)
        emit(`  %${p}_len_w =w copy %${p}_len_l`)
        emit(`  %${p}_need_len =w csgew %${p}_idx_w, %${p}_len_w`)
        emit(`  jnz %${p}_need_len, @${p}_bump_len, @${p}_store`)
        emit(`@${p}_bump_len`)
        emit(`  %${p}_next_len_w =w add %${p}_idx_w, 1`)
        emit(`  %${p}_next_len_l =l extsw %${p}_next_len_w`)
        emit(`  storel %${p}_next_len_l, %${p}_len_p`)
        emit(`@${p}_store`)
        emit(`  %${p}_idx2_l =l extsw %${p}_idx_w`)
        emit(`  %${p}_idx2_off =l shl %${p}_idx2_l, 3`)
        emit(`  %${p}_vals_p =l add %${p}_arr_ptr, 16`)
        emit(`  %${p}_item_p =l add %${p}_vals_p, %${p}_idx2_off`)
        emit(`  storel ${rhs}, %${p}_item_p`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_slow`)
        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") {
        // 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 ${text(sl.idx)})`)
        } 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
        depth = a3
        if (depth == 0) {
          v = s_read(a2)
          s_write(a1, v)
        } else {
          p = fresh()
          emit(`  %${p}_fp =l copy %fp`)
          d = 0
          while (d < depth) {
            emit(`  %${p}_fn_p_${text(d)} =l sub %${p}_fp, 24`)
            emit(`  %${p}_fn_${text(d)} =l loadl %${p}_fn_p_${text(d)}`)
            emit(`  %${p}_fn_ptr_${text(d)} =l and %${p}_fn_${text(d)}, -8`)
            emit(`  %${p}_outer_p_${text(d)} =l add %${p}_fn_ptr_${text(d)}, 40`)
            emit(`  %${p}_outer_${text(d)} =l loadl %${p}_outer_p_${text(d)}`)
            emit(`  %${p}_outer_ptr_${text(d)} =l and %${p}_outer_${text(d)}, -8`)
            emit(`  %${p}_fp =l add %${p}_outer_ptr_${text(d)}, 32`)
            d = d + 1
          }
          emit(`  %${p}_slotp =l add %${p}_fp, ${text(a2 * 8)}`)
          emit(`  %${p}_val =l loadl %${p}_slotp`)
          s_write(a1, `%${p}_val`)
        }
        continue
      }
      if (op == "put") {
        // mcode: put(val, slot, depth) — a2=slot, a3=depth
        v = s_read(a1)
        depth = a3
        if (depth == 0) {
          s_write(a2, v)
        } else {
          p = fresh()
          emit(`  %${p}_fp =l copy %fp`)
          d = 0
          while (d < depth) {
            emit(`  %${p}_fn_p_${text(d)} =l sub %${p}_fp, 24`)
            emit(`  %${p}_fn_${text(d)} =l loadl %${p}_fn_p_${text(d)}`)
            emit(`  %${p}_fn_ptr_${text(d)} =l and %${p}_fn_${text(d)}, -8`)
            emit(`  %${p}_outer_p_${text(d)} =l add %${p}_fn_ptr_${text(d)}, 40`)
            emit(`  %${p}_outer_${text(d)} =l loadl %${p}_outer_p_${text(d)}`)
            emit(`  %${p}_outer_ptr_${text(d)} =l and %${p}_outer_${text(d)}, -8`)
            emit(`  %${p}_fp =l add %${p}_outer_ptr_${text(d)}, 32`)
            d = d + 1
          }
          emit(`  %${p}_slotp =l add %${p}_fp, ${text(a2 * 8)}`)
          emit(`  storel ${v}, %${p}_slotp`)
        }
        continue
      }

      // --- Control flow ---

      if (op == "jump") {
        j_lbl = sanitize(a1)
        j_idx = label_pos[j_lbl]
        if (j_idx != null && j_idx < instr_idx) {
          emit_backedge_branch(j_lbl)
        } else {
          emit(`  jmp @${j_lbl}`)
        }
        last_was_term = true
        continue
      }
      if (op == "jump_true") {
        v = s_read(a1)
        p = fresh()
        jt_lbl = sanitize(a2)
        jt_idx = label_pos[jt_lbl]
        jt_backedge = jt_idx != null && jt_idx < instr_idx
        emit(`  %${p}_take =w ceql ${v}, ${text(qbe.js_true)}`)
        emit(`  jnz %${p}_take, @${p}_take, @${p}_f`)
        emit(`@${p}_take`)
        if (jt_backedge) {
          emit_backedge_branch(jt_lbl)
        } else {
          emit(`  jmp @${jt_lbl}`)
        }
        emit(`@${p}_f`)
        continue
      }
      if (op == "jump_false") {
        v = s_read(a1)
        p = fresh()
        jf_lbl = sanitize(a2)
        jf_idx = label_pos[jf_lbl]
        jf_backedge = jf_idx != null && jf_idx < instr_idx
        emit(`  %${p}_take =w ceql ${v}, ${text(qbe.js_false)}`)
        emit(`  jnz %${p}_take, @${p}_take, @${p}_t`)
        emit(`@${p}_take`)
        if (jf_backedge) {
          emit_backedge_branch(jf_lbl)
        } else {
          emit(`  jmp @${jf_lbl}`)
        }
        emit(`@${p}_t`)
        continue
      }
      if (op == "wary_true") {
        v = s_read(a1)
        p = fresh()
        wt_lbl = sanitize(a2)
        wt_idx = label_pos[wt_lbl]
        wt_backedge = wt_idx != null && wt_idx < instr_idx
        truthy = emit_truthy_w(v)
        emit(`  jnz ${truthy}, @${p}_take, @${p}_f`)
        emit(`@${p}_take`)
        if (wt_backedge) {
          emit_backedge_branch(wt_lbl)
        } else {
          emit(`  jmp @${wt_lbl}`)
        }
        emit(`@${p}_f`)
        continue
      }
      if (op == "wary_false") {
        v = s_read(a1)
        p = fresh()
        wf_lbl = sanitize(a2)
        wf_idx = label_pos[wf_lbl]
        wf_backedge = wf_idx != null && wf_idx < instr_idx
        truthy = emit_truthy_w(v)
        emit(`  jnz ${truthy}, @${p}_t, @${p}_take`)
        emit(`@${p}_take`)
        if (wf_backedge) {
          emit_backedge_branch(wf_lbl)
        } else {
          emit(`  jmp @${wf_lbl}`)
        }
        emit(`@${p}_t`)
        continue
      }
      if (op == "jump_null") {
        v = s_read(a1)
        p = fresh()
        jn_lbl = sanitize(a2)
        jn_idx = label_pos[jn_lbl]
        jn_backedge = jn_idx != null && jn_idx < instr_idx
        emit(`  %${p} =w ceql ${v}, ${text(qbe.js_null)}`)
        if (jn_backedge) {
          emit(`  jnz %${p}, @${p}_bn, @${p}_n`)
          emit(`@${p}_bn`)
          emit_backedge_branch(jn_lbl)
        } else {
          emit(`  jnz %${p}, @${jn_lbl}, @${p}_n`)
        }
        emit(`@${p}_n`)
        continue
      }
      if (op == "jump_empty") {
        v = s_read(a1)
        p = fresh()
        je_lbl = sanitize(a2)
        je_idx = label_pos[je_lbl]
        je_backedge = je_idx != null && je_idx < instr_idx
        emit(`  %${p} =w ceql ${v}, ${text(qbe.js_empty_text)}`)
        if (je_backedge) {
          emit(`  jnz %${p}, @${p}_bn, @${p}_n`)
          emit(`@${p}_bn`)
          emit_backedge_branch(je_lbl)
        } else {
          emit(`  jnz %${p}, @${je_lbl}, @${p}_n`)
        }
        emit(`@${p}_n`)
        continue
      }
      if (op == "jump_not_null") {
        v = s_read(a1)
        p = fresh()
        jnn_lbl = sanitize(a2)
        jnn_idx = label_pos[jnn_lbl]
        jnn_backedge = jnn_idx != null && jnn_idx < instr_idx
        emit(`  %${p} =w cnel ${v}, ${text(qbe.js_null)}`)
        if (jnn_backedge) {
          emit(`  jnz %${p}, @${p}_bn, @${p}_n`)
          emit(`@${p}_bn`)
          emit_backedge_branch(jnn_lbl)
        } else {
          emit(`  jnz %${p}, @${jnn_lbl}, @${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 == "apply") {
        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") {
        v = s_read(a1)
        lhs = s_read(a3)
        p = fresh()
        // JSFrame layout: [header,function,caller,address,slots...]
        // slots start at byte offset 32.
        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") {
        // Signal dispatcher to call frame in slot a1 and resume at @_segN.
        seg_counter = seg_counter + 1
        resume_val = seg_counter * 65536 + a2
        p = fresh()
        emit(`  %${p}_addrp =l sub %fp, 8`)
        // frame->address holds JS_NewInt32((seg << 16) | ret_slot), tagged.
        emit(`  storel ${text(resume_val * 2)}, %${p}_addrp`)
        emit(`  call $cell_rt_signal_call(l %ctx, l %fp, l ${text(a1)})`)
        emit(`  ret ${text(qbe.js_null)}`)
        emit(`@_seg${text(seg_counter)}`)
        // Dispatcher writes JS_EXCEPTION into ret slot on error; branch here.
        v = s_read(a2)
        emit(`  %${p}_exc =w ceql ${v}, ${text(qbe.js_exception)}`)
        if (has_handler && !in_handler) {
          emit(`  jnz %${p}_exc, @disruption_handler, @${p}_ok`)
        } else {
          needs_exc_ret = true
          emit(`  jnz %${p}_exc, @_exc_ret, @${p}_ok`)
        }
        emit(`@${p}_ok`)
        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") {
        // 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") {
        fn_arity = 0
        fn_nr_slots = 0
        if (a2 >= 0 && a2 < length(ir.functions)) {
          fn_arity = ir.functions[a2].nr_args
          fn_nr_slots = ir.functions[a2].nr_slots
        }
        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") {
        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") {
        lhs = s_read(a1)
        rhs = s_read(a2)
        p = fresh()
        emit(`  %${p}_ptag =l and ${lhs}, 7`)
        emit(`  %${p}_is_ptr =w ceql %${p}_ptag, 1`)
        emit(`  jnz %${p}_is_ptr, @${p}_arr_ptr, @${p}_slow`)
        emit(`@${p}_arr_ptr`)
        emit(`  %${p}_arr_ptr =l and ${lhs}, -8`)
        emit(`  %${p}_arr_hdr =l loadl %${p}_arr_ptr`)
        emit(`@${p}_arr_chase`)
        emit(`  %${p}_arr_ty =l and %${p}_arr_hdr, 7`)
        emit(`  %${p}_arr_is_fwd =w ceql %${p}_arr_ty, 7`)
        emit(`  jnz %${p}_arr_is_fwd, @${p}_arr_follow, @${p}_arr_chk`)
        emit(`@${p}_arr_follow`)
        emit(`  %${p}_arr_ptr =l shr %${p}_arr_hdr, 3`)
        emit(`  %${p}_arr_hdr =l loadl %${p}_arr_ptr`)
        emit(`  jmp @${p}_arr_chase`)
        emit(`@${p}_arr_chk`)
        emit(`  %${p}_arr_is_array =w ceql %${p}_arr_ty, 0`)
        emit(`  jnz %${p}_arr_is_array, @${p}_arr_stone_chk, @${p}_slow`)
        emit(`@${p}_arr_stone_chk`)
        emit(`  %${p}_arr_stone =l and %${p}_arr_hdr, 8`)
        emit(`  %${p}_arr_is_stone =w cnel %${p}_arr_stone, 0`)
        emit(`  jnz %${p}_arr_is_stone, @${p}_slow, @${p}_lens`)
        emit(`@${p}_lens`)
        emit(`  %${p}_len_p =l add %${p}_arr_ptr, 8`)
        emit(`  %${p}_len_l =l loadl %${p}_len_p`)
        emit(`  %${p}_len_w =w copy %${p}_len_l`)
        emit(`  %${p}_cap_l =l shr %${p}_arr_hdr, 8`)
        emit(`  %${p}_cap_w =w copy %${p}_cap_l`)
        emit(`  %${p}_in_cap =w csltw %${p}_len_w, %${p}_cap_w`)
        emit(`  jnz %${p}_in_cap, @${p}_store, @${p}_slow`)
        emit(`@${p}_store`)
        emit(`  %${p}_idx_l =l extsw %${p}_len_w`)
        emit(`  %${p}_idx_off =l shl %${p}_idx_l, 3`)
        emit(`  %${p}_vals_p =l add %${p}_arr_ptr, 16`)
        emit(`  %${p}_item_p =l add %${p}_vals_p, %${p}_idx_off`)
        emit(`  storel ${rhs}, %${p}_item_p`)
        emit(`  %${p}_next_len_w =w add %${p}_len_w, 1`)
        emit(`  %${p}_next_len_l =l extsw %${p}_next_len_w`)
        emit(`  storel %${p}_next_len_l, %${p}_len_p`)
        emit(`  jmp @${p}_done`)
        emit(`@${p}_slow`)
        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") {
        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 ${text(sl.idx)})`)
        } 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.label}, l ${flg_label.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")

  // Export nr_slots for main function so the module loader can use right-sized frames
  var main_name = export_name ? sanitize(export_name) : "cell_main"
  push(data_out, "export data $" + main_name + "_nr_slots = { w " + text(ir.main.nr_slots) + " }")
  push(data_out, "export data $cell_lit_count = { w " + text(length(str_entries)) + " }")
  if (length(str_entries) > 0) {
    lit_data = []
    si = 0
    while (si < length(str_entries)) {
      push(lit_data, `l ${str_entries[si].label}`)
      si = si + 1
    }
    push(data_out, "export data $cell_lit_table = { " + text(lit_data, ", ") + " }")
  }

  return {
    data: text(data_out, "\n"),
    functions: fn_bodies,
    helpers: emit_helpers(qbe)
  }
}

return qbe_emit
