// 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/DeadCodeElimination.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 dead_code_elimination_descriptor() -> @ir.HotPassDescriptor {
  @ir.HotPassDescriptor::new("dead-code-elimination", requires=[], 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 dead_code_elimination_summary() -> String {
  "Prune unreachable tails, dead dropped values, and dead-result structured control in hot IR regions."
}

///|
priv struct DeadCodeEliminationNodeUseCache {
  mut revision : Int32
  mut use_def : @ir.HotUseDef?
  first_unreachable_revision : Array[Int32]
  first_unreachable_absent : @bitset.BitSet
  first_unreachable_visits : Array[Int32]?
}

///|
priv struct DeadCodeEliminationFallthroughCache {
  revision : Array[Int32]
  result : @bitset.BitSet
}

///|
priv struct DeadCodeEliminationBranchUserCache {
  mut has_any_branch : Bool?
  mut has_incoming_by_label : @bitset.BitSet?
  mut by_label : Array[Array[Int32]]?
}

///|
priv struct DeadCodeEliminationPurityCache {
  revision : Array[Int32]
  result : @bitset.BitSet
}

///|
priv struct DeadCodeEliminationStackedForwarderTail {
  payload_if : Int32
  rewrite_region : @ir.HotRegionRef
  rewrite_idx : Int32
}

///|
fn dead_code_elimination_build_i32_eqz(
  func : @ir.HotFunc,
  operand : Int32,
) -> Int32 {
  @ir.hot_build_select(
    func,
    @lib.ValType::i32(),
    @ir.hot_build_const_i32(func, @lib.I32(0)),
    @ir.hot_build_const_i32(func, @lib.I32(1)),
    operand,
  )
}

///|
fn DeadCodeEliminationNodeUseCache::new(
  func : @ir.HotFunc,
  first_unreachable_visits~ : Array[Int32]? = None,
) -> DeadCodeEliminationNodeUseCache {
  let node_count = @ir.hot_node_count(func)
  {
    revision: -1,
    use_def: None,
    first_unreachable_revision: Array::make(node_count, -1),
    first_unreachable_absent: @bitset.BitSet::new(node_count),
    first_unreachable_visits,
  }
}

///|
fn dead_code_elimination_ensure_first_unreachable_cache_capacity(
  cache : DeadCodeEliminationNodeUseCache,
  node_id : Int32,
) -> Unit {
  while cache.first_unreachable_revision.length() <= node_id {
    cache.first_unreachable_revision.push(-1)
  }
  cache.first_unreachable_absent.grow(cache.first_unreachable_revision.length())
}

///|
fn DeadCodeEliminationBranchUserCache::new() -> DeadCodeEliminationBranchUserCache {
  { has_any_branch: None, has_incoming_by_label: None, by_label: None, }
}

///|
fn dead_code_elimination_invalidate_branch_user_cache(
  cache : DeadCodeEliminationBranchUserCache,
) -> Unit {
  cache.has_any_branch = None
  cache.has_incoming_by_label = None
  cache.by_label = None
}

///|
fn dead_code_elimination_new_fallthrough_cache(
  func : @ir.HotFunc,
) -> DeadCodeEliminationFallthroughCache {
  let revision = Array::make(@ir.hot_node_count(func), -1)
  let result = @bitset.BitSet::new(@ir.hot_node_count(func))
  { revision, result, }
}

///|
fn dead_code_elimination_ensure_fallthrough_cache_capacity(
  cache : DeadCodeEliminationFallthroughCache,
  node_id : Int32,
) -> Unit {
  while cache.revision.length() <= node_id {
    cache.revision.push(-1)
  }
  cache.result.grow(cache.revision.length())
}

///|
fn dead_code_elimination_new_purity_cache(
  func : @ir.HotFunc,
) -> DeadCodeEliminationPurityCache {
  let revision = Array::make(@ir.hot_node_count(func), -1)
  let result = @bitset.BitSet::new(@ir.hot_node_count(func))
  { revision, result, }
}

///|
fn dead_code_elimination_ensure_purity_cache_capacity(
  cache : DeadCodeEliminationPurityCache,
  node_id : Int32,
) -> Unit {
  while cache.revision.length() <= node_id {
    cache.revision.push(-1)
  }
  cache.result.grow(cache.revision.length())
}

///|
fn dead_code_elimination_flags_force_impure(flags : UInt32) -> Bool {
  (flags & @ir.HOT_FLAG_CONTROL) != 0U ||
  (flags & @ir.HOT_FLAG_BRANCH) != 0U ||
  (flags & @ir.HOT_FLAG_TERMINATOR) != 0U ||
  (flags & @ir.HOT_FLAG_CALL) != 0U ||
  (flags & @ir.HOT_FLAG_THROWS) != 0U ||
  (flags & @ir.HOT_FLAG_HAS_EXCEPTIONAL_SUCC) != 0U ||
  (flags & @ir.HOT_FLAG_MAY_TRAP) != 0U
}

///|
fn dead_code_elimination_node_is_intrinsically_impure(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if dead_code_elimination_flags_force_impure(@ir.hot_node_flags(func, node_id)) {
    return true
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::LocalGet
    | @ir.HotOp::LocalSet
    | @ir.HotOp::LocalTee
    | @ir.HotOp::GlobalGet
    | @ir.HotOp::GlobalSet
    | @ir.HotOp::Load
    | @ir.HotOp::Store
    | @ir.HotOp::MemorySize
    | @ir.HotOp::MemoryGrow
    | @ir.HotOp::MemoryCopy
    | @ir.HotOp::MemoryInit
    | @ir.HotOp::MemoryFill
    | @ir.HotOp::DataDrop
    | @ir.HotOp::TableGet
    | @ir.HotOp::TableSize
    | @ir.HotOp::TableSet
    | @ir.HotOp::TableGrow
    | @ir.HotOp::TableFill
    | @ir.HotOp::TableCopy
    | @ir.HotOp::TableInit
    | @ir.HotOp::ElemDrop
    | @ir.HotOp::Atomic
    | @ir.HotOp::Heap => true
    _ => false
  }
}

///|
fn dead_code_elimination_require_node_uses(
  cache : DeadCodeEliminationNodeUseCache,
  func : @ir.HotFunc,
) -> @ir.HotUseDef {
  let revision = @ir.hot_revision_current(func)
  match cache.use_def {
    Some(existing) if cache.revision == revision => existing
    _ => {
      let rebuilt = @ir.use_def_build_node_uses(func)
      cache.revision = revision
      cache.use_def = Some(rebuilt)
      rebuilt
    }
  }
}

///|
fn dead_code_elimination_node_use_count(
  cache : DeadCodeEliminationNodeUseCache,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Int32 {
  let use_def = dead_code_elimination_require_node_uses(cache, func)
  @ir.node_use_count(use_def, node_id)
}

///|
fn dead_code_elimination_collect_detached_nodes(
  func : @ir.HotFunc,
  node_id : Int32,
  out : Array[Int32],
  limit : Int32,
) -> Unit {
  if out.length() > limit || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return
  }
  out.push(node_id)
  if out.length() > limit {
    return
  }
  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
    }
    dead_code_elimination_collect_detached_nodes(func, child_id, out, limit)
    if out.length() > limit {
      return
    }
  }
}

///|
fn dead_code_elimination_region_result_arity(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
) -> Int32 {
  if region_ref == @ir.HotRegionRef::root() {
    return @ir.hot_region_result_arity(func, region_ref)
  }
  let holder = region_ref.holder
  match @ir.hot_node_get(func, holder).op {
    // Loop bodies fall through with the loop's result type, but their label
    // branch arity stays separate for continue-style branches.
    @ir.HotOp::Loop => @ir.hot_node_result_arity(func, holder)
    _ => @ir.hot_region_result_arity(func, region_ref)
  }
}

///|
fn dead_code_elimination_delete_unused_nodes(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  _node_use_cache : DeadCodeEliminationNodeUseCache,
  node_ids : Array[Int32],
) -> Unit {
  // Large detached subgraphs do not affect lowered output once roots/child
  // edges are gone; eagerly collecting them here is disproportionately
  // expensive on real modules.
  if node_ids.length() > 32 {
    return
  }
  let node_count = @ir.hot_node_count(func)
  let mut first_candidate = node_count
  let mut last_candidate = -1
  for node_id in node_ids {
    if node_id < 0 ||
      node_id >= node_count ||
      !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    let label_id = @ir.hot_control_node_label(func, node_id)
    if label_id >= 0 &&
      label_id < func.labels.length() &&
      @ir.hot_label_owner(func, label_id) == node_id {
      continue
    }
    if node_id < first_candidate {
      first_candidate = node_id
    }
    if node_id > last_candidate {
      last_candidate = node_id
    }
  }
  if last_candidate < 0 {
    return
  }
  let candidate_span = last_candidate - first_candidate + 1
  let candidates : Array[Int32] = []
  let in_candidate = @bitset.BitSet::new(candidate_span)
  for node_id in node_ids {
    if node_id < first_candidate ||
      node_id > last_candidate ||
      !@ir.hot_node_is_live(func, node_id) ||
      in_candidate[node_id - first_candidate] {
      continue
    }
    let label_id = @ir.hot_control_node_label(func, node_id)
    if label_id >= 0 &&
      label_id < func.labels.length() &&
      @ir.hot_label_owner(func, label_id) == node_id {
      continue
    }
    in_candidate[node_id - first_candidate] = true
    candidates.push(node_id)
  }
  if candidates.is_empty() {
    return
  }
  // Count references to this small candidate set once. Detached nodes can
  // still be referenced by other live detached nodes, so reachable use-def
  // counts alone are insufficient here.
  let references = Array::make(candidate_span, 0)
  for label_id = 0; label_id < func.labels.length(); label_id = label_id + 1 {
    let owner = @ir.hot_label_owner(func, label_id)
    if owner >= first_candidate &&
      owner <= last_candidate &&
      in_candidate[owner - first_candidate] {
      references[owner - first_candidate] = references[owner - first_candidate] +
        1
    }
  }
  for root_id in func.roots {
    if root_id >= first_candidate &&
      root_id <= last_candidate &&
      in_candidate[root_id - first_candidate] {
      references[root_id - first_candidate] = references[root_id -
        first_candidate] +
        1
    }
  }
  for user_id = 0; user_id < node_count; user_id = user_id + 1 {
    let user = func.nodes[user_id]
    if user.child_count == 0 || !@ir.hot_node_is_live(func, user_id) {
      continue
    }
    for slot = 0; slot < user.child_count; slot = slot + 1 {
      let child_id = func.children[user.child_base + slot]
      if child_id >= first_candidate &&
        child_id <= last_candidate &&
        child_id != user_id &&
        in_candidate[child_id - first_candidate] {
        references[child_id - first_candidate] = references[child_id -
          first_candidate] +
          1
      }
    }
  }
  let deleted : Array[Int32] = []
  let mut progressed = true
  while progressed {
    progressed = false
    for node_id in candidates {
      if !in_candidate[node_id - first_candidate] ||
        references[node_id - first_candidate] != 0 {
        continue
      }
      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 >= first_candidate &&
          child_id <= last_candidate &&
          child_id != node_id &&
          in_candidate[child_id - first_candidate] {
          references[child_id - first_candidate] = references[child_id -
            first_candidate] -
            1
        }
      }
      deleted.push(node_id)
      progressed = true
      in_candidate[node_id - first_candidate] = false
    }
  }
  if !deleted.is_empty() {
    @ir.hot_delete_detached_nodes(func, deleted)
    pass_mark_mutated(ctx, func)
  }
}

///|
fn dead_code_elimination_node_is_pure(
  purity_cache : DeadCodeEliminationPurityCache,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return true
  }
  let revision = @ir.hot_revision_current(func)
  dead_code_elimination_ensure_purity_cache_capacity(purity_cache, node_id)
  if purity_cache.revision[node_id] == revision {
    return purity_cache.result[node_id]
  }
  if dead_code_elimination_node_is_intrinsically_impure(func, node_id) {
    purity_cache.revision[node_id] = revision
    purity_cache.result[node_id] = false
    return false
  }
  let mut result = true
  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) &&
      !dead_code_elimination_node_is_pure(purity_cache, func, child_id) {
      result = false
      break
    }
  }
  purity_cache.revision[node_id] = revision
  purity_cache.result[node_id] = result
  result
}

