// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Copyright 2023 WebAssembly Community Group participants.
// Retained historical upstream notice; current derivation remains unconfirmed.
// Conservative attribution from pinned Binaryen version_130 counterpart:
// Copyright 2016 WebAssembly Community Group participants
// Counterpart: src/passes/CodePushing.cpp (5d704ad5); origin remains unconfirmed.
// 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.

///|
pub fn code_pushing_descriptor() -> @ir.HotPassDescriptor {
  @ir.HotPassDescriptor::new("code-pushing", invalidates=[
    @ir.HotAnalysis::cfg(),
    @ir.HotAnalysis::dominance(),
    @ir.HotAnalysis::liveness(),
    @ir.HotAnalysis::use_def(),
    @ir.HotAnalysis::effects(),
    @ir.HotAnalysis::loop_info(),
    @ir.HotAnalysis::ssa(),
  ])
}

///|
pub fn code_pushing_summary() -> String {
  "Sink safe local computations into the control-flow arm that actually consumes them."
}

///|
fn code_pushing_exact_instr_can_trap(instr : @lib.Instruction?) -> Bool {
  match instr {
    Some(@lib.Instruction::I32DivS)
    | Some(@lib.Instruction::I32DivU)
    | Some(@lib.Instruction::I32RemS)
    | Some(@lib.Instruction::I32RemU)
    | Some(@lib.Instruction::I64DivS)
    | Some(@lib.Instruction::I64DivU)
    | Some(@lib.Instruction::I64RemS)
    | Some(@lib.Instruction::I64RemU) => true
    _ => false
  }
}

///|
fn code_pushing_nonnull_struct_get_source_local(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Int32 {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return -1
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Heap => ()
    _ => return -1
  }
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.Instruction::StructGet(_, _))
    | Some(@lib.Instruction::StructGetS(_, _))
    | Some(@lib.Instruction::StructGetU(_, _)) => ()
    _ => return -1
  }
  if @ir.hot_child_count(func, node_id) != 1 {
    return -1
  }
  let source_id = @ir.hot_child_get(func, node_id, 0)
  if !code_pushing_node_is_local_get(func, source_id) {
    return -1
  }
  match @ir.hot_type_results(func, @ir.hot_node_type(func, source_id)) {
    [@lib.RefTypeValType(rt)] if rt.is_non_nullable() =>
      @ir.hot_node_get(func, source_id).imm0
    _ => -1
  }
}

///|
fn code_pushing_node_is_nontrapping_struct_get_from_local_get(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  code_pushing_nonnull_struct_get_source_local(func, node_id) >= 0
}

///|
fn code_pushing_node_is_memory_load(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.Instruction::I32Load(_))
    | Some(@lib.Instruction::I64Load(_))
    | Some(@lib.Instruction::F32Load(_))
    | Some(@lib.Instruction::F64Load(_))
    | Some(@lib.Instruction::I32Load8S(_))
    | Some(@lib.Instruction::I32Load8U(_))
    | Some(@lib.Instruction::I32Load16S(_))
    | Some(@lib.Instruction::I32Load16U(_))
    | Some(@lib.Instruction::I64Load8S(_))
    | Some(@lib.Instruction::I64Load8U(_))
    | Some(@lib.Instruction::I64Load16S(_))
    | Some(@lib.Instruction::I64Load16U(_))
    | Some(@lib.Instruction::I64Load32S(_))
    | Some(@lib.Instruction::I64Load32U(_)) => true
    _ => false
  }
}

///|
fn code_pushing_node_is_exact_no_effects_intrinsic_call(
  func : @ir.HotFunc,
  node_id : Int32,
  module_ctx : @ir.HotModuleContext?,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Call => ()
    _ => return false
  }
  let matches_intrinsic = match module_ctx {
    Some(ctx) =>
      @ir.hot_module_func_import_matches(
        ctx,
        @lib.FuncIdx::new(node.imm0.reinterpret_as_uint()),
        "binaryen-intrinsics",
        "call.without.effects",
      )
    None => false
  }
  if !matches_intrinsic {
    return false
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    let child_id = @ir.hot_child_get(func, node_id, slot)
    if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
      return false
    }
    if !@ir.effects_is_pure(@ir.effects_for_node(func, child_id)) ||
      @ir.hot_may_trap(func, child_id) {
      return false
    }
  }
  true
}

///|
fn code_pushing_relaxed_operands_are_movable(
  func : @ir.HotFunc,
  node_id : Int32,
  traps_never_happen : Bool,
  depth : Int32,
) -> Bool {
  if depth <= 0 {
    return false
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    let child = @ir.hot_child_get(func, node_id, slot)
    if child < 0 || !@ir.hot_node_is_live(func, child) {
      return false
    }
    match @ir.hot_node_get(func, child).op {
      @ir.LocalGet | @ir.GlobalGet | @ir.Const | @ir.RefNull | @ir.RefFunc => ()
      @ir.Unary
      | @ir.Binary
      | @ir.Compare
      | @ir.Convert
      | @ir.RefIsNull
      | @ir.TupleMake
      | @ir.TupleExtract =>
        if @ir.hot_may_trap(func, child) && !traps_never_happen {
          return false
        }
      _ => return false
    }
    if !code_pushing_relaxed_operands_are_movable(
        func,
        child,
        traps_never_happen,
        depth - 1,
      ) {
      return false
    }
  }
  true
}

///|
fn code_pushing_node_is_movable_value(
  func : @ir.HotFunc,
  node_id : Int32,
  traps_never_happen~ : Bool = false,
  ignore_implicit_traps~ : Bool = false,
  module_ctx~ : @ir.HotModuleContext? = None,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let exact_instr_can_trap = code_pushing_exact_instr_can_trap(
    @ir.hot_node_exact_instr(func, node_id),
  )
  if !traps_never_happen && exact_instr_can_trap {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  let movable_shape = match node.op {
    @ir.HotOp::Const
    | @ir.HotOp::Unary
    | @ir.HotOp::Binary
    | @ir.HotOp::Compare
    | @ir.HotOp::Convert
    | @ir.HotOp::RefNull
    | @ir.HotOp::RefIsNull
    | @ir.HotOp::RefFunc
    | @ir.HotOp::TupleMake
    | @ir.HotOp::TupleExtract =>
      @ir.effects_is_pure(@ir.effects_for_node(func, node_id)) ||
      (
        traps_never_happen &&
        exact_instr_can_trap &&
        code_pushing_relaxed_operands_are_movable(func, node_id, true, 64)
      )
    @ir.HotOp::GlobalGet => true
    @ir.HotOp::Load =>
      ignore_implicit_traps &&
      code_pushing_node_is_memory_load(func, node_id) &&
      code_pushing_relaxed_operands_are_movable(
        func, node_id, traps_never_happen, 64,
      )
    @ir.HotOp::Call =>
      code_pushing_node_is_exact_no_effects_intrinsic_call(
        func, node_id, module_ctx,
      )
    @ir.HotOp::Heap =>
      code_pushing_node_is_nontrapping_struct_get_from_local_get(func, node_id)
    _ => false
  }
  movable_shape
}

///|
fn code_pushing_node_is_global_get(func : @ir.HotFunc, node_id : Int32) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::GlobalGet => true
    _ => false
  }
}

///|
fn code_pushing_node_has_call_or_nonglobal_write_effect(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  let mask = @ir.effects_for_node(func, node_id)
  (
    mask &
    (
      @ir.EFFECT_MASK_CALL |
      @ir.EFFECT_MASK_THROW |
      @ir.EFFECT_MASK_MEMORY_WRITE |
      @ir.EFFECT_MASK_TABLE_WRITE
    )
  ) !=
  0U
}

///|
fn code_pushing_node_is_pure_nop_or_drop_const(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Nop => true
    @ir.HotOp::Drop if @ir.hot_child_count(func, node_id) == 1 => {
      let child_id = @ir.hot_child_get(func, node_id, 0)
      if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
        return false
      }
      match @ir.hot_node_get(func, child_id).op {
        @ir.HotOp::Const =>
          @ir.effects_is_pure(@ir.effects_for_node(func, node_id))
        _ => false
      }
    }
    _ => false
  }
}

///|
fn code_pushing_subtree_writes_global(
  func : @ir.HotFunc,
  node_id : Int32,
  global_id : Int32,
  module_ctx~ : @ir.HotModuleContext? = None,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return true
  }
  let node = @ir.hot_node_get(func, node_id)
  if node.op is @ir.HotOp::GlobalSet &&
    !globals_are_provably_disjoint(module_ctx, node.imm0, global_id) {
    return true
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if code_pushing_subtree_writes_global(
        func,
        @ir.hot_child_get(func, node_id, slot),
        global_id,
        module_ctx~,
      ) {
      return true
    }
  }
  false
}

///|
fn code_pushing_direct_global_set_disjoint_from_global_get(
  func : @ir.HotFunc,
  node_id : Int32,
  value_global_id : Int32,
  module_ctx~ : @ir.HotModuleContext? = None,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::GlobalSet if globals_are_provably_disjoint(
        module_ctx,
        node.imm0,
        value_global_id,
      ) => {
      for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
        if code_pushing_node_has_call_or_nonglobal_write_effect(
            func,
            @ir.hot_child_get(func, node_id, slot),
          ) ||
          code_pushing_subtree_writes_global(
            func,
            @ir.hot_child_get(func, node_id, slot),
            value_global_id,
            module_ctx~,
          ) {
          return false
        }
      }
      true
    }
    _ => false
  }
}

///|
fn code_pushing_node_is_disjoint_global_set_for_global_get(
  func : @ir.HotFunc,
  node_id : Int32,
  value_id : Int32,
  module_ctx~ : @ir.HotModuleContext? = None,
) -> Bool {
  if !code_pushing_node_is_global_get(func, value_id) ||
    node_id < 0 ||
    !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let value_global_id = @ir.hot_node_get(func, value_id).imm0
  if code_pushing_direct_global_set_disjoint_from_global_get(
      func,
      node_id,
      value_global_id,
      module_ctx~,
    ) {
    return true
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Block => {
      if code_pushing_node_has_call_or_nonglobal_write_effect(func, node_id) {
        return false
      }
      let body_ref = @ir.hot_region_body_ref(func, node_id)
      if @ir.hot_region_root_count(func, body_ref) == 0 {
        return false
      }
      for idx = 0
          idx < @ir.hot_region_root_count(func, body_ref)
          idx = idx + 1 {
        let root_id = @ir.hot_region_get(func, body_ref, idx)
        if !code_pushing_direct_global_set_disjoint_from_global_get(
            func,
            root_id,
            value_global_id,
            module_ctx~,
          ) &&
          !code_pushing_node_is_pure_nop_or_drop_const(func, root_id) {
          return false
        }
      }
      true
    }
    _ => false
  }
}

///|
fn code_pushing_node_is_local_get(func : @ir.HotFunc, node_id : Int32) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::LocalGet => true
    _ => false
  }
}

///|
fn code_pushing_node_has_any_local_get(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalGet => return true
    _ => ()
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if code_pushing_node_has_any_local_get(
        func,
        @ir.hot_child_get(func, node_id, slot),
      ) {
      return true
    }
  }
  false
}

///|
fn code_pushing_effects_safe_to_cross_for_value(mask : UInt32) -> Bool {
  (
    mask &
    (
      @ir.EFFECT_MASK_CALL |
      @ir.EFFECT_MASK_GLOBAL_STATE |
      @ir.EFFECT_MASK_MEMORY_WRITE
    )
  ) ==
  0U
}

///|
fn code_pushing_effects_safe_to_cross_for_heap_read(mask : UInt32) -> Bool {
  (
    mask &
    (
      @ir.EFFECT_MASK_CALL |
      @ir.EFFECT_MASK_THROW |
      @ir.EFFECT_MASK_GLOBAL_STATE |
      @ir.EFFECT_MASK_MEMORY_WRITE |
      @ir.EFFECT_MASK_TABLE_WRITE
    )
  ) ==
  0U
}

