// QBE IL Macro Layer
// Maps mcode VM operations to QBE intermediate language fragments.
// Each macro returns a backtick template string of QBE IL.
// Convention: p = unique prefix for temporaries/labels, result in %{p}

// ============================================================
// Constants
// ============================================================

def js_null = 7
def js_false = 3
def js_true = 35
def js_exception = 15
def js_empty_text = 11

// Shared closure vars for functions with >4 params
var _qop = null
var _qop2 = null
var _qflags = null

def int32_min = -2147483648
def int32_max = 2147483647
def mantissa_mask = 4503599627370495

// ============================================================
// Type Checks — each takes (p, v), produces %{p} as w (0 or 1)
// ============================================================

var is_int = function(p, v) {
  return `  %${p}.t =l and ${v}, 1
  %${p} =w ceql %${p}.t, 0
`
}

var is_number = function(p, v) {
  return `  %${p}.t1 =l and ${v}, 1
  %${p}.ii =w ceql %${p}.t1, 0
  %${p}.t2 =l and ${v}, 7
  %${p}.fi =w ceql %${p}.t2, 5
  %${p} =w or %${p}.ii, %${p}.fi
`
}

var is_null = function(p, v) {
  return `  %${p}.t =l and ${v}, 31
  %${p} =w ceql %${p}.t, 7
`
}

var is_bool = function(p, v) {
  return `  %${p}.t =l and ${v}, 31
  %${p} =w ceql %${p}.t, 3
`
}

var is_exception = function(p, v) {
  return `  %${p}.t =l and ${v}, 31
  %${p} =w ceql %${p}.t, 15
`
}

var is_ptr = function(p, v) {
  return `  %${p}.t =l and ${v}, 7
  %${p} =w ceql %${p}.t, 1
`
}

var is_imm_text = function(p, v) {
  return `  %${p}.t =l and ${v}, 31
  %${p} =w ceql %${p}.t, 11
`
}

var is_text = function(p, v) {
  return `  %${p}.imm =l and ${v}, 31
  %${p}.is_imm =w ceql %${p}.imm, 11
  jnz %${p}.is_imm, @${p}.yes, @${p}.chk_ptr
@${p}.chk_ptr
  %${p}.ptag =l and ${v}, 7
  %${p}.is_ptr =w ceql %${p}.ptag, 1
  jnz %${p}.is_ptr, @${p}.load_hdr, @${p}.no
@${p}.load_hdr
  %${p}.ptr =l and ${v}, -8
  %${p}.hdr =l loadl %${p}.ptr
@${p}.chase
  %${p}.ht =l and %${p}.hdr, 7
  %${p}.is_fwd =w ceql %${p}.ht, 7
  jnz %${p}.is_fwd, @${p}.follow, @${p}.chk_type
@${p}.follow
  %${p}.ptr =l shr %${p}.hdr, 3
  %${p}.hdr =l loadl %${p}.ptr
  jmp @${p}.chase
@${p}.chk_type
  %${p} =w ceql %${p}.ht, 2
  jmp @${p}.done
@${p}.yes
  %${p} =w copy 1
  jmp @${p}.done
@${p}.no
  %${p} =w copy 0
  jmp @${p}.done
@${p}.done
`
}

// ============================================================
// Value Extraction
// ============================================================

// get_int(p, v) — extract int32 as w from tagged int. Result: %{p}
var get_int = function(p, v) {
  return `  %${p}.sl =l sar ${v}, 1
  %${p} =w copy %${p}.sl
`
}

// get_bool(p, v) — extract bool as w. Result: %{p}
var get_bool = function(p, v) {
  return `  %${p}.sl =l shr ${v}, 5
  %${p} =w and %${p}.sl, 1
`
}

// get_ptr(p, v) — extract pointer as l. Result: %{p}
var get_ptr = function(p, v) {
  return `  %${p} =l and ${v}, -8
`
}

// get_float64(p, v) — decode short float to d. Result: %{p} as d
// Caller must handle zero check (sexp == 0 -> 0.0) before calling.
var get_float64 = function(p, v) {
  return `  %${p}.sign =l shr ${v}, 63
  %${p}.sexp =l shr ${v}, 55
  %${p}.sexp =l and %${p}.sexp, 255
  %${p}.mant =l shr ${v}, 3
  %${p}.mant =l and %${p}.mant, ${mantissa_mask}
  %${p}.dexp =l sub %${p}.sexp, 127
  %${p}.dexp =l add %${p}.dexp, 1023
  %${p}.s63 =l shl %${p}.sign, 63
  %${p}.e52 =l shl %${p}.dexp, 52
  %${p}.bits =l or %${p}.s63, %${p}.e52
  %${p}.bits =l or %${p}.bits, %${p}.mant
  %${p} =d cast %${p}.bits
`
}