///|
fn dead_code_elimination_record_branch_target(
  branch_users_by_label : Array[Array[Int32]],
  last_seen_branch : Array[Int32],
  branch_id : Int32,
  target : Int32,
) -> Unit {
  if target < 0 || target >= branch_users_by_label.length() {
    return
  }
  if last_seen_branch[target] == branch_id {
    return
  }
  last_seen_branch[target] = branch_id
  branch_users_by_label[target].push(branch_id)
}

///|
fn dead_code_elimination_record_incoming_branch_target(
  has_incoming_by_label : @bitset.BitSet,
  target : Int32,
) -> Unit {
  if target < 0 || target >= has_incoming_by_label.length() {
    return
  }
  has_incoming_by_label[target] = true
}

///|
fn dead_code_elimination_build_branch_users_by_label(
  func : @ir.HotFunc,
) -> Array[Array[Int32]] {
  let branch_users_by_label : Array[Array[Int32]] = []
  let last_seen_branch : Array[Int32] = []
  for label_id = 0; label_id < func.labels.length(); label_id = label_id + 1 {
    branch_users_by_label.push([])
    last_seen_branch.push(-1)
  }
  for branch_id = 0
      branch_id < @ir.hot_node_count(func)
      branch_id = branch_id + 1 {
    if !@ir.hot_node_is_live(func, branch_id) {
      continue
    }
    match @ir.hot_branch_target(func, branch_id) {
      Some(target) =>
        dead_code_elimination_record_branch_target(
          branch_users_by_label, last_seen_branch, branch_id, target,
        )
      _ => ()
    }
    for target in @ir.hot_branch_table_targets_for_node(func, branch_id) {
      dead_code_elimination_record_branch_target(
        branch_users_by_label, last_seen_branch, branch_id, target,
      )
    }
  }
  branch_users_by_label
}

///|
fn dead_code_elimination_build_has_incoming_branches_by_label(
  func : @ir.HotFunc,
) -> @bitset.BitSet {
  let has_incoming_by_label = @bitset.BitSet::new(func.labels.length())
  for branch_id = 0
      branch_id < @ir.hot_node_count(func)
      branch_id = branch_id + 1 {
    if !@ir.hot_node_is_live(func, branch_id) {
      continue
    }
    match @ir.hot_branch_target(func, branch_id) {
      Some(target) =>
        dead_code_elimination_record_incoming_branch_target(
          has_incoming_by_label, target,
        )
      _ => ()
    }
    for target in @ir.hot_branch_table_targets_for_node(func, branch_id) {
      dead_code_elimination_record_incoming_branch_target(
        has_incoming_by_label, target,
      )
    }
    let node = @ir.hot_node_get(func, branch_id)
    match node.op {
      @ir.HotOp::Continuation =>
        for target in @ir.hot_continuation_targets(func, branch_id) {
          dead_code_elimination_record_incoming_branch_target(
            has_incoming_by_label, target,
          )
        }
      @ir.HotOp::TryTable => {
        let info = @ir.hot_catch_info_get(func, node.imm1)
        for catch_ in info.catches {
          let target = match catch_ {
            @ir.HotCatchArm::CatchArm(_, label_id)
            | @ir.HotCatchArm::CatchRefArm(_, label_id)
            | @ir.HotCatchArm::CatchAllArm(label_id)
            | @ir.HotCatchArm::CatchAllRefArm(label_id) => label_id
          }
          dead_code_elimination_record_incoming_branch_target(
            has_incoming_by_label, target,
          )
        }
      }
      _ => ()
    }
  }
  has_incoming_by_label
}

///|
fn dead_code_elimination_build_empty_branch_users_by_label(
  func : @ir.HotFunc,
) -> Array[Array[Int32]] {
  let branch_users_by_label : Array[Array[Int32]] = []
  for _ in 0.. @bitset.BitSet {
  @bitset.BitSet::new(func.labels.length())
}

///|
fn dead_code_elimination_require_has_any_branch(
  cache : DeadCodeEliminationBranchUserCache,
  func : @ir.HotFunc,
) -> Bool {
  match cache.has_any_branch {
    Some(existing) => existing
    None => {
      let mut has_any_branch = false
      for node_id = 0; node_id < @ir.hot_node_count(func); node_id = node_id + 1 {
        if !@ir.hot_node_is_live(func, node_id) {
          continue
        }
        if @ir.hot_node_has_flag(func, node_id, @ir.HOT_FLAG_BRANCH) ||
          @ir.hot_node_get(func, node_id).op is @ir.HotOp::Continuation ||
          @ir.hot_node_get(func, node_id).op is @ir.HotOp::TryTable {
          has_any_branch = true
          break
        }
      }
      cache.has_any_branch = Some(has_any_branch)
      has_any_branch
    }
  }
}

///|
fn dead_code_elimination_require_has_incoming_branches_by_label(
  cache : DeadCodeEliminationBranchUserCache,
  func : @ir.HotFunc,
) -> @bitset.BitSet {
  match cache.has_incoming_by_label {
    Some(existing) => existing
    None => {
      let built = if dead_code_elimination_require_has_any_branch(cache, func) {
        dead_code_elimination_build_has_incoming_branches_by_label(func)
      } else {
        dead_code_elimination_build_empty_incoming_branches_by_label(func)
      }
      cache.has_incoming_by_label = Some(built)
      built
    }
  }
}

///|
fn dead_code_elimination_require_branch_users_by_label(
  cache : DeadCodeEliminationBranchUserCache,
  func : @ir.HotFunc,
) -> Array[Array[Int32]] {
  match cache.by_label {
    Some(existing) => existing
    None => {
      let built = if dead_code_elimination_require_has_any_branch(cache, func) {
        dead_code_elimination_build_branch_users_by_label(func)
      } else {
        dead_code_elimination_build_empty_branch_users_by_label(func)
      }
      cache.by_label = Some(built)
      built
    }
  }
}

///|
fn dead_code_elimination_can_voidify_control(
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  fallthrough_cache : DeadCodeEliminationFallthroughCache,
  node_id : Int32,
) -> Bool {
  if @ir.hot_node_result_arity(func, node_id) == 0 {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Block =>
      dead_code_elimination_label_has_incoming_branch(
        dead_code_elimination_require_has_incoming_branches_by_label(
          branch_user_cache, func,
        ),
        @ir.hot_control_node_label(func, node_id),
      ) ||
      !dead_code_elimination_region_may_fallthrough(
        func,
        branch_user_cache,
        fallthrough_cache,
        @ir.hot_region_body_ref(func, node_id),
      )
    @ir.HotOp::Loop =>
      @ir.hot_label_branch_arity(
        func,
        @ir.hot_control_node_label(func, node_id),
      ) ==
      0
    @ir.HotOp::If | @ir.HotOp::Try | @ir.HotOp::TryTable =>
      !dead_code_elimination_has_incoming_internal_region_branches(
        func,
        dead_code_elimination_require_has_incoming_branches_by_label(
          branch_user_cache, func,
        ),
        node_id,
      )
    _ => false
  }
}

///|
fn dead_code_elimination_label_has_incoming_branch(
  has_incoming_by_label : @bitset.BitSet,
  label_id : Int32,
) -> Bool {
  label_id >= 0 &&
  label_id < has_incoming_by_label.length() &&
  has_incoming_by_label[label_id]
}

///|
fn dead_code_elimination_region_has_incoming_branches(
  func : @ir.HotFunc,
  has_incoming_by_label : @bitset.BitSet,
  region_ref : @ir.HotRegionRef,
) -> Bool {
  if region_ref == @ir.HotRegionRef::root() {
    return false
  }
  dead_code_elimination_label_has_incoming_branch(
    has_incoming_by_label,
    @ir.hot_control_node_label(func, region_ref.holder),
  )
}

///|
fn dead_code_elimination_has_incoming_internal_region_branches(
  func : @ir.HotFunc,
  has_incoming_by_label : @bitset.BitSet,
  node_id : Int32,
) -> Bool {
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::If => {
      let then_has_branches = dead_code_elimination_region_has_incoming_branches(
        func,
        has_incoming_by_label,
        @ir.hot_region_then_ref(func, node_id),
      )
      let else_has_branches = match @ir.hot_region_else_ref(func, node_id) {
        Some(else_region) =>
          dead_code_elimination_region_has_incoming_branches(
            func, has_incoming_by_label, else_region,
          )
        None => false
      }
      then_has_branches || else_has_branches
    }
    @ir.HotOp::Try => {
      let body_has_branches = dead_code_elimination_region_has_incoming_branches(
        func,
        has_incoming_by_label,
        @ir.hot_region_body_ref(func, node_id),
      )
      let catch_has_branches = match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_region) =>
          dead_code_elimination_region_has_incoming_branches(
            func, has_incoming_by_label, catch_region,
          )
        None => false
      }
      body_has_branches || catch_has_branches
    }
    @ir.HotOp::TryTable =>
      dead_code_elimination_region_has_incoming_branches(
        func,
        has_incoming_by_label,
        @ir.hot_region_body_ref(func, node_id),
      ) ||
      dead_code_elimination_region_has_incoming_branches(
        func,
        has_incoming_by_label,
        @ir.hot_region_catch_list_ref(func, node_id),
      )
    _ => false
  }
}

///|
fn dead_code_elimination_op_has_fallthrough_edge(op : @ir.HotOp) -> Bool {
  match op {
    @ir.HotOp::Br
    | @ir.HotOp::BrTable
    | @ir.HotOp::Return
    | @ir.HotOp::ReturnCall
    | @ir.HotOp::ReturnCallIndirect
    | @ir.HotOp::ReturnCallRef
    | @ir.HotOp::Throw
    | @ir.HotOp::ThrowRef
    | @ir.HotOp::Rethrow
    | @ir.HotOp::Delegate
    | @ir.HotOp::Unreachable => false
    @ir.HotOp::BrIf
    | @ir.HotOp::BrOnNull
    | @ir.HotOp::BrOnNonNull
    | @ir.HotOp::BrOnCast
    | @ir.HotOp::BrOnCastFail
    | @ir.HotOp::Block
    | @ir.HotOp::Loop
    | @ir.HotOp::If
    | @ir.HotOp::Try
    | @ir.HotOp::TryTable => true
    _ => true
  }
}

///|
fn dead_code_elimination_region_may_fallthrough(
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  fallthrough_cache : DeadCodeEliminationFallthroughCache,
  region_ref : @ir.HotRegionRef,
) -> Bool {
  let root_count = @ir.hot_region_root_count(func, region_ref)
  let mut idx = 0
  while idx < root_count {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if node_id >= 0 &&
      @ir.hot_node_is_live(func, node_id) &&
      !dead_code_elimination_node_may_fallthrough(
        func, branch_user_cache, fallthrough_cache, node_id,
      ) {
      return false
    }
    idx += 1
  }
  true
}

///|
fn dead_code_elimination_region_has_terminal_last_live_root(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
) -> Bool {
  let mut idx = @ir.hot_region_root_count(func, region_ref) - 1
  while idx >= 0 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if node_id >= 0 && @ir.hot_node_is_live(func, node_id) {
      match @ir.hot_node_get(func, node_id).op {
        @ir.HotOp::Br
        | @ir.HotOp::BrTable
        | @ir.HotOp::Return
        | @ir.HotOp::ReturnCall
        | @ir.HotOp::ReturnCallIndirect
        | @ir.HotOp::ReturnCallRef
        | @ir.HotOp::Throw
        | @ir.HotOp::ThrowRef
        | @ir.HotOp::Rethrow
        | @ir.HotOp::Delegate
        | @ir.HotOp::Unreachable => return true
        _ => return false
      }
    }
    idx -= 1
  }
  false
}

///|
fn dead_code_elimination_region_has_source_preceding_tail_root(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  idx : Int32,
  node_id : Int32,
) -> Bool {
  // Lifted result roots can be stored after a terminator even though lowering
  // schedules them before it. Equal orders are repeated lanes of one
  // multivalue producer. Those roots are live dependencies, not a dead suffix.
  let node_order = @ir.hot_node_get(func, node_id).order
  if node_order < 0 {
    return false
  }
  for tail_idx = idx + 1
      tail_idx < @ir.hot_region_root_count(func, region_ref)
      tail_idx = tail_idx + 1 {
    let tail_id = @ir.hot_region_get(func, region_ref, tail_idx)
    if tail_id >= 0 &&
      @ir.hot_node_is_live(func, tail_id) &&
      (
        @ir.hot_node_result_arity(func, tail_id) > 0 ||
        @ir.hot_is_control_node(func, tail_id)
      ) &&
      @ir.hot_node_get(func, tail_id).order >= 0 &&
      @ir.hot_node_get(func, tail_id).order <= node_order {
      return true
    }
  }
  false
}

