///|
// Internal compilation pipeline.
// The full pass sequence (mirroring cmd/main in upstream qbe) lives here so it
// is reusable as a library. Debug dumps are written into a caller-provided
// buffer instead of stderr, which keeps the pipeline synchronous and makes the
// staged output testable.

///|
// Debug flags (P/A/I/L/M/N/C/F/S/R as in QBE main.c).
priv struct DbFlags {
  mut p : Bool
  mut a : Bool
  mut i : Bool
  mut l : Bool
  mut m : Bool
  mut n : Bool
  mut c : Bool
  mut f : Bool
  mut s : Bool
  mut r : Bool
  mut g : Bool
  mut k : Bool
}

///|
fn DbFlags::new() -> DbFlags {
  DbFlags::{
    p: false,
    a: false,
    i: false,
    l: false,
    m: false,
    n: false,
    c: false,
    f: false,
    s: false,
    r: false,
    g: false,
    k: false,
  }
}

///|
fn set_dbg(dbg : DbFlags, c : UInt16) -> Unit {
  match c {
    'P' | 'p' => dbg.p = true
    'A' | 'a' => dbg.a = true
    'I' | 'i' => dbg.i = true
    'L' | 'l' => dbg.l = true
    'M' | 'm' => dbg.m = true
    'N' | 'n' => dbg.n = true
    'C' | 'c' => dbg.c = true
    'F' | 'f' => dbg.f = true
    'S' | 's' => dbg.s = true
    'R' | 'r' => dbg.r = true
    'G' | 'g' => dbg.g = true
    'K' | 'k' => dbg.k = true
    _ => ()
  }
}