///|
fn code_pushing_node_has_rethrow_with_visited(
  func : @ir.HotFunc,
  node_id : Int32,
  visited : Array[Bool],
) -> Bool {
  if node_id < 0 ||
    node_id >= visited.length() ||
    visited[node_id] ||
    !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  visited[node_id] = true
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Rethrow => return true
    _ => ()
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if code_pushing_node_has_rethrow_with_visited(
        func,
        @ir.hot_child_get(func, node_id, slot),
        visited,
      ) {
      return true
    }
  }
  let region_has_rethrow = fn(region_ref : @ir.HotRegionRef) -> Bool {
    for idx = 0
        idx < @ir.hot_region_root_count(func, region_ref)
        idx = idx + 1 {
      if code_pushing_node_has_rethrow_with_visited(
          func,
          @ir.hot_region_get(func, region_ref, idx),
          visited,
        ) {
        return true
      }
    }
    false
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      region_has_rethrow(@ir.hot_region_body_ref(func, node_id))
    @ir.HotOp::If => {
      if region_has_rethrow(@ir.hot_region_then_ref(func, node_id)) {
        return true
      }
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_ref) => region_has_rethrow(else_ref)
        None => false
      }
    }
    @ir.HotOp::Try => {
      if region_has_rethrow(@ir.hot_region_body_ref(func, node_id)) {
        return true
      }
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_ref) => region_has_rethrow(catch_ref)
        None => false
      }
    }
    @ir.HotOp::TryTable =>
      region_has_rethrow(@ir.hot_region_body_ref(func, node_id)) ||
      region_has_rethrow(@ir.hot_region_catch_list_ref(func, node_id))
    _ => false
  }
}

///|
fn code_pushing_node_has_rethrow(func : @ir.HotFunc, node_id : Int32) -> Bool {
  code_pushing_node_has_rethrow_with_visited(
    func,
    node_id,
    Array::make(@ir.hot_node_count(func), false),
  )
}

///|
fn code_pushing_node_has_segment_order_barrier(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return true
  }
  let op = @ir.hot_node_get(func, node_id).op
  match op {
    @ir.HotOp::Throw
    | @ir.HotOp::Rethrow
    | @ir.HotOp::Try
    | @ir.HotOp::TryTable => true
    _ => {
      if code_pushing_node_has_rethrow(func, node_id) {
        return true
      }
      let mask = @ir.effects_for_node(func, node_id)
      (mask & @ir.EFFECT_MASK_CALL) != 0U
    }
  }
}

///|
fn code_pushing_node_safe_to_cross_for_value(
  func : @ir.HotFunc,
  node_id : Int32,
  value_id : Int32,
  module_ctx~ : @ir.HotModuleContext? = None,
  depth~ : Int32 = 64,
) -> Bool {
  if depth <= 0 || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  if code_pushing_node_has_rethrow(func, node_id) {
    return false
  }
  for slot = 0; slot < @ir.hot_child_count(func, value_id); slot = slot + 1 {
    if !code_pushing_node_safe_to_cross_for_value(
        func,
        node_id,
        @ir.hot_child_get(func, value_id, slot),
        module_ctx~,
        depth=depth - 1,
      ) {
      return false
    }
  }
  if code_pushing_node_is_global_get(func, value_id) {
    code_pushing_effects_safe_to_cross_for_value(
      @ir.effects_for_node(func, node_id),
    ) ||
    code_pushing_node_is_disjoint_global_set_for_global_get(
      func,
      node_id,
      value_id,
      module_ctx~,
    )
  } else if code_pushing_node_is_local_get(func, value_id) {
    let (_, writes) = code_pushing_count_local_gets_writes_in_node_bounded(
      func,
      node_id,
      @ir.hot_node_get(func, value_id).imm0,
      0,
      0,
    )
    writes == 0
  } else {
    if code_pushing_node_is_memory_load(func, value_id) {
      return code_pushing_effects_safe_to_cross_for_heap_read(
        @ir.effects_for_node(func, node_id),
      )
    }
    let source_local = code_pushing_nonnull_struct_get_source_local(
      func, value_id,
    )
    if source_local >= 0 {
      let (_, writes) = code_pushing_count_local_gets_writes_in_node_bounded(
        func, node_id, source_local, 0, 0,
      )
      writes == 0 &&
      code_pushing_effects_safe_to_cross_for_heap_read(
        @ir.effects_for_node(func, node_id),
      )
    } else {
      true
    }
  }
}

///|
fn code_pushing_node_writes_local_deep_limited(
  func : @ir.HotFunc,
  node_id : Int32,
  local_id : Int32,
  depth : Int32,
) -> Bool {
  if depth <= 0 {
    return true
  }
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalSet | @ir.HotOp::LocalTee if node.imm0 == local_id =>
      return true
    _ => ()
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if code_pushing_node_writes_local_deep_limited(
        func,
        @ir.hot_child_get(func, node_id, slot),
        local_id,
        depth - 1,
      ) {
      return true
    }
  }
  let region_has_write = fn(region_ref : @ir.HotRegionRef) -> Bool {
    for idx = 0
        idx < @ir.hot_region_root_count(func, region_ref)
        idx = idx + 1 {
      if code_pushing_node_writes_local_deep_limited(
          func,
          @ir.hot_region_get(func, region_ref, idx),
          local_id,
          depth - 1,
        ) {
        return true
      }
    }
    false
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      region_has_write(@ir.hot_region_body_ref(func, node_id))
    @ir.HotOp::If => {
      if region_has_write(@ir.hot_region_then_ref(func, node_id)) {
        return true
      }
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_ref) => region_has_write(else_ref)
        None => false
      }
    }
    _ => false
  }
}

///|
fn code_pushing_count_local_gets_in_node(
  func : @ir.HotFunc,
  node_id : Int32,
  local_id : Int32,
) -> Int32 {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return 0
  }
  let node = @ir.hot_node_get(func, node_id)
  let mut count = match node.op {
    @ir.HotOp::LocalGet if node.imm0 == local_id => 1
    _ => 0
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    count += code_pushing_count_local_gets_in_node(
      func,
      @ir.hot_child_get(func, node_id, slot),
      local_id,
    )
  }
  count
}

///|
// Stop only when BOTH queries exceed their budgets. Until then, a component
// below its budget still requires exhaustive traversal (especially zero writes).
// Early exits return both sentinels, never a partial negative proof.
fn code_pushing_count_local_gets_writes_in_node_bounded(
  func : @ir.HotFunc,
  node_id : Int32,
  local_id : Int32,
  max_gets : Int32,
  max_writes : Int32,
) -> (Int32, Int32) {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return (0, 0)
  }
  let node = @ir.hot_node_get(func, node_id)
  let mut gets = match node.op {
    @ir.HotOp::LocalGet if node.imm0 == local_id => 1
    _ => 0
  }
  let mut writes = match node.op {
    @ir.HotOp::LocalSet if node.imm0 == local_id => 1
    @ir.HotOp::LocalTee if node.imm0 == local_id => 1
    _ => 0
  }
  if gets > max_gets && writes > max_writes {
    return (max_gets + 1, max_writes + 1)
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    let (child_gets, child_writes) = code_pushing_count_local_gets_writes_in_node_bounded(
      func,
      @ir.hot_child_get(func, node_id, slot),
      local_id,
      max_gets - gets,
      max_writes - writes,
    )
    gets += child_gets
    writes += child_writes
    if gets > max_gets && writes > max_writes {
      return (max_gets + 1, max_writes + 1)
    }
  }
  (gets, writes)
}

///|
fn code_pushing_count_local_gets_in_region(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  local_id : Int32,
) -> Int32 {
  let mut count = 0
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    count += code_pushing_count_local_gets_in_node(
      func,
      @ir.hot_region_get(func, region_ref, idx),
      local_id,
    )
  }
  count
}

///|
fn code_pushing_count_local_gets_writes_in_region_prefix_bounded(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  local_id : Int32,
  end_idx : Int32,
  max_gets : Int32,
  max_writes : Int32,
) -> (Int32, Int32) {
  let mut gets = 0
  let mut writes = 0
  let root_count = @ir.hot_region_root_count(func, region_ref)
  let stop_idx = if end_idx < root_count { end_idx } else { root_count }
  for idx = 0; idx < stop_idx; idx = idx + 1 {
    let (root_gets, root_writes) = code_pushing_count_local_gets_writes_in_node_bounded(
      func,
      @ir.hot_region_get(func, region_ref, idx),
      local_id,
      max_gets - gets,
      max_writes - writes,
    )
    gets += root_gets
    writes += root_writes
    if gets > max_gets && writes > max_writes {
      return (max_gets + 1, max_writes + 1)
    }
  }
  (gets, writes)
}

///|
fn code_pushing_count_local_gets_writes_in_region_bounded(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  local_id : Int32,
  max_gets : Int32,
  max_writes : Int32,
) -> (Int32, Int32) {
  code_pushing_count_local_gets_writes_in_region_prefix_bounded(
    func,
    region_ref,
    local_id,
    @ir.hot_region_root_count(func, region_ref),
    max_gets,
    max_writes,
  )
}

///|
fn code_pushing_count_local_gets_writes_in_region_suffix_bounded(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  local_id : Int32,
  start_idx : Int32,
  max_gets : Int32,
  max_writes : Int32,
) -> (Int32, Int32) {
  let mut gets = 0
  let mut writes = 0
  let root_count = @ir.hot_region_root_count(func, region_ref)
  let mut idx = if start_idx > 0 { start_idx } else { 0 }
  while idx < root_count {
    let (root_gets, root_writes) = code_pushing_count_local_gets_writes_in_node_bounded(
      func,
      @ir.hot_region_get(func, region_ref, idx),
      local_id,
      max_gets - gets,
      max_writes - writes,
    )
    gets += root_gets
    writes += root_writes
    if gets > max_gets && writes > max_writes {
      return (max_gets + 1, max_writes + 1)
    }
    idx += 1
  }
  (gets, writes)
}

///|
fn code_pushing_region_writes_local_deep_limited(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  local_id : Int32,
  depth : Int32,
) -> Bool {
  if depth <= 0 {
    return true
  }
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    if code_pushing_node_writes_local_deep_limited(
        func,
        @ir.hot_region_get(func, region_ref, idx),
        local_id,
        depth,
      ) {
      return true
    }
  }
  false
}

///|
fn code_pushing_if_arms_write_local_deep_limited(
  func : @ir.HotFunc,
  if_id : Int32,
  local_id : Int32,
  depth : Int32,
) -> Bool {
  if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
    return false
  }
  if code_pushing_region_writes_local_deep_limited(
      func,
      @ir.hot_region_then_ref(func, if_id),
      local_id,
      depth,
    ) {
    return true
  }
  match @ir.hot_region_else_ref(func, if_id) {
    Some(else_ref) =>
      code_pushing_region_writes_local_deep_limited(
        func, else_ref, local_id, depth,
      )
    None => false
  }
}

///|
fn code_pushing_count_local_gets_in_if_arms(
  func : @ir.HotFunc,
  if_id : Int32,
  local_id : Int32,
) -> (Int32, Int32) {
  let then_gets = code_pushing_count_local_gets_in_region(
    func,
    @ir.hot_region_then_ref(func, if_id),
    local_id,
  )
  let else_gets = match @ir.hot_region_else_ref(func, if_id) {
    Some(else_ref) =>
      code_pushing_count_local_gets_in_region(func, else_ref, local_id)
    None => 0
  }
  (then_gets, else_gets)
}

///|
fn code_pushing_count_total_local_gets_writes_bounded(
  func : @ir.HotFunc,
  local_id : Int32,
  max_gets : Int32,
  max_writes : Int32,
) -> (Int32, Int32) {
  code_pushing_count_local_gets_writes_in_region_bounded(
    func,
    @ir.HotRegionRef::root(),
    local_id,
    max_gets,
    max_writes,
  )
}

///|
fn code_pushing_count_unique_local_gets_writes_in_node_bounded(
  func : @ir.HotFunc,
  node_id : Int32,
  local_id : Int32,
  max_gets : Int32,
  max_writes : Int32,
  visited : Array[Bool],
) -> (Int32, Int32) {
  if node_id < 0 ||
    node_id >= visited.length() ||
    !@ir.hot_node_is_live(func, node_id) {
    return (0, 0)
  }
  if visited[node_id] {
    return (0, 0)
  }
  visited[node_id] = true
  let node = @ir.hot_node_get(func, node_id)
  let mut gets = 0
  let mut writes = 0
  match node.op {
    @ir.HotOp::LocalGet if node.imm0 == local_id => gets += 1
    @ir.HotOp::LocalSet | @ir.HotOp::LocalTee if node.imm0 == local_id =>
      writes += 1
    _ => ()
  }
  if gets > max_gets && writes > max_writes {
    return (max_gets + 1, max_writes + 1)
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    let (child_gets, child_writes) = code_pushing_count_unique_local_gets_writes_in_node_bounded(
      func,
      @ir.hot_child_get(func, node_id, slot),
      local_id,
      max_gets - gets,
      max_writes - writes,
      visited,
    )
    gets += child_gets
    writes += child_writes
    if gets > max_gets && writes > max_writes {
      return (max_gets + 1, max_writes + 1)
    }
  }
  (gets, writes)
}