///|
fn dead_code_elimination_try_restore_source_order_before_terminator(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  idx : Int32,
  node_id : Int32,
) -> Bool {
  if !@ir.hot_is_terminator_node(func, node_id) {
    return false
  }
  let node_order = @ir.hot_node_get(func, node_id).order
  if node_order < 0 {
    return false
  }
  // Lifting can append result lanes after a source-later terminator, and
  // voidification can turn such a result root into structured control while
  // retaining its source order. Rotate only the contiguous represented lanes
  // or controls, so no unrelated root crosses the terminator. Plain void roots
  // are ordinary dead suffixes and must remain available for tail pruning.
  let mut tail_end = idx
  for tail_idx = idx + 1
      tail_idx < @ir.hot_region_root_count(func, region_ref)
      tail_idx = tail_idx + 1 {
    let tail_id = @ir.hot_region_get(func, region_ref, tail_idx)
    if tail_id < 0 ||
      !@ir.hot_node_is_live(func, tail_id) ||
      (
        @ir.hot_node_result_arity(func, tail_id) == 0 &&
        !@ir.hot_is_control_node(func, tail_id)
      ) ||
      @ir.hot_node_get(func, tail_id).order < 0 ||
      @ir.hot_node_get(func, tail_id).order > node_order {
      break
    }
    tail_end = tail_idx
  }
  if tail_end == idx {
    return false
  }
  let replacement : Array[Int32] = []
  for tail_idx = idx + 1; tail_idx <= tail_end; tail_idx = tail_idx + 1 {
    replacement.push(@ir.hot_region_get(func, region_ref, tail_idx))
  }
  replacement.push(node_id)
  ignore(
    pass_splice_region(
      ctx,
      func,
      region_ref,
      idx,
      tail_end - idx + 1,
      replacement,
    ),
  )
  true
}

///|
fn dead_code_elimination_node_may_fallthrough(
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  fallthrough_cache : DeadCodeEliminationFallthroughCache,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return true
  }
  let revision = @ir.hot_revision_current(func)
  dead_code_elimination_ensure_fallthrough_cache_capacity(
    fallthrough_cache, node_id,
  )
  if fallthrough_cache.revision[node_id] == revision {
    return fallthrough_cache.result[node_id]
  }
  let result = dead_code_elimination_node_may_fallthrough_uncached(
    func, branch_user_cache, fallthrough_cache, node_id,
  )
  fallthrough_cache.revision[node_id] = revision
  fallthrough_cache.result[node_id] = result
  result
}

///|
fn dead_code_elimination_node_may_fallthrough_uncached(
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  fallthrough_cache : DeadCodeEliminationFallthroughCache,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return true
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Block =>
      dead_code_elimination_label_has_incoming_branch(
        dead_code_elimination_require_has_incoming_branches_by_label(
          branch_user_cache, func,
        ),
        @ir.hot_control_node_label(func, node_id),
      ) ||
      dead_code_elimination_region_may_fallthrough(
        func,
        branch_user_cache,
        fallthrough_cache,
        @ir.hot_region_body_ref(func, node_id),
      )
    @ir.HotOp::Loop =>
      dead_code_elimination_region_may_fallthrough(
        func,
        branch_user_cache,
        fallthrough_cache,
        @ir.hot_region_body_ref(func, node_id),
      )
    @ir.HotOp::If => {
      let has_incoming_by_label = dead_code_elimination_require_has_incoming_branches_by_label(
        branch_user_cache, func,
      )
      if dead_code_elimination_label_has_incoming_branch(
          has_incoming_by_label,
          @ir.hot_control_node_label(func, node_id),
        ) {
        return true
      }
      if dead_code_elimination_region_may_fallthrough(
          func,
          branch_user_cache,
          fallthrough_cache,
          @ir.hot_region_then_ref(func, node_id),
        ) {
        return true
      }
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_region) =>
          dead_code_elimination_region_may_fallthrough(
            func, branch_user_cache, fallthrough_cache, else_region,
          )
        None => true
      }
    }
    @ir.HotOp::Try => {
      let has_incoming_by_label = dead_code_elimination_require_has_incoming_branches_by_label(
        branch_user_cache, func,
      )
      if dead_code_elimination_label_has_incoming_branch(
          has_incoming_by_label,
          @ir.hot_control_node_label(func, node_id),
        ) {
        return true
      }
      if dead_code_elimination_region_may_fallthrough(
          func,
          branch_user_cache,
          fallthrough_cache,
          @ir.hot_region_body_ref(func, node_id),
        ) {
        return true
      }
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_region) =>
          dead_code_elimination_region_may_fallthrough(
            func, branch_user_cache, fallthrough_cache, catch_region,
          )
        None => false
      }
    }
    @ir.HotOp::TryTable => {
      let has_incoming_by_label = dead_code_elimination_require_has_incoming_branches_by_label(
        branch_user_cache, func,
      )
      let catch_list_ref = @ir.hot_region_catch_list_ref(func, node_id)
      let catch_list_may_fallthrough = @ir.hot_region_root_count(
          func, catch_list_ref,
        ) >
        0 &&
        dead_code_elimination_region_may_fallthrough(
          func, branch_user_cache, fallthrough_cache, catch_list_ref,
        )
      dead_code_elimination_label_has_incoming_branch(
        has_incoming_by_label,
        @ir.hot_control_node_label(func, node_id),
      ) ||
      dead_code_elimination_region_may_fallthrough(
        func,
        branch_user_cache,
        fallthrough_cache,
        @ir.hot_region_body_ref(func, node_id),
      ) ||
      catch_list_may_fallthrough
    }
    _ => dead_code_elimination_op_has_fallthrough_edge(node.op)
  }
}

///|
fn dead_code_elimination_branch_payload_count(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Int32 {
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Br | @ir.HotOp::Delegate => node.child_count
    @ir.HotOp::BrIf
    | @ir.HotOp::BrTable
    | @ir.HotOp::BrOnNull
    | @ir.HotOp::BrOnNonNull
    | @ir.HotOp::BrOnCast
    | @ir.HotOp::BrOnCastFail =>
      if node.child_count == 0 {
        0
      } else {
        node.child_count - 1
      }
    _ => 0
  }
}

///|
fn DeadCodeEliminationStackedForwarderTail::new(
  payload_if : Int32,
  rewrite_region : @ir.HotRegionRef,
  rewrite_idx : Int32,
) -> DeadCodeEliminationStackedForwarderTail {
  { payload_if, rewrite_region, rewrite_idx, }
}

///|
fn dead_code_elimination_try_extract_stacked_forwarder_tail(
  func : @ir.HotFunc,
  candidate_node : Int32,
  rewrite_region : @ir.HotRegionRef,
  rewrite_idx : Int32,
) -> DeadCodeEliminationStackedForwarderTail? {
  if candidate_node < 0 ||
    !@ir.hot_node_is_live(func, candidate_node) ||
    @ir.hot_node_result_arity(func, candidate_node) == 0 {
    return None
  }
  match @ir.hot_node_get(func, candidate_node).op {
    @ir.HotOp::If =>
      Some(
        DeadCodeEliminationStackedForwarderTail::new(
          candidate_node, rewrite_region, rewrite_idx,
        ),
      )
    @ir.HotOp::Block => {
      let wrapper_body = @ir.hot_region_body_ref(func, candidate_node)
      let wrapper_root_count = @ir.hot_region_root_count(func, wrapper_body)
      if wrapper_root_count == 0 || wrapper_root_count > 2 {
        return None
      }
      let wrapper_prefix = @ir.hot_region_get(func, wrapper_body, 0)
      if wrapper_prefix < 0 || !@ir.hot_node_is_live(func, wrapper_prefix) {
        return None
      }
      match @ir.hot_node_get(func, wrapper_prefix).op {
        @ir.HotOp::Block => ()
        _ => return None
      }
      let wrapper_prefix_body = @ir.hot_region_body_ref(func, wrapper_prefix)
      let wrapper_prefix_root_count = @ir.hot_region_root_count(
        func, wrapper_prefix_body,
      )
      if wrapper_prefix_root_count < 2 {
        return None
      }
      let wrapper_br_idx = wrapper_prefix_root_count - 1
      let wrapper_br = @ir.hot_region_get(
        func, wrapper_prefix_body, wrapper_br_idx,
      )
      if wrapper_br < 0 ||
        !@ir.hot_node_is_live(func, wrapper_br) ||
        @ir.hot_branch_target(func, wrapper_br) !=
        Some(@ir.hot_control_node_label(func, candidate_node)) ||
        dead_code_elimination_branch_payload_count(func, wrapper_br) != 0 {
        return None
      }
      match @ir.hot_node_get(func, wrapper_br).op {
        @ir.HotOp::Br => ()
        _ => return None
      }
      dead_code_elimination_try_extract_stacked_forwarder_tail(
        func,
        @ir.hot_region_get(func, wrapper_prefix_body, wrapper_br_idx - 1),
        wrapper_prefix_body,
        wrapper_br_idx,
      )
    }
    _ => None
  }
}

