///|
/// Locations for one variable, not renamed instructions or executable SSA.
/// requires_entry_value means the variable is live before the first block's
/// local definitions: the caller must supply a parameter/initial value or report
/// a missing initialization according to the source language's own rules.
pub(all) struct PhiPlacement {
  variable : Int32
  definitions : Array[Int32]
  live_in : Array[Int32]
  phi_nodes : Array[Int32]
  requires_entry_value : Bool
}

///|
/// A self-contained input snapshot plus backward liveness and pruned IDF sites.
pub(all) struct SsaPlan {
  program : FlowProgram
  liveness : Liveness
  placements : Array[PhiPlacement]
}

///|
/// Plan live-in-pruned phi sites on entry-reachable blocks. Unreachable facts
/// are validated but do not affect the plan. Blocks' uses precede local defs.
/// This does not infer statement ordering, rename variables or rewrite an IR.
pub fn plan_ssa(input : FlowProgram) -> SsaPlan raise GraphError {
  validate_program(input)
  let program : FlowProgram = {
    graph: snapshot_graph(input.graph),
    variables: input.variables.copy(),
    definitions: input.definitions.map(row => row.copy()),
    uses: input.uses.map(row => row.copy()),
  }
  let live = liveness(program)
  let tree = dominators(program.graph)
  let placements : Array[PhiPlacement] = []
  for variable in 0..