///|
fn code_pushing_count_unique_total_local_gets_writes_bounded(
  func : @ir.HotFunc,
  local_id : Int32,
  max_gets : Int32,
  max_writes : Int32,
) -> (Int32, Int32) {
  let visited = Array::make(@ir.hot_node_count(func), false)
  let mut gets = 0
  let mut writes = 0
  for idx = 0; idx < @ir.hot_root_count(func); idx = idx + 1 {
    let (root_gets, root_writes) = code_pushing_count_unique_local_gets_writes_in_node_bounded(
      func,
      @ir.hot_root_get(func, idx),
      local_id,
      max_gets - gets,
      max_writes - writes,
      visited,
    )
    gets += root_gets
    writes += root_writes
    if gets > max_gets && writes > max_writes {
      return (max_gets + 1, max_writes + 1)
    }
  }
  (gets, writes)
}

///|
priv struct CodePushingLocalInventory {
  func : @ir.HotFunc
  total_gets : Array[Int32]
  total_writes : Array[Int32]
  unique_gets : Array[Int32]
  unique_writes : Array[Int32]
  scan_seen : Array[Bool]
}

///|
fn code_pushing_inventory_count_node(
  func : @ir.HotFunc,
  node_id : Int32,
  total_gets : Array[Int32],
  total_writes : Array[Int32],
) -> Unit {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalGet =>
      if node.imm0 >= 0 &&
        node.imm0 < total_gets.length() &&
        total_gets[node.imm0] <= 1_000_000 {
        total_gets[node.imm0] += 1
      }
    @ir.HotOp::LocalSet | @ir.HotOp::LocalTee =>
      if node.imm0 >= 0 &&
        node.imm0 < total_writes.length() &&
        total_writes[node.imm0] <= 1_000_000 {
        total_writes[node.imm0] += 1
      }
    _ => ()
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    code_pushing_inventory_count_node(
      func,
      @ir.hot_child_get(func, node_id, slot),
      total_gets,
      total_writes,
    )
  }
}

///|
fn CodePushingLocalInventory::from_func(
  func : @ir.HotFunc,
) -> CodePushingLocalInventory {
  let local_count = @ir.hot_local_count(func)
  let total_gets = Array::make(local_count, 0)
  let total_writes = Array::make(local_count, 0)
  for idx = 0; idx < @ir.hot_root_count(func); idx = idx + 1 {
    code_pushing_inventory_count_node(
      func,
      @ir.hot_root_get(func, idx),
      total_gets,
      total_writes,
    )
  }
  {
    func,
    total_gets,
    total_writes,
    unique_gets: Array::make(local_count, -1),
    unique_writes: Array::make(local_count, -1),
    scan_seen: Array::make(@ir.hot_node_count(func), false),
  }
}

///|
test "code-pushing inventory counts all local occurrences across control" {
  let func = pass_test_lift_first_func(
    "(module (func (param i32) (local i32 i32) i32.const 1 local.set 1 local.get 0 if local.get 1 drop else i32.const 2 local.tee 2 drop end local.get 1 drop local.get 2 drop))",
  )
  let inventory = CodePushingLocalInventory::from_func(func)
  assert_eq(inventory.total(0), (1, 0))
  assert_eq(inventory.total(1), (2, 1))
  assert_eq(inventory.total(2), (1, 1))
  for local_id = 0; local_id < 3; local_id = local_id + 1 {
    assert_eq(
      inventory.total(local_id),
      code_pushing_count_total_local_gets_writes_bounded(
        func, local_id, 1_000_000, 1_000_000,
      ),
    )
  }
}

///|
fn CodePushingLocalInventory::total(
  self : CodePushingLocalInventory,
  local_id : Int32,
) -> (Int32, Int32) {
  if local_id < 0 || local_id >= self.total_gets.length() {
    return (0, 0)
  }
  (self.total_gets[local_id], self.total_writes[local_id])
}

///|
fn CodePushingLocalInventory::unique_total(
  self : CodePushingLocalInventory,
  local_id : Int32,
) -> (Int32, Int32) {
  if local_id < 0 || local_id >= self.unique_gets.length() {
    return (0, 0)
  }
  if self.unique_gets[local_id] < 0 {
    let (gets, writes) = code_pushing_count_unique_total_local_gets_writes_bounded(
      self.func,
      local_id,
      1_000_000,
      1_000_000,
    )
    self.unique_gets[local_id] = gets
    self.unique_writes[local_id] = writes
  }
  (self.unique_gets[local_id], self.unique_writes[local_id])
}

///|
fn code_pushing_make_local_set_clone(
  func : @ir.HotFunc,
  local_id : Int32,
  value_id : Int32,
) -> Int32 {
  // Motion was proven safe by the caller. Give a moved global read its new
  // evaluation position so lowering does not recover its former stack lifetime.
  let value = @ir.hot_node_get(func, value_id)
  let moved_value = if value.op is @ir.HotOp::GlobalGet {
    @ir.hot_build_global_get(
      func,
      value.imm0,
      @ir.hot_type_results(func, value.ty)[0],
    )
  } else {
    value_id
  }
  @ir.hot_build_local_set(func, local_id, moved_value)
}

///|
fn code_pushing_region_cannot_fall_through_simple(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
) -> Bool {
  let root_count = @ir.hot_region_root_count(func, region_ref)
  if root_count == 0 {
    return false
  }
  let last_id = @ir.hot_region_get(func, region_ref, root_count - 1)
  if last_id < 0 || !@ir.hot_node_is_live(func, last_id) {
    return false
  }
  match @ir.hot_node_get(func, last_id).op {
    @ir.HotOp::Unreachable
    | @ir.HotOp::Return
    | @ir.HotOp::ReturnCall
    | @ir.HotOp::ReturnCallIndirect
    | @ir.HotOp::ReturnCallRef => true
    _ => false
  }
}

///|
fn code_pushing_push_point_kind(func : @ir.HotFunc, node_id : Int32) -> String {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return "none"
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::If => "if"
    @ir.HotOp::BrTable => "switch"
    @ir.HotOp::BrIf
    | @ir.HotOp::BrOnNull
    | @ir.HotOp::BrOnNonNull
    | @ir.HotOp::BrOnCast
    | @ir.HotOp::BrOnCastFail => "conditional-branch"
    @ir.HotOp::Drop => {
      if @ir.hot_child_count(func, node_id) != 1 {
        return "none"
      }
      let child_kind = code_pushing_push_point_kind(
        func,
        @ir.hot_child_get(func, node_id, 0),
      )
      if child_kind == "none" {
        "none"
      } else {
        "dropped-" + child_kind
      }
    }
    _ => "none"
  }
}

///|
fn code_pushing_insert_after_branch_value_drop(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  push_idx : Int32,
  push_id : Int32,
) -> Int32 {
  let insert_idx = push_idx + 1
  if insert_idx >= @ir.hot_region_root_count(func, region_ref) ||
    push_id < 0 ||
    !@ir.hot_node_is_live(func, push_id) {
    return insert_idx
  }
  let branch = @ir.hot_node_get(func, push_id)
  if !(branch.op is @ir.HotOp::BrIf) ||
    @ir.hot_child_count(func, push_id) != 2 ||
    !(@ir.hot_label_kind(func, branch.imm0) is @ir.HotLabelKind::BlockLabel) ||
    @ir.hot_label_branch_arity(func, branch.imm0) != 1 {
    return insert_idx
  }
  let drop_id = @ir.hot_region_get(func, region_ref, insert_idx)
  if drop_id < 0 || !@ir.hot_node_is_live(func, drop_id) {
    return insert_idx
  }
  let drop = @ir.hot_node_get(func, drop_id)
  if drop.op is @ir.HotOp::Drop &&
    @ir.hot_child_count(func, drop_id) == 1 &&
    @ir.hot_child_get(func, drop_id, 0) == @ir.hot_child_get(func, push_id, 0) {
    // The branch leaves its payload on the fallthrough stack. Discard that
    // value before moved sets so lowering does not spill it to a new local.
    return insert_idx + 1
  }
  insert_idx
}

///|
fn code_pushing_segment_window_diagnostic(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  set_idx : Int32,
  traps_never_happen~ : Bool = false,
  ignore_implicit_traps~ : Bool = false,
  inventory~ : CodePushingLocalInventory? = None,
  module_ctx~ : @ir.HotModuleContext? = None,
) -> String {
  if set_idx < 0 || set_idx >= @ir.hot_region_root_count(func, region_ref) {
    return "reject:no-local-set"
  }
  let set_id = @ir.hot_region_get(func, region_ref, set_idx)
  if set_id < 0 || !@ir.hot_node_is_live(func, set_id) {
    return "reject:no-local-set"
  }
  let set_node = @ir.hot_node_get(func, set_id)
  match set_node.op {
    @ir.HotOp::LocalSet => ()
    _ => return "reject:no-local-set"
  }
  if @ir.hot_node_result_arity(func, set_id) != 0 {
    return "reject:no-local-set"
  }
  let local_id = set_node.imm0
  let (prefix_gets, _) = code_pushing_count_local_gets_writes_in_region_prefix_bounded(
    func, region_ref, local_id, set_idx, 0, 1_000_000,
  )
  if prefix_gets != 0 {
    return "reject:prefix-local-read"
  }
  let (total_gets, total_writes) = match inventory {
    Some(inventory) => inventory.total(local_id)
    None =>
      code_pushing_count_total_local_gets_writes_bounded(
        func, local_id, 1_000_000, 2,
      )
  }
  if total_writes != 1 {
    return "reject:multiple-local-writes"
  }
  if total_gets == 0 {
    return "reject:no-local-use"
  }
  let value_id = @ir.hot_child_get(func, set_id, 0)
  if !code_pushing_node_is_movable_value(
      func,
      value_id,
      traps_never_happen~,
      ignore_implicit_traps~,
    ) {
    return "reject:not-pushable-value"
  }
  let mut idx = set_idx + 1
  while idx < @ir.hot_region_root_count(func, region_ref) {
    let candidate_id = @ir.hot_region_get(func, region_ref, idx)
    let kind = code_pushing_push_point_kind(func, candidate_id)
    if kind != "none" {
      if !code_pushing_node_safe_to_cross_for_value(
          func,
          candidate_id,
          value_id,
          module_ctx~,
        ) {
        return "reject:ordered-before-barrier"
      }
      return "candidate:" + kind
    }
    let (gets, writes) = code_pushing_count_local_gets_writes_in_node_bounded(
      func, candidate_id, local_id, 1, 0,
    )
    if writes != 0 {
      return "reject:multiple-local-writes"
    }
    if gets != 0 {
      return "reject:use-before-push-point"
    }
    if !code_pushing_node_safe_to_cross_for_value(
        func,
        candidate_id,
        value_id,
        module_ctx~,
      ) {
      return "reject:ordered-before-barrier"
    }
    idx += 1
  }
  "reject:no-push-point"
}

///|
fn code_pushing_direct_br_if_push_point_supported(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::BrIf => ()
    _ => return false
  }
  if node.imm0 < 0 {
    return false
  }
  let branch_arity = @ir.hot_label_branch_arity(func, node.imm0)
  match @ir.hot_label_kind(func, node.imm0) {
    @ir.HotLabelKind::BlockLabel | @ir.HotLabelKind::LoopLabel =>
      branch_arity == 0 && @ir.hot_child_count(func, node_id) == 1
    _ => false
  }
}

///|
fn code_pushing_single_set_conditional_branch_push_point_supported(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let mut branch_id = node_id
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Drop => {
      if @ir.hot_child_count(func, node_id) != 1 {
        return false
      }
      branch_id = @ir.hot_child_get(func, node_id, 0)
    }
    _ => ()
  }
  if branch_id < 0 || !@ir.hot_node_is_live(func, branch_id) {
    return false
  }
  let branch = @ir.hot_node_get(func, branch_id)
  if branch.imm0 < 0 {
    return false
  }
  let branch_arity = @ir.hot_label_branch_arity(func, branch.imm0)
  let child_count = @ir.hot_child_count(func, branch_id)
  match branch.op {
    @ir.HotOp::BrIf =>
      match @ir.hot_label_kind(func, branch.imm0) {
        @ir.HotLabelKind::BlockLabel =>
          (branch_arity == 0 && child_count == 1) ||
          (branch_arity == 1 && child_count == 2)
        @ir.HotLabelKind::LoopLabel => branch_arity == 0 && child_count == 1
        _ => false
      }
    @ir.HotOp::BrOnNull =>
      match @ir.hot_label_kind(func, branch.imm0) {
        @ir.HotLabelKind::BlockLabel | @ir.HotLabelKind::LoopLabel =>
          branch_arity == 0 && child_count == 1
        _ => false
      }
    @ir.HotOp::BrOnNonNull =>
      match @ir.hot_label_kind(func, branch.imm0) {
        @ir.HotLabelKind::BlockLabel =>
          branch_arity >= 1 && child_count >= 1 && child_count <= branch_arity
        _ => false
      }
    @ir.HotOp::BrOnCast | @ir.HotOp::BrOnCastFail =>
      match @ir.hot_label_kind(func, branch.imm0) {
        @ir.HotLabelKind::BlockLabel => branch_arity == 1 && child_count == 1
        _ => false
      }
    _ => false
  }
}