///|
fn dead_code_elimination_try_rewrite_branch_payload_forwarder(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  node_id : Int32,
) -> Bool {
  let trace_reason = fn(reason : String) { ignore(reason) }
  let trace_shape = fn(kind : String, payload : String) {
    ignore(kind)
    ignore(payload)
  }
  if node_id < 0 ||
    !@ir.hot_node_is_live(func, node_id) ||
    dead_code_elimination_branch_payload_count(func, node_id) != 1 {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Br => ()
    _ => return false
  }
  let payload_block = @ir.hot_child_get(func, node_id, 0)
  if payload_block < 0 || !@ir.hot_node_is_live(func, payload_block) {
    return false
  }
  match @ir.hot_node_get(func, payload_block).op {
    @ir.HotOp::Block => ()
    _ => return false
  }
  let payload_body = @ir.hot_region_body_ref(func, payload_block)
  let payload_root_count = @ir.hot_region_root_count(func, payload_body)
  if payload_root_count == 0 || payload_root_count > 2 {
    return false
  }
  let forwarder = @ir.hot_region_get(func, payload_body, 0)
  if forwarder < 0 || !@ir.hot_node_is_live(func, forwarder) {
    return false
  }
  match @ir.hot_node_get(func, forwarder).op {
    @ir.HotOp::Block => ()
    _ => return false
  }
  let forwarder_body = @ir.hot_region_body_ref(func, forwarder)
  let forwarder_root_count = @ir.hot_region_root_count(func, forwarder_body)
  if forwarder_root_count == 0 {
    trace_reason("empty-forwarder-body")
    return false
  }
  let mut inner_br_idx = forwarder_root_count - 1
  let mut inner_br = @ir.hot_region_get(func, forwarder_body, inner_br_idx)
  let mut trimmed_unreachable = false
  if inner_br_idx > 0 && inner_br >= 0 && @ir.hot_node_is_live(func, inner_br) {
    match @ir.hot_node_get(func, inner_br).op {
      @ir.HotOp::Unreachable => {
        trimmed_unreachable = true
        inner_br_idx = inner_br_idx - 1
        inner_br = @ir.hot_region_get(func, forwarder_body, inner_br_idx)
      }
      _ => ()
    }
  }
  if trimmed_unreachable &&
    inner_br >= 0 &&
    @ir.hot_node_is_live(func, inner_br) {
    match @ir.hot_node_get(func, inner_br).op {
      @ir.HotOp::If if @ir.hot_node_result_arity(func, inner_br) == 0 => {
        let arm_is_bare_br = fn(region_ref : @ir.HotRegionRef) -> Bool {
          let root_count = @ir.hot_region_root_count(func, region_ref)
          if root_count == 0 {
            return false
          }
          let tail = @ir.hot_region_get(func, region_ref, root_count - 1)
          if tail < 0 || !@ir.hot_node_is_live(func, tail) {
            return false
          }
          match @ir.hot_node_get(func, tail).op {
            @ir.HotOp::Br =>
              dead_code_elimination_branch_payload_count(func, tail) == 0
            _ => false
          }
        }
        let then_region = @ir.hot_region_then_ref(func, inner_br)
        let else_region = match @ir.hot_region_else_ref(func, inner_br) {
          Some(else_region) => else_region
          None => return false
        }
        let allow_simple_trim = payload_root_count == 1
        if arm_is_bare_br(then_region) && arm_is_bare_br(else_region) {
          if !allow_simple_trim {
            trace_reason("trimmed-unreachable-split-wrapper")
          } else {
            ignore(
              pass_splice_region(
                ctx,
                func,
                forwarder_body,
                inner_br_idx + 1,
                1,
                [],
              ),
            )
            dead_code_elimination_invalidate_branch_user_cache(
              branch_user_cache,
            )
            return true
          }
        }
      }
      _ => ()
    }
  }
  let mut payload_if = -1
  let mut stacked_tail = false
  let mut direct_tail_if = false
  let mut stacked_rewrite_region = forwarder_body
  let mut stacked_rewrite_idx = inner_br_idx
  if inner_br >= 0 &&
    @ir.hot_node_is_live(func, inner_br) &&
    @ir.hot_branch_target(func, inner_br) ==
    Some(@ir.hot_control_node_label(func, payload_block)) {
    match @ir.hot_node_get(func, inner_br).op {
      @ir.HotOp::Br =>
        if dead_code_elimination_branch_payload_count(func, inner_br) == 1 {
          payload_if = @ir.hot_child_get(func, inner_br, 0)
        } else if dead_code_elimination_branch_payload_count(func, inner_br) ==
          0 &&
          inner_br_idx > 0 {
          match
            dead_code_elimination_try_extract_stacked_forwarder_tail(
              func,
              @ir.hot_region_get(func, forwarder_body, inner_br_idx - 1),
              forwarder_body,
              inner_br_idx,
            ) {
            Some(match_) => {
              payload_if = match_.payload_if
              stacked_tail = true
              stacked_rewrite_region = match_.rewrite_region
              stacked_rewrite_idx = match_.rewrite_idx
            }
            None => return false
          }
        } else {
          trace_reason("inner-branch-payload-count")
          return false
        }
      _ => return false
    }
  } else if inner_br >= 0 && @ir.hot_node_is_live(func, inner_br) {
    match @ir.hot_node_get(func, inner_br).op {
      @ir.HotOp::If =>
        if @ir.hot_node_result_arity(func, inner_br) > 0 ||
          (
            @ir.hot_node_result_arity(func, inner_br) == 0 &&
            @ir.hot_node_result_arity(func, forwarder) > 0 &&
            payload_root_count == 2 &&
            forwarder_root_count >= 2 &&
            trimmed_unreachable
          ) {
          payload_if = inner_br
          direct_tail_if = true
        } else {
          trace_reason("inner-branch-target")
          return false
        }
      _ => {
        trace_reason("inner-branch-target")
        return false
      }
    }
  } else {
    trace_reason("inner-branch-target")
    return false
  }
  if payload_if < 0 ||
    !@ir.hot_node_is_live(func, payload_if) ||
    (
      @ir.hot_node_result_arity(func, payload_if) == 0 &&
      !(direct_tail_if &&
      @ir.hot_node_result_arity(func, forwarder) > 0 &&
      payload_root_count == 2 &&
      trimmed_unreachable)
    ) ||
    (
      !stacked_tail &&
      dead_code_elimination_node_use_count(node_use_cache, func, payload_if) !=
      1
    ) {
    trace_reason("payload-if-uses")
    return false
  }
  match @ir.hot_node_get(func, payload_if).op {
    @ir.HotOp::If => ()
    _ => {
      trace_reason("payload-not-if")
      return false
    }
  }
  let then_region = @ir.hot_region_then_ref(func, payload_if)
  let else_region = match @ir.hot_region_else_ref(func, payload_if) {
    Some(else_region) => else_region
    None => return false
  }
  if @ir.hot_region_root_count(func, then_region) == 0 ||
    @ir.hot_region_root_count(func, else_region) == 0 {
    trace_reason("empty-if-arm")
    return false
  }
  let then_tail = @ir.hot_region_get(
    func,
    then_region,
    @ir.hot_region_root_count(func, then_region) - 1,
  )
  let else_tail = @ir.hot_region_get(
    func,
    else_region,
    @ir.hot_region_root_count(func, else_region) - 1,
  )
  if then_tail < 0 ||
    else_tail < 0 ||
    !@ir.hot_node_is_live(func, then_tail) ||
    !@ir.hot_node_is_live(func, else_tail) {
    trace_reason("dead-if-arm-tail")
    return false
  }
  let then_is_br = match @ir.hot_node_get(func, then_tail).op {
    @ir.HotOp::Br => true
    _ => false
  }
  let else_is_br = match @ir.hot_node_get(func, else_tail).op {
    @ir.HotOp::Br => true
    _ => false
  }
  if !then_is_br || !else_is_br {
    trace_shape(
      "payload-forwarder-arm-shape",
      "node=\{node_id} payload_if=\{payload_if} then_tail=\{@ir.hot_node_get(func, then_tail).op}#\{then_tail} then_target=\{@debug.to_string(@ir.hot_branch_target(func, then_tail))} else_tail=\{@ir.hot_node_get(func, else_tail).op}#\{else_tail} else_target=\{@debug.to_string(@ir.hot_branch_target(func, else_tail))} stacked=\{@debug.to_string(stacked_tail)}",
    )
    if !stacked_tail || then_is_br == else_is_br {
      trace_reason("if-tail-not-br")
      return false
    }
    let payload_label = @ir.hot_control_node_label(func, payload_block)
    if then_is_br &&
      @ir.hot_branch_target(func, then_tail) != Some(payload_label) {
      trace_reason("then-tail-target")
      return false
    }
    if else_is_br &&
      @ir.hot_branch_target(func, else_tail) != Some(payload_label) {
      trace_reason("else-tail-target")
      return false
    }
    if !then_is_br {
      ignore(
        pass_splice_region(
          ctx,
          func,
          then_region,
          @ir.hot_region_root_count(func, then_region) - 1,
          1,
          [@ir.hot_build_br(func, payload_label, values=[then_tail])],
        ),
      )
    }
    if !else_is_br {
      ignore(
        pass_splice_region(
          ctx,
          func,
          else_region,
          @ir.hot_region_root_count(func, else_region) - 1,
          1,
          [@ir.hot_build_br(func, payload_label, values=[else_tail])],
        ),
      )
    }
  }

  @ir.hot_control_result_type_set(func, payload_if, @ir.hot_type_void(func))
  @ir.hot_control_result_type_set(func, forwarder, @ir.hot_type_void(func))
  pass_mark_mutated(ctx, func)
  if stacked_tail {
    let removed = pass_splice_region(
      ctx,
      func,
      stacked_rewrite_region,
      stacked_rewrite_idx,
      1,
      [@ir.hot_build_unreachable(func)],
    )
    dead_code_elimination_delete_unused_nodes(
      ctx, func, node_use_cache, removed,
    )
  } else if direct_tail_if {
    ignore(
      dead_code_elimination_ensure_explicit_unreachable_tail(
        ctx, func, forwarder_body, inner_br_idx,
      ),
    )
  } else {
    ignore(
      dead_code_elimination_replace_root_with_child(
        ctx, func, node_use_cache, forwarder_body, inner_br_idx, payload_if,
      ),
    )
    ignore(
      dead_code_elimination_ensure_explicit_unreachable_tail(
        ctx, func, forwarder_body, inner_br_idx,
      ),
    )
  }
  dead_code_elimination_invalidate_branch_user_cache(branch_user_cache)
  true
}

///|
fn dead_code_elimination_try_rewrite_branch_payload_forwarder_in_value_subtree(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  if dead_code_elimination_try_rewrite_branch_payload_forwarder(
      ctx, func, branch_user_cache, node_use_cache, node_id,
    ) {
    return true
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Block | @ir.HotOp::Loop => {
      let body = @ir.hot_region_body_ref(func, node_id)
      for root_idx = 0
          root_idx < @ir.hot_region_root_count(func, body)
          root_idx = root_idx + 1 {
        if dead_code_elimination_try_rewrite_branch_payload_forwarder_in_value_subtree(
            ctx,
            func,
            branch_user_cache,
            node_use_cache,
            @ir.hot_region_get(func, body, root_idx),
          ) {
          return true
        }
      }
      false
    }
    @ir.HotOp::Br => {
      for child_idx = 0
          child_idx < dead_code_elimination_branch_payload_count(func, node_id)
          child_idx = child_idx + 1 {
        if dead_code_elimination_try_rewrite_branch_payload_forwarder_in_value_subtree(
            ctx,
            func,
            branch_user_cache,
            node_use_cache,
            @ir.hot_child_get(func, node_id, child_idx),
          ) {
          return true
        }
      }
      false
    }
    _ => false
  }
}

///|
fn dead_code_elimination_try_rewrite_branch_payload_forwarder_in_subtree(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  node_id : Int32,
) -> Bool {
  if node_id < 0 ||
    !@ir.hot_node_is_live(func, node_id) ||
    @ir.hot_child_count(func, node_id) == 0 {
    return false
  }
  let value_id = @ir.hot_child_get(func, node_id, 0)
  if value_id < 0 || !@ir.hot_node_is_live(func, value_id) {
    return false
  }
  match @ir.hot_node_get(func, value_id).op {
    @ir.HotOp::Block | @ir.HotOp::Loop => ()
    _ => return false
  }
  if @ir.hot_node_count(func) > 10000 {
    let walk_chain = fn(start : Int32) -> Bool {
      let mut current = start
      for _ in 0..<16 {
        if current < 0 || !@ir.hot_node_is_live(func, current) {
          return false
        }
        if dead_code_elimination_try_rewrite_branch_payload_forwarder(
            ctx, func, branch_user_cache, node_use_cache, current,
          ) {
          return true
        }
        match @ir.hot_node_get(func, current).op {
          @ir.HotOp::Block | @ir.HotOp::Loop => {
            let body = @ir.hot_region_body_ref(func, current)
            let root_count = @ir.hot_region_root_count(func, body)
            if root_count == 0 {
              return false
            }
            current = if root_count == 1 {
              @ir.hot_region_get(func, body, 0)
            } else {
              @ir.hot_region_get(func, body, root_count - 1)
            }
          }
          @ir.HotOp::Br => {
            if dead_code_elimination_branch_payload_count(func, current) == 0 {
              return false
            }
            current = @ir.hot_child_get(func, current, 0)
          }
          _ => return false
        }
      }
      false
    }
    if walk_chain(value_id) {
      return true
    }
    match @ir.hot_node_get(func, value_id).op {
      @ir.HotOp::Block | @ir.HotOp::Loop => {
        let body = @ir.hot_region_body_ref(func, value_id)
        if @ir.hot_region_root_count(func, body) > 0 {
          let first_root = @ir.hot_region_get(func, body, 0)
          if first_root != value_id && walk_chain(first_root) {
            return true
          }
        }
      }
      _ => ()
    }
    return false
  }
  dead_code_elimination_try_rewrite_branch_payload_forwarder_in_value_subtree(
    ctx, func, branch_user_cache, node_use_cache, value_id,
  )
}

///|
fn dead_code_elimination_try_rewrite_branch_payload_forwarder_in_control_region(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  region_ref : @ir.HotRegionRef,
) -> Bool {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    let root_id = @ir.hot_region_get(func, region_ref, idx)
    if dead_code_elimination_try_rewrite_branch_payload_forwarder_in_control_tree(
        ctx, func, branch_user_cache, node_use_cache, root_id,
      ) {
      return true
    }
  }
  false
}

///|
fn dead_code_elimination_try_rewrite_branch_payload_forwarder_in_control_tree(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  if dead_code_elimination_try_rewrite_branch_payload_forwarder(
      ctx, func, branch_user_cache, node_use_cache, node_id,
    ) {
    return true
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      dead_code_elimination_try_rewrite_branch_payload_forwarder_in_control_region(
        ctx,
        func,
        branch_user_cache,
        node_use_cache,
        @ir.hot_region_body_ref(func, node_id),
      )
    @ir.HotOp::If => {
      if dead_code_elimination_try_rewrite_branch_payload_forwarder_in_control_region(
          ctx,
          func,
          branch_user_cache,
          node_use_cache,
          @ir.hot_region_then_ref(func, node_id),
        ) {
        return true
      }
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_region) =>
          dead_code_elimination_try_rewrite_branch_payload_forwarder_in_control_region(
            ctx, func, branch_user_cache, node_use_cache, else_region,
          )
        None => false
      }
    }
    @ir.HotOp::Try => {
      if dead_code_elimination_try_rewrite_branch_payload_forwarder_in_control_region(
          ctx,
          func,
          branch_user_cache,
          node_use_cache,
          @ir.hot_region_body_ref(func, node_id),
        ) {
        return true
      }
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_region) =>
          dead_code_elimination_try_rewrite_branch_payload_forwarder_in_control_region(
            ctx, func, branch_user_cache, node_use_cache, catch_region,
          )
        None => false
      }
    }
    @ir.HotOp::TryTable => {
      if dead_code_elimination_try_rewrite_branch_payload_forwarder_in_control_region(
          ctx,
          func,
          branch_user_cache,
          node_use_cache,
          @ir.hot_region_body_ref(func, node_id),
        ) {
        return true
      }
      dead_code_elimination_try_rewrite_branch_payload_forwarder_in_control_region(
        ctx,
        func,
        branch_user_cache,
        node_use_cache,
        @ir.hot_region_catch_list_ref(func, node_id),
      )
    }
    _ => false
  }
}

