// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.

///|
// Live locals at a raw branch target. The implicit function label has no
// local live-out: return values are already on the operand stack.
fn ssa_nomerge_raw_label_live(
  labels : Array[@bitset.BitSet],
  label : @lib.LabelIdx,
  local_count : Int32,
) -> @bitset.BitSet {
  let @lib.LabelIdx(raw) = label
  let index = labels.length() - 1 - raw.reinterpret_as_int()
  if index >= 0 && index < labels.length() {
    labels[index]
  } else if index == -1 {
    @bitset.BitSet::new(local_count)
  } else {
    abort("ssa-nomerge liveness: branch depth exceeds the function label")
  }
}

///|
fn ssa_nomerge_raw_loop_live(
  body : Array[@lib.Instruction],
  live_after : @bitset.BitSet,
  labels : Array[@bitset.BitSet],
  body_scans~ : Array[Int32]? = None,
) -> @bitset.BitSet {
  let header = @bitset.BitSet::new(live_after.length())
  let nested_labels = labels.copy()
  nested_labels.push(header)
  // Every transfer is monotone and each local's output bit depends only on
  // that local's input bit. For a monotone Boolean f, f(false) is already a
  // fixed point: false stays false, while true implies f(true) is true. Thus
  // evaluating the vector equation once from the empty header is sufficient.
  match body_scans {
    Some(scans) => scans[0] += 1
    None => ()
  }
  ssa_nomerge_raw_live_before(body, 0, live_after, nested_labels)
}

///|
fn ssa_nomerge_raw_live_before(
  instrs : Array[@lib.Instruction],
  start_idx : Int32,
  live_after : @bitset.BitSet,
  labels : Array[@bitset.BitSet],
) -> @bitset.BitSet {
  let local_count = live_after.length()
  let mut live = live_after.copy()
  for index = instrs.length() - 1; index >= start_idx; index = index - 1 {
    match instrs[index] {
      @lib.LocalGet(@lib.LocalIdx(raw)) => live[raw.reinterpret_as_int()] = true
      @lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) =>
        live[raw.reinterpret_as_int()] = false
      @lib.Br(label) =>
        live = ssa_nomerge_raw_label_live(labels, label, local_count).copy()
      @lib.BrIf(label)
      | @lib.BrOnNull(label)
      | @lib.BrOnNonNull(label)
      | @lib.BrOnCast(label, _, _, _)
      | @lib.BrOnCastFail(label, _, _, _)
      | @lib.BrOnCastDescEq(label, _, _)
      | @lib.BrOnCastDescEqFail(label, _, _) =>
        live = run_hot_pipeline_raw_bitset_union(
          live,
          ssa_nomerge_raw_label_live(labels, label, local_count),
        )
      @lib.BrTable(targets, fallback) => {
        live = ssa_nomerge_raw_label_live(labels, fallback, local_count).copy()
        for target in targets {
          live = run_hot_pipeline_raw_bitset_union(
            live,
            ssa_nomerge_raw_label_live(labels, target, local_count),
          )
        }
      }
      @lib.Block(_, @lib.Expr(body)) => {
        let nested_labels = labels.copy()
        nested_labels.push(live)
        live = ssa_nomerge_raw_live_before(body, 0, live, nested_labels)
      }
      @lib.If(_, then_body, else_body) => {
        let nested_labels = labels.copy()
        nested_labels.push(live)
        let then_live = ssa_nomerge_raw_live_before(
          then_body, 0, live, nested_labels,
        )
        let else_live = match else_body {
          Some(body) =>
            ssa_nomerge_raw_live_before(body, 0, live, nested_labels)
          None => live
        }
        live = run_hot_pipeline_raw_bitset_union(then_live, else_live)
      }
      @lib.Loop(_, @lib.Expr(body)) =>
        live = ssa_nomerge_raw_loop_live(body, live, labels)
      @lib.TryTable(_, _, @lib.Expr(body)) =>
        if run_hot_pipeline_raw_try_table_body_has_exceptional_edge(body) {
          // The raw alias rewriter declines these exception edges. Do not
          // infer dead locals while computing a containing region's inputs.
          live = @bitset.BitSet::make(local_count, true)
        } else {
          let nested_labels = labels.copy()
          nested_labels.push(live)
          live = ssa_nomerge_raw_live_before(body, 0, live, nested_labels)
        }
      @lib.Try(_, _, _, _) => live = @bitset.BitSet::make(local_count, true)
      @lib.Return
      | @lib.ReturnCall(_)
      | @lib.ReturnCallIndirect(_, _)
      | @lib.ReturnCallRef(_)
      | @lib.Throw(_)
      | @lib.ThrowRef
      | @lib.Rethrow(_)
      | @lib.Unreachable => live = @bitset.BitSet::new(local_count)
      _ => ()
    }
  }
  live
}