// to_float64(p, v) — convert any numeric value (int or short float) to d.
// Result: %{p} as d
var to_float64 = function(p, v) {
  return `  %${p}.tag =l and ${v}, 1
  %${p}.is_int =w ceql %${p}.tag, 0
  jnz %${p}.is_int, @${p}.from_int, @${p}.from_float
@${p}.from_int
  %${p}.isl =l sar ${v}, 1
  %${p}.iw =w copy %${p}.isl
  %${p} =d swtof %${p}.iw
  jmp @${p}.done
@${p}.from_float
  %${p}.fsexp =l shr ${v}, 55
  %${p}.fsexp =l and %${p}.fsexp, 255
  %${p}.is_zero =w ceql %${p}.fsexp, 0
  jnz %${p}.is_zero, @${p}.fzero, @${p}.fdecode
@${p}.fzero
  %${p} =d copy d_0.0
  jmp @${p}.done
@${p}.fdecode
  %${p}.fsign =l shr ${v}, 63
  %${p}.fmant =l shr ${v}, 3
  %${p}.fmant =l and %${p}.fmant, ${mantissa_mask}
  %${p}.fdexp =l sub %${p}.fsexp, 127
  %${p}.fdexp =l add %${p}.fdexp, 1023
  %${p}.fs63 =l shl %${p}.fsign, 63
  %${p}.fe52 =l shl %${p}.fdexp, 52
  %${p}.fbits =l or %${p}.fs63, %${p}.fe52
  %${p}.fbits =l or %${p}.fbits, %${p}.fmant
  %${p} =d cast %${p}.fbits
  jmp @${p}.done
@${p}.done
`
}

// ============================================================
// Value Creation
// ============================================================

// new_int(p, i) — tag int32 w as JSValue l. Result: %{p}
var new_int = function(p, i) {
  return `  %${p}.ext =l extuw ${i}
  %${p} =l shl %${p}.ext, 1
`
}

// new_bool(p, b) — tag bool w as JSValue l. Result: %{p}
var new_bool = function(p, b) {
  return `  %${p}.ext =l extuw ${b}
  %${p}.sh =l shl %${p}.ext, 5
  %${p} =l or %${p}.sh, 3
`
}

// new_float64 — C call to __JS_NewFloat64(ctx, val). Result: %{p}
var new_float64 = function(p, ctx, d) {
  return `  %${p} =l call $qbe_new_float64(l ${ctx}, d ${d})
`
}

// ============================================================
// Arithmetic — add/sub/mul/div/mod(p, ctx, a, b)
// Simple C call wrappers. Type dispatch is handled in mcode.cm.
// ============================================================

var add = function(p, ctx, a, b) {
  return `  %${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b})
`
}

var sub = function(p, ctx, a, b) {
  return `  %${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b})
`
}

var mul = function(p, ctx, a, b) {
  return `  %${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b})
`
}

var div = function(p, ctx, a, b) {
  return `  %${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b})
`
}

var mod = function(p, ctx, a, b) {
  return `  %${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b})
`
}

// ============================================================
// Comparisons — eq, ne, lt, le, gt, ge
// Each takes (p, ctx, a, b), produces %{p} as l (tagged JSValue bool)
// ============================================================