///|
fn dbg_from_flags(flags : String) -> DbFlags {
  let dbg = DbFlags::new()
  for j in 0.. Unit {
  for id in fn_.def_order {
    let b1 = fn_.blks[id]
    if b1.dom_link < 0 {
      continue
    }
    let name = b1.name
    let pad = " ".repeat(
      if 10 > name.length() {
        10 - name.length()
      } else {
        0
      },
    )
    out.write_string(pad)
    out.write_string(name)
    out.write_string(":")
    let mut c = b1.dom_link
    while c >= 0 {
      out.write_string(" ")
      out.write_string(fn_.blks[c].name)
      c = fn_.blks[c].dom_next
    }
    out.write_string("\n")
  }
}

///|
// Print the current function in IL format, mirroring C's printfn + "\n".
fn dbg_function(
  fn_ : @types.Fn,
  interner : @util.Interner,
  typs : Array[@types.Typ],
  out : StringBuilder,
) -> Unit {
  out.write_string(@parser.printfn(fn_, interner, typs))
  out.write_string("\n")
}

///|
// Run the target-independent frontend and SSA pipeline shared by every
// backend. Target-specific ABI lowering and instruction selection follow this
// helper in each backend driver.
fn run_frontend_passes(
  fn_ : @types.Fn,
  interner : @util.Interner,
  typs : Array[@types.Typ],
  dbg : DbFlags,
  cansel : Bool,
  out : StringBuilder,
) -> Unit raise {
  if dbg.p {
    out.write_string("\n> After parsing:\n")
    dbg_function(fn_, interner, typs, out)
  }
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
  @ssa.filluse(fn_)
  // promote (mem.c): -dM "After slot promotion".
  out.write_string(@mem.promote(fn_, dbg.m, interner, typs))
  @ssa.filluse(fn_)
  // ssa construction (ssa.c): filldom/fillfron/filllive/phiins/renblk.
  @cfg.filldom(fn_)
  if dbg.n {
    out.write_string("\n> Dominators:\n")
    dbg_dominators(fn_, out)
  }
  @cfg.fillfron(fn_)
  out.write_string(@live.filllive(fn_, false))
  @ssa.phiins(fn_)
  @ssa.renblk(fn_)
  @ssa.filluse(fn_)
  @ssa.ssacheck(fn_)
  if dbg.n {
    out.write_string("\n> After SSA construction:\n")
    dbg_function(fn_, interner, typs, out)
  }
  @cfg.fillalias(fn_)
  // loadopt (load.c): -dM "After load elimination".
  out.write_string(@ssa.loadopt(fn_, dbg.m, interner, typs))
  @ssa.filluse(fn_)
  @cfg.fillalias(fn_)
  // coalesce (mem.c): -dM "Slot coalescing".
  out.write_string(@mem.coalesce(fn_, dbg.m, interner, typs))
  @ssa.filluse(fn_)
  @cfg.filldom(fn_)
  @ssa.ssacheck(fn_)
  // gvn/gcm (gvn.c/gcm.c): -dG.
  out.write_string(@gvn.gvn(fn_, dbg.g, interner, typs))
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
  // simplcfg (cfg.c): -dC.
  out.write_string(@cfg.simplcfg(fn_, dbg.c, interner, typs))
  @ssa.filluse(fn_)
  @cfg.filldom(fn_)
  out.write_string(@gvn.gcm(fn_, dbg.g, interner, typs))
  @ssa.filluse(fn_)
  @ssa.ssacheck(fn_)
  if cansel {
    // ifconvert (ifopt.c): -dK, amd64 only.
    out.write_string(@ifopt.ifconvert(fn_, dbg.k, interner, typs))
    @cfg.fillrpo(fn_)
    @cfg.fillpreds(fn_)
    @ssa.filluse(fn_)
    @cfg.filldom(fn_)
    @ssa.ssacheck(fn_)
  }
}

///|
// Run the full compilation pipeline on a function (C func() in main.c).
// Debug output is appended to `out` according to the flags; in non-debug mode
// all debug strings are empty so this is silent.
fn run_passes(
  fn_ : @types.Fn,
  interner : @util.Interner,
  typs : Array[@types.Typ],
  dbg : DbFlags,
  out : StringBuilder,
) -> Unit raise {
  @types.init_amd64_target()
  run_frontend_passes(fn_, interner, typs, dbg, true, out)
  // T.abi (sysv.c): -dA.
  out.write_string(@abi_amd64.abi(fn_, typs, dbg.a, interner, typs))
  // simpl (simpl.c), then refill cfg/use.
  out.write_string(@ifopt.simpl(fn_, false, interner, typs))
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
  @ssa.filluse(fn_)
  // T.isel (amd64/isel.c): -dI.
  out.write_string(@isel.isel(fn_, interner, dbg.i, typs))
  // Post-isel pipeline (main.c): fillrpo, filllive, fillcost, spill, rega.
  @types.init_amd64_target()
  @cfg.fillrpo(fn_)
  out.write_string(@live.filllive(fn_, dbg.l))
  out.write_string(@spill.fillcost(fn_, dbg.s))
  out.write_string(@spill.spill(fn_, dbg.s, interner, typs))
  out.write_string(@rega.rega(fn_, dbg.r, interner, typs))
  @cfg.fillrpo(fn_)
  @cfg.simpljmp(fn_)
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
}

///|
// Parse IL text into functions, data sections, top-level order and types.
fn parse_module(
  text : String,
  file_name : String,
) -> (
  Array[@types.Fn],
  Array[@types.Dat],
  Array[String],
  Array[@types.Typ],
  @util.Interner,
) raise {
  let lexer = @lexer.Lexer::new(text, file_name)
  let tokens = lexer.tokenize()
  let parser = @parser.Parser::new(tokens, file_name)
  parser.parse()
  (
    parser.get_funcs(),
    parser.get_datas(),
    parser.get_order(),
    parser.get_typs(),
    parser.interner_ref(),
  )
}

///|
// Translate the GAS flavor string into (gasloc, gassym, apple), matching
// QBE's -G. "m" is the Mach-O flavor.
fn gas_setting(gas : String) -> (String, String, Bool) raise {
  match gas {
    "e" => (".L", "", false)
    "m" => ("L", "_", true)
    g => raise @util.QbeError::CompileError("unknown gas flavor '\{g}'")
  }
}

///|
// Emit assembly for the whole module in input order (data and functions
// interleaved as parsed).
fn emit_module(
  funcs : Array[@types.Fn],
  datas : Array[@types.Dat],
  order : Array[String],
  typs : Array[@types.Typ],
  interner : @util.Interner,
  gasloc : String,
  gassym : String,
  apple : Bool,
  sb : StringBuilder,
) -> Unit raise {
  @types.fp_stash_reset()
  let mut fi = 0
  let mut di = 0
  for item in order {
    if item == "f" {
      let fn_ = funcs[fi]
      fi = fi + 1
      run_passes(fn_, interner, typs, DbFlags::new(), sb)
      @emit_amd64.emitfn(fn_, interner, gasloc, gassym, apple, sb)
      sb.write_string("/* end function \{fn_.name} */\n\n")
    } else {
      while di < datas.length() {
        let d = datas[di]
        di = di + 1
        @emit_amd64.gasemitdat(d, gasloc, gassym, sb)
        if d.kind == @types.DEnd {
          sb.write_string("/* end data */\n\n")
          break
        }
      }
    }
  }
  @emit_amd64.gasemitfin(gasloc, apple, sb)
}

///|
// Run wasm compilation pipeline on a function.
// Reuses SSA passes (target-agnostic), skips spill/rega (wasm has no
// physical registers), and runs wasm-specific ABI + isel.
fn run_passes_wasm(
  fn_ : @types.Fn,
  interner : @util.Interner,
  typs : Array[@types.Typ],
  dbg : DbFlags,
  out : StringBuilder,
) -> Unit raise {
  run_frontend_passes(fn_, interner, typs, dbg, false, out)
  // Wasm ABI lowering
  out.write_string(@abi_wasm.abi_wasm(fn_, typs, dbg.a, interner, typs))
  @cfg.fillpreds(fn_)
  @ssa.filluse(fn_)
  // Wasm instruction selection
  out.write_string(@isel_wasm.isel_wasm(fn_, interner, dbg.i, typs))
  // Skip spill/rega — wasm has no physical registers
}

///|
// Emit WAT module for the whole module in input order.
fn emit_wasm_module(
  funcs : Array[@types.Fn],
  datas : Array[@types.Dat],
  order : Array[String],
  typs : Array[@types.Typ],
  interner : @util.Interner,
  sb : StringBuilder,
) -> Unit raise {
  @types.fp_stash_reset()
  for fn_ in funcs {
    run_passes_wasm(fn_, interner, typs, DbFlags::new(), sb)
  }
  @emit_wasm.emit_wat_module(funcs, datas, order, interner, sb)
}

///|
// Run RISC-V compilation pipeline on a function.
// Reuses SSA passes (target-agnostic), skips spill/rega (RISC-V uses a
// different register allocation strategy), and runs RISC-V-specific ABI + isel.
fn run_passes_rv64(
  fn_ : @types.Fn,
  interner : @util.Interner,
  typs : Array[@types.Typ],
  dbg : DbFlags,
  out : StringBuilder,
) -> Unit raise {
  @types.init_rv64_target()
  run_frontend_passes(fn_, interner, typs, dbg, false, out)
  // RISC-V ABI lowering
  out.write_string(@abi_rv64.abi_rv64(fn_, typs, dbg.a, interner, typs))
  out.write_string(@ifopt.simpl(fn_, false, interner, typs))
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
  @ssa.filluse(fn_)
  // RISC-V instruction selection
  out.write_string(@isel_rv64.isel_rv64(fn_, interner, dbg.i, typs))
  // Post-isel: fillrpo, filllive, fillcost, spill, rega.
  @types.init_rv64_target()
  @cfg.fillrpo(fn_)
  out.write_string(@live.filllive(fn_, dbg.l))
  out.write_string(@spill.fillcost(fn_, dbg.s))
  out.write_string(@spill.spill(fn_, dbg.s, interner, typs))
  out.write_string(@rega.rega(fn_, dbg.r, interner, typs))
  @cfg.fillrpo(fn_)
  @cfg.simpljmp(fn_)
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
}

///|
// Emit RISC-V assembly for the whole module in input order.
fn emit_rv64_module(
  funcs : Array[@types.Fn],
  datas : Array[@types.Dat],
  order : Array[String],
  typs : Array[@types.Typ],
  interner : @util.Interner,
  gasloc : String,
  gassym : String,
  sb : StringBuilder,
) -> Unit raise {
  @types.fp_stash_reset()
  @emit_rv64.rv64_emit_reset()
  let mut fi = 0
  let mut di = 0
  for item in order {
    if item == "f" {
      let fn_ = funcs[fi]
      fi = fi + 1
      run_passes_rv64(fn_, interner, typs, DbFlags::new(), sb)
      sb.write_string(
        @emit_rv64.emit_rv64(fn_, interner, gasloc, gassym, false, typs),
      )
      sb.write_string("/* end function \{fn_.name} */\n\n")
    } else {
      while di < datas.length() {
        let d = datas[di]
        di = di + 1
        @emit_rv64.gasemitdat_rv64(d, gasloc, gassym, sb)
        if d.kind == @types.DEnd {
          sb.write_string("/* end data */\n\n")
          break
        }
      }
    }
  }
  @emit_rv64.gasemitfin_rv64(gasloc, sb)
}

///|
// Run LoongArch64 (la64, LP64D) compilation pipeline on a function.
// Reuses the target-agnostic SSA passes, runs the LoongArch ABI + isel, then
// the shared spill/rega passes with the la64 machine configuration.
fn run_passes_la64(
  fn_ : @types.Fn,
  interner : @util.Interner,
  typs : Array[@types.Typ],
  dbg : DbFlags,
  out : StringBuilder,
) -> Unit raise {
  @types.init_la64_target()
  run_frontend_passes(fn_, interner, typs, dbg, false, out)
  // LoongArch ABI lowering
  out.write_string(@abi_la64.abi_la64(fn_, typs, dbg.a, interner, typs))
  out.write_string(@ifopt.simpl(fn_, false, interner, typs))
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
  @ssa.filluse(fn_)
  // LoongArch instruction selection
  out.write_string(@isel_la64.isel_la64(fn_, interner, dbg.i, typs))
  // Post-isel: fillrpo, filllive, fillcost, spill, rega.
  @types.init_la64_target()
  @cfg.fillrpo(fn_)
  out.write_string(@live.filllive(fn_, dbg.l))
  out.write_string(@spill.fillcost(fn_, dbg.s))
  out.write_string(@spill.spill(fn_, dbg.s, interner, typs))
  out.write_string(@rega.rega(fn_, dbg.r, interner, typs))
  @cfg.fillrpo(fn_)
  @cfg.simpljmp(fn_)
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
}

///|
// Emit LoongArch assembly for the whole module in input order (data and
// functions interleaved as parsed, followed by the float constant pool).
fn emit_la64_module(
  funcs : Array[@types.Fn],
  datas : Array[@types.Dat],
  order : Array[String],
  typs : Array[@types.Typ],
  interner : @util.Interner,
  gasloc : String,
  gassym : String,
  sb : StringBuilder,
) -> Unit raise {
  @types.fp_stash_reset()
  let mut fi = 0
  let mut di = 0
  for item in order {
    if item == "f" {
      let fn_ = funcs[fi]
      fi = fi + 1
      run_passes_la64(fn_, interner, typs, DbFlags::new(), sb)
      sb.write_string(@emit_la64.emit_la64(fn_, interner, gasloc, gassym, typs))
      sb.write_string("/* end function \{fn_.name} */\n\n")
    } else {
      while di < datas.length() {
        let d = datas[di]
        di = di + 1
        @emit_la64.gasemitdat_la64(d, gasloc, gassym, sb)
        if d.kind == @types.DEnd {
          sb.write_string("/* end data */\n\n")
          break
        }
      }
    }
  }
  @emit_la64.gasemitfin_la64(gasloc, sb)
}

///|
// Run ARM64 (AArch64, AAPCS64 ELF) compilation pipeline on a function.
// Reuses the target-agnostic SSA passes, runs the ARM64 ABI + isel, then the
// shared spill/rega passes with the arm64 machine configuration.
fn run_passes_arm64(
  fn_ : @types.Fn,
  interner : @util.Interner,
  typs : Array[@types.Typ],
  dbg : DbFlags,
  apple : Bool,
  out : StringBuilder,
) -> Unit raise {
  @types.init_arm64_target()
  run_frontend_passes(fn_, interner, typs, dbg, false, out)
  // ARM64 ABI lowering
  out.write_string(
    @abi_arm64.abi_arm64(fn_, typs, dbg.a, interner, apple, typs),
  )
  out.write_string(@ifopt.simpl(fn_, false, interner, typs))
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
  @ssa.filluse(fn_)
  // ARM64 instruction selection
  out.write_string(@isel_arm64.isel_arm64(fn_, interner, dbg.i, typs))
  // Post-isel: fillrpo, filllive, fillcost, spill, rega.
  @types.init_arm64_target()
  @cfg.fillrpo(fn_)
  out.write_string(@live.filllive(fn_, dbg.l))
  out.write_string(@spill.fillcost(fn_, dbg.s))
  out.write_string(@spill.spill(fn_, dbg.s, interner, typs))
  out.write_string(@rega.rega(fn_, dbg.r, interner, typs))
  @cfg.fillrpo(fn_)
  @cfg.simpljmp(fn_)
  @cfg.fillrpo(fn_)
  @cfg.fillpreds(fn_)
}

///|
// Emit ARM64 assembly for the whole module in input order (data and functions
// interleaved as parsed, followed by the float constant pool).
fn emit_arm64_module(
  funcs : Array[@types.Fn],
  datas : Array[@types.Dat],
  order : Array[String],
  typs : Array[@types.Typ],
  interner : @util.Interner,
  gasloc : String,
  gassym : String,
  apple : Bool,
  sb : StringBuilder,
) -> Unit raise {
  @types.fp_stash_reset()
  @emit_arm64.arm64_emit_reset()
  let mut fi = 0
  let mut di = 0
  for item in order {
    if item == "f" {
      let fn_ = funcs[fi]
      fi = fi + 1
      run_passes_arm64(fn_, interner, typs, DbFlags::new(), apple, sb)
      sb.write_string(
        @emit_arm64.emit_arm64(fn_, interner, typs, gasloc, gassym, apple),
      )
      sb.write_string("/* end function \{fn_.name} */\n\n")
    } else {
      while di < datas.length() {
        let d = datas[di]
        di = di + 1
        @emit_amd64.gasemitdat(d, gasloc, gassym, sb)
        if d.kind == @types.DEnd {
          sb.write_string("/* end data */\n\n")
          break
        }
      }
    }
  }
  @emit_amd64.gasemitfin(gasloc, apple, sb)
}