///|
fn dead_code_elimination_try_rewrite_split_local_set_wrapper_forwarder(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  idx : Int32,
  node_id : Int32,
) -> Bool {
  ignore(idx)
  let trace_split = fn(reason : String) { ignore(reason) }
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    trace_split("precheck")
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::LocalSet | @ir.HotOp::LocalTee => ()
    _ => {
      trace_split("node-not-local-set")
      return false
    }
  }
  if @ir.hot_child_count(func, node_id) == 0 {
    trace_split("local-set-no-child")
    return false
  }
  let value_id = @ir.hot_child_get(func, node_id, 0)
  if value_id < 0 || !@ir.hot_node_is_live(func, value_id) {
    trace_split("value-dead")
    return false
  }
  match @ir.hot_node_get(func, value_id).op {
    @ir.HotOp::Block => ()
    _ => {
      trace_split("value-not-block")
      return false
    }
  }
  if @ir.hot_node_result_arity(func, value_id) == 0 {
    trace_split("value-not-result")
    return false
  }
  let outer_body = @ir.hot_region_body_ref(func, value_id)
  let outer_root_count = @ir.hot_region_root_count(func, outer_body)
  if outer_root_count == 0 ||
    (outer_root_count > 2 && !(@ir.hot_local_count(func) > 700)) {
    trace_split("outer-root-count")
    return false
  }
  let outer_wrapper = @ir.hot_region_get(func, outer_body, 0)
  if outer_wrapper < 0 || !@ir.hot_node_is_live(func, outer_wrapper) {
    trace_split("outer-wrapper-dead")
    return false
  }
  match @ir.hot_node_get(func, outer_wrapper).op {
    @ir.HotOp::Block => ()
    _ => {
      trace_split("outer-wrapper-not-block")
      return false
    }
  }
  let outer_wrapper_body = @ir.hot_region_body_ref(func, outer_wrapper)
  let outer_wrapper_root_count = @ir.hot_region_root_count(
    func, outer_wrapper_body,
  )
  if outer_wrapper_root_count == 0 {
    trace_split("outer-wrapper-root-count")
    return false
  }
  let mut payload_block = -1
  let mut outer_br = -1
  if outer_wrapper_root_count == 1 {
    outer_br = @ir.hot_region_get(func, outer_wrapper_body, 0)
    if outer_br < 0 || !@ir.hot_node_is_live(func, outer_br) {
      trace_split("outer-br-dead")
      return false
    }
    match @ir.hot_node_get(func, outer_br).op {
      @ir.HotOp::Br => ()
      _ => {
        trace_split("outer-br-not-br")
        return false
      }
    }
    if @ir.hot_branch_target(func, outer_br) !=
      Some(@ir.hot_control_node_label(func, value_id)) ||
      dead_code_elimination_branch_payload_count(func, outer_br) != 1 {
      trace_split("outer-br-shape")
      return false
    }
    payload_block = @ir.hot_child_get(func, outer_br, 0)
  } else {
    payload_block = @ir.hot_region_get(
      func,
      outer_wrapper_body,
      outer_wrapper_root_count - 2,
    )
    outer_br = @ir.hot_region_get(
      func,
      outer_wrapper_body,
      outer_wrapper_root_count - 1,
    )
    if payload_block < 0 ||
      outer_br < 0 ||
      !@ir.hot_node_is_live(func, payload_block) ||
      !@ir.hot_node_is_live(func, outer_br) ||
      @ir.hot_branch_target(func, outer_br) !=
      Some(@ir.hot_control_node_label(func, value_id)) ||
      dead_code_elimination_branch_payload_count(func, outer_br) != 0 {
      trace_split("outer-br-shape")
      return false
    }
    match @ir.hot_node_get(func, outer_br).op {
      @ir.HotOp::Br => ()
      _ => {
        trace_split("outer-br-not-br")
        return false
      }
    }
  }
  if payload_block < 0 ||
    !@ir.hot_node_is_live(func, payload_block) ||
    @ir.hot_node_result_arity(func, payload_block) == 0 {
    trace_split("payload-dead")
    return false
  }
  match @ir.hot_node_get(func, payload_block).op {
    @ir.HotOp::Block => ()
    _ => {
      trace_split("payload-not-block")
      return false
    }
  }
  let payload_body = @ir.hot_region_body_ref(func, payload_block)
  let payload_root_count = @ir.hot_region_root_count(func, payload_body)
  if payload_root_count == 0 || payload_root_count > 2 {
    trace_split("payload-root-count")
    return false
  }
  let payload_forwarder = @ir.hot_region_get(func, payload_body, 0)
  if payload_forwarder < 0 || !@ir.hot_node_is_live(func, payload_forwarder) {
    trace_split("payload-forwarder-dead")
    return false
  }
  match @ir.hot_node_get(func, payload_forwarder).op {
    @ir.HotOp::Block => ()
    _ => {
      trace_split("payload-forwarder-not-block")
      return false
    }
  }
  let payload_forwarder_body = @ir.hot_region_body_ref(func, payload_forwarder)
  let payload_forwarder_root_count = @ir.hot_region_root_count(
    func, payload_forwarder_body,
  )
  if outer_wrapper_root_count == 1 &&
    payload_root_count == 2 &&
    payload_forwarder_root_count >= 2 {
    let direct_br_idx = payload_forwarder_root_count - 1
    let direct_br = @ir.hot_region_get(
      func, payload_forwarder_body, direct_br_idx,
    )
    let payload_value = @ir.hot_region_get(func, payload_body, 1)
    if direct_br >= 0 &&
      payload_value >= 0 &&
      @ir.hot_node_is_live(func, direct_br) &&
      @ir.hot_node_is_live(func, payload_value) &&
      @ir.hot_node_result_arity(func, payload_value) == 1 {
      match @ir.hot_node_get(func, direct_br).op {
        @ir.HotOp::Br =>
          if @ir.hot_branch_target(func, direct_br) ==
            Some(@ir.hot_control_node_label(func, payload_block)) &&
            dead_code_elimination_branch_payload_count(func, direct_br) == 1 {
            let direct_if = @ir.hot_child_get(func, direct_br, 0)
            if direct_if >= 0 &&
              @ir.hot_node_is_live(func, direct_if) &&
              @ir.hot_node_result_arity(func, direct_if) == 1 {
              match @ir.hot_node_get(func, direct_if).op {
                @ir.HotOp::If => {
                  let then_region = @ir.hot_region_then_ref(func, direct_if)
                  let else_region = match
                    @ir.hot_region_else_ref(func, direct_if) {
                    Some(else_region) => else_region
                    None => {
                      trace_split("generated-if-no-else")
                      return false
                    }
                  }
                  let arm_is_bare_br = fn(
                    region_ref : @ir.HotRegionRef,
                  ) -> Bool {
                    let root_count = @ir.hot_region_root_count(func, region_ref)
                    if root_count == 0 {
                      return false
                    }
                    let tail = @ir.hot_region_get(
                      func,
                      region_ref,
                      root_count - 1,
                    )
                    if tail < 0 || !@ir.hot_node_is_live(func, tail) {
                      return false
                    }
                    match @ir.hot_node_get(func, tail).op {
                      @ir.HotOp::Br =>
                        dead_code_elimination_branch_payload_count(func, tail) ==
                        0
                      _ => false
                    }
                  }
                  if arm_is_bare_br(then_region) && arm_is_bare_br(else_region) {
                    @ir.hot_control_result_type_set(
                      func,
                      direct_if,
                      @ir.hot_type_void(func),
                    )
                    ignore(
                      dead_code_elimination_replace_root_with_child(
                        ctx, func, node_use_cache, payload_forwarder_body, direct_br_idx,
                        direct_if,
                      ),
                    )
                    ignore(
                      dead_code_elimination_ensure_explicit_unreachable_tail(
                        ctx, func, payload_forwarder_body, direct_br_idx,
                      ),
                    )
                    dead_code_elimination_invalidate_branch_user_cache(
                      branch_user_cache,
                    )
                    trace_split("generated-result-if-voidified")
                    return true
                  }
                }
                _ => ()
              }
            }
          }
        _ => ()
      }
    }
  }
  if payload_forwarder_root_count == 1 {
    let direct_br = @ir.hot_region_get(func, payload_forwarder_body, 0)
    if direct_br >= 0 && @ir.hot_node_is_live(func, direct_br) {
      match @ir.hot_node_get(func, direct_br).op {
        @ir.HotOp::Br =>
          if dead_code_elimination_branch_payload_count(func, direct_br) == 1 {
            let direct_if = @ir.hot_child_get(func, direct_br, 0)
            if direct_if >= 0 &&
              @ir.hot_node_is_live(func, direct_if) &&
              @ir.hot_node_result_arity(func, direct_if) == 1 {
              match @ir.hot_node_get(func, direct_if).op {
                @ir.HotOp::If => {
                  let then_region = @ir.hot_region_then_ref(func, direct_if)
                  let else_region = match
                    @ir.hot_region_else_ref(func, direct_if) {
                    Some(else_region) => else_region
                    None => {
                      trace_split("direct-if-no-else")
                      return false
                    }
                  }
                  let if_label = @ir.hot_control_node_label(func, direct_if)
                  let direct_target = match
                    @ir.hot_branch_target(func, direct_br) {
                    Some(target) => target
                    None => {
                      trace_split("direct-br-no-target")
                      return false
                    }
                  }
                  let try_build = fn(
                    branch_ref : @ir.HotRegionRef,
                    body_ref : @ir.HotRegionRef,
                    negate_condition : Bool,
                  ) -> Bool {
                    if @ir.hot_region_root_count(func, branch_ref) != 1 ||
                      @ir.hot_region_root_count(func, body_ref) == 0 {
                      return false
                    }
                    let branch_root = @ir.hot_region_get(func, branch_ref, 0)
                    if branch_root < 0 ||
                      !@ir.hot_node_is_live(func, branch_root) {
                      return false
                    }
                    let branch_node = @ir.hot_node_get(func, branch_root)
                    match branch_node.op {
                      @ir.HotOp::Br if branch_node.child_count == 0 => ()
                      _ => return false
                    }
                    if branch_node.imm0 == if_label {
                      return false
                    }
                    let body_tail_idx = @ir.hot_region_root_count(
                        func, body_ref,
                      ) -
                      1
                    let body_payload = @ir.hot_region_get(
                      func, body_ref, body_tail_idx,
                    )
                    if body_payload < 0 ||
                      !@ir.hot_node_is_live(func, body_payload) ||
                      @ir.hot_node_result_arity(func, body_payload) != 1 {
                      return false
                    }
                    let body_prefix : Array[Int32] = []
                    for root_idx = 0
                        root_idx < body_tail_idx
                        root_idx = root_idx + 1 {
                      let root_id = @ir.hot_region_get(func, body_ref, root_idx)
                      if @ir.cfg_terminator_edge_kinds(
                          @ir.hot_node_get(func, root_id).op,
                        ).length() >
                        0 {
                        return false
                      }
                      body_prefix.push(root_id)
                    }
                    let guard_condition = if negate_condition {
                      dead_code_elimination_build_i32_eqz(
                        func,
                        @ir.hot_child_get(func, direct_if, 0),
                      )
                    } else {
                      @ir.hot_child_get(func, direct_if, 0)
                    }
                    let replacement = [
                        @ir.hot_build_br_if(
                          func,
                          branch_node.imm0,
                          guard_condition,
                        ),
                      ] +
                      body_prefix +
                      [
                        @ir.hot_build_br(func, direct_target, values=[
                          body_payload,
                        ]),
                        @ir.hot_build_unreachable(func),
                      ]
                    ignore(
                      pass_splice_region(
                        ctx, func, payload_forwarder_body, 0, 1, replacement,
                      ),
                    )
                    dead_code_elimination_invalidate_branch_user_cache(
                      branch_user_cache,
                    )
                    trace_split("direct-branch-if")
                    return true
                  }
                  if try_build(else_region, then_region, true) ||
                    try_build(then_region, else_region, false) {
                    return true
                  }
                }
                _ => ()
              }
            }
          }
        _ => ()
      }
    }
  }
  if outer_wrapper_root_count == 1 &&
    payload_root_count == 2 &&
    payload_forwarder_root_count >= 2 {
    let payload_forwarder_tail = @ir.hot_region_get(
      func,
      payload_forwarder_body,
      payload_forwarder_root_count - 2,
    )
    let payload_forwarder_last = @ir.hot_region_get(
      func,
      payload_forwarder_body,
      payload_forwarder_root_count - 1,
    )
    if payload_forwarder_tail >= 0 &&
      payload_forwarder_last >= 0 &&
      @ir.hot_node_is_live(func, payload_forwarder_tail) &&
      @ir.hot_node_is_live(func, payload_forwarder_last) {
      let tail_is_void_if = match
        @ir.hot_node_get(func, payload_forwarder_tail).op {
        @ir.HotOp::If =>
          @ir.hot_node_result_arity(func, payload_forwarder_tail) == 0
        _ => false
      }
      let last_is_unreachable = match
        @ir.hot_node_get(func, payload_forwarder_last).op {
        @ir.HotOp::Unreachable => true
        _ => false
      }
      if tail_is_void_if && last_is_unreachable {
        @ir.hot_control_result_type_set(
          func,
          payload_forwarder_tail,
          @ir.hot_type_void(func),
        )
        let then_region = @ir.hot_region_then_ref(func, payload_forwarder_tail)
        let else_region = match
          @ir.hot_region_else_ref(func, payload_forwarder_tail) {
          Some(else_region) => else_region
          None => {
            trace_split("void-if-no-else")
            return false
          }
        }
        let arm_is_bare_br = fn(region_ref : @ir.HotRegionRef) -> Bool {
          let root_count = @ir.hot_region_root_count(func, region_ref)
          if root_count == 0 {
            return false
          }
          let tail = @ir.hot_region_get(func, region_ref, root_count - 1)
          if tail < 0 || !@ir.hot_node_is_live(func, tail) {
            return false
          }
          match @ir.hot_node_get(func, tail).op {
            @ir.HotOp::Br =>
              dead_code_elimination_branch_payload_count(func, tail) == 0
            _ => false
          }
        }
        if !arm_is_bare_br(then_region) || !arm_is_bare_br(else_region) {
          trace_split("void-if-arm-shape")
          return false
        }
        let payload_block_type = match
          @ir.hot_type_get(func, @ir.hot_node_type(func, payload_block)) {
          @ir.HotTypeInfo::BlockResult(block_type) => block_type
          @ir.HotTypeInfo::BlockResultResolved(block_type, _, _) => block_type
          _ => {
            trace_split("payload-block-type")
            return false
          }
        }
        let lifted_payload_roots : Array[Int32] = []
        for root_idx = 0; root_idx < payload_root_count; root_idx = root_idx + 1 {
          lifted_payload_roots.push(
            @ir.hot_region_get(func, payload_body, root_idx),
          )
        }
        let lifted_payload = @ir.hot_build_block(
          func, payload_block_type, lifted_payload_roots,
        )
        let lifted_outer_br = @ir.hot_build_br(
          func,
          @ir.hot_control_node_label(func, payload_block),
          values=[lifted_payload],
        )
        @ir.hot_replace_node(
          func,
          outer_br,
          @ir.hot_node_get(func, lifted_outer_br),
        )
        let payload_wrapper_roots : Array[Int32] = [outer_wrapper]
        for root_idx = 1; root_idx < outer_root_count; root_idx = root_idx + 1 {
          payload_wrapper_roots.push(
            @ir.hot_region_get(func, outer_body, root_idx),
          )
        }
        @ir.hot_region_replace_body(
          func,
          @ir.hot_region_body_ref(func, payload_block),
          payload_wrapper_roots,
        )
        @ir.hot_child_set(func, node_id, 0, payload_block)
        pass_mark_mutated(ctx, func)
        dead_code_elimination_invalidate_branch_user_cache(branch_user_cache)
        trace_split("void-if-lifted-payload")
        return true
      }
    }
  }
  if payload_root_count == 2 && payload_forwarder_root_count >= 2 {
    let tail_idx = payload_forwarder_root_count - 1
    let tail_br = @ir.hot_region_get(func, payload_forwarder_body, tail_idx)
    let payload_if = @ir.hot_region_get(
      func,
      payload_forwarder_body,
      tail_idx - 1,
    )
    if tail_br >= 0 &&
      payload_if >= 0 &&
      @ir.hot_node_is_live(func, tail_br) &&
      @ir.hot_node_is_live(func, payload_if) &&
      @ir.hot_branch_target(func, tail_br) ==
      Some(@ir.hot_control_node_label(func, payload_block)) &&
      dead_code_elimination_branch_payload_count(func, tail_br) == 0 &&
      @ir.hot_node_result_arity(func, payload_if) == 1 {
      match @ir.hot_node_get(func, tail_br).op {
        @ir.HotOp::Br =>
          match @ir.hot_node_get(func, payload_if).op {
            @ir.HotOp::If => {
              let then_region = @ir.hot_region_then_ref(func, payload_if)
              let else_region = match
                @ir.hot_region_else_ref(func, payload_if) {
                Some(else_region) => else_region
                None => {
                  trace_split("stacked-if-no-else")
                  return false
                }
              }
              let arm_is_bare_br = fn(region_ref : @ir.HotRegionRef) -> Bool {
                let root_count = @ir.hot_region_root_count(func, region_ref)
                if root_count == 0 {
                  return false
                }
                let tail = @ir.hot_region_get(func, region_ref, root_count - 1)
                if tail < 0 || !@ir.hot_node_is_live(func, tail) {
                  return false
                }
                match @ir.hot_node_get(func, tail).op {
                  @ir.HotOp::Br =>
                    dead_code_elimination_branch_payload_count(func, tail) == 0
                  _ => false
                }
              }
              if arm_is_bare_br(then_region) && arm_is_bare_br(else_region) {
                @ir.hot_control_result_type_set(
                  func,
                  payload_if,
                  @ir.hot_type_void(func),
                )
                let removed = pass_splice_region(
                  ctx,
                  func,
                  payload_forwarder_body,
                  tail_idx,
                  1,
                  [@ir.hot_build_unreachable(func)],
                )
                dead_code_elimination_delete_unused_nodes(
                  ctx, func, node_use_cache, removed,
                )
                dead_code_elimination_invalidate_branch_user_cache(
                  branch_user_cache,
                )
                trace_split("generated-stacked-if-voidified")
                return true
              }
            }
            _ => ()
          }
        _ => ()
      }
    }
  }
  if payload_forwarder_root_count < 2 {
    trace_split("payload-forwarder-root-count")
    return false
  }
  let match_ = match
    dead_code_elimination_try_extract_stacked_forwarder_tail(
      func,
      @ir.hot_region_get(
        func,
        payload_forwarder_body,
        payload_forwarder_root_count - 2,
      ),
      payload_forwarder_body,
      payload_forwarder_root_count - 1,
    ) {
    Some(match_) => match_
    None => {
      trace_split("stacked-tail-none")
      return false
    }
  }
  let inner_br = @ir.hot_region_get(
    func,
    match_.rewrite_region,
    match_.rewrite_idx,
  )
  if inner_br < 0 ||
    !@ir.hot_node_is_live(func, inner_br) ||
    @ir.hot_branch_target(func, inner_br) !=
    Some(@ir.hot_control_node_label(func, payload_block)) {
    trace_split("inner-br-target")
    return false
  }
  match @ir.hot_node_get(func, inner_br).op {
    @ir.HotOp::Br => ()
    _ => {
      trace_split("inner-br-not-br")
      return false
    }
  }

  let node_roots : Array[Int32] = []
  for root_idx = 0; root_idx < payload_root_count; root_idx = root_idx + 1 {
    node_roots.push(@ir.hot_region_get(func, payload_body, root_idx))
  }
  for root_idx = 1; root_idx < outer_root_count; root_idx = root_idx + 1 {
    node_roots.push(@ir.hot_region_get(func, outer_body, root_idx))
  }

  @ir.hot_control_result_type_set(
    func,
    match_.payload_if,
    @ir.hot_type_void(func),
  )
  let outer_br_replacement = @ir.hot_build_br(
    func,
    @ir.hot_control_node_label(func, payload_block),
  )
  @ir.hot_replace_node(
    func,
    outer_br,
    @ir.hot_node_get(func, outer_br_replacement),
  )
  let inner_br_replacement = @ir.hot_build_br(
    func,
    @ir.hot_control_node_label(func, value_id),
  )
  @ir.hot_replace_node(
    func,
    inner_br,
    @ir.hot_node_get(func, inner_br_replacement),
  )
  let wrapper_roots : Array[Int32] = []
  for root_idx = 0
      root_idx < outer_wrapper_root_count - 2
      root_idx = root_idx + 1 {
    wrapper_roots.push(@ir.hot_region_get(func, outer_wrapper_body, root_idx))
  }
  wrapper_roots.push(value_id)
  wrapper_roots.push(outer_br)
  @ir.hot_region_replace_body(func, outer_wrapper_body, wrapper_roots)
  @ir.hot_region_replace_body(
    func,
    @ir.hot_region_body_ref(func, value_id),
    node_roots,
  )
  @ir.hot_region_replace_body(
    func,
    @ir.hot_region_body_ref(func, payload_block),
    [outer_wrapper],
  )
  @ir.hot_child_set(func, node_id, 0, payload_block)
  pass_mark_mutated(ctx, func)
  ignore(
    pass_splice_region(
      ctx,
      func,
      match_.rewrite_region,
      match_.rewrite_idx,
      1,
      [@ir.hot_build_unreachable(func)],
    ),
  )
  dead_code_elimination_invalidate_branch_user_cache(branch_user_cache)
  trace_split("success")
  true
}