// Helper: generate comparison for a given op string and int comparison QBE op
// reads _qflags = {int_cmp_op, float_id, is_eq, is_ne, null_true} from closure
var cmp = function(p, ctx, a, b) {
  var int_cmp_op = _qflags.int_cmp_op
  var float_cmp_op_id = _qflags.float_id
  var eq_only = 0
  var mismatch_val = js_false
  var null_val = js_false
  if (_qflags.is_eq || _qflags.is_ne) {
    eq_only = 1
  }
  if (_qflags.is_ne) {
    mismatch_val = js_true
  }
  if (_qflags.null_true) {
    null_val = js_true
  }
  return `@${p}.start
  %${p}.at =l and ${a}, 1
  %${p}.bt =l and ${b}, 1
  %${p}.not_int =l or %${p}.at, %${p}.bt
  jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
@${p}.int_path
  %${p}.ia =l sar ${a}, 1
  %${p}.ib =l sar ${b}, 1
  %${p}.iw =w copy %${p}.ia
  %${p}.ibw =w copy %${p}.ib
  %${p}.cr =w ${int_cmp_op} %${p}.iw, %${p}.ibw
  %${p}.crext =l extuw %${p}.cr
  %${p}.sh =l shl %${p}.crext, 5
  %${p} =l or %${p}.sh, 3
  jmp @${p}.done
@${p}.not_both_int
  %${p}.a_tag5 =l and ${a}, 31
  %${p}.b_tag5 =l and ${b}, 31
  %${p}.a_is_null =w ceql %${p}.a_tag5, 7
  %${p}.b_is_null =w ceql %${p}.b_tag5, 7
  %${p}.both_null =w and %${p}.a_is_null, %${p}.b_is_null
  jnz %${p}.both_null, @${p}.null_path, @${p}.chk_bool
@${p}.null_path
  %${p} =l copy ${null_val}
  jmp @${p}.done
@${p}.chk_bool
  %${p}.a_is_bool =w ceql %${p}.a_tag5, 3
  %${p}.b_is_bool =w ceql %${p}.b_tag5, 3
  %${p}.both_bool =w and %${p}.a_is_bool, %${p}.b_is_bool
  jnz %${p}.both_bool, @${p}.bool_path, @${p}.chk_num
@${p}.bool_path
  %${p}.ba =l shr ${a}, 5
  %${p}.baw =w and %${p}.ba, 1
  %${p}.bb =l shr ${b}, 5
  %${p}.bbw =w and %${p}.bb, 1
  %${p}.bcr =w ${int_cmp_op} %${p}.baw, %${p}.bbw
  %${p}.bcrext =l extuw %${p}.bcr
  %${p}.bsh =l shl %${p}.bcrext, 5
  %${p} =l or %${p}.bsh, 3
  jmp @${p}.done
@${p}.chk_num
  %${p}.a_is_num =w call $JS_IsNumber(l ${a})
  %${p}.b_is_num =w call $JS_IsNumber(l ${b})
  %${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
  jnz %${p}.both_num, @${p}.num_path, @${p}.chk_text
@${p}.num_path
  %${p}.fcr =w call $qbe_float_cmp(l ${ctx}, w ${float_cmp_op_id}, l ${a}, l ${b})
  %${p}.fcrext =l extuw %${p}.fcr
  %${p}.fsh =l shl %${p}.fcrext, 5
  %${p} =l or %${p}.fsh, 3
  jmp @${p}.done
@${p}.chk_text
  %${p}.a_is_text =w call $JS_IsText(l ${a})
  %${p}.b_is_text =w call $JS_IsText(l ${b})
  %${p}.both_text =w and %${p}.a_is_text, %${p}.b_is_text
  jnz %${p}.both_text, @${p}.text_path, @${p}.mismatch
@${p}.text_path
  %${p}.scmp =w call $js_string_compare_value(l ${ctx}, l ${a}, l ${b}, w ${eq_only})
  %${p}.tcr =w ${int_cmp_op} %${p}.scmp, 0
  %${p}.tcrext =l extuw %${p}.tcr
  %${p}.tsh =l shl %${p}.tcrext, 5
  %${p} =l or %${p}.tsh, 3
  jmp @${p}.done
@${p}.mismatch
  %${p} =l copy ${mismatch_val}
  jmp @${p}.done
@${p}.done
`
}

// Comparison op IDs matching MACH_EQ..MACH_GE order for qbe_float_cmp
// MACH_EQ=0, NEQ=1, LT=2, LE=3, GT=4, GE=5
// null_true: eq, le, ge return true for null==null; ne, lt, gt return false
var eq = function(p, ctx, a, b) {
  _qflags = {int_cmp_op: "ceqw", float_id: 0, is_eq: true, is_ne: false, null_true: true}
  return cmp(p, ctx, a, b)
}

var ne = function(p, ctx, a, b) {
  _qflags = {int_cmp_op: "cnew", float_id: 1, is_eq: false, is_ne: true, null_true: false}
  return cmp(p, ctx, a, b)
}

var lt = function(p, ctx, a, b) {
  _qflags = {int_cmp_op: "csltw", float_id: 2, is_eq: false, is_ne: false, null_true: false}
  return cmp(p, ctx, a, b)
}

var le = function(p, ctx, a, b) {
  _qflags = {int_cmp_op: "cslew", float_id: 3, is_eq: false, is_ne: false, null_true: true}
  return cmp(p, ctx, a, b)
}

var gt = function(p, ctx, a, b) {
  _qflags = {int_cmp_op: "csgtw", float_id: 4, is_eq: false, is_ne: false, null_true: false}
  return cmp(p, ctx, a, b)
}

var ge = function(p, ctx, a, b) {
  _qflags = {int_cmp_op: "csgew", float_id: 5, is_eq: false, is_ne: false, null_true: true}
  return cmp(p, ctx, a, b)
}