///|
fn code_pushing_node_has_branch(func : @ir.HotFunc, node_id : Int32) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Br
    | @ir.HotOp::BrIf
    | @ir.HotOp::BrTable
    | @ir.HotOp::BrOnNull
    | @ir.HotOp::BrOnNonNull
    | @ir.HotOp::BrOnCast
    | @ir.HotOp::BrOnCastFail
    | @ir.HotOp::Delegate
    | @ir.HotOp::Return
    | @ir.HotOp::ReturnCall
    | @ir.HotOp::ReturnCallIndirect
    | @ir.HotOp::ReturnCallRef => return true
    _ => ()
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if code_pushing_node_has_branch(
        func,
        @ir.hot_child_get(func, node_id, slot),
      ) {
      return true
    }
  }
  false
}

///|
fn code_pushing_node_has_unreachable(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Unreachable => return true
    _ => ()
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if code_pushing_node_has_unreachable(
        func,
        @ir.hot_child_get(func, node_id, slot),
      ) {
      return true
    }
  }
  false
}

///|
fn code_pushing_region_has_leading_unreachable_before(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  end_idx : Int32,
) -> Bool {
  if end_idx <= 0 || @ir.hot_region_root_count(func, region_ref) == 0 {
    return false
  }
  let root_id = @ir.hot_region_get(func, region_ref, 0)
  if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
    return false
  }
  match @ir.hot_node_get(func, root_id).op {
    @ir.HotOp::Unreachable => true
    _ => false
  }
}

///|
fn code_pushing_try_flatten_dead_block_before_unreachable(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  block_idx : Int32,
) -> Bool {
  let block_id = @ir.hot_region_get(func, region_ref, block_idx)
  if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
    return false
  }
  match @ir.hot_node_get(func, block_id).op {
    @ir.HotOp::Block => ()
    _ => return false
  }
  let root_count = @ir.hot_region_root_count(func, region_ref)
  let block_has_neighboring_unreachable = if block_idx + 1 < root_count {
    let next_id = @ir.hot_region_get(func, region_ref, block_idx + 1)
    if next_id >= 0 && @ir.hot_node_is_live(func, next_id) {
      match @ir.hot_node_get(func, next_id).op {
        @ir.HotOp::Unreachable => true
        _ => false
      }
    } else {
      false
    }
  } else {
    false
  }
  if !block_has_neighboring_unreachable &&
    !code_pushing_region_has_leading_unreachable_before(
      func, region_ref, block_idx,
    ) {
    return false
  }
  let body_ref = @ir.hot_region_body_ref(func, block_id)
  let body_count = @ir.hot_region_root_count(func, body_ref)
  if body_count == 0 {
    return false
  }
  let mut unreachable_idx = -1
  for idx = 0; idx < body_count; idx = idx + 1 {
    let root_id = @ir.hot_region_get(func, body_ref, idx)
    match @ir.hot_node_get(func, root_id).op {
      @ir.HotOp::Unreachable => {
        if unreachable_idx >= 0 {
          return false
        }
        unreachable_idx = idx
      }
      _ => ()
    }
  }
  if unreachable_idx != 0 && unreachable_idx != body_count - 1 {
    return false
  }
  let replacement : Array[Int32] = Array::new(capacity=body_count)
  let append_body_root = fn(idx : Int32) -> Bool {
    let root_id = @ir.hot_region_get(func, body_ref, idx)
    if code_pushing_node_has_branch(func, root_id) {
      return false
    }
    if idx != unreachable_idx && @ir.hot_node_result_arity(func, root_id) > 1 {
      return false
    }
    replacement.push(root_id)
    true
  }
  if unreachable_idx == 0 {
    for idx = 1; idx < body_count; idx = idx + 1 {
      let root_id = @ir.hot_region_get(func, body_ref, idx)
      if !code_pushing_node_is_movable_value(
          func,
          root_id,
          traps_never_happen=ctx.traps_never_happen,
          ignore_implicit_traps=ctx.ignore_implicit_traps,
          module_ctx=ctx.module_ctx,
        ) {
        return false
      }
      if !append_body_root(idx) {
        return false
      }
    }
    if !append_body_root(0) {
      return false
    }
  } else {
    for idx = 0; idx < body_count; idx = idx + 1 {
      if !append_body_root(idx) {
        return false
      }
    }
  }
  ignore(pass_splice_region(ctx, func, region_ref, block_idx, 1, replacement))
  @ir.hot_region_replace_body(func, body_ref, [])
  true
}

///|
fn code_pushing_try_sink_set_before_later_barrier(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  set_idx : Int32,
  inventory : CodePushingLocalInventory,
) -> Bool {
  if set_idx + 1 >= @ir.hot_region_root_count(func, region_ref) {
    return false
  }
  let set_id = @ir.hot_region_get(func, region_ref, set_idx)
  if set_id < 0 || !@ir.hot_node_is_live(func, set_id) {
    return false
  }
  let set_node = @ir.hot_node_get(func, set_id)
  match set_node.op {
    @ir.HotOp::LocalSet => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, set_id) != 0 {
    return false
  }
  let value_id = @ir.hot_child_get(func, set_id, 0)
  if !code_pushing_node_is_movable_value(
      func,
      value_id,
      traps_never_happen=ctx.traps_never_happen,
      ignore_implicit_traps=ctx.ignore_implicit_traps,
      module_ctx=ctx.module_ctx,
    ) {
    return false
  }
  if !code_pushing_node_is_global_get(func, value_id) {
    return false
  }
  let local_id = set_node.imm0
  let (total_gets, total_writes) = inventory.total(local_id)
  if total_writes != 1 || total_gets == 0 {
    return false
  }

  let mut barrier_idx = -1
  let mut idx = set_idx + 1
  let max_idx = set_idx + 32
  while idx < @ir.hot_region_root_count(func, region_ref) && idx <= max_idx {
    let candidate_id = @ir.hot_region_get(func, region_ref, idx)
    let (gets, writes) = code_pushing_count_local_gets_writes_in_node_bounded(
      func, candidate_id, local_id, 1, 0,
    )
    if writes != 0 {
      return false
    }
    if gets != 0 ||
      !code_pushing_node_safe_to_cross_for_value(
        func,
        candidate_id,
        value_id,
        module_ctx=ctx.module_ctx,
      ) {
      barrier_idx = idx
      break
    }
    idx += 1
  }
  if barrier_idx <= set_idx + 1 {
    return false
  }
  if barrier_idx < 0 {
    return false
  }
  let pushed_set = code_pushing_make_local_set_clone(func, local_id, value_id)
  ignore(
    pass_splice_region(ctx, func, region_ref, barrier_idx, 0, [pushed_set]),
  )
  let nop = @ir.hot_build_nop(func)
  pass_replace_node(ctx, func, set_id, @ir.hot_node_get(func, nop))
  true
}

///|
fn code_pushing_value_is_ordered_set_movable(
  func : @ir.HotFunc,
  value_id : Int32,
  traps_never_happen~ : Bool = false,
  ignore_implicit_traps~ : Bool = false,
  module_ctx~ : @ir.HotModuleContext? = None,
) -> Bool {
  code_pushing_node_is_movable_value(
    func,
    value_id,
    traps_never_happen~,
    ignore_implicit_traps~,
    module_ctx~,
  ) ||
  code_pushing_node_is_local_get(func, value_id)
}

///|
fn code_pushing_node_is_drop_const_window_separator(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Drop => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, node_id) != 0 ||
    @ir.hot_child_count(func, node_id) != 1 {
    return false
  }
  let child_id = @ir.hot_child_get(func, node_id, 0)
  if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
    return false
  }
  match @ir.hot_node_get(func, child_id).op {
    @ir.HotOp::Const =>
      @ir.effects_is_pure(@ir.effects_for_node(func, child_id))
    _ => false
  }
}

///|
fn code_pushing_node_is_drop_local_get_window_separator(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Drop => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, node_id) != 0 ||
    @ir.hot_child_count(func, node_id) != 1 {
    return false
  }
  let child_id = @ir.hot_child_get(func, node_id, 0)
  code_pushing_node_is_local_get(func, child_id)
}

///|
fn code_pushing_node_is_drop_global_get_window_separator(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Drop => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, node_id) != 0 ||
    @ir.hot_child_count(func, node_id) != 1 {
    return false
  }
  let child_id = @ir.hot_child_get(func, node_id, 0)
  code_pushing_node_is_global_get(func, child_id)
}

///|
fn code_pushing_node_is_safe_drop_window_separator(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  code_pushing_node_is_drop_const_window_separator(func, node_id) ||
  code_pushing_node_is_drop_local_get_window_separator(func, node_id) ||
  code_pushing_node_is_drop_global_get_window_separator(func, node_id)
}

///|
fn code_pushing_try_sink_local_get_set_after_if(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  set_idx : Int32,
  inventory : CodePushingLocalInventory,
) -> Bool {
  if set_idx + 1 >= @ir.hot_region_root_count(func, region_ref) {
    return false
  }
  let set_id = @ir.hot_region_get(func, region_ref, set_idx)
  if set_id < 0 || !@ir.hot_node_is_live(func, set_id) {
    return false
  }
  let set_node = @ir.hot_node_get(func, set_id)
  match set_node.op {
    @ir.HotOp::LocalSet => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, set_id) != 0 {
    return false
  }
  let value_id = @ir.hot_child_get(func, set_id, 0)
  if !code_pushing_node_is_local_get(func, value_id) {
    return false
  }
  let source_local = @ir.hot_node_get(func, value_id).imm0
  let dest_local = set_node.imm0
  if source_local == dest_local {
    return false
  }
  let (dest_total_gets, dest_total_writes) = inventory.total(dest_local)
  // Binaryen does not move an unused copy after a final if: there is no later
  // consumer to justify segment pushing, and optimizeIntoIf has no using arm.
  if dest_total_gets == 0 || dest_total_writes != 1 {
    return false
  }
  let mut idx = set_idx + 1
  let max_idx = set_idx + 32
  while idx < @ir.hot_region_root_count(func, region_ref) && idx <= max_idx {
    let candidate_id = @ir.hot_region_get(func, region_ref, idx)
    // An ordinary or exceptional exit may reach a later read without passing
    // the proposed new write location. Check every crossed root, not just ifs.
    if code_pushing_node_has_branch(func, candidate_id) ||
      (
        @ir.effects_for_node(func, candidate_id) &
        (@ir.EFFECT_MASK_CALL | @ir.EFFECT_MASK_THROW)
      ) !=
      0U {
      return false
    }
    let (dest_gets, dest_writes) = code_pushing_count_local_gets_writes_in_node_bounded(
      func, candidate_id, dest_local, 1, 0,
    )
    if dest_gets != 0 || dest_writes != 0 {
      return false
    }
    if !code_pushing_node_safe_to_cross_for_value(
        func,
        candidate_id,
        value_id,
        module_ctx=ctx.module_ctx,
      ) {
      return false
    }
    let (_, source_writes) = code_pushing_count_local_gets_writes_in_node_bounded(
      func, candidate_id, source_local, 0, 0,
    )
    if source_writes != 0 {
      return false
    }
    match @ir.hot_node_get(func, candidate_id).op {
      @ir.HotOp::If => {
        if @ir.hot_node_result_arity(func, candidate_id) != 0 {
          return false
        }
        if code_pushing_node_has_branch(func, candidate_id) ||
          code_pushing_node_has_unreachable(func, candidate_id) {
          return false
        }
        if code_pushing_node_writes_local_deep_limited(
            func, candidate_id, source_local, 8,
          ) ||
          code_pushing_if_arms_write_local_deep_limited(
            func, candidate_id, source_local, 8,
          ) {
          return false
        }
        let pushed_set = code_pushing_make_local_set_clone(
          func, dest_local, value_id,
        )
        ignore(
          pass_splice_region(ctx, func, region_ref, idx + 1, 0, [pushed_set]),
        )
        let nop = @ir.hot_build_nop(func)
        pass_replace_node(ctx, func, set_id, @ir.hot_node_get(func, nop))
        return true
      }
      _ => ()
    }
    idx += 1
  }
  false
}

