///|
// Completed roots and operand trees are immutable during lifting. Region bodies
// are separate scopes, exactly as in lowering's first-effect analysis. A stable
// sentinel is required because the function keeps growing between queries.
fn hot_lift_impl_first_effect_order(
  func : HotFunc,
  node : NodeId,
  cache : Array[Int32],
  node_visits~ : Array[Int32]? = None,
) -> Int32 {
  if node < 0 || !hot_node_is_live(func, node) {
    return 2147483647
  }
  while cache.length() <= node {
    cache.push(-1)
  }
  if cache[node] >= 0 {
    return cache[node]
  }
  match node_visits {
    Some(visits) => visits[0] += 1
    None => ()
  }
  let own = effects_mask_for_flags(hot_node_flags(func, node)) |
    effects_mask_for_node_exact(func, node)
  cache[node] = if hot_lower_impl_has_external_effects(own) {
    hot_node_get(func, node).order
  } else {
    2147483647
  }
  for input in hot_lower_impl_input_info(func, node).nodes {
    let order = hot_lift_impl_first_effect_order(
      func,
      input,
      cache,
      node_visits~,
    )
    if order < cache[node] {
      cache[node] = order
    }
  }
  cache[node]
}