// ============================================================
// Unary Ops
// ============================================================

// neg(p, ctx, v) — negate via C call (type guards in mcode)
var neg = function(p, ctx, v) {
  return `  %${p} =l call $qbe_float_neg(l ${ctx}, l ${v})
`
}

// inc(p, ctx, v) — increment via C call (type guards in mcode)
var inc = function(p, ctx, v) {
  return `  %${p} =l call $qbe_float_inc(l ${ctx}, l ${v})
`
}

// dec(p, ctx, v) — decrement via C call (type guards in mcode)
var dec = function(p, ctx, v) {
  return `  %${p} =l call $qbe_float_dec(l ${ctx}, l ${v})
`
}

// lnot(p, ctx, v) — logical not. C call to JS_ToBool, then negate inline.
var lnot = function(p, ctx, v) {
  return `  %${p}.bval =w call $JS_ToBool(l ${ctx}, l ${v})
  %${p}.neg =w ceqw %${p}.bval, 0
  %${p}.nex =l extuw %${p}.neg
  %${p}.sh =l shl %${p}.nex, 5
  %${p} =l or %${p}.sh, 3
`
}

// bnot(p, ctx, v) — bitwise not via C call
var bnot = function(p, ctx, v) {
  return `  %${p} =l call $qbe_bnot(l ${ctx}, l ${v})
`
}

// ============================================================
// Bitwise Ops — band, bor, bxor, shl, shr, ushr
// Both operands must be numeric. Int fast path, float -> convert to int32.
// ============================================================

var band = function(p, ctx, a, b) {
  return `  %${p} =l call $qbe_bitwise_and(l ${ctx}, l ${a}, l ${b})
`
}

var bor = function(p, ctx, a, b) {
  return `  %${p} =l call $qbe_bitwise_or(l ${ctx}, l ${a}, l ${b})
`
}

var bxor = function(p, ctx, a, b) {
  return `  %${p} =l call $qbe_bitwise_xor(l ${ctx}, l ${a}, l ${b})
`
}

var shl = function(p, ctx, a, b) {
  return `  %${p} =l call $qbe_shift_shl(l ${ctx}, l ${a}, l ${b})
`
}

var shr = function(p, ctx, a, b) {
  return `  %${p} =l call $qbe_shift_sar(l ${ctx}, l ${a}, l ${b})
`
}

var ushr = function(p, ctx, a, b) {
  return `  %${p} =l call $qbe_shift_shr(l ${ctx}, l ${a}, l ${b})
`
}

// ============================================================
// Decomposed per-type-path operations
// These map directly to the new IR ops emitted by mcode.cm.
// ============================================================

// --- Text concat ---
var concat = function(p, ctx, a, b) {
  return `  %${p} =l call $JS_ConcatString(l ${ctx}, l ${a}, l ${b})
`
}

// --- Comparisons (int path) ---
var cmp_int = function(p, a, b, qbe_op) {
  return `  %${p}.ia =l sar ${a}, 1
  %${p}.ib =l sar ${b}, 1
  %${p}.iaw =w copy %${p}.ia
  %${p}.ibw =w copy %${p}.ib
  %${p}.cr =w ${qbe_op} %${p}.iaw, %${p}.ibw
  %${p}.crext =l extuw %${p}.cr
  %${p}.sh =l shl %${p}.crext, 5
  %${p} =l or %${p}.sh, 3
`
}

var eq_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "ceqw") }
var ne_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "cnew") }
var lt_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "csltw") }
var le_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "cslew") }
var gt_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "csgtw") }
var ge_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "csgew") }

// --- Comparisons (float path) ---
// reads _qop from closure (op_id)
var cmp_float = function(p, ctx, a, b) {
  var op_id = _qop
  return `  %${p}.fcr =w call $qbe_float_cmp(l ${ctx}, w ${op_id}, l ${a}, l ${b})
  %${p}.fcrext =l extuw %${p}.fcr
  %${p}.fsh =l shl %${p}.fcrext, 5
  %${p} =l or %${p}.fsh, 3
`
}

var eq_float = function(p, ctx, a, b) { _qop = 0; return cmp_float(p, ctx, a, b) }
var ne_float = function(p, ctx, a, b) { _qop = 1; return cmp_float(p, ctx, a, b) }
var lt_float = function(p, ctx, a, b) { _qop = 2; return cmp_float(p, ctx, a, b) }
var le_float = function(p, ctx, a, b) { _qop = 3; return cmp_float(p, ctx, a, b) }
var gt_float = function(p, ctx, a, b) { _qop = 4; return cmp_float(p, ctx, a, b) }
var ge_float = function(p, ctx, a, b) { _qop = 5; return cmp_float(p, ctx, a, b) }