///|
fn code_pushing_try_sink_generated_prefix_sets_after_br_on_non_null(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  set_idx : Int32,
  inventory : CodePushingLocalInventory,
) -> Bool {
  if set_idx + 2 >= @ir.hot_region_root_count(func, region_ref) {
    return false
  }
  let first_id = @ir.hot_region_get(func, region_ref, set_idx)
  if first_id < 0 || !@ir.hot_node_is_live(func, first_id) {
    return false
  }
  match @ir.hot_node_get(func, first_id).op {
    @ir.HotOp::LocalSet => ()
    _ => return false
  }
  let mut branch_idx = -1
  let mut idx = set_idx + 1
  let max_idx = set_idx + 16
  while idx < @ir.hot_region_root_count(func, region_ref) && idx <= max_idx {
    let root_id = @ir.hot_region_get(func, region_ref, idx)
    if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
      return false
    }
    match @ir.hot_node_get(func, root_id).op {
      @ir.HotOp::BrOnNonNull => {
        branch_idx = idx
        break
      }
      _ => ()
    }
    idx += 1
  }
  if branch_idx < 0 {
    return false
  }
  let branch_id = @ir.hot_region_get(func, region_ref, branch_idx)
  let branch = @ir.hot_node_get(func, branch_id)
  if branch.imm0 < 0 || @ir.hot_child_count(func, branch_id) < 1 {
    return false
  }
  match @ir.hot_label_kind(func, branch.imm0) {
    @ir.HotLabelKind::BlockLabel => ()
    _ => return false
  }
  let set_ids : Array[Int32] = []
  let local_ids : Array[Int32] = []
  let value_ids : Array[Int32] = []
  for scan_idx = set_idx; scan_idx < branch_idx; scan_idx = scan_idx + 1 {
    let root_id = @ir.hot_region_get(func, region_ref, scan_idx)
    if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
      return false
    }
    let root = @ir.hot_node_get(func, root_id)
    match root.op {
      @ir.HotOp::LocalSet => {
        if @ir.hot_node_result_arity(func, root_id) != 0 {
          return false
        }
        let local_id = root.imm0
        if local_ids.any(fn(existing) { existing == local_id }) {
          return false
        }
        let value_id = @ir.hot_child_get(func, root_id, 0)
        if !code_pushing_node_is_movable_value(
            func,
            value_id,
            traps_never_happen=ctx.traps_never_happen,
            ignore_implicit_traps=ctx.ignore_implicit_traps,
            module_ctx=ctx.module_ctx,
          ) ||
          code_pushing_node_has_any_local_get(func, value_id) {
          continue
        }
        let (prefix_gets, _) = code_pushing_count_local_gets_writes_in_region_prefix_bounded(
          func, region_ref, local_id, set_idx, 0, 1_000_000,
        )
        if prefix_gets != 0 {
          return false
        }
        let mut safe_to_branch = true
        for cross_idx = scan_idx + 1
            cross_idx <= branch_idx
            cross_idx = cross_idx + 1 {
          let cross_id = @ir.hot_region_get(func, region_ref, cross_idx)
          let (gets, writes) = code_pushing_count_local_gets_writes_in_node_bounded(
            func, cross_id, local_id, 1, 0,
          )
          if gets != 0 ||
            writes != 0 ||
            !code_pushing_node_safe_to_cross_for_value(
              func,
              cross_id,
              value_id,
              module_ctx=ctx.module_ctx,
            ) {
            safe_to_branch = false
          }
        }
        if !safe_to_branch {
          continue
        }
        let (total_gets, total_writes) = inventory.unique_total(local_id)
        if total_writes != 1 || total_gets == 0 {
          continue
        }
        let (suffix_gets, suffix_writes) = code_pushing_count_local_gets_writes_in_region_suffix_bounded(
          func,
          region_ref,
          local_id,
          branch_idx + 1,
          total_gets,
          0,
        )
        if suffix_writes == 0 && suffix_gets == total_gets {
          set_ids.push(root_id)
          local_ids.push(local_id)
          value_ids.push(value_id)
        }
      }
      _ =>
        if code_pushing_push_point_kind(func, root_id) != "none" {
          return false
        }
    }
  }
  if set_ids.length() == 0 {
    return false
  }
  let pushed_sets : Array[Int32] = []
  for move_idx = 0; move_idx < set_ids.length(); move_idx = move_idx + 1 {
    pushed_sets.push(
      code_pushing_make_local_set_clone(
        func,
        local_ids[move_idx],
        value_ids[move_idx],
      ),
    )
  }
  ignore(
    pass_splice_region(ctx, func, region_ref, branch_idx + 1, 0, pushed_sets),
  )
  for move_idx = 0; move_idx < set_ids.length(); move_idx = move_idx + 1 {
    let nop = @ir.hot_build_nop(func)
    pass_replace_node(ctx, func, set_ids[move_idx], @ir.hot_node_get(func, nop))
  }
  true
}

///|
fn code_pushing_try_sink_ordered_sets_after_push_point(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  set_idx : Int32,
  inventory : CodePushingLocalInventory,
) -> Bool {
  if set_idx + 2 >= @ir.hot_region_root_count(func, region_ref) {
    return false
  }
  let set_ids : Array[Int32] = []
  let local_ids : Array[Int32] = []
  let value_ids : Array[Int32] = []
  let source_local_ids : Array[Int32] = []
  let mut saw_drop_global_get_separator = false
  let mut idx = set_idx
  while idx < @ir.hot_region_root_count(func, region_ref) {
    let root_id = @ir.hot_region_get(func, region_ref, idx)
    let kind = code_pushing_push_point_kind(func, root_id)
    if kind != "none" {
      break
    }
    if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
      return false
    }
    let root_node = @ir.hot_node_get(func, root_id)
    match root_node.op {
      @ir.HotOp::LocalSet => {
        if @ir.hot_node_result_arity(func, root_id) != 0 {
          return false
        }
        let local_id = root_node.imm0
        if local_ids.any(fn(existing) { existing == local_id }) {
          return false
        }
        let value_id = @ir.hot_child_get(func, root_id, 0)
        if !code_pushing_value_is_ordered_set_movable(
            func,
            value_id,
            traps_never_happen=ctx.traps_never_happen,
            ignore_implicit_traps=ctx.ignore_implicit_traps,
            module_ctx=ctx.module_ctx,
          ) {
          return false
        }
        let source_local = if code_pushing_node_is_local_get(func, value_id) {
          @ir.hot_node_get(func, value_id).imm0
        } else {
          if code_pushing_node_has_any_local_get(func, value_id) {
            return false
          }
          -1
        }
        if source_local == local_id ||
          local_ids.any(fn(existing) { existing == source_local }) {
          return false
        }
        let (prefix_gets, _) = code_pushing_count_local_gets_writes_in_region_prefix_bounded(
          func, region_ref, local_id, set_idx, 0, 1_000_000,
        )
        if prefix_gets != 0 {
          return false
        }
        let (total_gets, total_writes) = inventory.total(local_id)
        if total_writes != 1 || total_gets == 0 {
          return false
        }
        set_ids.push(root_id)
        local_ids.push(local_id)
        value_ids.push(value_id)
        source_local_ids.push(source_local)
      }
      @ir.HotOp::Nop =>
        // Binaryen's segment optimizer can carry harmless roots such as nops
        // in the window while preserving the moved local.set order.
        if set_ids.length() == 0 ||
          @ir.hot_node_result_arity(func, root_id) != 0 {
          return false
        }
      @ir.HotOp::Drop if code_pushing_node_is_safe_drop_window_separator(
          func, root_id,
        ) => {
        // Source-backed non-adjacent slices admit only dead drop(const),
        // drop(local.get), and ordinary/dropped-if drop(global.get) roots as
        // bounded separators. Binaryen v130 probes did not move the same
        // global-get separator window across br_if, so that push point is
        // rejected after we discover the destination below.
        if set_ids.length() == 0 {
          return false
        }
        if code_pushing_node_is_drop_global_get_window_separator(func, root_id) {
          saw_drop_global_get_separator = true
        }
      }
      _ => return false
    }
    idx += 1
  }
  if set_ids.length() < 2 || idx >= @ir.hot_region_root_count(func, region_ref) {
    return false
  }
  let push_id = @ir.hot_region_get(func, region_ref, idx)
  let push_kind = code_pushing_push_point_kind(func, push_id)
  let mut if_id = -1
  let mut branch_id = -1
  if push_kind == "if" {
    if_id = push_id
  } else if push_kind == "dropped-if" && @ir.hot_child_count(func, push_id) == 1 {
    if_id = @ir.hot_child_get(func, push_id, 0)
  } else if push_kind == "conditional-branch" &&
    (
      code_pushing_direct_br_if_push_point_supported(func, push_id) ||
      code_pushing_single_set_conditional_branch_push_point_supported(
        func, push_id,
      )
    ) {
    // Value-carrying br_if may lower as a direct HOT BrIf with branch payload
    // children even though the source WAT is `drop (br_if ...)`. Use the same
    // shape gate as the single-set conditional-branch slice, then let whole-root
    // local accounting below reject payloads/conditions/guards that read moved
    // locals.
    if saw_drop_global_get_separator {
      return false
    }
    branch_id = push_id
  } else if push_kind == "dropped-conditional-branch" &&
    code_pushing_single_set_conditional_branch_push_point_supported(
      func, push_id,
    ) {
    if saw_drop_global_get_separator {
      return false
    }
    branch_id = push_id
  } else {
    return false
  }
  if if_id >= 0 {
    if !@ir.hot_node_is_live(func, if_id) {
      return false
    }
    if push_kind == "if" && @ir.hot_node_result_arity(func, if_id) != 0 {
      return false
    }
    if push_kind == "dropped-if" &&
      @ir.hot_node_result_arity(func, push_id) != 0 {
      return false
    }
  } else if branch_id < 0 || !@ir.hot_node_is_live(func, branch_id) {
    return false
  }
  for move_idx = 0; move_idx < set_ids.length(); move_idx = move_idx + 1 {
    let local_id = local_ids[move_idx]
    let value_id = value_ids[move_idx]
    let source_local = source_local_ids[move_idx]
    if !code_pushing_node_safe_to_cross_for_value(
        func,
        push_id,
        value_id,
        module_ctx=ctx.module_ctx,
      ) {
      return false
    }
    if source_local >= 0 {
      if local_ids.any(fn(moved_local) { moved_local == source_local }) {
        return false
      }
      if if_id >= 0 {
        if code_pushing_if_arms_write_local_deep_limited(
            func, if_id, source_local, 8,
          ) {
          return false
        }
      } else {
        let (_, source_writes) = code_pushing_count_local_gets_writes_in_node_bounded(
          func, branch_id, source_local, 0, 0,
        )
        if source_writes != 0 {
          return false
        }
      }
    }
    if if_id >= 0 {
      let (then_gets, else_gets) = code_pushing_count_local_gets_in_if_arms(
        func, if_id, local_id,
      )
      if then_gets + else_gets != 0 {
        return false
      }
    } else {
      let (branch_gets, branch_writes) = code_pushing_count_local_gets_writes_in_node_bounded(
        func, branch_id, local_id, 0, 0,
      )
      if branch_gets != 0 || branch_writes != 0 {
        return false
      }
    }
    let (total_gets, total_writes) = inventory.total(local_id)
    if total_writes != 1 || total_gets == 0 {
      return false
    }
    let (suffix_gets, suffix_writes) = code_pushing_count_local_gets_writes_in_region_suffix_bounded(
      func,
      region_ref,
      local_id,
      idx + 1,
      total_gets,
      0,
    )
    if suffix_writes != 0 || suffix_gets != total_gets {
      return false
    }
  }
  let pushed_sets : Array[Int32] = []
  for move_idx = 0; move_idx < set_ids.length(); move_idx = move_idx + 1 {
    pushed_sets.push(
      code_pushing_make_local_set_clone(
        func,
        local_ids[move_idx],
        value_ids[move_idx],
      ),
    )
  }
  let insert_idx = code_pushing_insert_after_branch_value_drop(
    func, region_ref, idx, push_id,
  )
  ignore(pass_splice_region(ctx, func, region_ref, insert_idx, 0, pushed_sets))
  for move_idx = 0; move_idx < set_ids.length(); move_idx = move_idx + 1 {
    let nop = @ir.hot_build_nop(func)
    pass_replace_node(ctx, func, set_ids[move_idx], @ir.hot_node_get(func, nop))
  }
  true
}

///|
fn code_pushing_zero_read_push_point_supported(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::If => @ir.hot_node_result_arity(func, node_id) == 0
    @ir.HotOp::Drop => {
      if @ir.hot_node_result_arity(func, node_id) != 0 ||
        @ir.hot_child_count(func, node_id) != 1 {
        return false
      }
      match @ir.hot_node_get(func, @ir.hot_child_get(func, node_id, 0)).op {
        @ir.HotOp::If => true
        _ => false
      }
    }
    @ir.HotOp::BrIf => {
      if !code_pushing_direct_br_if_push_point_supported(func, node_id) {
        return false
      }
      match @ir.hot_label_kind(func, node.imm0) {
        @ir.HotLabelKind::BlockLabel => true
        _ => false
      }
    }
    _ => false
  }
}