///|
fn dead_code_elimination_try_fold_nonfallthrough_prefix_into_branch_payload(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  idx : Int32,
  node_id : Int32,
) -> Bool {
  if idx <= 0 ||
    node_id < 0 ||
    !@ir.hot_node_is_live(func, node_id) ||
    @ir.hot_node_count(func) > 5000 ||
    idx + 1 != @ir.hot_region_root_count(func, region_ref) ||
    dead_code_elimination_branch_payload_count(func, node_id) != 1 {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Br => ()
    _ => return false
  }
  let payload_id = @ir.hot_child_get(func, node_id, 0)
  if payload_id < 0 ||
    !@ir.hot_node_is_live(func, payload_id) ||
    @ir.hot_node_result_arity(func, payload_id) != 1 {
    return false
  }
  let prefix_id = @ir.hot_region_get(func, region_ref, idx - 1)
  if prefix_id < 0 ||
    !@ir.hot_node_is_live(func, prefix_id) ||
    @ir.hot_node_result_arity(func, prefix_id) != 0 {
    return false
  }
  match @ir.hot_node_get(func, prefix_id).op {
    @ir.HotOp::Block => ()
    _ => return false
  }
  let prefix_region = @ir.hot_region_body_ref(func, prefix_id)
  let prefix_root_count = @ir.hot_region_root_count(func, prefix_region)
  if prefix_root_count == 0 || prefix_root_count > 2 {
    return false
  }
  match @ir.hot_node_get(func, payload_id).op {
    @ir.HotOp::Const | @ir.HotOp::LocalGet | @ir.HotOp::LocalTee => ()
    _ => return false
  }
  let payload_types = @ir.hot_type_results(
    func,
    @ir.hot_node_type(func, payload_id),
  )
  if payload_types.length() != 1 {
    return false
  }
  let payload_block = @ir.hot_build_block(
    func,
    @lib.BlockType::val_type(payload_types[0]),
    [prefix_id, payload_id],
  )
  @ir.hot_child_set(func, node_id, 0, payload_block)
  ignore(pass_splice_region(ctx, func, region_ref, idx - 1, 1, []))
  pass_mark_mutated(ctx, func)
  true
}

///|
priv struct DeadCodeEliminationPreservedCleanupRoot {
  node_id : Int32
  needs_drop : Bool
}

///|
fn DeadCodeEliminationPreservedCleanupRoot::new(
  node_id : Int32,
  needs_drop : Bool,
) -> DeadCodeEliminationPreservedCleanupRoot {
  { node_id, needs_drop, }
}

///|
fn dead_code_elimination_append_preserved_prefix_root(
  func : @ir.HotFunc,
  out : Array[DeadCodeEliminationPreservedCleanupRoot],
  child_id : Int32,
) -> Bool {
  if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
    return false
  }
  out.push(
    DeadCodeEliminationPreservedCleanupRoot::new(
      child_id,
      @ir.hot_node_result_arity(func, child_id) > 0,
    ),
  )
  true
}