// --- Comparisons (text path) ---
// reads _qop (qbe_op) and _qop2 (eq_only) from closure
var cmp_text = function(p, ctx, a, b) {
  var qbe_op = _qop
  var eq_only = _qop2
  return `  %${p}.scmp =w call $js_string_compare_value(l ${ctx}, l ${a}, l ${b}, w ${eq_only})
  %${p}.tcr =w ${qbe_op} %${p}.scmp, 0
  %${p}.tcrext =l extuw %${p}.tcr
  %${p}.tsh =l shl %${p}.tcrext, 5
  %${p} =l or %${p}.tsh, 3
`
}

var eq_text = function(p, ctx, a, b) { _qop = "ceqw"; _qop2 = 1; return cmp_text(p, ctx, a, b) }
var ne_text = function(p, ctx, a, b) { _qop = "cnew"; _qop2 = 1; return cmp_text(p, ctx, a, b) }
var lt_text = function(p, ctx, a, b) { _qop = "csltw"; _qop2 = 0; return cmp_text(p, ctx, a, b) }
var le_text = function(p, ctx, a, b) { _qop = "cslew"; _qop2 = 0; return cmp_text(p, ctx, a, b) }
var gt_text = function(p, ctx, a, b) { _qop = "csgtw"; _qop2 = 0; return cmp_text(p, ctx, a, b) }
var ge_text = function(p, ctx, a, b) { _qop = "csgew"; _qop2 = 0; return cmp_text(p, ctx, a, b) }

// --- Comparisons (bool path) ---
var eq_bool = function(p, a, b) {
  return `  %${p}.cr =w ceql ${a}, ${b}
  %${p}.crext =l extuw %${p}.cr
  %${p}.sh =l shl %${p}.crext, 5
  %${p} =l or %${p}.sh, 3
`
}

var ne_bool = function(p, a, b) {
  return `  %${p}.cr =w cnel ${a}, ${b}
  %${p}.crext =l extuw %${p}.cr
  %${p}.sh =l shl %${p}.crext, 5
  %${p} =l or %${p}.sh, 3
`
}

// --- Type guard: is_identical (chases forwarding pointers via C helper) ---
var is_identical = function(p, a, b) {
  return `  %${p} =l call $cell_rt_is_identical(l %ctx, l ${a}, l ${b})
`
}

// ============================================================
// Module export
// ============================================================

return {
  // constants
  js_null: js_null,
  js_false: js_false,
  js_true: js_true,
  js_exception: js_exception,
  js_empty_text: js_empty_text,
  // type checks
  is_int: is_int,
  is_number: is_number,
  is_null: is_null,
  is_bool: is_bool,
  is_exception: is_exception,
  is_ptr: is_ptr,
  is_imm_text: is_imm_text,
  is_text: is_text,
  // value extraction
  get_int: get_int,
  get_bool: get_bool,
  get_ptr: get_ptr,
  get_float64: get_float64,
  to_float64: to_float64,
  // value creation
  new_int: new_int,
  new_bool: new_bool,
  new_float64: new_float64,
  // arithmetic
  add: add,
  sub: sub,
  mul: mul,
  div: div,
  mod: mod,
  // comparisons
  eq: eq,
  ne: ne,
  lt: lt,
  le: le,
  gt: gt,
  ge: ge,
  // unary
  neg: neg,
  inc: inc,
  dec: dec,
  lnot: lnot,
  bnot: bnot,
  // bitwise
  band: band,
  bor: bor,
  bxor: bxor,
  shl: shl,
  shr: shr,
  ushr: ushr,
  // text concat
  concat: concat,
  // decomposed comparisons (int)
  eq_int: eq_int,
  ne_int: ne_int,
  lt_int: lt_int,
  le_int: le_int,
  gt_int: gt_int,
  ge_int: ge_int,
  // decomposed comparisons (float)
  eq_float: eq_float,
  ne_float: ne_float,
  lt_float: lt_float,
  le_float: le_float,
  gt_float: gt_float,
  ge_float: ge_float,
  // decomposed comparisons (text)
  eq_text: eq_text,
  ne_text: ne_text,
  lt_text: lt_text,
  le_text: le_text,
  gt_text: gt_text,
  ge_text: ge_text,
  // decomposed comparisons (bool)
  eq_bool: eq_bool,
  ne_bool: ne_bool,
  // type guard
  is_identical: is_identical
}