///|
fn code_pushing_try_sink_zero_read_set_after_push_point(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  set_idx : Int32,
  inventory : CodePushingLocalInventory,
) -> Bool {
  let root_count = @ir.hot_region_root_count(func, region_ref)
  if set_idx < 0 || set_idx + 2 >= root_count {
    return false
  }
  let set_id = @ir.hot_region_get(func, region_ref, set_idx)
  if set_id < 0 || !@ir.hot_node_is_live(func, set_id) {
    return false
  }
  let set_node = @ir.hot_node_get(func, set_id)
  match set_node.op {
    @ir.HotOp::LocalSet => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, set_id) != 0 {
    return false
  }
  let local_id = set_node.imm0
  let (total_gets, total_writes) = inventory.unique_total(local_id)
  if total_gets != 0 || total_writes != 1 {
    return false
  }
  let value_id = @ir.hot_child_get(func, set_id, 0)
  if !code_pushing_node_is_movable_value(
      func,
      value_id,
      traps_never_happen=ctx.traps_never_happen,
      ignore_implicit_traps=ctx.ignore_implicit_traps,
      module_ctx=ctx.module_ctx,
    ) {
    return false
  }
  let mut push_idx = -1
  let mut idx = set_idx + 1
  while idx < root_count - 1 {
    let candidate_id = @ir.hot_region_get(func, region_ref, idx)
    let kind = code_pushing_push_point_kind(func, candidate_id)
    if kind != "none" {
      if !code_pushing_zero_read_push_point_supported(func, candidate_id) {
        return false
      }
      push_idx = idx
      break
    }
    let (_, writes) = code_pushing_count_local_gets_writes_in_node_bounded(
      func, candidate_id, local_id, 0, 0,
    )
    if writes != 0 ||
      code_pushing_node_has_segment_order_barrier(func, candidate_id) ||
      !code_pushing_node_safe_to_cross_for_value(
        func,
        candidate_id,
        value_id,
        module_ctx=ctx.module_ctx,
      ) {
      return false
    }
    idx += 1
  }
  if push_idx < 0 {
    return false
  }
  let push_id = @ir.hot_region_get(func, region_ref, push_idx)
  if !code_pushing_node_safe_to_cross_for_value(
      func,
      push_id,
      value_id,
      module_ctx=ctx.module_ctx,
    ) {
    return false
  }
  let pushed_set = code_pushing_make_local_set_clone(func, local_id, value_id)
  ignore(
    pass_splice_region(ctx, func, region_ref, push_idx + 1, 0, [pushed_set]),
  )
  let nop = @ir.hot_build_nop(func)
  pass_replace_node(ctx, func, set_id, @ir.hot_node_get(func, nop))
  true
}

///|
fn code_pushing_try_sink_set_after_if_push_point(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  set_idx : Int32,
  inventory : CodePushingLocalInventory,
) -> Bool {
  if set_idx < 0 || set_idx >= @ir.hot_region_root_count(func, region_ref) {
    return false
  }
  let preflight_set_id = @ir.hot_region_get(func, region_ref, set_idx)
  if preflight_set_id < 0 || !@ir.hot_node_is_live(func, preflight_set_id) {
    return false
  }
  let preflight_set = @ir.hot_node_get(func, preflight_set_id)
  match preflight_set.op {
    @ir.HotOp::LocalSet => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, preflight_set_id) != 0 {
    return false
  }
  let preflight_value_id = @ir.hot_child_get(func, preflight_set_id, 0)
  // Reject the common nonmovable and no-push-point cases before the diagnostic's
  // whole-function local-use inventory. The diagnostic still owns exact
  // candidate classification once this behavior-preserving preflight succeeds.
  if !code_pushing_node_is_movable_value(
      func,
      preflight_value_id,
      traps_never_happen=ctx.traps_never_happen,
      ignore_implicit_traps=ctx.ignore_implicit_traps,
    ) {
    return false
  }
  let mut has_later_push_point = false
  for scan = set_idx + 1
      scan < @ir.hot_region_root_count(func, region_ref)
      scan = scan + 1 {
    if code_pushing_push_point_kind(
        func,
        @ir.hot_region_get(func, region_ref, scan),
      ) !=
      "none" {
      has_later_push_point = true
      break
    }
  }
  if !has_later_push_point {
    return false
  }
  let diagnostic = code_pushing_segment_window_diagnostic(
    func,
    region_ref,
    set_idx,
    traps_never_happen=ctx.traps_never_happen,
    ignore_implicit_traps=ctx.ignore_implicit_traps,
    inventory=Some(inventory),
    module_ctx=ctx.module_ctx,
  )
  if diagnostic != "candidate:if" &&
    diagnostic != "candidate:dropped-if" &&
    diagnostic != "candidate:conditional-branch" &&
    diagnostic != "candidate:dropped-conditional-branch" {
    return false
  }
  let set_id = @ir.hot_region_get(func, region_ref, set_idx)
  if set_id < 0 || !@ir.hot_node_is_live(func, set_id) {
    return false
  }
  let set_node = @ir.hot_node_get(func, set_id)
  match set_node.op {
    @ir.HotOp::LocalSet => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, set_id) != 0 {
    return false
  }
  let value_id = @ir.hot_child_get(func, set_id, 0)
  if !code_pushing_node_is_movable_value(
      func,
      value_id,
      traps_never_happen=ctx.traps_never_happen,
      ignore_implicit_traps=ctx.ignore_implicit_traps,
      module_ctx=ctx.module_ctx,
    ) {
    return false
  }
  let local_id = set_node.imm0
  let mut push_idx = -1
  let mut idx = set_idx + 1
  let mut if_id = -1
  let mut branch_id = -1
  let mut saw_drop_global_get_separator = false
  while idx < @ir.hot_region_root_count(func, region_ref) {
    let candidate_id = @ir.hot_region_get(func, region_ref, idx)
    let kind = code_pushing_push_point_kind(func, candidate_id)
    if kind != "none" {
      if kind == "if" {
        if_id = candidate_id
      } else if kind == "dropped-if" &&
        @ir.hot_child_count(func, candidate_id) == 1 {
        if_id = @ir.hot_child_get(func, candidate_id, 0)
      } else if (
          kind == "conditional-branch" || kind == "dropped-conditional-branch"
        ) &&
        code_pushing_single_set_conditional_branch_push_point_supported(
          func, candidate_id,
        ) {
        if saw_drop_global_get_separator ||
          (
            set_idx > 0 &&
            code_pushing_node_is_drop_global_get_window_separator(
              func,
              @ir.hot_region_get(func, region_ref, set_idx - 1),
            )
          ) {
          return false
        }
        branch_id = candidate_id
      } else {
        return false
      }
      push_idx = idx
      break
    }
    if code_pushing_node_has_segment_order_barrier(func, candidate_id) {
      return false
    }
    if code_pushing_node_is_drop_global_get_window_separator(func, candidate_id) {
      saw_drop_global_get_separator = true
    }
    idx += 1
  }
  if push_idx < 0 {
    return false
  }
  if branch_id >= 0 {
    // Whole-root accounting below rejects conditions or branch values that read
    // the moved local; this first slice only moves after a local-independent
    // br_if when every use is in the same block suffix.
    ()
  } else {
    if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
      return false
    }
    if diagnostic == "candidate:if" &&
      @ir.hot_node_result_arity(func, if_id) != 0 {
      return false
    }
    let (then_gets, else_gets) = code_pushing_count_local_gets_in_if_arms(
      func, if_id, local_id,
    )
    if then_gets + else_gets != 0 {
      return false
    }
  }
  let (total_gets, total_writes) = inventory.total(local_id)
  if total_writes != 1 || total_gets == 0 {
    return false
  }
  let (suffix_gets, suffix_writes) = code_pushing_count_local_gets_writes_in_region_suffix_bounded(
    func,
    region_ref,
    local_id,
    push_idx + 1,
    total_gets,
    0,
  )
  if suffix_writes != 0 || suffix_gets != total_gets {
    return false
  }
  let pushed_set = code_pushing_make_local_set_clone(func, local_id, value_id)
  let insert_idx = code_pushing_insert_after_branch_value_drop(
    func,
    region_ref,
    push_idx,
    @ir.hot_region_get(func, region_ref, push_idx),
  )
  ignore(pass_splice_region(ctx, func, region_ref, insert_idx, 0, [pushed_set]))
  let nop = @ir.hot_build_nop(func)
  pass_replace_node(ctx, func, set_id, @ir.hot_node_get(func, nop))
  true
}

///|
fn code_pushing_try_sink_consecutive_sets_into_if_arm(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  set_idx : Int32,
  inventory : CodePushingLocalInventory,
) -> Bool {
  if set_idx + 2 >= @ir.hot_region_root_count(func, region_ref) {
    return false
  }
  let set_ids : Array[Int32] = []
  let local_ids : Array[Int32] = []
  let value_ids : Array[Int32] = []
  let source_local_ids : Array[Int32] = []
  let mut idx = set_idx
  while idx < @ir.hot_region_root_count(func, region_ref) {
    let root_id = @ir.hot_region_get(func, region_ref, idx)
    if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
      return false
    }
    match @ir.hot_node_get(func, root_id).op {
      @ir.HotOp::LocalSet => {
        if @ir.hot_node_result_arity(func, root_id) != 0 {
          return false
        }
        let local_id = @ir.hot_node_get(func, root_id).imm0
        if local_ids.any(fn(existing) { existing == local_id }) {
          return false
        }
        let value_id = @ir.hot_child_get(func, root_id, 0)
        if !code_pushing_value_is_ordered_set_movable(
            func,
            value_id,
            traps_never_happen=ctx.traps_never_happen,
            ignore_implicit_traps=ctx.ignore_implicit_traps,
            module_ctx=ctx.module_ctx,
          ) {
          return false
        }
        let source_local = if code_pushing_node_is_local_get(func, value_id) {
          @ir.hot_node_get(func, value_id).imm0
        } else {
          if code_pushing_node_has_any_local_get(func, value_id) {
            return false
          }
          -1
        }
        if source_local == local_id {
          return false
        }
        if local_ids.any(fn(existing) { existing == source_local }) {
          ()
        } else if source_local_ids.any(fn(existing) { existing == local_id }) {
          return false
        }
        set_ids.push(root_id)
        local_ids.push(local_id)
        value_ids.push(value_id)
        source_local_ids.push(source_local)
      }
      @ir.HotOp::If if @ir.hot_node_result_arity(func, root_id) == 0 => break
      _ => return false
    }
    idx += 1
  }
  if set_ids.length() < 2 || idx >= @ir.hot_region_root_count(func, region_ref) {
    return false
  }
  let if_id = @ir.hot_region_get(func, region_ref, idx)
  if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
    return false
  }
  if @ir.hot_child_count(func, if_id) > 0 {
    let if_cond = @ir.hot_child_get(func, if_id, 0)
    for move_idx = 0; move_idx < value_ids.length(); move_idx = move_idx + 1 {
      if !code_pushing_node_safe_to_cross_for_value(
          func,
          if_cond,
          value_ids[move_idx],
          module_ctx=ctx.module_ctx,
        ) {
        return false
      }
    }
  }
  let arm_targets : Array[Int32] = []
  for move_idx = 0; move_idx < local_ids.length(); move_idx = move_idx + 1 {
    let local_id = local_ids[move_idx]
    let (then_gets, else_gets) = code_pushing_count_local_gets_in_if_arms(
      func, if_id, local_id,
    )
    if (then_gets == 0 && else_gets == 0) || (then_gets > 0 && else_gets > 0) {
      return false
    }
    arm_targets.push(if then_gets > 0 { 1 } else { 2 })
  }
  for move_idx = 0; move_idx < local_ids.length(); move_idx = move_idx + 1 {
    let local_id = local_ids[move_idx]
    let value_id = value_ids[move_idx]
    let source_local = source_local_ids[move_idx]
    let (prefix_gets, _) = code_pushing_count_local_gets_writes_in_region_prefix_bounded(
      func, region_ref, local_id, set_idx, 0, 1_000_000,
    )
    if prefix_gets != 0 {
      return false
    }
    if !code_pushing_node_safe_to_cross_for_value(
        func,
        if_id,
        value_id,
        module_ctx=ctx.module_ctx,
      ) {
      return false
    }
    if source_local >= 0 &&
      code_pushing_if_arms_write_local_deep_limited(
        func, if_id, source_local, 8,
      ) {
      return false
    }
    if source_local >= 0 {
      for source_idx = 0; source_idx < move_idx; source_idx = source_idx + 1 {
        if local_ids[source_idx] == source_local &&
          arm_targets[source_idx] != arm_targets[move_idx] {
          // A moved dependency must be initialized on the same path as the set
          // that reads it.
          return false
        }
      }
    }
    if code_pushing_if_arms_write_local_deep_limited(func, if_id, local_id, 8) {
      return false
    }
    let (total_gets, total_writes) = inventory.total(local_id)
    if total_writes != 1 || total_gets == 0 {
      return false
    }
    let mut internal_gets = 0
    for later_idx = move_idx + 1
        later_idx < set_ids.length()
        later_idx = later_idx + 1 {
      let (gets, writes) = code_pushing_count_local_gets_writes_in_node_bounded(
        func,
        set_ids[later_idx],
        local_id,
        total_gets,
        0,
      )
      if writes != 0 {
        return false
      }
      internal_gets += gets
    }
    let (then_gets, else_gets) = code_pushing_count_local_gets_in_if_arms(
      func, if_id, local_id,
    )
    if total_gets != internal_gets + then_gets + else_gets {
      return false
    }
  }
  let then_pushed : Array[Int32] = []
  let else_pushed : Array[Int32] = []
  for move_idx = 0; move_idx < set_ids.length(); move_idx = move_idx + 1 {
    let pushed = code_pushing_make_local_set_clone(
      func,
      local_ids[move_idx],
      value_ids[move_idx],
    )
    if arm_targets[move_idx] == 1 {
      then_pushed.push(pushed)
    } else {
      else_pushed.push(pushed)
    }
  }
  if then_pushed.length() > 0 {
    ignore(
      pass_splice_region(
        ctx,
        func,
        @ir.hot_region_then_ref(func, if_id),
        0,
        0,
        then_pushed,
      ),
    )
  }
  if else_pushed.length() > 0 {
    match @ir.hot_region_else_ref(func, if_id) {
      Some(else_ref) =>
        ignore(pass_splice_region(ctx, func, else_ref, 0, 0, else_pushed))
      None => return false
    }
  }
  for move_idx = 0; move_idx < set_ids.length(); move_idx = move_idx + 1 {
    let nop = @ir.hot_build_nop(func)
    pass_replace_node(ctx, func, set_ids[move_idx], @ir.hot_node_get(func, nop))
  }
  true
}