///|
fn dead_code_elimination_collect_first_unreachable_child_cleanup(
  cache : DeadCodeEliminationNodeUseCache,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Array[DeadCodeEliminationPreservedCleanupRoot]? {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  let revision = @ir.hot_revision_current(func)
  dead_code_elimination_ensure_first_unreachable_cache_capacity(cache, node_id)
  if cache.first_unreachable_revision[node_id] == revision &&
    cache.first_unreachable_absent[node_id] {
    return None
  }
  match cache.first_unreachable_visits {
    Some(visits) => visits[0] += 1
    None => ()
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Unreachable =>
      return Some([DeadCodeEliminationPreservedCleanupRoot::new(node_id, false)])
    _ => ()
  }
  if @ir.hot_is_control_node(func, node_id) {
    cache.first_unreachable_revision[node_id] = revision
    cache.first_unreachable_absent[node_id] = true
    return None
  }
  let prefix_roots : Array[DeadCodeEliminationPreservedCleanupRoot] = []
  for child_idx = 0
      child_idx < @ir.hot_child_count(func, node_id)
      child_idx = child_idx + 1 {
    let child_id = @ir.hot_child_get(func, node_id, child_idx)
    if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
      cache.first_unreachable_revision[node_id] = revision
      cache.first_unreachable_absent[node_id] = true
      return None
    }
    match
      dead_code_elimination_collect_first_unreachable_child_cleanup(
        cache, func, child_id,
      ) {
      Some(child_cleanup) => {
        let plan : Array[DeadCodeEliminationPreservedCleanupRoot] = []
        for prefix in prefix_roots {
          plan.push(prefix)
        }
        for cleanup in child_cleanup {
          plan.push(cleanup)
        }
        return Some(plan)
      }
      None =>
        if !dead_code_elimination_append_preserved_prefix_root(
            func, prefix_roots, child_id,
          ) {
          cache.first_unreachable_revision[node_id] = revision
          cache.first_unreachable_absent[node_id] = true
          return None
        }
    }
  }
  cache.first_unreachable_revision[node_id] = revision
  cache.first_unreachable_absent[node_id] = true
  None
}

///|
fn dead_code_elimination_try_preserve_prefix_before_unreachable_child(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  region_ref : @ir.HotRegionRef,
  idx : Int32,
  node_id : Int32,
) -> Bool {
  let plan = match
    dead_code_elimination_collect_first_unreachable_child_cleanup(
      node_use_cache, func, node_id,
    ) {
    Some(plan) => plan
    None => return false
  }
  if plan.length() == 1 && plan[0].node_id == node_id && !plan[0].needs_drop {
    return false
  }
  let replacement : Array[Int32] = []
  for root in plan {
    replacement.push(
      if root.needs_drop {
        @ir.hot_build_drop(func, root.node_id)
      } else {
        root.node_id
      },
    )
  }
  let removed = pass_splice_region(ctx, func, region_ref, idx, 1, replacement)
  dead_code_elimination_delete_unused_nodes(ctx, func, node_use_cache, removed)
  true
}

///|
fn dead_code_elimination_try_replace_literal_unreachable_loop_root(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  region_ref : @ir.HotRegionRef,
  idx : Int32,
  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::Loop => ()
    _ => return false
  }
  let body_ref = @ir.hot_region_body_ref(func, node_id)
  if @ir.hot_region_root_count(func, body_ref) != 1 {
    return false
  }
  let body_root = @ir.hot_region_get(func, body_ref, 0)
  if body_root < 0 || !@ir.hot_node_is_live(func, body_root) {
    return false
  }
  match @ir.hot_node_get(func, body_root).op {
    @ir.HotOp::Unreachable => ()
    _ => return false
  }
  ignore(
    dead_code_elimination_replace_root_with_child(
      ctx, func, node_use_cache, region_ref, idx, body_root,
    ),
  )
  true
}

///|
fn dead_code_elimination_try_voidify_split_drop_control(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  fallthrough_cache : DeadCodeEliminationFallthroughCache,
  purity_cache : DeadCodeEliminationPurityCache,
  region_ref : @ir.HotRegionRef,
  idx : Int32,
  node_id : Int32,
) -> Bool {
  if node_id < 0 ||
    !@ir.hot_node_is_live(func, node_id) ||
    @ir.hot_node_result_arity(func, node_id) == 0 ||
    idx + 1 >= @ir.hot_region_root_count(func, region_ref) {
    return false
  }
  let drop_id = @ir.hot_region_get(func, region_ref, idx + 1)
  if drop_id < 0 ||
    !@ir.hot_node_is_live(func, drop_id) ||
    @ir.hot_child_count(func, drop_id) != 1 ||
    @ir.hot_child_get(func, drop_id, 0) != node_id {
    return false
  }
  match @ir.hot_node_get(func, drop_id).op {
    @ir.HotOp::Drop => ()
    _ => return false
  }
  if !dead_code_elimination_voidify_control(
      ctx, func, branch_user_cache, node_use_cache, fallthrough_cache, purity_cache,
      node_id,
    ) {
    return false
  }
  ignore(
    dead_code_elimination_remove_roots(
      ctx,
      func,
      node_use_cache,
      region_ref,
      idx + 1,
      1,
    ),
  )
  true
}

///|
fn dead_code_elimination_try_flatten_untargeted_void_block_root(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  region_ref : @ir.HotRegionRef,
  idx : Int32,
  node_id : Int32,
) -> Bool {
  if region_ref != @ir.HotRegionRef::root() ||
    node_id < 0 ||
    !@ir.hot_node_is_live(func, node_id) ||
    @ir.hot_node_result_arity(func, node_id) != 0 {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Block => ()
    _ => return false
  }
  if dead_code_elimination_label_has_incoming_branch(
      dead_code_elimination_require_has_incoming_branches_by_label(
        branch_user_cache, func,
      ),
      @ir.hot_control_node_label(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) < 3 {
    return false
  }
  let last_root = @ir.hot_region_get(
    func,
    body_ref,
    @ir.hot_region_root_count(func, body_ref) - 1,
  )
  if last_root >= 0 && @ir.hot_node_is_live(func, last_root) {
    match @ir.hot_node_get(func, last_root).op {
      @ir.HotOp::Unreachable => return false
      _ => ()
    }
  }
  let replacement : Array[Int32] = []
  for root_idx = 0
      root_idx < @ir.hot_region_root_count(func, body_ref)
      root_idx = root_idx + 1 {
    replacement.push(@ir.hot_region_get(func, body_ref, root_idx))
  }
  let removed = pass_splice_region(ctx, func, region_ref, idx, 1, replacement)
  dead_code_elimination_delete_unused_nodes(ctx, func, node_use_cache, removed)
  true
}

///|
fn dead_code_elimination_visit_node_regions(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  fallthrough_cache : DeadCodeEliminationFallthroughCache,
  purity_cache : DeadCodeEliminationPurityCache,
  node_id : Int32,
) -> Bool {
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      dead_code_elimination_visit_region(
        ctx,
        func,
        branch_user_cache,
        node_use_cache,
        fallthrough_cache,
        purity_cache,
        @ir.hot_region_body_ref(func, node_id),
      )
    @ir.HotOp::If => {
      let mut changed = dead_code_elimination_visit_region(
        ctx,
        func,
        branch_user_cache,
        node_use_cache,
        fallthrough_cache,
        purity_cache,
        @ir.hot_region_then_ref(func, node_id),
      )
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_region) =>
          changed = dead_code_elimination_visit_region(
              ctx, func, branch_user_cache, node_use_cache, fallthrough_cache, purity_cache,
              else_region,
            ) ||
            changed
        None => ()
      }
      changed
    }
    @ir.HotOp::Try => {
      let mut changed = dead_code_elimination_visit_region(
        ctx,
        func,
        branch_user_cache,
        node_use_cache,
        fallthrough_cache,
        purity_cache,
        @ir.hot_region_body_ref(func, node_id),
      )
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_region) =>
          changed = dead_code_elimination_visit_region(
              ctx, func, branch_user_cache, node_use_cache, fallthrough_cache, purity_cache,
              catch_region,
            ) ||
            changed
        None => ()
      }
      changed
    }
    @ir.HotOp::TryTable =>
      dead_code_elimination_visit_region(
        ctx,
        func,
        branch_user_cache,
        node_use_cache,
        fallthrough_cache,
        purity_cache,
        @ir.hot_region_body_ref(func, node_id),
      ) ||
      dead_code_elimination_visit_region(
        ctx,
        func,
        branch_user_cache,
        node_use_cache,
        fallthrough_cache,
        purity_cache,
        @ir.hot_region_catch_list_ref(func, node_id),
      )
    _ => false
  }
}

///|
fn dead_code_elimination_voidify_control(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  fallthrough_cache : DeadCodeEliminationFallthroughCache,
  purity_cache : DeadCodeEliminationPurityCache,
  node_id : Int32,
) -> Bool {
  if !dead_code_elimination_can_voidify_control(
      func, branch_user_cache, fallthrough_cache, node_id,
    ) {
    return false
  }
  let control_op = @ir.hot_node_get(func, node_id).op
  let is_loop = match control_op {
    @ir.HotOp::Loop => true
    _ => false
  }
  let label_id = @ir.hot_control_node_label(func, node_id)
  let has_incoming_by_label = dead_code_elimination_require_has_incoming_branches_by_label(
    branch_user_cache, func,
  )
  if !is_loop &&
    dead_code_elimination_label_has_incoming_branch(
      has_incoming_by_label, label_id,
    ) {
    return false
  }
  let branch_users_by_label = dead_code_elimination_require_branch_users_by_label(
    branch_user_cache, func,
  )
  let detached : Array[Int32] = []
  let branch_rewrites : Array[@ir.HotChildSpanRewrite] = []
  if !is_loop {
    if label_id < 0 || label_id >= branch_users_by_label.length() {
      return false
    }
    for branch_id in branch_users_by_label[label_id] {
      if !@ir.hot_node_is_live(func, branch_id) {
        continue
      }
      let payload_count = dead_code_elimination_branch_payload_count(
        func, branch_id,
      )
      if payload_count == 0 {
        continue
      }
      for slot = 0; slot < payload_count; slot = slot + 1 {
        let child_id = @ir.hot_child_get(func, branch_id, slot)
        if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
          continue
        }
        if !dead_code_elimination_node_is_pure(purity_cache, func, child_id) {
          return false
        }
        dead_code_elimination_collect_detached_nodes(
          func, child_id, detached, 32,
        )
        if detached.length() > 32 {
          break
        }
      }
      let new_children : Array[Int32] = []
      if payload_count < @ir.hot_child_count(func, branch_id) {
        new_children.push(
          @ir.hot_child_get(
            func,
            branch_id,
            @ir.hot_child_count(func, branch_id) - 1,
          ),
        )
      }
      branch_rewrites.push(
        @ir.HotChildSpanRewrite::new(branch_id, new_children),
      )
    }
  }
  @ir.hot_replace_child_spans_and_control_result_type(
    func,
    node_id,
    @ir.hot_type_void(func),
    branch_rewrites,
  )
  pass_mark_mutated(ctx, func)
  dead_code_elimination_delete_unused_nodes(ctx, func, node_use_cache, detached)
  ignore(
    dead_code_elimination_visit_node_regions(
      ctx, func, branch_user_cache, node_use_cache, fallthrough_cache, purity_cache,
      node_id,
    ),
  )
  true
}

