///|
fn graph_structure(
  graph : Graph,
  forward : Dominance,
) -> (Array[NaturalLoop], Array[Component]) {
  let n = graph.nodes.length()
  let (succ, pred) = adjacency(graph)

  // Finish an iterative DFS before traversing the reversed graph.
  let seen = Array::make(n, false)
  let finished : Array[Int32] = []
  for start in 0..= 0; index = index - 1 {
    let start = finished[index]
    if assigned[start] {
      continue
    }
    let members = []
    let stack = [start]
    assigned[start] = true
    while !stack.is_empty() {
      let node = stack.pop().unwrap()
      members.push(node)
      for previous in pred[node] {
        if forward.reachable[previous] && !assigned[previous] {
          assigned[previous] = true
          stack.push(previous)
        }
      }
    }
    members.sort()
    groups.push(members)
  }
  groups.sort_by(fn(left, right) { left[0].compare(right[0]) })
  let group_index = Array::make(n, -1)
  for index, members in groups {
    for node in members {
      group_index[node] = index
    }
  }
  let components : Array[Component] = []
  for index, members in groups {
    let entry_nodes = []
    for node in members {
      if node == graph.entry ||
        pred[node].any(fn(previous) {
          forward.reachable[previous] && group_index[previous] != index
        }) {
        entry_nodes.push(node)
      }
    }
    components.push({
      members,
      entry_nodes,
      cyclic: members.length() > 1 || succ[members[0]].contains(members[0]),
      multiple_entries: entry_nodes.length() > 1,
    })
  }

  // Collect all back edges for a header before taking predecessor closure.
  let by_header : Array[Array[Int32]] = Array::makei(n, _ => [])
  for (latch, header) in graph.edges {
    if dominates_validated(forward, header, latch) {
      by_header[header].push(latch)
    }
  }
  let loops : Array[NaturalLoop] = []
  let loop_membership : Array[Array[Bool]] = []
  for header, latches in by_header {
    if latches.is_empty() {
      continue
    }
    latches.sort()
    let included = Array::make(n, false)
    let considered = Array::make(n, false)
    let stack : Array[Int32] = []
    included[header] = true
    considered[header] = true
    for latch in latches {
      if !included[latch] {
        included[latch] = true
        considered[latch] = true
        stack.push(latch)
      }
    }
    while !stack.is_empty() {
      let node = stack.pop().unwrap()
      for previous in pred[node] {
        if considered[previous] || !forward.reachable[previous] {
          continue
        }
        considered[previous] = true
        if dominates_validated(forward, header, previous) {
          included[previous] = true
          stack.push(previous)
        }
      }
    }
    let members = []
    for node in 0..= parent_size {
        continue
      }
      if natural_loop.members.all(fn(node) { loop_membership[candidate][node] }) {
        parent = Some(candidate)
        parent_size = outer.members.length()
      }
    }
    loops[index] = { ..natural_loop, parent, }
  }
  (loops, components)
}