///|
fn code_pushing_try_sink_set_into_if_then(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  set_idx : Int32,
  inventory : CodePushingLocalInventory,
) -> Bool {
  if set_idx + 1 >= @ir.hot_region_root_count(func, region_ref) {
    return false
  }
  let set_id = @ir.hot_region_get(func, region_ref, set_idx)
  if set_id < 0 || !@ir.hot_node_is_live(func, set_id) {
    return false
  }
  let set_node = @ir.hot_node_get(func, set_id)
  match set_node.op {
    @ir.HotOp::LocalSet => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, set_id) != 0 {
    return false
  }
  let value_id = @ir.hot_child_get(func, set_id, 0)
  if !code_pushing_node_is_movable_value(
      func,
      value_id,
      traps_never_happen=ctx.traps_never_happen,
      ignore_implicit_traps=ctx.ignore_implicit_traps,
      module_ctx=ctx.module_ctx,
    ) &&
    !code_pushing_node_is_local_get(func, value_id) {
    return false
  }
  let source_local = if code_pushing_node_is_local_get(func, value_id) {
    @ir.hot_node_get(func, value_id).imm0
  } else {
    -1
  }
  if source_local == set_node.imm0 {
    return false
  }

  let mut if_idx = set_idx + 1
  let mut found_if = false
  while if_idx < @ir.hot_region_root_count(func, region_ref) {
    let candidate_id = @ir.hot_region_get(func, region_ref, if_idx)
    if candidate_id < 0 || !@ir.hot_node_is_live(func, candidate_id) {
      return false
    }
    match @ir.hot_node_get(func, candidate_id).op {
      @ir.HotOp::If if @ir.hot_node_result_arity(func, candidate_id) == 0 => {
        let (then_gets, else_gets) = code_pushing_count_local_gets_in_if_arms(
          func,
          candidate_id,
          set_node.imm0,
        )
        if then_gets + else_gets > 0 {
          found_if = true
          break
        }
        if !code_pushing_node_safe_to_cross_for_value(
            func,
            candidate_id,
            value_id,
            module_ctx=ctx.module_ctx,
          ) {
          return false
        }
      }
      _ => {
        let (gets, writes) = code_pushing_count_local_gets_writes_in_node_bounded(
          func,
          candidate_id,
          set_node.imm0,
          0,
          0,
        )
        if gets != 0 ||
          writes != 0 ||
          !code_pushing_node_safe_to_cross_for_value(
            func,
            candidate_id,
            value_id,
            module_ctx=ctx.module_ctx,
          ) {
          return false
        }
      }
    }
    if_idx += 1
  }
  if !found_if {
    return false
  }
  let if_id = @ir.hot_region_get(func, region_ref, if_idx)
  if @ir.hot_child_count(func, if_id) > 0 {
    let if_cond = @ir.hot_child_get(func, if_id, 0)
    if !code_pushing_node_safe_to_cross_for_value(
        func,
        if_cond,
        value_id,
        module_ctx=ctx.module_ctx,
      ) {
      return false
    }
  }
  if @ir.hot_node_result_arity(func, if_id) != 0 {
    return false
  }
  if source_local >= 0 &&
    code_pushing_if_arms_write_local_deep_limited(func, if_id, source_local, 8) {
    return false
  }
  let local_id = set_node.imm0
  let (then_gets, else_gets) = code_pushing_count_local_gets_in_if_arms(
    func, if_id, local_id,
  )
  let arm_gets = then_gets + else_gets
  if arm_gets == 0 || (then_gets > 0 && else_gets > 0) {
    return false
  }
  let (total_gets, total_writes) = inventory.total(local_id)
  if total_writes != 1 {
    return false
  }
  let then_ref = @ir.hot_region_then_ref(func, if_id)
  let else_ref = @ir.hot_region_else_ref(func, if_id)
  let target_ref = if then_gets > 0 { Some(then_ref) } else { else_ref }
  let mut gets_are_safe = total_gets == arm_gets
  if !gets_are_safe && total_gets > arm_gets {
    let (prefix_gets, prefix_writes) = code_pushing_count_local_gets_writes_in_region_prefix_bounded(
      func, region_ref, local_id, set_idx, 0, 0,
    )
    let (suffix_gets, suffix_writes) = code_pushing_count_local_gets_writes_in_region_suffix_bounded(
      func,
      region_ref,
      local_id,
      if_idx + 1,
      total_gets - arm_gets,
      0,
    )
    let non_consuming_ref = if then_gets > 0 {
      else_ref
    } else {
      Some(then_ref)
    }
    let non_consuming_exits = match non_consuming_ref {
      Some(ref_) => code_pushing_region_cannot_fall_through_simple(func, ref_)
      None => false
    }
    gets_are_safe = prefix_gets == 0 &&
      prefix_writes == 0 &&
      suffix_writes == 0 &&
      suffix_gets > 0 &&
      total_gets == arm_gets + suffix_gets &&
      non_consuming_exits
  }
  if !gets_are_safe {
    return false
  }
  match target_ref {
    Some(target_ref) => {
      ignore(@ir.hot_weaken_body_local_to_nullable_ref(func, local_id))
      let pushed_set = code_pushing_make_local_set_clone(
        func, local_id, value_id,
      )
      ignore(pass_splice_region(ctx, func, target_ref, 0, 0, [pushed_set]))
      let nop = @ir.hot_build_nop(func)
      pass_replace_node(ctx, func, set_id, @ir.hot_node_get(func, nop))
      true
    }
    None => false
  }
}

///|
fn code_pushing_scan_node_children_once(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_id : Int32,
  inventory : CodePushingLocalInventory,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let mut changed = false
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    let child_id = @ir.hot_child_get(func, node_id, slot)
    if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
      continue
    }
    while inventory.scan_seen.length() <= child_id {
      inventory.scan_seen.push(false)
    }
    if inventory.scan_seen[child_id] {
      continue
    }
    inventory.scan_seen[child_id] = true
    match @ir.hot_node_get(func, child_id).op {
      @ir.HotOp::Block | @ir.HotOp::Loop =>
        if code_pushing_scan_region_once(
            ctx,
            func,
            @ir.hot_region_body_ref(func, child_id),
            inventory,
          ) {
          changed = true
        }
      @ir.HotOp::If => {
        if code_pushing_scan_region_once(
            ctx,
            func,
            @ir.hot_region_then_ref(func, child_id),
            inventory,
          ) {
          changed = true
        }
        match @ir.hot_region_else_ref(func, child_id) {
          Some(else_ref) =>
            if code_pushing_scan_region_once(ctx, func, else_ref, inventory) {
              changed = true
            }
          None => ()
        }
      }
      _ => ()
    }
    if code_pushing_scan_node_children_once(ctx, func, child_id, inventory) {
      changed = true
    }
  }
  changed
}

///|
fn code_pushing_try_region_root_once(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  idx : Int32,
  inventory : CodePushingLocalInventory,
) -> Int32 {
  if code_pushing_try_flatten_dead_block_before_unreachable(
      ctx, func, region_ref, idx,
    ) {
    return 1
  }
  if code_pushing_try_sink_set_before_later_barrier(
      ctx, func, region_ref, idx, inventory,
    ) ||
    code_pushing_try_sink_generated_prefix_sets_after_br_on_non_null(
      ctx, func, region_ref, idx, inventory,
    ) ||
    code_pushing_try_sink_ordered_sets_after_push_point(
      ctx, func, region_ref, idx, inventory,
    ) ||
    code_pushing_try_sink_local_get_set_after_if(
      ctx, func, region_ref, idx, inventory,
    ) ||
    code_pushing_try_sink_zero_read_set_after_push_point(
      ctx, func, region_ref, idx, inventory,
    ) ||
    code_pushing_try_sink_set_after_if_push_point(
      ctx, func, region_ref, idx, inventory,
    ) ||
    code_pushing_try_sink_consecutive_sets_into_if_arm(
      ctx, func, region_ref, idx, inventory,
    ) ||
    code_pushing_try_sink_set_into_if_then(
      ctx, func, region_ref, idx, inventory,
    ) {
    return 2
  }
  0
}

///|
fn code_pushing_scan_region_once(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  inventory : CodePushingLocalInventory,
) -> Bool {
  let mut changed = false
  let mut idx = 0
  while idx < @ir.hot_region_root_count(func, region_ref) {
    match
      code_pushing_try_region_root_once(ctx, func, region_ref, idx, inventory) {
      1 => {
        changed = true
        continue
      }
      2 => changed = true
      _ => ()
    }
    idx += 1
  }

  // Sinking a consumer can make an earlier producer movable. Walk roots in
  // reverse once so an entire local-copy dependency chain is processed in
  // producer order without restarting the function after every rewrite.
  idx = @ir.hot_region_root_count(func, region_ref) - 1
  while idx >= 0 {
    match
      code_pushing_try_region_root_once(ctx, func, region_ref, idx, inventory) {
      1 => {
        changed = true
        if idx >= @ir.hot_region_root_count(func, region_ref) {
          idx = @ir.hot_region_root_count(func, region_ref) - 1
        }
        continue
      }
      2 => changed = true
      _ => ()
    }
    idx -= 1
  }

  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    while inventory.scan_seen.length() <= node_id {
      inventory.scan_seen.push(false)
    }
    if inventory.scan_seen[node_id] {
      continue
    }
    inventory.scan_seen[node_id] = true
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::Block | @ir.HotOp::Loop =>
        if code_pushing_scan_region_once(
            ctx,
            func,
            @ir.hot_region_body_ref(func, node_id),
            inventory,
          ) {
          changed = true
        }
      @ir.HotOp::If => {
        if code_pushing_scan_region_once(
            ctx,
            func,
            @ir.hot_region_then_ref(func, node_id),
            inventory,
          ) {
          changed = true
        }
        match @ir.hot_region_else_ref(func, node_id) {
          Some(else_ref) =>
            if code_pushing_scan_region_once(ctx, func, else_ref, inventory) {
              changed = true
            }
          None => ()
        }
      }
      @ir.HotOp::Try => {
        if code_pushing_scan_region_once(
            ctx,
            func,
            @ir.hot_region_body_ref(func, node_id),
            inventory,
          ) {
          changed = true
        }
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(catch_ref) =>
            if code_pushing_scan_region_once(ctx, func, catch_ref, inventory) {
              changed = true
            }
          None => ()
        }
      }
      @ir.HotOp::TryTable => {
        if code_pushing_scan_region_once(
            ctx,
            func,
            @ir.hot_region_body_ref(func, node_id),
            inventory,
          ) {
          changed = true
        }
        if code_pushing_scan_region_once(
            ctx,
            func,
            @ir.hot_region_catch_list_ref(func, node_id),
            inventory,
          ) {
          changed = true
        }
      }
      _ =>
        if code_pushing_scan_node_children_once(ctx, func, node_id, inventory) {
          changed = true
        }
    }
  }
  changed
}