///|
fn dead_code_elimination_try_voidify_nonfallthrough_block_with_pure_incoming_branches(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  fallthrough_cache : DeadCodeEliminationFallthroughCache,
  purity_cache : DeadCodeEliminationPurityCache,
  node_id : Int32,
) -> Bool {
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Block => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, node_id) == 0 {
    return false
  }
  let label_id = @ir.hot_control_node_label(func, node_id)
  let has_incoming_by_label = dead_code_elimination_require_has_incoming_branches_by_label(
    branch_user_cache, func,
  )
  let body_ref = @ir.hot_region_body_ref(func, node_id)
  if !dead_code_elimination_label_has_incoming_branch(
      has_incoming_by_label, label_id,
    ) ||
    !dead_code_elimination_region_has_terminal_last_live_root(func, body_ref) ||
    dead_code_elimination_region_may_fallthrough(
      func, branch_user_cache, fallthrough_cache, body_ref,
    ) {
    return false
  }
  let branch_users_by_label = dead_code_elimination_require_branch_users_by_label(
    branch_user_cache, func,
  )
  if label_id < 0 || label_id >= branch_users_by_label.length() {
    return false
  }
  if branch_users_by_label[label_id].length() != 1 {
    return false
  }
  let detached : Array[Int32] = []
  let branch_rewrites : Array[@ir.HotChildSpanRewrite] = []
  for branch_id in branch_users_by_label[label_id] {
    if !@ir.hot_node_is_live(func, branch_id) {
      continue
    }
    match @ir.hot_node_get(func, branch_id).op {
      @ir.HotOp::Br | @ir.HotOp::BrIf => ()
      _ => return false
    }
    let payload_count = dead_code_elimination_branch_payload_count(
      func, branch_id,
    )
    if payload_count != 1 {
      return false
    }
    for slot = 0; slot < payload_count; slot = slot + 1 {
      let child_id = @ir.hot_child_get(func, branch_id, slot)
      if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
        continue
      }
      if !dead_code_elimination_node_is_pure(purity_cache, func, child_id) {
        return false
      }
      dead_code_elimination_collect_detached_nodes(func, child_id, detached, 32)
      if detached.length() > 32 {
        break
      }
    }
    let new_children : Array[Int32] = []
    if payload_count < @ir.hot_child_count(func, branch_id) {
      new_children.push(
        @ir.hot_child_get(
          func,
          branch_id,
          @ir.hot_child_count(func, branch_id) - 1,
        ),
      )
    }
    branch_rewrites.push(@ir.HotChildSpanRewrite::new(branch_id, new_children))
  }
  @ir.hot_replace_child_spans_and_control_result_type(
    func,
    node_id,
    @ir.hot_type_void(func),
    branch_rewrites,
  )
  pass_mark_mutated(ctx, func)
  dead_code_elimination_delete_unused_nodes(ctx, func, node_use_cache, detached)
  ignore(
    dead_code_elimination_visit_node_regions(
      ctx, func, branch_user_cache, node_use_cache, fallthrough_cache, purity_cache,
      node_id,
    ),
  )
  true
}

///|
fn dead_code_elimination_remove_roots(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  region_ref : @ir.HotRegionRef,
  idx : Int32,
  remove_count : Int32,
) -> Bool {
  if remove_count <= 0 {
    return false
  }
  let removed_roots = pass_splice_region(
    ctx,
    func,
    region_ref,
    idx,
    remove_count,
    [],
  )
  let detached : Array[Int32] = []
  for node_id in removed_roots {
    dead_code_elimination_collect_detached_nodes(func, node_id, detached, 32)
    if detached.length() > 32 {
      break
    }
  }
  dead_code_elimination_delete_unused_nodes(ctx, func, node_use_cache, detached)
  true
}

///|
fn dead_code_elimination_replace_root_with_child(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  region_ref : @ir.HotRegionRef,
  idx : Int32,
  child_id : Int32,
) -> Bool {
  let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [child_id])
  dead_code_elimination_delete_unused_nodes(ctx, func, node_use_cache, removed)
  true
}

///|
fn dead_code_elimination_needs_explicit_unreachable_tail(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 ||
    !@ir.hot_node_is_live(func, node_id) ||
    @ir.hot_node_result_arity(func, node_id) != 0 {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Block
    | @ir.HotOp::Loop
    | @ir.HotOp::If
    | @ir.HotOp::Try
    | @ir.HotOp::TryTable => true
    _ => false
  }
}

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

///|
fn dead_code_elimination_ensure_explicit_unreachable_tail(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  idx : Int32,
) -> Bool {
  if dead_code_elimination_has_explicit_unreachable_tail(func, region_ref, idx) {
    return false
  }
  ignore(
    pass_splice_region(ctx, func, region_ref, idx + 1, 0, [
      @ir.hot_build_unreachable(func),
    ]),
  )
  true
}

///|
fn dead_code_elimination_finish_nonfallthrough_final_root(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  fallthrough_cache : DeadCodeEliminationFallthroughCache,
  region_ref : @ir.HotRegionRef,
  idx : Int32,
  node_id : Int32,
) -> Bool {
  idx + 1 == @ir.hot_region_root_count(func, region_ref) &&
  dead_code_elimination_needs_explicit_unreachable_tail(func, node_id) &&
  !dead_code_elimination_node_may_fallthrough(
    func, branch_user_cache, fallthrough_cache, node_id,
  ) &&
  dead_code_elimination_ensure_explicit_unreachable_tail(
    ctx, func, region_ref, idx,
  )
}

///|
fn dead_code_elimination_visit_region(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  branch_user_cache : DeadCodeEliminationBranchUserCache,
  node_use_cache : DeadCodeEliminationNodeUseCache,
  fallthrough_cache : DeadCodeEliminationFallthroughCache,
  purity_cache : DeadCodeEliminationPurityCache,
  region_ref : @ir.HotRegionRef,
) -> Bool {
  let mut changed = false
  let result_arity = dead_code_elimination_region_result_arity(func, region_ref)
  let mut idx = 0
  while idx < @ir.hot_region_root_count(func, region_ref) {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      idx += 1
      continue
    }

    changed = dead_code_elimination_visit_node_regions(
        ctx, func, branch_user_cache, node_use_cache, fallthrough_cache, purity_cache,
        node_id,
      ) ||
      changed

    if dead_code_elimination_try_restore_source_order_before_terminator(
        ctx, func, region_ref, idx, node_id,
      ) {
      changed = true
      continue
    }

    if dead_code_elimination_try_flatten_untargeted_void_block_root(
        ctx, func, branch_user_cache, node_use_cache, region_ref, idx, node_id,
      ) {
      changed = true
      continue
    }
    if dead_code_elimination_try_replace_literal_unreachable_loop_root(
        ctx, func, node_use_cache, region_ref, idx, node_id,
      ) {
      changed = true
      continue
    }
    if dead_code_elimination_try_preserve_prefix_before_unreachable_child(
        ctx, func, node_use_cache, region_ref, idx, node_id,
      ) {
      changed = true
      continue
    }
    if dead_code_elimination_try_rewrite_branch_payload_forwarder(
        ctx, func, branch_user_cache, node_use_cache, node_id,
      ) {
      changed = true
      continue
    }
    match @ir.hot_node_get(func, node_id).op {
      @ir.HotOp::LocalSet | @ir.HotOp::LocalTee =>
        if dead_code_elimination_try_rewrite_branch_payload_forwarder_in_subtree(
            ctx, func, branch_user_cache, node_use_cache, node_id,
          ) {
          changed = true
          continue
        }
      _ => ()
    }
    if dead_code_elimination_try_fold_nonfallthrough_prefix_into_branch_payload(
        ctx, func, region_ref, idx, node_id,
      ) {
      changed = true
      continue
    }
    if dead_code_elimination_try_rewrite_split_local_set_wrapper_forwarder(
        ctx, func, branch_user_cache, node_use_cache, idx, node_id,
      ) {
      changed = true
      ignore(
        dead_code_elimination_try_rewrite_branch_payload_forwarder_in_subtree(
          ctx, func, branch_user_cache, node_use_cache, node_id,
        ),
      )
      idx += 1
      continue
    }
    if dead_code_elimination_try_voidify_split_drop_control(
        ctx, func, branch_user_cache, node_use_cache, fallthrough_cache, purity_cache,
        region_ref, idx, node_id,
      ) {
      changed = true
      continue
    }

    let is_final_root = idx + 1 == @ir.hot_region_root_count(func, region_ref)
    let node_result_arity = @ir.hot_node_result_arity(func, node_id)
    let node_is_control = @ir.hot_is_control_node(func, node_id)
    let needs_unreachable_tail = dead_code_elimination_needs_explicit_unreachable_tail(
      func, node_id,
    )
    let mut node_use_count = -1

    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @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) &&
            dead_code_elimination_node_use_count(node_use_cache, func, child_id) ==
            1 &&
            (
              dead_code_elimination_voidify_control(
                ctx, func, branch_user_cache, node_use_cache, fallthrough_cache,
                purity_cache, child_id,
              ) ||
              dead_code_elimination_try_voidify_nonfallthrough_block_with_pure_incoming_branches(
                ctx, func, branch_user_cache, node_use_cache, fallthrough_cache,
                purity_cache, child_id,
              )
            ) {
            ignore(
              dead_code_elimination_replace_root_with_child(
                ctx, func, node_use_cache, region_ref, idx, child_id,
              ),
            )
            ignore(
              dead_code_elimination_finish_nonfallthrough_final_root(
                ctx, func, branch_user_cache, fallthrough_cache, region_ref, idx,
                child_id,
              ),
            )
            changed = true
            continue
          }
        }
      _ => ()
    }

    if result_arity == 0 && node_result_arity > 0 {
      if is_final_root &&
        ({
          if node_use_count < 0 {
            node_use_count = dead_code_elimination_node_use_count(
              node_use_cache, func, node_id,
            )
          }
          node_use_count == 1
        }) &&
        dead_code_elimination_node_is_pure(purity_cache, func, node_id) {
        ignore(
          dead_code_elimination_remove_roots(
            ctx, func, node_use_cache, region_ref, idx, 1,
          ),
        )
        changed = true
        continue
      }
      if is_final_root &&
        ({
          if node_use_count < 0 {
            node_use_count = dead_code_elimination_node_use_count(
              node_use_cache, func, node_id,
            )
          }
          node_use_count == 1
        }) &&
        dead_code_elimination_voidify_control(
          ctx, func, branch_user_cache, node_use_cache, fallthrough_cache, purity_cache,
          node_id,
        ) {
        ignore(
          dead_code_elimination_finish_nonfallthrough_final_root(
            ctx, func, branch_user_cache, fallthrough_cache, region_ref, idx, node_id,
          ),
        )
        changed = true
        continue
      }
    }

    let node_may_fallthrough = if !is_final_root ||
      (node_is_control && node_result_arity > 0) ||
      needs_unreachable_tail {
      dead_code_elimination_node_may_fallthrough(
        func, branch_user_cache, fallthrough_cache, node_id,
      )
    } else {
      true
    }

    if is_final_root &&
      ({
        if node_use_count < 0 {
          node_use_count = dead_code_elimination_node_use_count(
            node_use_cache, func, node_id,
          )
        }
        node_use_count == 1
      }) &&
      node_result_arity > 0 &&
      !node_may_fallthrough &&
      dead_code_elimination_voidify_control(
        ctx, func, branch_user_cache, node_use_cache, fallthrough_cache, purity_cache,
        node_id,
      ) {
      ignore(
        dead_code_elimination_finish_nonfallthrough_final_root(
          ctx, func, branch_user_cache, fallthrough_cache, region_ref, idx, node_id,
        ),
      )
      changed = true
      continue
    }

    if !node_may_fallthrough &&
      !dead_code_elimination_region_has_source_preceding_tail_root(
        func, region_ref, idx, node_id,
      ) {
      let keep_unreachable_tail = needs_unreachable_tail &&
        dead_code_elimination_has_explicit_unreachable_tail(
          func, region_ref, idx,
        ) &&
        idx + 2 == @ir.hot_region_root_count(func, region_ref)
      let keep_tail_count = if keep_unreachable_tail { 1 } else { 0 }
      let removable_tail_count = @ir.hot_region_root_count(func, region_ref) -
        (idx + 1) -
        keep_tail_count
      if removable_tail_count > 0 {
        ignore(
          dead_code_elimination_remove_roots(
            ctx,
            func,
            node_use_cache,
            region_ref,
            idx + 1,
            removable_tail_count,
          ),
        )
        changed = true
      }
      if needs_unreachable_tail &&
        idx + 1 == @ir.hot_region_root_count(func, region_ref) &&
        dead_code_elimination_ensure_explicit_unreachable_tail(
          ctx, func, region_ref, idx,
        ) {
        changed = true
        idx += 2
        continue
      }
    }
    idx += 1
  }
  changed
}

///|
fn dead_code_elimination_run(
  ctx : HotPassContext,
  func : @ir.HotFunc,
) -> HotPassResult {
  let branch_user_cache = DeadCodeEliminationBranchUserCache::new()
  let node_use_cache = DeadCodeEliminationNodeUseCache::new(func)
  let fallthrough_cache = dead_code_elimination_new_fallthrough_cache(func)
  let purity_cache = dead_code_elimination_new_purity_cache(func)
  if dead_code_elimination_visit_region(
      ctx,
      func,
      branch_user_cache,
      node_use_cache,
      fallthrough_cache,
      purity_cache,
      @ir.HotRegionRef::root(),
    ) {
    HotPassResult::changed()
  } else {
    HotPassResult::unchanged()
  }
}