///|
fn code_pushing_scan_all_control_nodes_once(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  inventory : CodePushingLocalInventory,
) -> Bool {
  let mut changed = false
  for node_id = 0; node_id < @ir.hot_node_count(func); node_id = node_id + 1 {
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    while inventory.scan_seen.length() <= node_id {
      inventory.scan_seen.push(false)
    }
    if inventory.scan_seen[node_id] {
      continue
    }
    inventory.scan_seen[node_id] = true
    match @ir.hot_node_get(func, node_id).op {
      @ir.HotOp::Block | @ir.HotOp::Loop =>
        if code_pushing_scan_region_once(
            ctx,
            func,
            @ir.hot_region_body_ref(func, node_id),
            inventory,
          ) {
          changed = true
        }
      @ir.HotOp::If => {
        if code_pushing_scan_region_once(
            ctx,
            func,
            @ir.hot_region_then_ref(func, node_id),
            inventory,
          ) {
          changed = true
        }
        match @ir.hot_region_else_ref(func, node_id) {
          Some(else_ref) =>
            if code_pushing_scan_region_once(ctx, func, else_ref, inventory) {
              changed = true
            }
          None => ()
        }
      }
      _ => ()
    }
  }
  changed
}

///|
fn code_pushing_raw_instr_count_local_accesses(
  instr : @lib.Instruction,
  local_id : UInt32,
) -> (Int32, Int32) {
  let mut gets = 0
  let mut writes = 0
  match instr {
    @lib.LocalGet(@lib.LocalIdx(index)) if index == local_id => gets = 1
    @lib.LocalSet(@lib.LocalIdx(index)) if index == local_id => writes = 1
    @lib.LocalTee(@lib.LocalIdx(index)) if index == local_id => writes = 1
    @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
      for nested in body {
        let (nested_gets, nested_writes) = code_pushing_raw_instr_count_local_accesses(
          nested, local_id,
        )
        gets += nested_gets
        writes += nested_writes
      }
    @lib.If(_, then_body, else_body) => {
      for nested in then_body {
        let (nested_gets, nested_writes) = code_pushing_raw_instr_count_local_accesses(
          nested, local_id,
        )
        gets += nested_gets
        writes += nested_writes
      }
      match else_body {
        Some(body) =>
          for nested in body {
            let (nested_gets, nested_writes) = code_pushing_raw_instr_count_local_accesses(
              nested, local_id,
            )
            gets += nested_gets
            writes += nested_writes
          }
        None => ()
      }
    }
    @lib.Try(_, @lib.Expr(body), catches, _) => {
      for nested in body {
        let (nested_gets, nested_writes) = code_pushing_raw_instr_count_local_accesses(
          nested, local_id,
        )
        gets += nested_gets
        writes += nested_writes
      }
      for catch_ in catches {
        let catch_body = match catch_ {
          @lib.LegacyCatch(_, @lib.Expr(body))
          | @lib.LegacyCatchAll(@lib.Expr(body)) => body
        }
        for nested in catch_body {
          let (nested_gets, nested_writes) = code_pushing_raw_instr_count_local_accesses(
            nested, local_id,
          )
          gets += nested_gets
          writes += nested_writes
        }
      }
    }
    @lib.TryTable(_, _, @lib.Expr(body)) =>
      for nested in body {
        let (nested_gets, nested_writes) = code_pushing_raw_instr_count_local_accesses(
          nested, local_id,
        )
        gets += nested_gets
        writes += nested_writes
      }
    _ => ()
  }
  (gets, writes)
}

///|
fn code_pushing_raw_instrs_count_local_accesses(
  instrs : Array[@lib.Instruction],
  local_id : UInt32,
) -> (Int32, Int32) {
  let mut gets = 0
  let mut writes = 0
  for instr in instrs {
    let (instr_gets, instr_writes) = code_pushing_raw_instr_count_local_accesses(
      instr, local_id,
    )
    gets += instr_gets
    writes += instr_writes
  }
  (gets, writes)
}

///|
fn code_pushing_raw_instrs_are_nonthrowing_catch_body(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for instr in instrs {
    match instr {
      @lib.Call(_)
      | @lib.CallIndirect(_)
      | @lib.CallRef(_)
      | @lib.ReturnCall(_)
      | @lib.ReturnCallIndirect(_)
      | @lib.ReturnCallRef(_)
      | @lib.Throw(_)
      | @lib.ThrowRef
      | @lib.Rethrow(_) => return false
      @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
        if !code_pushing_raw_instrs_are_nonthrowing_catch_body(body) {
          return false
        }
      @lib.If(_, then_instrs, else_body) => {
        if !code_pushing_raw_instrs_are_nonthrowing_catch_body(then_instrs) {
          return false
        }
        match else_body {
          Some(else_instrs) =>
            if !code_pushing_raw_instrs_are_nonthrowing_catch_body(else_instrs) {
              return false
            }
          None => ()
        }
      }
      @lib.Try(_, _, _, _) | @lib.TryTable(_, _, _) => return false
      _ => ()
    }
  }
  true
}

///|
fn code_pushing_raw_legacy_try_body_is_caught(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for instr in instrs {
    match instr {
      @lib.Call(_)
      | @lib.CallIndirect(_)
      | @lib.CallRef(_)
      | @lib.ReturnCall(_)
      | @lib.ReturnCallIndirect(_)
      | @lib.ReturnCallRef(_)
      | @lib.ThrowRef
      | @lib.Rethrow(_) => return false
      @lib.Throw(_) => ()
      @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
        if !code_pushing_raw_legacy_try_body_is_caught(body) {
          return false
        }
      @lib.If(_, then_instrs, else_body) => {
        if !code_pushing_raw_legacy_try_body_is_caught(then_instrs) {
          return false
        }
        match else_body {
          Some(else_instrs) =>
            if !code_pushing_raw_legacy_try_body_is_caught(else_instrs) {
              return false
            }
          None => ()
        }
      }
      @lib.Try(_, @lib.Expr(body), [], Some(@lib.LabelIdx(0U))) =>
        if !code_pushing_raw_legacy_try_body_is_caught(body) {
          return false
        }
      @lib.Try(_, _, _, _) | @lib.TryTable(_, _, _) => return false
      _ => ()
    }
  }
  true
}

///|
fn code_pushing_raw_legacy_try_is_fully_caught(
  instr : @lib.Instruction,
) -> Bool {
  match instr {
    @lib.Try(
      _,
      @lib.Expr(body),
      [@lib.LegacyCatchAll(@lib.Expr(catch_body))],
      None
    ) =>
      code_pushing_raw_legacy_try_body_is_caught(body) &&
      code_pushing_raw_instrs_are_nonthrowing_catch_body(catch_body)
    _ => false
  }
}

///|
fn code_pushing_raw_instrs_have_legacy_try(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for instr in instrs {
    match instr {
      @lib.Try(_, _, _, _) => return true
      @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
        if code_pushing_raw_instrs_have_legacy_try(body) {
          return true
        }
      @lib.If(_, then_instrs, else_body) => {
        if code_pushing_raw_instrs_have_legacy_try(then_instrs) {
          return true
        }
        match else_body {
          Some(else_instrs) =>
            if code_pushing_raw_instrs_have_legacy_try(else_instrs) {
              return true
            }
          None => ()
        }
      }
      @lib.TryTable(_, _, @lib.Expr(body)) =>
        if code_pushing_raw_instrs_have_legacy_try(body) {
          return true
        }
      _ => ()
    }
  }
  false
}

///|
fn code_pushing_raw_try_move_const_set_after_branch(
  instrs : Array[@lib.Instruction],
  function_body : Array[@lib.Instruction],
  access_counts : @hashmap.HashMap[UInt32, (Int32, Int32)],
) -> Bool {
  if instrs.length() < 7 {
    return false
  }
  let mut value_idx = 0
  while value_idx + 2 < instrs.length() {
    let local_id = match (instrs[value_idx], instrs[value_idx + 1]) {
      (@lib.I32Const(_), @lib.LocalSet(@lib.LocalIdx(local_id))) => local_id
      _ => {
        value_idx += 1
        continue
      }
    }
    if !code_pushing_raw_legacy_try_is_fully_caught(instrs[value_idx + 2]) {
      value_idx += 1
      continue
    }
    let (total_gets, total_writes) = match access_counts.get(local_id) {
      Some(counts) => counts
      None => {
        let counts = code_pushing_raw_instrs_count_local_accesses(
          function_body, local_id,
        )
        access_counts[local_id] = counts
        counts
      }
    }
    if total_gets == 0 || total_writes != 1 {
      value_idx += 1
      continue
    }
    let mut branch_idx = value_idx + 3
    let mut blocked = false
    while branch_idx < instrs.length() {
      match instrs[branch_idx] {
        @lib.BrIf(_) => break
        @lib.LocalGet(@lib.LocalIdx(index))
        | @lib.LocalSet(@lib.LocalIdx(index))
        | @lib.LocalTee(@lib.LocalIdx(index)) if index == local_id => {
          blocked = true
          break
        }
        @lib.Call(_)
        | @lib.CallIndirect(_)
        | @lib.CallRef(_)
        | @lib.ReturnCall(_)
        | @lib.ReturnCallIndirect(_)
        | @lib.ReturnCallRef(_)
        | @lib.Throw(_)
        | @lib.ThrowRef
        | @lib.Rethrow(_)
        | @lib.Try(_, _, _, _)
        | @lib.TryTable(_, _, _) => {
          blocked = true
          break
        }
        _ => ()
      }
      branch_idx += 1
    }
    if blocked || branch_idx >= instrs.length() {
      value_idx += 1
      continue
    }
    let mut suffix_gets = 0
    for suffix_idx = branch_idx + 1
        suffix_idx < instrs.length()
        suffix_idx = suffix_idx + 1 {
      let (gets, writes) = code_pushing_raw_instr_count_local_accesses(
        instrs[suffix_idx],
        local_id,
      )
      if writes != 0 {
        blocked = true
      }
      suffix_gets += gets
    }
    if blocked || suffix_gets != total_gets {
      value_idx += 1
      continue
    }
    let value = instrs[value_idx]
    let set = instrs[value_idx + 1]
    ignore(instrs.remove(value_idx))
    ignore(instrs.remove(value_idx))
    let shifted_branch_idx = branch_idx - 2
    instrs.insert(shifted_branch_idx + 1, value)
    instrs.insert(shifted_branch_idx + 2, set)
    return true
  }
  false
}

///|
fn code_pushing_raw_rewrite_legacy_instrs_cached(
  instrs : Array[@lib.Instruction],
  function_body : Array[@lib.Instruction],
  access_counts : @hashmap.HashMap[UInt32, (Int32, Int32)],
) -> (Array[@lib.Instruction], Bool) {
  let rewritten = instrs.copy()
  let mut changed = false
  for idx = 0; idx < rewritten.length(); idx = idx + 1 {
    let (next, nested_changed) = pass_rewrite_structured_instr(rewritten[idx], fn(
      body,
    ) {
      code_pushing_raw_rewrite_legacy_instrs_cached(
        body, function_body, access_counts,
      )
    })
    if nested_changed {
      rewritten[idx] = next
      changed = true
    }
  }
  while code_pushing_raw_try_move_const_set_after_branch(
          rewritten, function_body, access_counts,
        ) {
    changed = true
  }
  (rewritten, changed)
}

///|
fn code_pushing_raw_rewrite_legacy_instrs(
  instrs : Array[@lib.Instruction],
  function_body : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
  let access_counts : @hashmap.HashMap[UInt32, (Int32, Int32)] = @hashmap.new()
  code_pushing_raw_rewrite_legacy_instrs_cached(
    instrs, function_body, access_counts,
  )
}

///|
fn code_pushing_run_raw_legacy_func(func : @lib.Func) -> (@lib.Func, Bool)? {
  let @lib.Func(locals, @lib.Expr(body)) = func
  if !code_pushing_raw_instrs_have_legacy_try(body) {
    return None
  }
  let (rewritten, changed) = code_pushing_raw_rewrite_legacy_instrs(body, body)
  Some((@lib.Func::new(locals, @lib.Expr::new(rewritten)), changed))
}

///|
fn code_pushing_run(ctx : HotPassContext, func : @ir.HotFunc) -> HotPassResult {
  let inventory = CodePushingLocalInventory::from_func(func)
  let mut changed = code_pushing_scan_region_once(
    ctx,
    func,
    @ir.HotRegionRef::root(),
    inventory,
  )
  if code_pushing_scan_all_control_nodes_once(ctx, func, inventory) {
    changed = true
  }
  if changed {
    HotPassResult::changed()
  } else {
    HotPassResult::unchanged()
  }
}