// 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 2015 WebAssembly Community Group participants
// Counterpart: src/passes/SimplifyLocals.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 simplify_locals_descriptor() -> @ir.HotPassDescriptor {
  @ir.HotPassDescriptor::new("simplify-locals", 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 simplify_locals_summary() -> String {
  "Sink local.sets into later gets, lift structured block/if/loop returns when safe, canonicalize equivalent copies, and remove dead local writes."
}

///|
pub fn simplify_locals_notee_descriptor() -> @ir.HotPassDescriptor {
  @ir.HotPassDescriptor::new("simplify-locals-notee", 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(),
  ])
}

///|
fn simplify_locals_notee_alias_descriptor() -> @ir.HotPassDescriptor {
  @ir.HotPassDescriptor::new("simplify-locals-no-tee", 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 simplify_locals_notee_summary() -> String {
  "Run the structured no-tee SimplifyLocals variant: sink single-use local traffic, synthesize control results, canonicalize equivalent copies, and remove dead writes without creating sink tees."
}

///|
pub fn simplify_locals_nonesting_descriptor() -> @ir.HotPassDescriptor {
  @ir.HotPassDescriptor::new("simplify-locals-nonesting", 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(),
  ])
}

///|
fn simplify_locals_nonesting_alias_descriptor() -> @ir.HotPassDescriptor {
  @ir.HotPassDescriptor::new("simplify-locals-no-nesting", 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 simplify_locals_nonesting_summary() -> String {
  "Run the flat SimplifyLocals variant: retarget copy traffic and clean dead locals without creating tees, control results, or ordinary new nesting."
}

///|
pub fn simplify_locals_nostructure_descriptor() -> @ir.HotPassDescriptor {
  @ir.HotPassDescriptor::new("simplify-locals-nostructure", 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(),
  ])
}

///|
fn simplify_locals_nostructure_alias_descriptor() -> @ir.HotPassDescriptor {
  @ir.HotPassDescriptor::new("simplify-locals-no-structure", 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 simplify_locals_notee_nostructure_descriptor() -> @ir.HotPassDescriptor {
  @ir.HotPassDescriptor::new("simplify-locals-notee-nostructure", 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 simplify_locals_nostructure_summary() -> String {
  "Run the no-structure SimplifyLocals variant: sink local traffic and dead writes without synthesizing block/if/loop result rewrites."
}

///|
pub fn simplify_locals_notee_nostructure_summary() -> String {
  "Run Binaryen's size-biased no-tee/no-structure SimplifyLocals spelling: sink local traffic and dead writes without fresh tees or structure-result rewrites."
}

///|
priv struct SimplifyLocalsPolicy {
  allow_structure_rewrites : Bool
  allow_sink_tee : Bool
  allow_nesting : Bool
}

///|
fn SimplifyLocalsPolicy::new(
  allow_structure_rewrites : Bool,
  allow_sink_tee : Bool,
  allow_nesting : Bool,
) -> SimplifyLocalsPolicy {
  { allow_structure_rewrites, allow_sink_tee, allow_nesting, }
}

///|
priv struct SimplifyLocalsEffects {
  mask : @ir.EffectMask
  local_reads : Array[Int32]
  local_writes : Array[Int32]
  global_reads : Array[Int32]
  global_writes : Array[Int32]
}

///|
priv struct SimplifyLocalsSinkableInfo {
  node_id : Int32
  effects : SimplifyLocalsEffects
}

///|
priv struct SimplifyLocalsSinkables {
  module_ctx : @ir.HotModuleContext?
  entries : Array[SimplifyLocalsSinkableInfo?]
  active_pos : Array[Int32]
  active_locals : Array[Int32]
  effect_bit_counts : Array[Int32]
  local_read_counts : Array[Int32]
  local_write_counts : Array[Int32]
  scratch_local_read_stamps : Array[Int32]
  scratch_local_write_stamps : Array[Int32]
  mut next_scratch_stamp : Int32
  source_gets : Array[Array[(Int32, Int32)]]
  captures : PassSourceCaptures
  mut effect_scan_workspace : SimplifyLocalsEffectScanWorkspace?
  mut value_order_minimums : SimplifyLocalsValueOrderMinimums?
}

///|
priv struct SimplifyLocalsTailSet {
  root_idx : Int32
  node_id : Int32
  local_id : Int32
  value_id : Int32
}

///|
priv struct SimplifyLocalsBranchExit {
  region_ref : @ir.HotRegionRef
  set_idx : Int32
  set_id : Int32
  br_idx : Int32
  br_id : Int32
  value_id : Int32
}

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

///|
fn simplify_locals_new_sinkables(
  func : @ir.HotFunc,
  module_ctx~ : @ir.HotModuleContext? = None,
  source_gets~ : Array[Array[(Int32, Int32)]]? = None,
  captures~ : PassSourceCaptures? = None,
) -> SimplifyLocalsSinkables {
  let captures = match captures {
    Some(captures) => captures
    None => PassSourceCaptures::new(func)
  }
  let source_gets = match source_gets {
    Some(reads) => reads
    None => {
      let reads : Array[Array[(Int32, Int32)]] = Array::makei(
        @ir.hot_local_count(func),
        fn(_) { [] },
      )
      for id = 0; id < @ir.hot_node_count(func); id = id + 1 {
        if @ir.hot_node_is_live(func, id) {
          let node = @ir.hot_node_get(func, id)
          if node.op is @ir.HotOp::LocalGet &&
            node.imm0 >= 0 &&
            node.imm0 < reads.length() {
            reads[node.imm0].push((node.order, id))
          }
        }
      }
      for local_reads in reads {
        local_reads.sort_by(fn(a, b) { a.0.compare(b.0) })
      }
      reads
    }
  }
  {
    module_ctx,
    entries: Array::make(@ir.hot_local_count(func), None),
    active_pos: Array::make(@ir.hot_local_count(func), -1),
    active_locals: [],
    effect_bit_counts: Array::make(10, 0),
    local_read_counts: Array::make(@ir.hot_local_count(func), 0),
    local_write_counts: Array::make(@ir.hot_local_count(func), 0),
    scratch_local_read_stamps: Array::make(@ir.hot_local_count(func), 0),
    scratch_local_write_stamps: Array::make(@ir.hot_local_count(func), 0),
    next_scratch_stamp: 1,
    source_gets,
    captures,
    effect_scan_workspace: None,
    value_order_minimums: None,
  }
}

///|
fn simplify_locals_has_earlier_source_read(
  func : @ir.HotFunc,
  sinkables : SimplifyLocalsSinkables,
  local_id : Int32,
  definition_order : Int32,
  current_order : Int32,
) -> Bool {
  let reads = sinkables.source_gets[local_id]
  let mut lo = 0
  let mut hi = reads.length()
  while lo < hi {
    let mid = lo + (hi - lo) / 2
    if reads[mid].0 <= definition_order {
      lo = mid + 1
    } else {
      hi = mid
    }
  }
  while lo < reads.length() && reads[lo].0 < current_order {
    let id = reads[lo].1
    if @ir.hot_node_is_live(func, id) {
      let read = @ir.hot_node_get(func, id)
      if read.op is @ir.HotOp::LocalGet &&
        read.imm0 == local_id &&
        read.order > definition_order &&
        read.order < current_order {
        return true
      }
    }
    lo += 1
  }
  false
}

///|
fn simplify_locals_accumulate_effect_mask_counts(
  counts : Array[Int32],
  mask : @ir.EffectMask,
  delta : Int32,
) -> Unit {
  let mut bit = 1U
  let mut idx = 0
  while idx < counts.length() {
    if (mask & bit) != 0U {
      counts[idx] = counts[idx] + delta
    }
    bit = bit << 1
    idx = idx + 1
  }
}

///|
fn simplify_locals_accumulate_local_effect_counts(
  counts : Array[Int32],
  effects : Array[Int32],
  delta : Int32,
) -> Unit {
  for local_id in effects {
    if local_id >= 0 && local_id < counts.length() {
      counts[local_id] = counts[local_id] + delta
    }
  }
}

///|
fn simplify_locals_sinkables_add_effects(
  sinkables : SimplifyLocalsSinkables,
  effects : SimplifyLocalsEffects,
) -> Unit {
  simplify_locals_accumulate_effect_mask_counts(
    sinkables.effect_bit_counts,
    effects.mask,
    1,
  )
  simplify_locals_accumulate_local_effect_counts(
    sinkables.local_read_counts,
    effects.local_reads,
    1,
  )
  simplify_locals_accumulate_local_effect_counts(
    sinkables.local_write_counts,
    effects.local_writes,
    1,
  )
}

///|
fn simplify_locals_sinkables_remove_effects(
  sinkables : SimplifyLocalsSinkables,
  effects : SimplifyLocalsEffects,
) -> Unit {
  simplify_locals_accumulate_effect_mask_counts(
    sinkables.effect_bit_counts,
    effects.mask,
    -1,
  )
  simplify_locals_accumulate_local_effect_counts(
    sinkables.local_read_counts,
    effects.local_reads,
    -1,
  )
  simplify_locals_accumulate_local_effect_counts(
    sinkables.local_write_counts,
    effects.local_writes,
    -1,
  )
}

///|
fn simplify_locals_sinkables_have_effect(
  sinkables : SimplifyLocalsSinkables,
  bit : @ir.EffectMask,
) -> Bool {
  let mut mask_bit = 1U
  let mut idx = 0
  while idx < sinkables.effect_bit_counts.length() {
    if mask_bit == bit {
      return sinkables.effect_bit_counts[idx] > 0
    }
    mask_bit = mask_bit << 1
    idx = idx + 1
  }
  false
}

///|
fn simplify_locals_sinkables_may_conflict(
  sinkables : SimplifyLocalsSinkables,
  rhs_mask : @ir.EffectMask,
  rhs_local_read : Int32,
  rhs_local_write : Int32,
) -> Bool {
  if sinkables.active_locals.is_empty() || rhs_mask == 0U {
    return false
  }

  if simplify_locals_effects_has(rhs_mask, @ir.EFFECT_MASK_CALL) ||
    simplify_locals_effects_has(
      rhs_mask,
      @ir.EFFECT_MASK_CONTROL | @ir.EFFECT_MASK_THROW | @ir.EFFECT_MASK_TRAP,
    ) {
    return true
  }

  // Pending calls can observe any nonlocal state; pending control, throws,
  // and traps can determine whether a later effect executes at all. Consult
  // the exact ordering check even when the RHS has no matching effect bit.
  if simplify_locals_sinkables_have_effect(sinkables, @ir.EFFECT_MASK_CALL) ||
    simplify_locals_sinkables_have_effect(sinkables, @ir.EFFECT_MASK_CONTROL) ||
    simplify_locals_sinkables_have_effect(sinkables, @ir.EFFECT_MASK_THROW) ||
    simplify_locals_sinkables_have_effect(sinkables, @ir.EFFECT_MASK_TRAP) {
    return true
  }

  if simplify_locals_effects_has(rhs_mask, @ir.EFFECT_MASK_LOCAL_STATE) {
    if rhs_local_read >= 0 &&
      rhs_local_read < sinkables.local_write_counts.length() &&
      sinkables.local_write_counts[rhs_local_read] > 0 {
      return true
    }
    if rhs_local_write >= 0 &&
      rhs_local_write < sinkables.local_read_counts.length() &&
      (
        sinkables.local_read_counts[rhs_local_write] > 0 ||
        sinkables.local_write_counts[rhs_local_write] > 0
      ) {
      return true
    }
  }

  if simplify_locals_effects_has(rhs_mask, @ir.EFFECT_MASK_GLOBAL_STATE) &&
    simplify_locals_sinkables_have_effect(
      sinkables,
      @ir.EFFECT_MASK_GLOBAL_STATE,
    ) {
    return true
  }

  let rhs_reads_memory = simplify_locals_effects_has(
    rhs_mask,
    @ir.EFFECT_MASK_MEMORY_READ,
  )
  let rhs_writes_memory = simplify_locals_effects_has(
    rhs_mask,
    @ir.EFFECT_MASK_MEMORY_WRITE,
  )
  if (
      rhs_reads_memory &&
      simplify_locals_sinkables_have_effect(
        sinkables,
        @ir.EFFECT_MASK_MEMORY_WRITE,
      )
    ) ||
    (
      rhs_writes_memory &&
      (
        simplify_locals_sinkables_have_effect(
          sinkables,
          @ir.EFFECT_MASK_MEMORY_READ,
        ) ||
        simplify_locals_sinkables_have_effect(
          sinkables,
          @ir.EFFECT_MASK_MEMORY_WRITE,
        )
      )
    ) {
    return true
  }

  let rhs_reads_table = simplify_locals_effects_has(
    rhs_mask,
    @ir.EFFECT_MASK_TABLE_READ,
  )
  let rhs_writes_table = simplify_locals_effects_has(
    rhs_mask,
    @ir.EFFECT_MASK_TABLE_WRITE,
  )
  if (
      rhs_reads_table &&
      simplify_locals_sinkables_have_effect(
        sinkables,
        @ir.EFFECT_MASK_TABLE_WRITE,
      )
    ) ||
    (
      rhs_writes_table &&
      (
        simplify_locals_sinkables_have_effect(
          sinkables,
          @ir.EFFECT_MASK_TABLE_READ,
        ) ||
        simplify_locals_sinkables_have_effect(
          sinkables,
          @ir.EFFECT_MASK_TABLE_WRITE,
        )
      )
    ) {
    return true
  }

  false
}

///|
fn simplify_locals_sinkables_get(
  sinkables : SimplifyLocalsSinkables,
  local_id : Int32,
) -> SimplifyLocalsSinkableInfo? {
  if local_id < 0 || local_id >= sinkables.entries.length() {
    return None
  }
  sinkables.entries[local_id]
}

///|
fn simplify_locals_sinkables_set(
  sinkables : SimplifyLocalsSinkables,
  local_id : Int32,
  info : SimplifyLocalsSinkableInfo,
) -> Unit {
  if local_id < 0 || local_id >= sinkables.entries.length() {
    return
  }
  if sinkables.active_pos[local_id] < 0 {
    sinkables.active_pos[local_id] = sinkables.active_locals.length()
    sinkables.active_locals.push(local_id)
  } else {
    match sinkables.entries[local_id] {
      Some(previous) =>
        simplify_locals_sinkables_remove_effects(sinkables, previous.effects)
      None => ()
    }
  }
  sinkables.entries[local_id] = Some(info)
  simplify_locals_sinkables_add_effects(sinkables, info.effects)
}

///|
fn simplify_locals_sinkables_clear_local(
  sinkables : SimplifyLocalsSinkables,
  local_id : Int32,
) -> Unit {
  if local_id < 0 || local_id >= sinkables.entries.length() {
    return
  }
  let pos = sinkables.active_pos[local_id]
  if pos >= 0 {
    match sinkables.entries[local_id] {
      Some(info) =>
        simplify_locals_sinkables_remove_effects(sinkables, info.effects)
      None => ()
    }
    let last_local = sinkables.active_locals.pop().unwrap()
    if last_local != local_id {
      sinkables.active_locals[pos] = last_local
      sinkables.active_pos[last_local] = pos
    }
    sinkables.active_pos[local_id] = -1
  }
  sinkables.entries[local_id] = None
}

///|
fn simplify_locals_sinkables_should_bulk_clear(
  sinkables : SimplifyLocalsSinkables,
) -> Bool {
  sinkables.active_locals.length() * 4 >= sinkables.local_read_counts.length()
}

///|
fn simplify_locals_sinkables_zero_aggregate_counts(
  sinkables : SimplifyLocalsSinkables,
) -> Unit {
  for idx = 0; idx < sinkables.effect_bit_counts.length(); idx = idx + 1 {
    sinkables.effect_bit_counts[idx] = 0
  }
  for idx = 0; idx < sinkables.local_read_counts.length(); idx = idx + 1 {
    sinkables.local_read_counts[idx] = 0
    sinkables.local_write_counts[idx] = 0
  }
}

///|
fn simplify_locals_clear_sinkables(sinkables : SimplifyLocalsSinkables) -> Unit {
  if simplify_locals_sinkables_should_bulk_clear(sinkables) {
    while !sinkables.active_locals.is_empty() {
      let local_id = sinkables.active_locals.pop().unwrap()
      sinkables.entries[local_id] = None
      sinkables.active_pos[local_id] = -1
    }
    simplify_locals_sinkables_zero_aggregate_counts(sinkables)
    return
  }
  while !sinkables.active_locals.is_empty() {
    let local_id = sinkables.active_locals.pop().unwrap()
    match sinkables.entries[local_id] {
      Some(info) =>
        simplify_locals_sinkables_remove_effects(sinkables, info.effects)
      None => ()
    }
    sinkables.entries[local_id] = None
    sinkables.active_pos[local_id] = -1
  }
}

///|
fn simplify_locals_clear_throwing_sinkables(
  sinkables : SimplifyLocalsSinkables,
) -> Unit {
  let mut idx = 0
  while idx < sinkables.active_locals.length() {
    let local_id = sinkables.active_locals[idx]
    match sinkables.entries[local_id] {
      Some(info) if simplify_locals_effects_has(
          info.effects.mask,
          @ir.EFFECT_MASK_THROW,
        ) => {
        simplify_locals_sinkables_clear_local(sinkables, local_id)
        continue
      }
      _ => ()
    }
    idx = idx + 1
  }
}

///|
fn simplify_locals_empty_effects() -> SimplifyLocalsEffects {
  {
    mask: 0U,
    local_reads: [],
    local_writes: [],
    global_reads: [],
    global_writes: [],
  }
}

///|
fn simplify_locals_next_scratch_stamp(
  sinkables : SimplifyLocalsSinkables,
) -> Int32 {
  let stamp = sinkables.next_scratch_stamp
  sinkables.next_scratch_stamp = stamp + 1
  stamp
}

///|
fn simplify_locals_record_sparse_local_effect(
  local_ids : Array[Int32],
  stamps : Array[Int32],
  stamp : Int32,
  local_id : Int32,
) -> Unit {
  if local_id < 0 || local_id >= stamps.length() {
    return
  }
  if stamps[local_id] == stamp {
    return
  }
  stamps[local_id] = stamp
  local_ids.push(local_id)
}

///|
fn simplify_locals_record_exact_local_effects(
  func : @ir.HotFunc,
  node_id : Int32,
  local_reads : Array[Int32],
  local_writes : Array[Int32],
  read_stamps : Array[Int32],
  write_stamps : Array[Int32],
  stamp : Int32,
) -> Unit {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalGet =>
      simplify_locals_record_sparse_local_effect(
        local_reads,
        read_stamps,
        stamp,
        node.imm0,
      )
    @ir.HotOp::LocalSet | @ir.HotOp::LocalTee =>
      simplify_locals_record_sparse_local_effect(
        local_writes,
        write_stamps,
        stamp,
        node.imm0,
      )
    _ => ()
  }
}

///|
fn simplify_locals_record_exact_global_effects(
  func : @ir.HotFunc,
  node_id : Int32,
  global_reads : Array[Int32],
  global_writes : Array[Int32],
) -> Unit {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::GlobalGet =>
      if node.imm0 >= 0 && !global_reads.contains(node.imm0) {
        global_reads.push(node.imm0)
      }
    @ir.HotOp::GlobalSet =>
      if node.imm0 >= 0 && !global_writes.contains(node.imm0) {
        global_writes.push(node.imm0)
      }
    _ => ()
  }
}

///|
fn simplify_locals_collect_region_local_effects(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  local_reads : Array[Int32],
  local_writes : Array[Int32],
  read_stamps : Array[Int32],
  write_stamps : Array[Int32],
  stamp : Int32,
) -> Unit {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    simplify_locals_collect_subtree_local_effects(
      func,
      @ir.hot_region_get(func, region_ref, idx),
      local_reads,
      local_writes,
      read_stamps,
      write_stamps,
      stamp,
    )
  }
}

///|
fn simplify_locals_collect_subtree_local_effects(
  func : @ir.HotFunc,
  node_id : Int32,
  local_reads : Array[Int32],
  local_writes : Array[Int32],
  read_stamps : Array[Int32],
  write_stamps : Array[Int32],
  stamp : Int32,
) -> Unit {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return
  }
  simplify_locals_record_exact_local_effects(
    func, node_id, local_reads, local_writes, read_stamps, write_stamps, stamp,
  )
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::If => {
      simplify_locals_collect_subtree_local_effects(
        func,
        @ir.hot_child_get(func, node_id, 0),
        local_reads,
        local_writes,
        read_stamps,
        write_stamps,
        stamp,
      )
      simplify_locals_collect_region_local_effects(
        func,
        @ir.hot_region_then_ref(func, node_id),
        local_reads,
        local_writes,
        read_stamps,
        write_stamps,
        stamp,
      )
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_ref) =>
          simplify_locals_collect_region_local_effects(
            func, else_ref, local_reads, local_writes, read_stamps, write_stamps,
            stamp,
          )
        None => ()
      }
      return
    }
    @ir.HotOp::Try => {
      simplify_locals_collect_region_local_effects(
        func,
        @ir.hot_region_body_ref(func, node_id),
        local_reads,
        local_writes,
        read_stamps,
        write_stamps,
        stamp,
      )
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_ref) =>
          simplify_locals_collect_region_local_effects(
            func, catch_ref, local_reads, local_writes, read_stamps, write_stamps,
            stamp,
          )
        None => ()
      }
      return
    }
    @ir.HotOp::TryTable => {
      simplify_locals_collect_region_local_effects(
        func,
        @ir.hot_region_body_ref(func, node_id),
        local_reads,
        local_writes,
        read_stamps,
        write_stamps,
        stamp,
      )
      simplify_locals_collect_region_local_effects(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
        local_reads,
        local_writes,
        read_stamps,
        write_stamps,
        stamp,
      )
      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)
    simplify_locals_collect_subtree_local_effects(
      func, child_id, local_reads, local_writes, read_stamps, write_stamps, stamp,
    )
  }
}

///|
fn simplify_locals_collect_region_global_effects(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  global_reads : Array[Int32],
  global_writes : Array[Int32],
) -> Unit {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    simplify_locals_collect_subtree_global_effects(
      func,
      @ir.hot_region_get(func, region_ref, idx),
      global_reads,
      global_writes,
    )
  }
}

///|
fn simplify_locals_collect_subtree_global_effects(
  func : @ir.HotFunc,
  node_id : Int32,
  global_reads : Array[Int32],
  global_writes : Array[Int32],
) -> Unit {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return
  }
  simplify_locals_record_exact_global_effects(
    func, node_id, global_reads, global_writes,
  )
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::If => {
      simplify_locals_collect_subtree_global_effects(
        func,
        @ir.hot_child_get(func, node_id, 0),
        global_reads,
        global_writes,
      )
      simplify_locals_collect_region_global_effects(
        func,
        @ir.hot_region_then_ref(func, node_id),
        global_reads,
        global_writes,
      )
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_ref) =>
          simplify_locals_collect_region_global_effects(
            func, else_ref, global_reads, global_writes,
          )
        None => ()
      }
      return
    }
    @ir.HotOp::Try => {
      simplify_locals_collect_region_global_effects(
        func,
        @ir.hot_region_body_ref(func, node_id),
        global_reads,
        global_writes,
      )
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_ref) =>
          simplify_locals_collect_region_global_effects(
            func, catch_ref, global_reads, global_writes,
          )
        None => ()
      }
      return
    }
    @ir.HotOp::TryTable => {
      simplify_locals_collect_region_global_effects(
        func,
        @ir.hot_region_body_ref(func, node_id),
        global_reads,
        global_writes,
      )
      simplify_locals_collect_region_global_effects(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
        global_reads,
        global_writes,
      )
      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)
    simplify_locals_collect_subtree_global_effects(
      func, child_id, global_reads, global_writes,
    )
  }
}

///|
fn simplify_locals_effects_mask_for_flags(flags : UInt32) -> @ir.EffectMask {
  let mut mask = 0U
  if (flags & @ir.HOT_FLAG_CONTROL) != 0U ||
    (flags & @ir.HOT_FLAG_BRANCH) != 0U ||
    (flags & @ir.HOT_FLAG_TERMINATOR) != 0U {
    mask = mask | @ir.EFFECT_MASK_CONTROL
  }
  if (flags & @ir.HOT_FLAG_CALL) != 0U {
    mask = mask | @ir.EFFECT_MASK_CALL | @ir.EFFECT_MASK_THROW
  }
  if (flags & @ir.HOT_FLAG_THROWS) != 0U ||
    (flags & @ir.HOT_FLAG_HAS_EXCEPTIONAL_SUCC) != 0U {
    mask = mask | @ir.EFFECT_MASK_THROW
  }
  if (flags & @ir.HOT_FLAG_MAY_TRAP) != 0U {
    mask = mask | @ir.EFFECT_MASK_TRAP
  }
  mask
}

///|
fn simplify_locals_effects_mask_for_atomic_exact(
  instr : @lib.Instruction?,
) -> @ir.EffectMask {
  match instr {
    Some(@lib.Instruction::MemoryAtomicNotify(_)) =>
      @ir.EFFECT_MASK_MEMORY_WRITE | @ir.EFFECT_MASK_TRAP
    Some(@lib.Instruction::MemoryAtomicWait32(_))
    | Some(@lib.Instruction::MemoryAtomicWait64(_))
    | Some(@lib.Instruction::I32AtomicLoad(_, _))
    | Some(@lib.Instruction::I64AtomicLoad(_, _))
    | Some(@lib.Instruction::I32AtomicLoad8U(_, _))
    | Some(@lib.Instruction::I32AtomicLoad16U(_, _))
    | Some(@lib.Instruction::I64AtomicLoad8U(_, _))
    | Some(@lib.Instruction::I64AtomicLoad16U(_, _))
    | Some(@lib.Instruction::I64AtomicLoad32U(_, _))
    | Some(@lib.Instruction::StructAtomicGet(_, _, _))
    | Some(@lib.Instruction::StructAtomicGetS(_, _, _))
    | Some(@lib.Instruction::StructAtomicGetU(_, _, _)) =>
      // Treat atomic reads as memory-ordering barriers for local sinking: they
      // read memory, but ordinary read-only loads must not commute across them.
      @ir.EFFECT_MASK_MEMORY_READ |
      @ir.EFFECT_MASK_MEMORY_WRITE |
      @ir.EFFECT_MASK_TRAP
    Some(@lib.Instruction::I32AtomicStore(_, _))
    | Some(@lib.Instruction::I64AtomicStore(_, _))
    | Some(@lib.Instruction::I32AtomicStore8(_, _))
    | Some(@lib.Instruction::I32AtomicStore16(_, _))
    | Some(@lib.Instruction::I64AtomicStore8(_, _))
    | Some(@lib.Instruction::I64AtomicStore16(_, _))
    | Some(@lib.Instruction::I64AtomicStore32(_, _)) =>
      @ir.EFFECT_MASK_MEMORY_WRITE | @ir.EFFECT_MASK_TRAP
    Some(@lib.Instruction::AtomicRmw(_, _, _))
    | Some(@lib.Instruction::AtomicCmpxchg(_, _, _)) =>
      @ir.EFFECT_MASK_MEMORY_READ |
      @ir.EFFECT_MASK_MEMORY_WRITE |
      @ir.EFFECT_MASK_TRAP
    _ =>
      @ir.EFFECT_MASK_MEMORY_READ |
      @ir.EFFECT_MASK_MEMORY_WRITE |
      @ir.EFFECT_MASK_TRAP
  }
}

///|
fn simplify_locals_effects_mask_for_heap_exact(
  instr : @lib.Instruction?,
) -> @ir.EffectMask {
  match instr {
    Some(@lib.Instruction::StructGet(_, _))
    | Some(@lib.Instruction::StructGetS(_, _))
    | Some(@lib.Instruction::StructGetU(_, _))
    | Some(@lib.Instruction::ArrayGet(_))
    | Some(@lib.Instruction::ArrayGetS(_))
    | Some(@lib.Instruction::ArrayGetU(_))
    | Some(@lib.Instruction::ArrayLoad(_, _, _))
    | Some(@lib.Instruction::ArrayLen)
    | Some(@lib.Instruction::RefGetDesc(_)) =>
      @ir.EFFECT_MASK_MEMORY_READ | @ir.EFFECT_MASK_TRAP
    Some(@lib.Instruction::StructSet(_, _))
    | Some(@lib.Instruction::ArraySet(_))
    | Some(@lib.Instruction::ArrayStore(_, _, _))
    | Some(@lib.Instruction::ArrayFill(_)) =>
      @ir.EFFECT_MASK_MEMORY_WRITE | @ir.EFFECT_MASK_TRAP
    Some(@lib.Instruction::StructNew(_))
    | Some(@lib.Instruction::StructNewDefault(_))
    | Some(@lib.Instruction::StructNewDesc(_))
    | Some(@lib.Instruction::StructNewDefaultDesc(_))
    | Some(@lib.Instruction::ArrayNew(_))
    | Some(@lib.Instruction::ArrayNewDefault(_))
    | Some(@lib.Instruction::ArrayNewFixed(_, _))
    | Some(@lib.Instruction::ArrayNewData(_, _))
    | Some(@lib.Instruction::ArrayNewElem(_, _))
    | Some(@lib.Instruction::ArrayCopy(_, _))
    | Some(@lib.Instruction::ArrayInitData(_, _))
    | Some(@lib.Instruction::ArrayInitElem(_, _)) =>
      @ir.EFFECT_MASK_MEMORY_READ |
      @ir.EFFECT_MASK_MEMORY_WRITE |
      @ir.EFFECT_MASK_TRAP
    Some(@lib.Instruction::RefAsNonNull)
    | Some(@lib.Instruction::I31GetS)
    | Some(@lib.Instruction::I31GetU) => @ir.EFFECT_MASK_TRAP
    Some(@lib.Instruction::RefEq)
    | Some(@lib.Instruction::AnyConvertExtern)
    | Some(@lib.Instruction::ExternConvertAny)
    | Some(@lib.Instruction::RefI31) => 0U
    _ => @ir.EFFECT_MASK_TRAP
  }
}

///|
fn simplify_locals_effects_mask_for_exact_node(
  func : @ir.HotFunc,
  node_id : Int32,
) -> @ir.EffectMask {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return 0U
  }
  let node = @ir.hot_node_get(func, node_id)
  let exact_mask = match node.op {
    @ir.HotOp::LocalGet | @ir.HotOp::LocalSet | @ir.HotOp::LocalTee =>
      @ir.EFFECT_MASK_LOCAL_STATE
    @ir.HotOp::GlobalGet | @ir.HotOp::GlobalSet => @ir.EFFECT_MASK_GLOBAL_STATE
    @ir.HotOp::Load => @ir.EFFECT_MASK_MEMORY_READ
    @ir.HotOp::Store => @ir.EFFECT_MASK_MEMORY_WRITE
    @ir.HotOp::MemorySize => @ir.EFFECT_MASK_MEMORY_READ
    @ir.HotOp::MemoryGrow =>
      @ir.EFFECT_MASK_MEMORY_READ | @ir.EFFECT_MASK_MEMORY_WRITE
    @ir.HotOp::MemoryCopy | @ir.HotOp::MemoryInit =>
      @ir.EFFECT_MASK_MEMORY_READ | @ir.EFFECT_MASK_MEMORY_WRITE
    @ir.HotOp::MemoryFill | @ir.HotOp::DataDrop => @ir.EFFECT_MASK_MEMORY_WRITE
    @ir.HotOp::TableGet | @ir.HotOp::TableSize => @ir.EFFECT_MASK_TABLE_READ
    @ir.HotOp::TableSet
    | @ir.HotOp::TableGrow
    | @ir.HotOp::TableFill
    | @ir.HotOp::ElemDrop => @ir.EFFECT_MASK_TABLE_WRITE
    @ir.HotOp::TableCopy | @ir.HotOp::TableInit =>
      @ir.EFFECT_MASK_TABLE_READ | @ir.EFFECT_MASK_TABLE_WRITE
    @ir.HotOp::Binary | @ir.HotOp::Unary | @ir.HotOp::Convert =>
      match @ir.hot_node_exact_instr(func, node_id) {
        Some(@lib.I32DivS)
        | Some(@lib.I32DivU)
        | Some(@lib.I32RemS)
        | Some(@lib.I32RemU)
        | Some(@lib.I64DivS)
        | Some(@lib.I64DivU)
        | Some(@lib.I64RemS)
        | Some(@lib.I64RemU)
        | Some(@lib.I32TruncF32S)
        | Some(@lib.I32TruncF32U)
        | Some(@lib.I32TruncF64S)
        | Some(@lib.I32TruncF64U)
        | Some(@lib.I64TruncF32S)
        | Some(@lib.I64TruncF32U)
        | Some(@lib.I64TruncF64S)
        | Some(@lib.I64TruncF64U)
        | Some(@lib.RefAsNonNull) => @ir.EFFECT_MASK_TRAP
        _ => 0U
      }
    @ir.HotOp::StringOp =>
      match @ir.hot_node_exact_instr(func, node_id) {
        Some(@lib.StringEncodeUtf8Array)
        | Some(@lib.StringEncodeWtf16Array)
        | Some(@lib.StringEncodeLossyUtf8Array)
        | Some(@lib.StringEncodeWtf8Array) =>
          @ir.EFFECT_MASK_MEMORY_WRITE | @ir.EFFECT_MASK_TRAP
        Some(@lib.StringNewUtf8Array)
        | Some(@lib.StringNewWtf16Array)
        | Some(@lib.StringNewLossyUtf8Array)
        | Some(@lib.StringNewWtf8Array) =>
          @ir.EFFECT_MASK_MEMORY_READ | @ir.EFFECT_MASK_TRAP
        Some(@lib.StringEq) => 0U
        _ => @ir.EFFECT_MASK_TRAP
      }
    @ir.HotOp::Simd => {
      let mut mask = 0U
      if (node.flags & @ir.HOT_FLAG_LOAD) != 0U {
        mask = mask | @ir.EFFECT_MASK_MEMORY_READ
      }
      if (node.flags & @ir.HOT_FLAG_STORE) != 0U {
        mask = mask | @ir.EFFECT_MASK_MEMORY_WRITE
      }
      mask
    }
    @ir.HotOp::Atomic =>
      simplify_locals_effects_mask_for_atomic_exact(
        @ir.hot_node_exact_instr(func, node_id),
      )
    @ir.HotOp::Heap =>
      simplify_locals_effects_mask_for_heap_exact(
        @ir.hot_node_exact_instr(func, node_id),
      )
    _ => 0U
  }
  simplify_locals_effects_mask_for_flags(node.flags) | exact_mask
}

///|
fn simplify_locals_exact_local_read_id(node : @ir.HotNode) -> Int32 {
  match node.op {
    @ir.HotOp::LocalGet => node.imm0
    _ => -1
  }
}

///|
fn simplify_locals_exact_local_write_id(node : @ir.HotNode) -> Int32 {
  match node.op {
    @ir.HotOp::LocalSet | @ir.HotOp::LocalTee => node.imm0
    _ => -1
  }
}

///|
fn simplify_locals_exact_global_read_id(node : @ir.HotNode) -> Int32 {
  match node.op {
    @ir.HotOp::GlobalGet => node.imm0
    _ => -1
  }
}

///|
fn simplify_locals_exact_global_write_id(node : @ir.HotNode) -> Int32 {
  match node.op {
    @ir.HotOp::GlobalSet => node.imm0
    _ => -1
  }
}

///|
fn simplify_locals_effect_scan_push_region(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  pending : Array[Int32],
) -> Unit {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    pending.push(@ir.hot_region_get(func, region_ref, idx))
  }
}

///|
fn simplify_locals_effects_mask_for_subtree_scan(
  func : @ir.HotFunc,
  node_id : Int32,
  workspace~ : SimplifyLocalsEffectScanWorkspace? = None,
) -> (@ir.EffectMask, Int32) {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return (0U, 0)
  }
  if @ir.hot_child_count(func, node_id) == 0 &&
    !@ir.hot_node_kind_is_control(func, node_id) {
    return (simplify_locals_effects_mask_for_exact_node(func, node_id), 1)
  }
  let pending : Array[Int32] = [node_id]
  let marks = match workspace {
    Some(workspace) => workspace.begin(func)
    None => Array::make(@ir.hot_node_count(func), 0)
  }
  let epoch = match workspace {
    Some(workspace) => workspace.epoch
    None => 1
  }
  let mut mask = 0U
  let mut visited_nodes = 0
  while !pending.is_empty() {
    let current_id = pending.pop().unwrap()
    if current_id < 0 ||
      current_id >= marks.length() ||
      marks[current_id] == epoch ||
      !@ir.hot_node_is_live(func, current_id) {
      continue
    }
    marks[current_id] = epoch
    visited_nodes += 1
    mask = mask | simplify_locals_effects_mask_for_exact_node(func, current_id)
    let current = @ir.hot_node_get(func, current_id)
    for slot = 0; slot < @ir.hot_child_count(func, current_id); slot = slot + 1 {
      if !simplify_locals_is_region_child_slot(func, current_id, slot) {
        pending.push(@ir.hot_child_get(func, current_id, slot))
      }
    }
    match current.op {
      @ir.HotOp::Block | @ir.HotOp::Loop =>
        simplify_locals_effect_scan_push_region(
          func,
          @ir.hot_region_body_ref(func, current_id),
          pending,
        )
      @ir.HotOp::If => {
        simplify_locals_effect_scan_push_region(
          func,
          @ir.hot_region_then_ref(func, current_id),
          pending,
        )
        match @ir.hot_region_else_ref(func, current_id) {
          Some(else_ref) =>
            simplify_locals_effect_scan_push_region(func, else_ref, pending)
          None => ()
        }
      }
      @ir.HotOp::Try => {
        simplify_locals_effect_scan_push_region(
          func,
          @ir.hot_region_body_ref(func, current_id),
          pending,
        )
        match @ir.hot_region_catch_ref(func, current_id) {
          Some(catch_ref) =>
            simplify_locals_effect_scan_push_region(func, catch_ref, pending)
          None => ()
        }
      }
      @ir.HotOp::TryTable => {
        simplify_locals_effect_scan_push_region(
          func,
          @ir.hot_region_body_ref(func, current_id),
          pending,
        )
        simplify_locals_effect_scan_push_region(
          func,
          @ir.hot_region_catch_list_ref(func, current_id),
          pending,
        )
      }
      _ => ()
    }
  }
  (mask, visited_nodes)
}

///|
fn simplify_locals_effects_mask_for_subtree(
  func : @ir.HotFunc,
  node_id : Int32,
  workspace~ : SimplifyLocalsEffectScanWorkspace? = None,
) -> @ir.EffectMask {
  simplify_locals_effects_mask_for_subtree_scan(func, node_id, workspace~).0
}

///|
fn simplify_locals_effects_for_pending_local_set(
  func : @ir.HotFunc,
  node_id : Int32,
  node : @ir.HotNode,
  sinkables : SimplifyLocalsSinkables,
) -> SimplifyLocalsEffects {
  let effects = simplify_locals_empty_effects()
  let stamp = simplify_locals_next_scratch_stamp(sinkables)
  let value_mask = if node.child_count != 0 {
    let value = @ir.hot_child_get(func, node_id, 0)
    let workspace = if @ir.hot_node_count(func) >= 32 &&
      @ir.hot_child_count(func, value) > 0 {
      match sinkables.effect_scan_workspace {
        Some(workspace) => Some(workspace)
        None => {
          let workspace = SimplifyLocalsEffectScanWorkspace::new()
          sinkables.effect_scan_workspace = Some(workspace)
          Some(workspace)
        }
      }
    } else {
      None
    }
    simplify_locals_effects_mask_for_subtree(func, value, workspace~)
  } else {
    0U
  }
  if node.child_count != 0 &&
    simplify_locals_effects_has(value_mask, @ir.EFFECT_MASK_LOCAL_STATE) {
    simplify_locals_collect_subtree_local_effects(
      func,
      @ir.hot_child_get(func, node_id, 0),
      effects.local_reads,
      effects.local_writes,
      sinkables.scratch_local_read_stamps,
      sinkables.scratch_local_write_stamps,
      stamp,
    )
  }
  if node.child_count != 0 &&
    simplify_locals_effects_has(value_mask, @ir.EFFECT_MASK_GLOBAL_STATE) {
    simplify_locals_collect_subtree_global_effects(
      func,
      @ir.hot_child_get(func, node_id, 0),
      effects.global_reads,
      effects.global_writes,
    )
  }
  simplify_locals_record_sparse_local_effect(
    effects.local_writes,
    sinkables.scratch_local_write_stamps,
    stamp,
    node.imm0,
  )
  { ..effects, mask: value_mask | @ir.EFFECT_MASK_LOCAL_STATE, }
}

///|
fn simplify_locals_effects_has(
  mask : @ir.EffectMask,
  bit : @ir.EffectMask,
) -> Bool {
  (mask & bit) != 0U
}

///|
fn simplify_locals_effects_has_local(
  local_ids : Array[Int32],
  local_id : Int32,
) -> Bool {
  if local_id < 0 {
    return false
  }
  for current_id in local_ids {
    if current_id == local_id {
      return true
    }
  }
  false
}

///|
fn simplify_locals_effects_conflict_exact_local(
  lhs : SimplifyLocalsEffects,
  rhs_local_read : Int32,
  rhs_local_write : Int32,
) -> Bool {
  if simplify_locals_effects_has_local(lhs.local_writes, rhs_local_read) {
    return true
  }
  if simplify_locals_effects_has_local(lhs.local_reads, rhs_local_write) ||
    simplify_locals_effects_has_local(lhs.local_writes, rhs_local_write) {
    return true
  }
  false
}

///|
fn simplify_locals_effects_conflict_exact_global(
  module_ctx : @ir.HotModuleContext?,
  lhs : SimplifyLocalsEffects,
  rhs_global_read : Int32,
  rhs_global_write : Int32,
) -> Bool {
  if lhs.global_reads.is_empty() && lhs.global_writes.is_empty() {
    return true
  }
  if rhs_global_read < 0 && rhs_global_write < 0 {
    return true
  }
  if rhs_global_read >= 0 {
    for write in lhs.global_writes {
      if !globals_are_provably_disjoint(module_ctx, write, rhs_global_read) {
        return true
      }
    }
  }
  if rhs_global_write >= 0 {
    for read in lhs.global_reads {
      if !globals_are_provably_disjoint(module_ctx, read, rhs_global_write) {
        return true
      }
    }
    for write in lhs.global_writes {
      if !globals_are_provably_disjoint(module_ctx, write, rhs_global_write) {
        return true
      }
    }
  }
  false
}

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

///|
fn simplify_locals_effects_local_state_only(mask : @ir.EffectMask) -> Bool {
  (
    mask &
    (
      @ir.EFFECT_MASK_CALL |
      @ir.EFFECT_MASK_CONTROL |
      @ir.EFFECT_MASK_THROW |
      @ir.EFFECT_MASK_TRAP |
      @ir.EFFECT_MASK_GLOBAL_STATE |
      @ir.EFFECT_MASK_MEMORY_READ |
      @ir.EFFECT_MASK_MEMORY_WRITE |
      @ir.EFFECT_MASK_TABLE_READ |
      @ir.EFFECT_MASK_TABLE_WRITE
    )
  ) ==
  0U
}

///|
fn simplify_locals_effects_ordered_before(
  lhs : SimplifyLocalsEffects,
  rhs_mask : @ir.EffectMask,
  rhs_local_read : Int32,
  rhs_local_write : Int32,
  rhs_global_read : Int32,
  rhs_global_write : Int32,
  module_ctx~ : @ir.HotModuleContext? = None,
) -> Bool {
  // An unrelated local read neither traps nor changes observable state.
  // It may precede a pending control/call value when that value cannot change
  // the read local. Writes need the full ordering and exception checks below.
  if rhs_mask == @ir.EFFECT_MASK_LOCAL_STATE &&
    rhs_local_read >= 0 &&
    rhs_local_write < 0 &&
    !simplify_locals_effects_conflict_exact_local(
      lhs, rhs_local_read, rhs_local_write,
    ) {
    return false
  }
  let lhs_has_call = simplify_locals_effects_has(lhs.mask, @ir.EFFECT_MASK_CALL)
  let rhs_has_call = simplify_locals_effects_has(rhs_mask, @ir.EFFECT_MASK_CALL)
  if lhs_has_call || rhs_has_call {
    let local_state_conflict = simplify_locals_effects_has(
        lhs.mask,
        @ir.EFFECT_MASK_LOCAL_STATE,
      ) &&
      simplify_locals_effects_has(rhs_mask, @ir.EFFECT_MASK_LOCAL_STATE) &&
      simplify_locals_effects_conflict_exact_local(
        lhs, rhs_local_read, rhs_local_write,
      )
    if lhs_has_call &&
      !rhs_has_call &&
      simplify_locals_effects_local_state_only(rhs_mask) &&
      !local_state_conflict {
      ()
    } else if rhs_has_call &&
      !lhs_has_call &&
      simplify_locals_effects_local_state_only(lhs.mask) &&
      !local_state_conflict {
      ()
    } else {
      return true
    }
  }

  let lhs_non_linear = simplify_locals_effects_has(
    lhs.mask,
    @ir.EFFECT_MASK_CONTROL | @ir.EFFECT_MASK_THROW,
  )
  let rhs_non_linear = simplify_locals_effects_has(
    rhs_mask,
    @ir.EFFECT_MASK_CONTROL | @ir.EFFECT_MASK_THROW,
  )
  if (lhs_non_linear || rhs_non_linear) && lhs.mask != 0U && rhs_mask != 0U {
    return true
  }

  if simplify_locals_effects_has(lhs.mask, @ir.EFFECT_MASK_LOCAL_STATE) &&
    simplify_locals_effects_has(rhs_mask, @ir.EFFECT_MASK_LOCAL_STATE) &&
    simplify_locals_effects_conflict_exact_local(
      lhs, rhs_local_read, rhs_local_write,
    ) {
    return true
  }

  if simplify_locals_effects_has(lhs.mask, @ir.EFFECT_MASK_GLOBAL_STATE) &&
    simplify_locals_effects_has(rhs_mask, @ir.EFFECT_MASK_GLOBAL_STATE) &&
    simplify_locals_effects_conflict_exact_global(
      module_ctx, lhs, rhs_global_read, rhs_global_write,
    ) {
    return true
  }

  let lhs_reads_memory = simplify_locals_effects_has(
    lhs.mask,
    @ir.EFFECT_MASK_MEMORY_READ,
  )
  let lhs_writes_memory = simplify_locals_effects_has(
    lhs.mask,
    @ir.EFFECT_MASK_MEMORY_WRITE,
  )
  let rhs_reads_memory = simplify_locals_effects_has(
    rhs_mask,
    @ir.EFFECT_MASK_MEMORY_READ,
  )
  let rhs_writes_memory = simplify_locals_effects_has(
    rhs_mask,
    @ir.EFFECT_MASK_MEMORY_WRITE,
  )
  if lhs_writes_memory && (rhs_reads_memory || rhs_writes_memory) {
    return true
  }
  if lhs_reads_memory && rhs_writes_memory {
    return true
  }

  let lhs_reads_table = simplify_locals_effects_has(
    lhs.mask,
    @ir.EFFECT_MASK_TABLE_READ,
  )
  let lhs_writes_table = simplify_locals_effects_has(
    lhs.mask,
    @ir.EFFECT_MASK_TABLE_WRITE,
  )
  let rhs_reads_table = simplify_locals_effects_has(
    rhs_mask,
    @ir.EFFECT_MASK_TABLE_READ,
  )
  let rhs_writes_table = simplify_locals_effects_has(
    rhs_mask,
    @ir.EFFECT_MASK_TABLE_WRITE,
  )
  if lhs_writes_table && (rhs_reads_table || rhs_writes_table) {
    return true
  }
  if lhs_reads_table && rhs_writes_table {
    return true
  }

  let lhs_traps = simplify_locals_effects_has(lhs.mask, @ir.EFFECT_MASK_TRAP)
  let rhs_traps = simplify_locals_effects_has(rhs_mask, @ir.EFFECT_MASK_TRAP)
  let readonly_trap_commutable = simplify_locals_effects_readonly_trap_commutable(
      lhs.mask,
    ) &&
    simplify_locals_effects_readonly_trap_commutable(rhs_mask)
  let lhs_has_non_local_effects = simplify_locals_effects_has(
    lhs.mask,
    @ir.EFFECT_MASK_CALL |
    @ir.EFFECT_MASK_CONTROL |
    @ir.EFFECT_MASK_THROW |
    @ir.EFFECT_MASK_TRAP |
    @ir.EFFECT_MASK_GLOBAL_STATE |
    @ir.EFFECT_MASK_MEMORY_READ |
    @ir.EFFECT_MASK_MEMORY_WRITE |
    @ir.EFFECT_MASK_TABLE_READ |
    @ir.EFFECT_MASK_TABLE_WRITE,
  )
  if lhs_traps && rhs_mask != 0U && !readonly_trap_commutable {
    return true
  }
  if rhs_traps && lhs_has_non_local_effects && !readonly_trap_commutable {
    return true
  }

  false
}

///|
fn simplify_locals_invalidate_sinkables(
  func : @ir.HotFunc,
  node_id : Int32,
  node : @ir.HotNode,
  sinkables : SimplifyLocalsSinkables,
  skip_local~ : Int32 = -1,
) -> Unit {
  let effects_mask = simplify_locals_effects_mask_for_exact_node(func, node_id)
  if effects_mask == 0U {
    return
  }
  let effects_local_read = simplify_locals_exact_local_read_id(node)
  let effects_local_write = simplify_locals_exact_local_write_id(node)
  let effects_global_read = simplify_locals_exact_global_read_id(node)
  let effects_global_write = simplify_locals_exact_global_write_id(node)
  let clear_all = simplify_locals_effects_has(
      effects_mask,
      @ir.EFFECT_MASK_CALL,
    ) ||
    simplify_locals_effects_has(
      effects_mask,
      @ir.EFFECT_MASK_CONTROL | @ir.EFFECT_MASK_THROW,
    )
  if clear_all && skip_local < 0 {
    simplify_locals_clear_sinkables(sinkables)
    return
  }
  if !simplify_locals_sinkables_may_conflict(
      sinkables, effects_mask, effects_local_read, effects_local_write,
    ) {
    return
  }
  let mut idx = 0
  while idx < sinkables.active_locals.length() {
    let local_id = sinkables.active_locals[idx]
    if local_id == skip_local {
      idx = idx + 1
      continue
    }
    match sinkables.entries[local_id] {
      Some(info) =>
        if !@ir.hot_node_is_live(func, info.node_id) ||
          simplify_locals_effects_ordered_before(
            info.effects,
            effects_mask,
            effects_local_read,
            effects_local_write,
            effects_global_read,
            effects_global_write,
            module_ctx=sinkables.module_ctx,
          ) {
          simplify_locals_sinkables_clear_local(sinkables, local_id)
          continue
        }
      None => ()
    }
    idx = idx + 1
  }
}

///|
// Factories pass their immediately allocated, never-attached wrapper.
fn simplify_locals_take_new_wrapper(
  func : @ir.HotFunc,
  replacement_id : Int32,
  retirement_scans : Array[Int32]?,
) -> @ir.HotNode {
  let replacement = @ir.hot_node_get(func, replacement_id)
  // The builder allocated this node immediately before extraction, and no
  // root or child reference has been attached to it. Keep ordinary checked
  // deletion for all nodes whose detached ownership is not proved here.
  if replacement_id == @ir.hot_node_count(func) - 1 {
    @ir.hot_delete_detached_node(func, replacement_id)
  } else {
    match retirement_scans {
      Some(scans) => scans[0] += 1
      None => ()
    }
    @ir.hot_delete_node(func, replacement_id)
  }
  replacement
}

///|
fn simplify_locals_make_nop(
  func : @ir.HotFunc,
  retirement_scans~ : Array[Int32]? = None,
) -> @ir.HotNode {
  let replacement_id = @ir.hot_build_nop(func)
  simplify_locals_take_new_wrapper(func, replacement_id, retirement_scans)
}

///|
fn simplify_locals_make_drop(
  func : @ir.HotFunc,
  value_id : Int32,
  retirement_scans~ : Array[Int32]? = None,
) -> @ir.HotNode {
  let replacement_id = @ir.hot_build_drop(func, value_id)
  simplify_locals_take_new_wrapper(func, replacement_id, retirement_scans)
}

///|
fn simplify_locals_make_local_get(
  func : @ir.HotFunc,
  local_id : Int32,
  retirement_scans~ : Array[Int32]? = None,
) -> @ir.HotNode {
  let replacement_id = @ir.hot_build_local_get(func, local_id)
  simplify_locals_take_new_wrapper(func, replacement_id, retirement_scans)
}

///|
fn simplify_locals_make_local_tee(
  func : @ir.HotFunc,
  set_id : Int32,
  retirement_scans~ : Array[Int32]? = None,
) -> @ir.HotNode {
  let replacement_id = @ir.hot_build_local_tee_from_set(func, set_id)
  simplify_locals_take_new_wrapper(func, replacement_id, retirement_scans)
}

///|
/// Keep the captured value at its source position when removing a carrier.
/// Fresh wrapper order can move dependent conditional reads before this write.
fn simplify_locals_make_local_set(
  func : @ir.HotFunc,
  local_id : Int32,
  value_id : Int32,
  retirement_scans~ : Array[Int32]? = None,
) -> @ir.HotNode {
  let replacement_id = @ir.hot_build_local_set_from_value(
    func, local_id, value_id,
  )
  simplify_locals_take_new_wrapper(func, replacement_id, retirement_scans)
}

///|
fn simplify_locals_child_use_count_capped_at_two(
  func : @ir.HotFunc,
  target_id : Int32,
) -> Int32 {
  let mut count = 0
  for node_id = 0; node_id < @ir.hot_node_count(func); node_id = node_id + 1 {
    let node = func.nodes[node_id]
    if node.child_count == 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    for slot = 0; slot < node.child_count; slot = slot + 1 {
      if func.children[node.child_base + slot] == target_id {
        count += 1
        if count == 2 {
          return 2
        }
      }
    }
  }
  count
}

///|
fn simplify_locals_two_child_uses_are_unique(
  func : @ir.HotFunc,
  first_id : Int32,
  second_id : Int32,
) -> Bool {
  if first_id == second_id {
    return simplify_locals_child_use_count_capped_at_two(func, first_id) == 1
  }
  let mut first_count = 0
  let mut second_count = 0
  for node_id = 0; node_id < @ir.hot_node_count(func); node_id = node_id + 1 {
    let node = func.nodes[node_id]
    if node.child_count == 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    for slot = 0; slot < node.child_count; slot = slot + 1 {
      let child_id = func.children[node.child_base + slot]
      if child_id == first_id {
        first_count += 1
        if first_count == 2 {
          return false
        }
      } else if child_id == second_id {
        second_count += 1
        if second_count == 2 {
          return false
        }
      }
    }
  }
  first_count == 1 && second_count == 1
}

///|
fn simplify_locals_local_type_id(
  func : @ir.HotFunc,
  local_id : Int32,
  fallback_id : Int32,
) -> Int32 {
  match @ir.hot_local_type(func, local_id) {
    Some(local_type) =>
      @ir.hot_type_intern_block_result(
        func,
        @lib.BlockType::val_type(local_type),
      )
    None =>
      match @ir.hot_type_results(func, @ir.hot_node_type(func, fallback_id)) {
        [value_type] =>
          @ir.hot_type_intern_block_result(
            func,
            @lib.BlockType::val_type(value_type),
          )
        _ => @ir.hot_type_void(func)
      }
  }
}

///|
fn simplify_locals_local_type_is_defaultable(
  func : @ir.HotFunc,
  local_id : Int32,
) -> Bool {
  match @ir.hot_local_type(func, local_id) {
    Some(@lib.RefTypeValType(ref_type)) =>
      @lib.RefType::is_defaultable(ref_type)
    Some(@lib.BotValType) => false
    Some(_) => true
    None => false
  }
}

///|
fn simplify_locals_region_last_live_non_nop_root_idx(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
) -> Int32? {
  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::Nop => ()
        _ => return Some(idx)
      }
    }
    idx = idx - 1
  }
  None
}

///|
fn simplify_locals_region_next_live_non_nop_root_idx(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  after_idx : Int32,
) -> Int32? {
  let mut idx = after_idx + 1
  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) {
      match @ir.hot_node_get(func, node_id).op {
        @ir.HotOp::Nop => ()
        _ => return Some(idx)
      }
    }
    idx = idx + 1
  }
  None
}

///|
fn simplify_locals_region_tail_local_set(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
) -> SimplifyLocalsTailSet? {
  let root_idx = match
    simplify_locals_region_last_live_non_nop_root_idx(func, region_ref) {
    Some(root_idx) => root_idx
    None => return None
  }
  let node_id = @ir.hot_region_get(func, region_ref, root_idx)
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalSet =>
      Some({
        root_idx,
        node_id,
        local_id: node.imm0,
        value_id: @ir.hot_child_get(func, node_id, 0),
      })
    _ => None
  }
}

///|
fn simplify_locals_region_root_index_of(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  node_id : Int32,
) -> Int32? {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    if @ir.hot_region_get(func, region_ref, idx) == node_id {
      return Some(idx)
    }
  }
  None
}

///|
fn simplify_locals_replace_tail_set_with_result_value(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  tail : SimplifyLocalsTailSet,
) -> Unit {
  let root_count = @ir.hot_region_root_count(func, region_ref)
  let trailing_nop_idx = if root_count > 0 {
    let last_idx = root_count - 1
    let last_id = @ir.hot_region_get(func, region_ref, last_idx)
    if last_idx != tail.root_idx &&
      last_id >= 0 &&
      @ir.hot_node_is_live(func, last_id) &&
      @ir.hot_node_get(func, last_id).op is @ir.HotOp::Nop {
      Some(last_idx)
    } else {
      None
    }
  } else {
    None
  }
  match simplify_locals_region_root_index_of(func, region_ref, tail.value_id) {
    Some(value_idx) if value_idx != tail.root_idx => {
      // A carried result already executes at this earlier root. Removing its
      // consumer must not move the computation past intervening effects.
      ignore(
        @ir.hot_region_set(
          func,
          region_ref,
          tail.root_idx,
          @ir.hot_build_nop(func),
        ),
      )
      return
    }
    _ => ()
  }
  // Stack operands may have been evaluated before intervening void roots.
  // Keep that source position when promoting the operand to a region result.
  let source_orders = @ir.hot_source_first_effect_orders(func)
  let value_order = @ir.hot_node_get(func, tail.value_id).order
  for idx = 0; idx < tail.root_idx; idx = idx + 1 {
    let root = @ir.hot_region_get(func, region_ref, idx)
    if root >= 0 &&
      root < source_orders.length() &&
      source_orders[root] > value_order &&
      source_orders[root] < @ir.hot_node_count(func) {
      ignore(
        @ir.hot_region_set(
          func,
          region_ref,
          tail.root_idx,
          @ir.hot_build_nop(func),
        ),
      )
      @ir.hot_region_insert(func, region_ref, idx, [tail.value_id])
      return
    }
  }
  // Binaryen first enlarges structured arms with a trailing nop, then nops the
  // original local.set and installs its value in that result slot. Preserve
  // that source-owned shape even when doing the rewrite immediately in Hot IR.
  ignore(
    @ir.hot_region_set(func, region_ref, tail.root_idx, @ir.hot_build_nop(func)),
  )
  match trailing_nop_idx {
    Some(result_idx) =>
      ignore(@ir.hot_region_set(func, region_ref, result_idx, tail.value_id))
    None => @ir.hot_region_insert(func, region_ref, root_count, [tail.value_id])
  }
}

///|
fn simplify_locals_build_one_armed_if_then_body(
  func : @ir.HotFunc,
  then_ref : @ir.HotRegionRef,
  value_id : Int32,
) -> Array[Int32] {
  let then_body : Array[Int32] = []
  let mut value_already_present = false
  for idx = 0; idx < @ir.hot_region_root_count(func, then_ref); idx = idx + 1 {
    let root_id = @ir.hot_region_get(func, then_ref, idx)
    if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
      continue
    }
    if root_id == value_id {
      value_already_present = true
    }
    then_body.push(root_id)
  }
  if !value_already_present {
    then_body.push(value_id)
  }
  then_body
}

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

///|
fn simplify_locals_subtree_can_exit_before_fallthrough(
  func : @ir.HotFunc,
  root_id : Int32,
) -> Bool {
  let labels : Array[Int32] = []
  fn visit(node_id : Int32) -> Bool {
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      return false
    }
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::Return
      | @ir.HotOp::ReturnCall
      | @ir.HotOp::ReturnCallIndirect
      | @ir.HotOp::ReturnCallRef
      | @ir.HotOp::Throw
      | @ir.HotOp::ThrowRef
      | @ir.HotOp::Rethrow
      | @ir.HotOp::Delegate => return true
      // A call can transfer to an enclosing exception handler, which can
      // observe a local write made before the call.
      @ir.HotOp::Call | @ir.HotOp::CallIndirect | @ir.HotOp::CallRef =>
        return true
      _ => ()
    }
    match @ir.hot_branch_target(func, node_id) {
      Some(label) => if !labels.contains(label) { return true }
      None => ()
    }
    for label in @ir.hot_branch_table_targets_for_node(func, node_id) {
      if !labels.contains(label) {
        return true
      }
    }
    for label in @ir.hot_continuation_targets(func, node_id) {
      if !labels.contains(label) {
        return true
      }
    }
    let label = if @ir.hot_node_kind_is_control(func, node_id) {
      @ir.hot_control_node_label(func, node_id)
    } else {
      -1
    }
    if label >= 0 {
      labels.push(label)
    }
    // Child spans include structured bodies. Internal label exits still
    // reach the following write; only exits from this subtree block motion.
    for child in @ir.hot_child_span(func, node_id) {
      if visit(child) {
        return true
      }
    }
    if label >= 0 {
      ignore(labels.pop())
    }
    false
  }
  visit(root_id)
}

///|
fn simplify_locals_root_allows_moving_local_set_value_after(
  func : @ir.HotFunc,
  node_id : Int32,
  local_id : Int32,
  target_label : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return true
  }
  !simplify_locals_subtree_mentions_local(func, node_id, local_id) &&
  !simplify_locals_subtree_has_branch_to_label(func, node_id, target_label) &&
  !simplify_locals_subtree_can_exit_before_fallthrough(func, node_id)
}

///|
fn simplify_locals_collect_block_return_candidates(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  target_label : Int32,
  get_counts : Array[Int32],
) -> Array[SimplifyLocalsTailSet] {
  let candidates : Array[SimplifyLocalsTailSet] = []
  let trailing_roots : Array[Int32] = []
  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) {
      let node = @ir.hot_node_get(func, node_id)
      match node.op {
        @ir.HotOp::Nop => ()
        @ir.HotOp::LocalSet => {
          let local_id = node.imm0
          let value_id = @ir.hot_child_get(func, node_id, 0)
          let mut allowed = local_id >= 0 &&
            local_id < get_counts.length() &&
            get_counts[local_id] != 0 &&
            simplify_locals_value_is_pure(func, value_id) &&
            !simplify_locals_subtree_has_branch_to_label(
              func, value_id, target_label,
            ) &&
            !simplify_locals_subtree_has_typed_control(func, value_id)
          if allowed {
            for trailing_id in trailing_roots {
              if !simplify_locals_root_allows_moving_local_set_value_after(
                  func, trailing_id, local_id, target_label,
                ) {
                allowed = false
                break
              }
            }
          }
          if allowed {
            candidates.push({ root_idx: idx, node_id, local_id, value_id, })
          }
          trailing_roots.push(node_id)
        }
        _ => trailing_roots.push(node_id)
      }
    }
    idx = idx - 1
  }
  candidates
}

///|
fn simplify_locals_following_root_local_get_order(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  after_idx : Int32,
) -> Array[Int32] {
  let order : Array[Int32] = []
  let seen = Array::make(@ir.hot_local_count(func), false)
  for idx = after_idx + 1
      idx < @ir.hot_region_root_count(func, region_ref)
      idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::Nop => ()
      @ir.HotOp::LocalGet =>
        if node.imm0 >= 0 && node.imm0 < seen.length() && !seen[node.imm0] {
          seen[node.imm0] = true
          order.push(node.imm0)
        }
      _ => ()
    }
  }
  order
}

///|
fn simplify_locals_find_branch_exit_set_for_local(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  br_idx : Int32,
  target_label : Int32,
  local_id : Int32,
) -> SimplifyLocalsBranchExit? {
  let br_id = @ir.hot_region_get(func, region_ref, br_idx)
  let mut idx = br_idx - 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) {
      idx = idx - 1
      continue
    }
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::Nop => ()
      @ir.HotOp::LocalSet if node.imm0 == local_id => {
        let value_id = @ir.hot_child_get(func, node_id, 0)
        if !simplify_locals_value_is_pure(func, value_id) ||
          simplify_locals_subtree_has_branch_to_label(
            func, value_id, target_label,
          ) ||
          simplify_locals_subtree_has_typed_control(func, value_id) {
          return None
        }
        return Some({
          region_ref,
          set_idx: idx,
          set_id: node_id,
          br_idx,
          br_id,
          value_id,
        })
      }
      _ =>
        if !simplify_locals_root_allows_moving_local_set_value_after(
            func, node_id, local_id, target_label,
          ) {
          return None
        }
    }
    idx = idx - 1
  }
  None
}

///|
fn simplify_locals_node_is_nonfallthrough_exit(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Unreachable
    | @ir.HotOp::Br
    | @ir.HotOp::BrTable
    | @ir.HotOp::Return
    | @ir.HotOp::ReturnCall
    | @ir.HotOp::ReturnCallIndirect
    | @ir.HotOp::ReturnCallRef
    | @ir.HotOp::Throw
    | @ir.HotOp::Rethrow
    | @ir.HotOp::ThrowRef
    | @ir.HotOp::Delegate => true
    _ => false
  }
}

///|
fn simplify_locals_region_is_unreachableish(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
) -> Bool {
  match simplify_locals_region_last_live_non_nop_root_idx(func, region_ref) {
    Some(root_idx) =>
      simplify_locals_node_is_nonfallthrough_exit(
        func,
        @ir.hot_region_get(func, region_ref, root_idx),
      )
    None => false
  }
}

///|
fn simplify_locals_control_has_typed_branch_payload(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  @ir.hot_label_branch_arity(func, @ir.hot_control_node_label(func, node_id)) !=
  0
}

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

///|
fn simplify_locals_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 simplify_locals_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 simplify_locals_try_extract_stacked_forwarder_tail(
  func : @ir.HotFunc,
  candidate_node : Int32,
  rewrite_region : @ir.HotRegionRef,
  rewrite_idx : Int32,
) -> SimplifyLocalsStackedForwarderTail? {
  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(
        SimplifyLocalsStackedForwarderTail::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)) ||
        simplify_locals_branch_payload_count(func, wrapper_br) != 0 {
        return None
      }
      match @ir.hot_node_get(func, wrapper_br).op {
        @ir.HotOp::Br => ()
        _ => return None
      }
      simplify_locals_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 simplify_locals_try_rewrite_split_local_set_wrapper_forwarder(
  func : @ir.HotFunc,
  node_id : Int32,
  detached : Array[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::LocalSet | @ir.HotOp::LocalTee => ()
    _ => return false
  }
  if @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) ||
    @ir.hot_node_result_arity(func, value_id) == 0 {
    return false
  }
  match @ir.hot_node_get(func, value_id).op {
    @ir.HotOp::Block => ()
    _ => 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 {
    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) {
    return false
  }
  match @ir.hot_node_get(func, outer_wrapper).op {
    @ir.HotOp::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 {
    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) {
      return false
    }
    match @ir.hot_node_get(func, outer_br).op {
      @ir.HotOp::Br => ()
      _ => return false
    }
    if @ir.hot_branch_target(func, outer_br) !=
      Some(@ir.hot_control_node_label(func, value_id)) ||
      simplify_locals_branch_payload_count(func, outer_br) != 1 {
      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)) ||
      simplify_locals_branch_payload_count(func, outer_br) != 0 {
      return false
    }
    match @ir.hot_node_get(func, outer_br).op {
      @ir.HotOp::Br => ()
      _ => return false
    }
  }
  if payload_block < 0 ||
    !@ir.hot_node_is_live(func, payload_block) ||
    @ir.hot_node_result_arity(func, payload_block) == 0 {
    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 payload_forwarder = @ir.hot_region_get(func, payload_body, 0)
  if payload_forwarder < 0 || !@ir.hot_node_is_live(func, payload_forwarder) {
    return false
  }
  match @ir.hot_node_get(func, payload_forwarder).op {
    @ir.HotOp::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 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 simplify_locals_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 => 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 => 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 {
                      simplify_locals_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),
                      ]
                    for
                      removed_id in @ir.hot_region_splice(
                        func, payload_forwarder_body, 0, 1, replacement,
                      ) {
                      detached.push(removed_id)
                    }
                    return true
                  }
                  if try_build(else_region, then_region, true) ||
                    try_build(then_region, else_region, false) {
                    return true
                  }
                }
                _ => ()
              }
            }
          }
        _ => ()
      }
    }
  }
  if payload_forwarder_root_count < 2 {
    return false
  }
  let match_ = match
    simplify_locals_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 => 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)) {
    return false
  }
  match @ir.hot_node_get(func, inner_br).op {
    @ir.HotOp::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] = []
  let wrapper_prefix_count = if outer_wrapper_root_count == 1 {
    0
  } else {
    outer_wrapper_root_count - 2
  }
  for root_idx = 0; root_idx < wrapper_prefix_count; 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)
  for
    removed_id in @ir.hot_region_splice(
      func,
      match_.rewrite_region,
      match_.rewrite_idx,
      1,
      [@ir.hot_build_unreachable(func)],
    ) {
    detached.push(removed_id)
  }
  true
}

///|
fn simplify_locals_node_targets_label(
  func : @ir.HotFunc,
  node_id : Int32,
  label_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Continuation =>
      @ir.hot_continuation_targets(func, node_id).contains(label_id)
    @ir.HotOp::BrTable =>
      if @ir.hot_branch_table_default_target(func, node.imm0) == label_id {
        true
      } else {
        let targets = @ir.hot_branch_table_targets_for_node(func, node_id)
        for target in targets {
          if target == label_id {
            return true
          }
        }
        false
      }
    _ => @ir.hot_branch_target(func, node_id) == Some(label_id)
  }
}

///|
fn simplify_locals_is_discardable_default_gc_allocation(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.Instruction::StructNewDefault(_)) => true
    _ => false
  }
}

///|
fn simplify_locals_value_can_clone_node(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return true
  }
  // A local consumes one lane of a shared multi-result producer. Copying
  // that producer into the local's read repeats the entire operation and
  // loses the lane selected by the original stack consumption.
  if @ir.hot_node_result_arity(func, node_id) != 1 {
    return false
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Load => false
    _ => !@ir.hot_node_kind_is_control(func, node_id)
  }
}

///|
fn simplify_locals_subtree_reads_only_params(
  func : @ir.HotFunc,
  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::LocalGet => return @ir.hot_local_is_param(func, node.imm0)
    @ir.HotOp::LocalSet | @ir.HotOp::LocalTee => return false
    _ => ()
  }
  if @ir.hot_node_kind_is_control(func, node_id) {
    return false
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if simplify_locals_is_region_child_slot(func, node_id, slot) {
      return false
    }
    if !simplify_locals_subtree_reads_only_params(
        func,
        @ir.hot_child_get(func, node_id, slot),
      ) {
      return false
    }
  }
  true
}

///|
fn simplify_locals_value_can_move_one_use(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if simplify_locals_value_can_clone_node(func, node_id) {
    return true
  }
  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::Load =>
      @ir.hot_node_result_arity(func, node_id) == 1 &&
      @ir.hot_child_count(func, node_id) == 1 &&
      simplify_locals_subtree_reads_only_params(
        func,
        @ir.hot_child_get(func, node_id, 0),
      )
    @ir.HotOp::If => {
      if @ir.hot_node_result_arity(func, node_id) != 1 {
        return false
      }
      if @ir.hot_child_count(func, node_id) <= 0 {
        return false
      }
      let cond_id = @ir.hot_child_get(func, node_id, 0)
      cond_id < 0 ||
      !@ir.hot_node_is_live(func, cond_id) ||
      (
        !@ir.hot_node_kind_is_control(func, cond_id) &&
        @ir.hot_node_result_arity(func, cond_id) == 1
      )
    }
    _ => false
  }
}

///|
fn simplify_locals_value_can_tee(func : @ir.HotFunc, node_id : Int32) -> Bool {
  node_id < 0 ||
  !@ir.hot_node_is_live(func, node_id) ||
  @ir.hot_node_result_arity(func, node_id) == 1
}

///|
fn simplify_locals_try_consume_following_local_get(
  func : @ir.HotFunc,
  parent_region : @ir.HotRegionRef,
  root_idx : Int32,
  local_id : Int32,
  detached : Array[Int32],
) -> Bool {
  let next_idx = match
    simplify_locals_region_next_live_non_nop_root_idx(
      func, parent_region, root_idx,
    ) {
    Some(next_idx) => next_idx
    None => return false
  }
  let next_id = @ir.hot_region_get(func, parent_region, next_idx)
  if next_id < 0 || !@ir.hot_node_is_live(func, next_id) {
    return false
  }
  let next = @ir.hot_node_get(func, next_id)
  match next.op {
    @ir.HotOp::LocalGet if next.imm0 == local_id => {
      let removed = @ir.hot_region_splice(func, parent_region, next_idx, 1, [])
      for removed_id in removed {
        detached.push(removed_id)
      }
      true
    }
    _ => false
  }
}

///|
fn simplify_locals_inline_input_count(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Int32 {
  // A single syntactic local.get in a loop can execute many times and see
  // writes from the backedge. Region roots are not expression operands.
  // Leave them to the region scanner, which manages reaching definitions
  // and effect barriers; only inline into inputs evaluated before entry.
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Block | @ir.HotOp::Try | @ir.HotOp::TryTable => 0
    @ir.HotOp::Loop =>
      @ir.hot_label_branch_arity(
        func,
        @ir.hot_control_node_label(func, node_id),
      )
    @ir.HotOp::If => 1
    _ => @ir.hot_child_count(func, node_id)
  }
}

///|
fn simplify_locals_try_inline_local_get_child(
  func : @ir.HotFunc,
  node_id : Int32,
  local_id : Int32,
  replacement_id : Int32,
  allow_nesting : Bool,
  detached : Array[Int32],
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  let replacement_is_copy = match
    simplify_locals_direct_local_get_value(func, replacement_id) {
    Some(_) => true
    None => false
  }
  let parent_is_local_set = match node.op {
    @ir.HotOp::LocalSet => true
    _ => false
  }
  let input_count = simplify_locals_inline_input_count(func, node_id)
  for slot = 0; slot < input_count; 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
    }
    let child = @ir.hot_node_get(func, child_id)
    match child.op {
      @ir.HotOp::LocalGet if child.imm0 == local_id => {
        if !allow_nesting && !replacement_is_copy && !parent_is_local_set {
          return false
        }
        @ir.hot_child_set(func, node_id, slot, replacement_id)
        detached.push(child_id)
        return true
      }
      _ =>
        if simplify_locals_try_inline_local_get_child(
            func, child_id, local_id, replacement_id, allow_nesting, detached,
          ) {
          return true
        }
    }
  }
  false
}

///|
fn simplify_locals_try_inline_leading_local_get_child(
  func : @ir.HotFunc,
  node_id : Int32,
  local_id : Int32,
  replacement_id : Int32,
  allow_nesting : Bool,
  detached : Array[Int32],
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  let replacement_is_copy = match
    simplify_locals_direct_local_get_value(func, replacement_id) {
    Some(_) => true
    None => false
  }
  let parent_is_local_set = match node.op {
    @ir.HotOp::LocalSet => true
    _ => false
  }
  let input_count = simplify_locals_inline_input_count(func, node_id)
  for slot = 0; slot < input_count; 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
    }
    let child = @ir.hot_node_get(func, child_id)
    match child.op {
      @ir.HotOp::Nop => continue
      @ir.HotOp::LocalGet if child.imm0 == local_id => {
        if !allow_nesting && !replacement_is_copy && !parent_is_local_set {
          return false
        }
        @ir.hot_child_set(func, node_id, slot, replacement_id)
        detached.push(child_id)
        return true
      }
      _ =>
        return simplify_locals_try_inline_leading_local_get_child(
          func, child_id, local_id, replacement_id, allow_nesting, detached,
        )
    }
  }
  false
}

///|
fn simplify_locals_try_move_control_to_leading_local_get_child(
  func : @ir.HotFunc,
  node_id : Int32,
  local_id : Int32,
  replacement_id : Int32,
  detached : Array[Int32],
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let input_count = simplify_locals_inline_input_count(func, node_id)
  for slot = 0; slot < input_count; 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
    }
    let child = @ir.hot_node_get(func, child_id)
    match child.op {
      @ir.HotOp::Nop => continue
      @ir.HotOp::LocalGet if child.imm0 == local_id => {
        let label_id = @ir.hot_control_node_label(func, replacement_id)
        @ir.hot_replace_node(
          func,
          child_id,
          @ir.hot_node_get(func, replacement_id),
        )
        @ir.hot_label_owner_set(func, label_id, child_id)
        simplify_locals_queue_rehomed_wrapper_for_deletion(
          func, replacement_id, detached,
        )
        return true
      }
      _ =>
        return simplify_locals_try_move_control_to_leading_local_get_child(
          func, child_id, local_id, replacement_id, detached,
        )
    }
  }
  false
}

///|
fn simplify_locals_try_inline_following_local_get(
  func : @ir.HotFunc,
  parent_region : @ir.HotRegionRef,
  root_idx : Int32,
  local_id : Int32,
  replacement_id : Int32,
  allow_nesting : Bool,
  detached : Array[Int32],
) -> Bool {
  if replacement_id < 0 ||
    !@ir.hot_node_is_live(func, replacement_id) ||
    @ir.hot_node_result_arity(func, replacement_id) != 1 {
    return false
  }
  let next_idx = match
    simplify_locals_region_next_live_non_nop_root_idx(
      func, parent_region, root_idx,
    ) {
    Some(next_idx) => next_idx
    None => return false
  }
  let next_id = @ir.hot_region_get(func, parent_region, next_idx)
  let replacement_is_control = replacement_id >= 0 &&
    @ir.hot_node_is_live(func, replacement_id) &&
    @ir.hot_node_kind_is_control(func, replacement_id)
  if replacement_is_control &&
    next_id >= 0 &&
    @ir.hot_node_is_live(func, next_id) {
    let next = @ir.hot_node_get(func, next_id)
    match next.op {
      @ir.HotOp::LocalGet if next.imm0 == local_id => {
        let label_id = @ir.hot_control_node_label(func, replacement_id)
        @ir.hot_replace_node(
          func,
          next_id,
          @ir.hot_node_get(func, replacement_id),
        )
        @ir.hot_label_owner_set(func, label_id, next_id)
        simplify_locals_queue_rehomed_wrapper_for_deletion(
          func, replacement_id, detached,
        )
        return true
      }
      _ =>
        return simplify_locals_try_move_control_to_leading_local_get_child(
          func, next_id, local_id, replacement_id, detached,
        )
    }
  }
  if !replacement_is_control &&
    next_id >= 0 &&
    @ir.hot_node_is_live(func, next_id) &&
    replacement_id >= 0 &&
    @ir.hot_node_is_live(func, replacement_id) &&
    @ir.hot_node_result_arity(func, replacement_id) == 1 {
    let next = @ir.hot_node_get(func, next_id)
    match next.op {
      @ir.HotOp::LocalGet if next.imm0 == local_id => {
        if !allow_nesting &&
          simplify_locals_direct_local_get_value(func, replacement_id) == None {
          return false
        }
        let removed = @ir.hot_region_splice(func, parent_region, next_idx, 1, [
          replacement_id,
        ])
        for removed_id in removed {
          detached.push(removed_id)
        }
        return true
      }
      _ => ()
    }
  }
  if simplify_locals_value_is_child_inline_safe(func, replacement_id) {
    return simplify_locals_try_inline_local_get_child(
      func, next_id, local_id, replacement_id, allow_nesting, detached,
    )
  }
  let replacement_is_copy = match
    simplify_locals_direct_local_get_value(func, replacement_id) {
    Some(_) => true
    None => false
  }
  if next_id >= 0 && @ir.hot_node_is_live(func, next_id) {
    let next = @ir.hot_node_get(func, next_id)
    match next.op {
      @ir.HotOp::LocalGet if next.imm0 == local_id &&
        replacement_id >= 0 &&
        @ir.hot_node_is_live(func, replacement_id) &&
        @ir.hot_node_result_arity(func, replacement_id) == 1 => {
        if !allow_nesting && !replacement_is_copy {
          return false
        }
        @ir.hot_replace_node(
          func,
          next_id,
          @ir.hot_node_get(func, replacement_id),
        )
        detached.push(replacement_id)
        return true
      }
      _ => ()
    }
  }
  simplify_locals_try_inline_leading_local_get_child(
    func, next_id, local_id, replacement_id, allow_nesting, detached,
  )
}

///|
fn simplify_locals_count_local_gets(func : @ir.HotFunc) -> Array[Int32] {
  let counts = Array::make(@ir.hot_local_count(func), 0)
  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
    }
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::LocalGet =>
        if node.imm0 >= 0 && node.imm0 < counts.length() {
          counts[node.imm0] = counts[node.imm0] + 1
        }
      _ => ()
    }
  }
  counts
}

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

///|
fn simplify_locals_discount_unreachable_root_tail_gets(
  func : @ir.HotFunc,
  counts : Array[Int32],
) -> Unit {
  let root = @ir.HotRegionRef::root()
  let mut terminated = false
  for idx = 0; idx < @ir.hot_region_root_count(func, root); idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, root, idx)
    if terminated {
      simplify_locals_discount_subtree_local_gets(func, node_id, counts)
    } else if simplify_locals_node_is_nonfallthrough_exit(func, node_id) {
      terminated = true
    }
  }
}

///|
fn simplify_locals_value_is_pure(func : @ir.HotFunc, 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)
  if (
      simplify_locals_effects_mask_for_exact_node(func, node_id) &
      (
        @ir.EFFECT_MASK_TRAP |
        @ir.EFFECT_MASK_MEMORY_WRITE |
        @ir.EFFECT_MASK_TABLE_WRITE
      )
    ) !=
    0U {
    return false
  }
  let op_is_pure = match node.op {
    @ir.HotOp::Nop
    | @ir.HotOp::Const
    | @ir.HotOp::LocalGet
    | @ir.HotOp::GlobalGet
    | @ir.HotOp::Drop
    | @ir.HotOp::Select
    | @ir.HotOp::RefNull
    | @ir.HotOp::RefIsNull
    | @ir.HotOp::RefFunc
    | @ir.HotOp::RefTest
    | @ir.HotOp::RefTestDesc
    | @ir.HotOp::Unary
    | @ir.HotOp::Binary
    | @ir.HotOp::Compare
    | @ir.HotOp::TupleMake
    | @ir.HotOp::TupleExtract
    | @ir.HotOp::StringOp
    | @ir.HotOp::MemorySize
    | @ir.HotOp::TableSize
    | @ir.HotOp::Simd => true
    _ => false
  }
  if !op_is_pure {
    return false
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    let child_id = @ir.hot_child_get(func, node_id, slot)
    if child_id >= 0 &&
      @ir.hot_node_is_live(func, child_id) &&
      !simplify_locals_value_is_pure(func, child_id) {
      return false
    }
  }
  true
}

///|
fn simplify_locals_value_is_child_inline_safe(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return true
  }
  if simplify_locals_effects_has(
      simplify_locals_effects_mask_for_subtree(func, node_id),
      @ir.EFFECT_MASK_GLOBAL_STATE,
    ) {
    return false
  }
  if simplify_locals_value_is_pure(func, node_id) {
    return true
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Block => {
      let body_ref = @ir.hot_region_body_ref(func, node_id)
      for idx = 0
          idx < @ir.hot_region_root_count(func, body_ref)
          idx = idx + 1 {
        if !simplify_locals_value_is_child_inline_safe(
            func,
            @ir.hot_region_get(func, body_ref, idx),
          ) {
          return false
        }
      }
      true
    }
    @ir.HotOp::If => {
      if !simplify_locals_value_is_child_inline_safe(
          func,
          @ir.hot_child_get(func, node_id, 0),
        ) {
        return false
      }
      let then_ref = @ir.hot_region_then_ref(func, node_id)
      for idx = 0
          idx < @ir.hot_region_root_count(func, then_ref)
          idx = idx + 1 {
        if !simplify_locals_value_is_child_inline_safe(
            func,
            @ir.hot_region_get(func, then_ref, idx),
          ) {
          return false
        }
      }
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_ref) =>
          for idx = 0
              idx < @ir.hot_region_root_count(func, else_ref)
              idx = idx + 1 {
            if !simplify_locals_value_is_child_inline_safe(
                func,
                @ir.hot_region_get(func, else_ref, idx),
              ) {
              return false
            }
          }
        None => ()
      }
      true
    }
    _ => false
  }
}

///|
fn simplify_locals_mark_reachable(
  func : @ir.HotFunc,
  node_id : Int32,
  reachable : Array[Bool],
) -> Unit {
  if node_id < 0 ||
    node_id >= reachable.length() ||
    !@ir.hot_node_is_live(func, node_id) ||
    reachable[node_id] {
    return
  }
  reachable[node_id] = true
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    simplify_locals_mark_reachable(
      func,
      @ir.hot_child_get(func, node_id, slot),
      reachable,
    )
  }
}

///|
fn simplify_locals_build_reachable_nodes(func : @ir.HotFunc) -> Array[Bool] {
  let reachable = Array::make(@ir.hot_node_count(func), false)
  for root_idx = 0; root_idx < @ir.hot_root_count(func); root_idx = root_idx + 1 {
    simplify_locals_mark_reachable(
      func,
      @ir.hot_root_get(func, root_idx),
      reachable,
    )
  }
  reachable
}

///|
fn simplify_locals_build_label_owner_nodes(func : @ir.HotFunc) -> Array[Bool] {
  let label_owners = Array::make(@ir.hot_node_count(func), false)
  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 >= 0 &&
      owner < label_owners.length() &&
      @ir.hot_node_is_live(func, owner) {
      label_owners[owner] = true
    }
  }
  label_owners
}

///|
priv struct SimplifyLocalsDetachedControlOwner {
  replacement_id : Int32
  detached_id : Int32
}

///|
fn simplify_locals_rehome_detached_control_owners(
  func : @ir.HotFunc,
  owners : Array[SimplifyLocalsDetachedControlOwner],
) -> Unit {
  for owner in owners {
    if owner.replacement_id < 0 ||
      !@ir.hot_node_is_live(func, owner.replacement_id) {
      continue
    }
    if owner.detached_id < 0 || !@ir.hot_node_is_live(func, owner.detached_id) {
      continue
    }
    let detached = @ir.hot_node_get(func, owner.detached_id)
    let label_id = match detached.op {
      @ir.HotOp::Block
      | @ir.HotOp::Loop
      | @ir.HotOp::If
      | @ir.HotOp::Try
      | @ir.HotOp::TryTable =>
        @ir.hot_control_node_label(func, owner.detached_id)
      _ => continue
    }
    @ir.hot_label_owner_set(func, label_id, owner.replacement_id)
  }
}

///|
fn simplify_locals_queue_rehomed_wrapper_for_deletion(
  func : @ir.HotFunc,
  wrapper_id : Int32,
  detached : Array[Int32],
) -> Unit {
  if wrapper_id < 0 || !@ir.hot_node_is_live(func, wrapper_id) {
    return
  }
  // The wrapper's value has already been installed at a new owner. Sever the
  // old child edge before recursive detached-node deletion so that deleting the
  // local.set/local.tee cannot also delete the rehomed value subtree.
  @ir.hot_replace_node(func, wrapper_id, simplify_locals_make_nop(func))
  detached.push(wrapper_id)
}

///|
fn simplify_locals_delete_detached_nodes(
  func : @ir.HotFunc,
  node_ids : Array[Int32],
) -> Unit {
  if node_ids.is_empty() {
    return
  }
  let reachable = simplify_locals_build_reachable_nodes(func)
  let label_owners = simplify_locals_build_label_owner_nodes(func)
  let seen = Array::make(@ir.hot_node_count(func), false)
  for node_id in node_ids {
    if node_id < 0 || node_id >= reachable.length() || seen[node_id] {
      continue
    }
    seen[node_id] = true
    if !@ir.hot_node_is_live(func, node_id) ||
      reachable[node_id] ||
      label_owners[node_id] {
      continue
    }
    @ir.hot_delete_detached_node(func, node_id)
  }
}

///|
fn simplify_locals_record_local_set(
  func : @ir.HotFunc,
  node_id : Int32,
  node : @ir.HotNode,
  sinkables : SimplifyLocalsSinkables,
  first_cycle : Bool,
  allow_sink_tee : Bool,
  get_counts : Array[Int32],
  deferred_sets : Array[Int32],
) -> Bool {
  let local_id = node.imm0
  let mut changed = false
  match simplify_locals_sinkables_get(sinkables, local_id) {
    Some(previous_info) =>
      // A lifted block can revisit its existing pending writes. Replacing a
      // write with a drop at that point removes the current definition too.
      if previous_info.node_id != node_id &&
        @ir.hot_node_is_live(func, previous_info.node_id) {
        let previous_node = @ir.hot_node_get(func, previous_info.node_id)
        match previous_node.op {
          @ir.HotOp::LocalSet => {
            let previous_value_id = @ir.hot_child_get(
              func,
              previous_info.node_id,
              0,
            )
            // A later root can consume a read evaluated between these writes.
            // Keep its reaching definition even though tree order has not yet
            // visited the consumer containing that stacked read.
            if !simplify_locals_has_earlier_source_read(
                func,
                sinkables,
                local_id,
                previous_node.order,
                node.order,
              ) {
              @ir.hot_replace_node(
                func,
                previous_info.node_id,
                simplify_locals_make_drop(func, previous_value_id),
              )
              changed = true
            }
          }
          _ => ()
        }
      }
    None => ()
  }
  simplify_locals_sinkables_clear_local(sinkables, local_id)
  if (first_cycle || !allow_sink_tee) && get_counts[local_id] > 1 {
    deferred_sets.push(node_id)
    return changed
  }
  simplify_locals_sinkables_set(sinkables, local_id, {
    node_id,
    effects: simplify_locals_effects_for_pending_local_set(
      func, node_id, node, sinkables,
    ),
  })
  changed
}

///|
fn simplify_locals_source_order_inputs(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Array[Int32] {
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Block
    | @ir.HotOp::Try
    | @ir.HotOp::TryTable
    | @ir.HotOp::Nop
    | @ir.HotOp::Unreachable
    | @ir.HotOp::Const
    | @ir.HotOp::RefNull
    | @ir.HotOp::LocalGet
    | @ir.HotOp::GlobalGet
    | @ir.HotOp::MemorySize
    | @ir.HotOp::TableSize
    | @ir.HotOp::DataDrop
    | @ir.HotOp::ElemDrop
    | @ir.HotOp::RefFunc => []
    @ir.HotOp::Loop => {
      let out : Array[Int32] = []
      let input_count = @ir.hot_label_branch_arity(
        func,
        @ir.hot_control_node_label(func, node_id),
      )
      for idx = 0; idx < input_count; idx = idx + 1 {
        out.push(@ir.hot_child_get(func, node_id, idx))
      }
      out
    }
    @ir.HotOp::If => [@ir.hot_child_get(func, node_id, 0)]
    _ => {
      let out : Array[Int32] = []
      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 {
          out.push(child_id)
        }
      }
      out
    }
  }
}

///|
fn simplify_locals_collect_preceding_call_dependency(
  func : @ir.HotFunc,
  node_id : Int32,
  before_node_id : Int32,
  seen : Array[Bool],
  out : Array[Int32],
) -> Unit {
  if node_id < 0 ||
    node_id >= seen.length() ||
    seen[node_id] ||
    !@ir.hot_node_is_live(func, node_id) {
    return
  }
  seen[node_id] = true
  if node_id < before_node_id &&
    @ir.hot_node_result_arity(func, node_id) > 0 &&
    !@ir.effects_is_pure(@ir.effects_for_node(func, node_id)) {
    out.push(node_id)
    return
  }
  for input_id in simplify_locals_source_order_inputs(func, node_id) {
    simplify_locals_collect_preceding_call_dependency(
      func, input_id, before_node_id, seen, out,
    )
  }
}

///|
fn simplify_locals_source_input_min_node_id(
  func : @ir.HotFunc,
  node_id : Int32,
  memo : Array[Int32],
  sentinel : Int32,
) -> Int32 {
  if node_id < 0 ||
    node_id >= memo.length() ||
    !@ir.hot_node_is_live(func, node_id) {
    return sentinel
  }
  if memo[node_id] >= 0 {
    return memo[node_id]
  }
  let mut min_id = node_id
  // Store a conservative bound before descending in case the graph shares a
  // node through more than one input path.
  memo[node_id] = node_id
  for input_id in simplify_locals_source_order_inputs(func, node_id) {
    let candidate = simplify_locals_source_input_min_node_id(
      func, input_id, memo, sentinel,
    )
    if candidate < min_id {
      min_id = candidate
    }
  }
  memo[node_id] = min_id
  min_id
}

///|
fn simplify_locals_future_input_suffix_min_node_ids(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
) -> Array[Int32] {
  let root_count = @ir.hot_region_root_count(func, region_ref)
  let sentinel = @ir.hot_node_count(func)
  let memo = Array::make(sentinel, -1)
  let suffix_mins = Array::make(root_count + 1, sentinel)
  let mut idx = root_count
  while idx > 0 {
    idx -= 1
    let root_id = @ir.hot_region_get(func, region_ref, idx)
    let mut min_id = suffix_mins[idx + 1]
    if root_id >= 0 && @ir.hot_node_is_live(func, root_id) {
      for input_id in simplify_locals_source_order_inputs(func, root_id) {
        let candidate = simplify_locals_source_input_min_node_id(
          func, input_id, memo, sentinel,
        )
        if candidate < min_id {
          min_id = candidate
        }
      }
    }
    suffix_mins[idx] = min_id
  }
  suffix_mins
}

///|
fn simplify_locals_preceding_future_call_dependencies(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  root_idx : Int32,
  current_root : Int32,
  future_input_suffix_mins~ : Array[Int32]? = None,
  future_roots_scanned~ : Array[Int32]? = None,
) -> Array[Int32] {
  match @ir.hot_node_get(func, current_root).op {
    @ir.HotOp::Call | @ir.HotOp::CallIndirect | @ir.HotOp::CallRef => ()
    _ => return []
  }
  if @ir.hot_node_result_arity(func, current_root) != 0 {
    return []
  }
  match future_input_suffix_mins {
    Some(mins) if root_idx + 1 < mins.length() &&
      mins[root_idx + 1] >= current_root => return []
    _ => ()
  }
  let seen = Array::make(@ir.hot_node_count(func), false)
  let out : Array[Int32] = []
  for idx = root_idx + 1
      idx < @ir.hot_region_root_count(func, region_ref)
      idx = idx + 1 {
    match future_roots_scanned {
      Some(scanned) => scanned[0] += 1
      None => ()
    }
    let future_root = @ir.hot_region_get(func, region_ref, idx)
    for input_id in simplify_locals_source_order_inputs(func, future_root) {
      simplify_locals_collect_preceding_call_dependency(
        func, input_id, current_root, seen, out,
      )
    }
  }
  out.sort_by(fn(left, right) { left.compare(right) })
  out
}

///|
fn simplify_locals_scan_region(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  sinkables : SimplifyLocalsSinkables,
  first_cycle : Bool,
  get_counts : Array[Int32],
  detached : Array[Int32],
  policy : SimplifyLocalsPolicy,
  protect_control_embedded_local_tee : Bool,
  inside_control : Bool,
  deferred_sets : Array[Int32],
  future_roots_scanned~ : Array[Int32]? = None,
) -> Bool {
  if protect_control_embedded_local_tee &&
    inside_control &&
    simplify_locals_region_has_control_embedded_local_tee(
      func, region_ref, true,
    ) {
    return false
  }
  let capture_depth = sinkables.captures.touched.length()
  let mut changed = false
  let mut previous_root = -1
  let mut lane = 0
  let initial_revision = @ir.hot_revision_current(func)
  let mut future_input_suffix_mins : Array[Int32]? = None
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    if @ir.hot_region_root_count(func, region_ref) >= 16 &&
      @ir.hot_revision_current(func) == initial_revision &&
      @ir.hot_node_result_arity(func, node_id) == 0 {
      match @ir.hot_node_get(func, node_id).op {
        @ir.HotOp::Call | @ir.HotOp::CallIndirect | @ir.HotOp::CallRef =>
          match future_input_suffix_mins {
            None =>
              future_input_suffix_mins = Some(
                simplify_locals_future_input_suffix_min_node_ids(
                  func, region_ref,
                ),
              )
            Some(_) => ()
          }
        _ => ()
      }
    }
    let current_suffix_mins = if @ir.hot_revision_current(func) ==
      initial_revision {
      future_input_suffix_mins
    } else {
      None
    }
    for
      dependency_id in simplify_locals_preceding_future_call_dependencies(
        func,
        region_ref,
        idx,
        node_id,
        future_input_suffix_mins=current_suffix_mins,
        future_roots_scanned~,
      ) {
      if simplify_locals_scan_node(
          func,
          dependency_id,
          sinkables,
          first_cycle,
          get_counts,
          detached,
          policy,
          None,
          -1,
          protect_control_embedded_local_tee,
          inside_control,
          false,
          deferred_sets,
        ) {
        changed = true
      }
    }
    let flat_parent_is_local_set = if idx + 1 <
      @ir.hot_region_root_count(func, region_ref) {
      let next_id = @ir.hot_region_get(func, region_ref, idx + 1)
      if next_id >= 0 && @ir.hot_node_is_live(func, next_id) {
        match @ir.hot_node_get(func, next_id).op {
          @ir.HotOp::LocalSet => true
          _ => false
        }
      } else {
        false
      }
    } else {
      false
    }
    let arity = @ir.hot_node_result_arity(func, node_id)
    lane = if previous_root == node_id { lane + 1 } else { 0 }
    let force_eval = arity == 0 ||
      (
        sinkables.captures.is_region_capture(node_id, capture_depth) &&
        (lane == 0 || lane % arity == 0)
      )
    previous_root = node_id
    if simplify_locals_scan_node(
        func,
        node_id,
        sinkables,
        first_cycle,
        get_counts,
        detached,
        policy,
        Some(region_ref),
        idx,
        protect_control_embedded_local_tee,
        inside_control,
        flat_parent_is_local_set,
        deferred_sets,
        force_eval~,
      ) {
      changed = true
    }
    sinkables.captures.mark(node_id)
  }
  sinkables.captures.rewind(capture_depth)
  changed
}

///|
fn simplify_locals_node_has_any_branch(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Br
    | @ir.HotOp::BrIf
    | @ir.HotOp::BrTable
    | @ir.HotOp::BrOnNull
    | @ir.HotOp::BrOnNonNull
    | @ir.HotOp::BrOnCast
    | @ir.HotOp::BrOnCastFail
    | @ir.HotOp::Delegate => return true
    // An on-label handler can leave the current linear fallthrough before a
    // later write, so facts learned after the resume do not dominate the exit.
    @ir.HotOp::Continuation =>
      if !@ir.hot_continuation_targets(func, node_id).is_empty() {
        return true
      }
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      if simplify_locals_region_has_any_branch(
          func,
          @ir.hot_region_body_ref(func, node_id),
        ) {
        return true
      }
    @ir.HotOp::If => {
      if simplify_locals_node_has_any_branch(
          func,
          @ir.hot_child_get(func, node_id, 0),
        ) ||
        simplify_locals_region_has_any_branch(
          func,
          @ir.hot_region_then_ref(func, node_id),
        ) {
        return true
      }
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_ref) =>
          if simplify_locals_region_has_any_branch(func, else_ref) {
            return true
          }
        None => ()
      }
    }
    @ir.HotOp::Try => {
      if simplify_locals_region_has_any_branch(
          func,
          @ir.hot_region_body_ref(func, node_id),
        ) {
        return true
      }
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_ref) =>
          if simplify_locals_region_has_any_branch(func, catch_ref) {
            return true
          }
        None => ()
      }
    }
    @ir.HotOp::TryTable =>
      if simplify_locals_region_has_any_branch(
          func,
          @ir.hot_region_body_ref(func, node_id),
        ) ||
        simplify_locals_region_has_any_branch(
          func,
          @ir.hot_region_catch_list_ref(func, node_id),
        ) {
        return true
      }
    _ =>
      for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
        if simplify_locals_node_has_any_branch(
            func,
            @ir.hot_child_get(func, node_id, slot),
          ) {
          return true
        }
      }
  }
  false
}

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

///|
fn simplify_locals_scan_node(
  func : @ir.HotFunc,
  node_id : Int32,
  sinkables : SimplifyLocalsSinkables,
  first_cycle : Bool,
  get_counts : Array[Int32],
  detached : Array[Int32],
  policy : SimplifyLocalsPolicy,
  parent_region : @ir.HotRegionRef?,
  root_idx : Int32,
  protect_control_embedded_local_tee : Bool,
  inside_control : Bool,
  flat_sink_parent_is_local_set : Bool,
  deferred_sets : Array[Int32],
  force_eval~ : Bool = false,
) -> Bool {
  if !force_eval && sinkables.captures.contains(node_id) {
    return false
  }
  let capture_depth = sinkables.captures.touched.length()
  let changed = simplify_locals_scan_node_uncaptured(
    func, node_id, sinkables, first_cycle, get_counts, detached, policy, parent_region,
    root_idx, protect_control_embedded_local_tee, inside_control, flat_sink_parent_is_local_set,
    deferred_sets,
  )
  sinkables.captures.rewind(capture_depth)
  changed
}

///|
fn simplify_locals_scan_node_uncaptured(
  func : @ir.HotFunc,
  node_id : Int32,
  sinkables : SimplifyLocalsSinkables,
  first_cycle : Bool,
  get_counts : Array[Int32],
  detached : Array[Int32],
  policy : SimplifyLocalsPolicy,
  parent_region : @ir.HotRegionRef?,
  root_idx : Int32,
  protect_control_embedded_local_tee : Bool,
  inside_control : Bool,
  flat_sink_parent_is_local_set : Bool,
  deferred_sets : Array[Int32],
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  if protect_control_embedded_local_tee &&
    inside_control &&
    simplify_locals_node_has_control_embedded_local_tee(func, node_id, true) {
    return false
  }
  let mut changed = false
  let node = @ir.hot_node_get(func, node_id)
  let mut previous_input = -1
  let mut lane = 0
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop | @ir.HotOp::If =>
      for index = 0
          index < pass_value_operand_count(func, node_id)
          index = index + 1 {
        let slot = pass_value_operand_slot(node, index)
        let input = @ir.hot_child_get(func, node_id, slot)
        let arity = @ir.hot_node_result_arity(func, input)
        lane = if previous_input == input { lane + 1 } else { 0 }
        let force_eval = arity == 0 || (lane > 0 && lane % arity == 0)
        previous_input = input
        if simplify_locals_scan_node(
            func,
            input,
            sinkables,
            first_cycle,
            get_counts,
            detached,
            policy,
            None,
            -1,
            protect_control_embedded_local_tee,
            inside_control,
            false,
            deferred_sets,
            force_eval~,
          ) {
          changed = true
        }
        if !(node.op is @ir.HotOp::If && slot == 0) {
          sinkables.captures.mark(input)
        }
      }
    _ => ()
  }
  match node.op {
    @ir.HotOp::Block => {
      let body_ref = @ir.hot_region_body_ref(func, node_id)
      if simplify_locals_scan_region(
          func, body_ref, sinkables, first_cycle, get_counts, detached, policy, protect_control_embedded_local_tee,
          true, deferred_sets,
        ) {
        changed = true
      }
      let keep_sinkables = !simplify_locals_region_has_any_branch(
          func, body_ref,
        ) &&
        !(protect_control_embedded_local_tee &&
        simplify_locals_region_has_control_embedded_local_tee(
          func, body_ref, true,
        ))
      if !keep_sinkables {
        simplify_locals_clear_sinkables(sinkables)
      }
      if policy.allow_structure_rewrites {
        match parent_region {
          Some(parent_region) =>
            if simplify_locals_try_rewrite_block_return(
                func,
                parent_region,
                root_idx,
                node_id,
                get_counts,
                detached,
                allow_multi_use=policy.allow_sink_tee,
              ) {
              return true
            }
          None => ()
        }
      }
      return changed
    }
    @ir.HotOp::Loop => {
      let loop_sinkables = simplify_locals_new_sinkables(
        func,
        source_gets=Some(sinkables.source_gets),
        captures=Some(sinkables.captures),
        module_ctx=sinkables.module_ctx,
      )
      if simplify_locals_scan_region(
          func,
          @ir.hot_region_body_ref(func, node_id),
          loop_sinkables,
          first_cycle,
          get_counts,
          detached,
          policy,
          protect_control_embedded_local_tee,
          true,
          deferred_sets,
        ) {
        changed = true
      }
      simplify_locals_clear_sinkables(sinkables)
      if policy.allow_structure_rewrites {
        match parent_region {
          Some(parent_region) =>
            if simplify_locals_try_rewrite_loop_return(
                func,
                parent_region,
                root_idx,
                node_id,
                get_counts,
                detached,
                allow_multi_use=policy.allow_sink_tee,
              ) {
              return true
            }
          None => ()
        }
      }
      return changed
    }
    @ir.HotOp::If => {
      simplify_locals_clear_sinkables(sinkables)
      let then_sinkables = simplify_locals_new_sinkables(
        func,
        source_gets=Some(sinkables.source_gets),
        captures=Some(sinkables.captures),
        module_ctx=sinkables.module_ctx,
      )
      if simplify_locals_scan_region(
          func,
          @ir.hot_region_then_ref(func, node_id),
          then_sinkables,
          first_cycle,
          get_counts,
          detached,
          policy,
          protect_control_embedded_local_tee,
          true,
          deferred_sets,
        ) {
        changed = true
      }
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_ref) => {
          let else_sinkables = simplify_locals_new_sinkables(
            func,
            source_gets=Some(sinkables.source_gets),
            captures=Some(sinkables.captures),
            module_ctx=sinkables.module_ctx,
          )
          if simplify_locals_scan_region(
              func, else_ref, else_sinkables, first_cycle, get_counts, detached,
              policy, protect_control_embedded_local_tee, true, deferred_sets,
            ) {
            changed = true
          }
        }
        None => ()
      }
      simplify_locals_clear_sinkables(sinkables)
      if policy.allow_structure_rewrites {
        match parent_region {
          Some(parent_region) =>
            if simplify_locals_try_rewrite_if_return(
                func,
                parent_region,
                root_idx,
                node_id,
                get_counts,
                detached,
                allow_multi_use=policy.allow_sink_tee,
              ) {
              return true
            }
          None => ()
        }
      }
      return changed
    }
    @ir.HotOp::Try => {
      // Binaryen only drops pending sinkables whose values may throw at a
      // try boundary: moving those into the body would put the throw under a
      // handler that previously could not catch it. Nonthrowing values may
      // still sink into the body, but no pending value is kept after the try.
      simplify_locals_clear_throwing_sinkables(sinkables)
      if simplify_locals_scan_region(
          func,
          @ir.hot_region_body_ref(func, node_id),
          sinkables,
          first_cycle,
          get_counts,
          detached,
          policy,
          protect_control_embedded_local_tee,
          true,
          deferred_sets,
        ) {
        changed = true
      }
      simplify_locals_clear_sinkables(sinkables)
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_ref) => {
          let catch_sinkables = simplify_locals_new_sinkables(
            func,
            source_gets=Some(sinkables.source_gets),
            captures=Some(sinkables.captures),
            module_ctx=sinkables.module_ctx,
          )
          if simplify_locals_scan_region(
              func, catch_ref, catch_sinkables, first_cycle, get_counts, detached,
              policy, protect_control_embedded_local_tee, true, deferred_sets,
            ) {
            changed = true
          }
        }
        None => ()
      }
      return changed
    }
    @ir.HotOp::TryTable => {
      // Match the try boundary above for the try_table body. Catch-list roots
      // are branch-target handlers, so they are still scanned with a fresh
      // sinkable set.
      simplify_locals_clear_throwing_sinkables(sinkables)
      if simplify_locals_scan_region(
          func,
          @ir.hot_region_body_ref(func, node_id),
          sinkables,
          first_cycle,
          get_counts,
          detached,
          policy,
          protect_control_embedded_local_tee,
          true,
          deferred_sets,
        ) {
        changed = true
      }
      simplify_locals_clear_sinkables(sinkables)
      let catch_sinkables = simplify_locals_new_sinkables(
        func,
        source_gets=Some(sinkables.source_gets),
        captures=Some(sinkables.captures),
        module_ctx=sinkables.module_ctx,
      )
      if simplify_locals_scan_region(
          func,
          @ir.hot_region_catch_list_ref(func, node_id),
          catch_sinkables,
          first_cycle,
          get_counts,
          detached,
          policy,
          protect_control_embedded_local_tee,
          true,
          deferred_sets,
        ) {
        changed = true
      }
      return changed
    }
    _ =>
      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)
        let scan_child_id = match node.op {
          @ir.HotOp::Drop if slot == 0 &&
            child_id >= 0 &&
            @ir.hot_node_is_live(func, child_id) =>
            match @ir.hot_node_get(func, child_id).op {
              @ir.HotOp::LocalTee => {
                let root_drop_tee = root_idx >= 0 &&
                  root_idx < @ir.hot_root_count(func) &&
                  node_id == @ir.hot_root_get(func, root_idx)
                let tee_value = @ir.hot_child_get(func, child_id, 0)
                if root_drop_tee &&
                  tee_value >= 0 &&
                  simplify_locals_child_use_count_capped_at_two(func, tee_value) ==
                  1 {
                  tee_value
                } else {
                  -1
                }
              }
              _ => child_id
            }
          _ => child_id
        }
        if scan_child_id < 0 || !@ir.hot_node_is_live(func, scan_child_id) {
          continue
        }
        if simplify_locals_scan_node(
            func,
            scan_child_id,
            sinkables,
            first_cycle,
            get_counts,
            detached,
            policy,
            None,
            -1,
            protect_control_embedded_local_tee,
            inside_control,
            match node.op {
              @ir.HotOp::LocalSet => true
              _ => false
            },
            deferred_sets,
          ) {
          changed = true
        }
      }
  }

  let mut current = @ir.hot_node_get(func, node_id)
  match current.op {
    @ir.HotOp::LocalGet => {
      let local_id = current.imm0
      match simplify_locals_sinkables_get(sinkables, local_id) {
        Some(info) =>
          if @ir.hot_node_is_live(func, info.node_id) {
            let pending_node = @ir.hot_node_get(func, info.node_id)
            match pending_node.op {
              @ir.HotOp::LocalSet => {
                // Tree visitation can reach an old stacked read after a later
                // block write. That read cannot consume the pending definition.
                // Replacing an existing write can give it a later factory
                // allocation order without moving its execution position.
                let pending_order = if pending_node.order < info.node_id {
                  pending_node.order
                } else {
                  info.node_id
                }
                let read_order = if current.order < node_id {
                  current.order
                } else {
                  node_id
                }
                if read_order < pending_order {
                  simplify_locals_sinkables_clear_local(sinkables, local_id)
                  simplify_locals_invalidate_sinkables(
                    func, node_id, current, sinkables,
                  )
                  return changed
                }
                // A previous cycle can place an older read in a later root's
                // operand tree. Do not sink its definition into the first read
                // visited by tree order if that would cross the older read.
                if get_counts[local_id] > 1 &&
                  simplify_locals_has_earlier_source_read(
                    func,
                    sinkables,
                    local_id,
                    pending_node.order,
                    current.order,
                  ) {
                  simplify_locals_sinkables_clear_local(sinkables, local_id)
                  simplify_locals_invalidate_sinkables(
                    func, node_id, current, sinkables,
                  )
                  return changed
                }
                let value_id = @ir.hot_child_get(func, info.node_id, 0)
                let one_use = first_cycle || get_counts[local_id] == 1
                let pending_copy_local = simplify_locals_direct_local_get_value(
                  func, value_id,
                )
                if !policy.allow_nesting &&
                  pending_copy_local == None &&
                  !flat_sink_parent_is_local_set {
                  simplify_locals_sinkables_clear_local(sinkables, local_id)
                  simplify_locals_invalidate_sinkables(
                    func, node_id, current, sinkables,
                  )
                  return changed
                }
                if !policy.allow_nesting && !one_use {
                  match pending_copy_local {
                    Some(source_local) => {
                      @ir.hot_replace_node(
                        func,
                        node_id,
                        simplify_locals_make_local_get(func, source_local),
                      )
                      if source_local >= 0 && source_local < get_counts.length() {
                        get_counts[source_local] = get_counts[source_local] + 1
                      }
                      if local_id >= 0 &&
                        local_id < get_counts.length() &&
                        get_counts[local_id] > 0 {
                        get_counts[local_id] = get_counts[local_id] - 1
                      }
                      simplify_locals_sinkables_clear_local(sinkables, local_id)
                      simplify_locals_invalidate_sinkables(
                        func, node_id, current, sinkables,
                      )
                      return true
                    }
                    None => ()
                  }
                }
                if one_use &&
                  simplify_locals_value_can_move_one_use(func, value_id) {
                  @ir.hot_replace_node(
                    func,
                    node_id,
                    @ir.hot_node_get(func, value_id),
                    preserve_value_order=true,
                  )
                  detached.push(value_id)
                } else if policy.allow_sink_tee &&
                  !one_use &&
                  simplify_locals_value_can_tee(func, value_id) {
                  @ir.hot_replace_node(
                    func,
                    node_id,
                    simplify_locals_make_local_tee(func, info.node_id),
                    preserve_value_order=true,
                  )
                } else {
                  simplify_locals_sinkables_clear_local(sinkables, local_id)
                  simplify_locals_invalidate_sinkables(
                    func, node_id, current, sinkables,
                  )
                  return changed
                }
                @ir.hot_replace_node(
                  func,
                  info.node_id,
                  simplify_locals_make_nop(func),
                )
                simplify_locals_sinkables_clear_local(sinkables, local_id)
                changed = true
              }
              _ => simplify_locals_sinkables_clear_local(sinkables, local_id)
            }
          } else {
            simplify_locals_sinkables_clear_local(sinkables, local_id)
          }
        None => ()
      }
      simplify_locals_invalidate_sinkables(func, node_id, current, sinkables)
      return changed
    }
    _ => ()
  }

  match current.op {
    @ir.HotOp::Drop => {
      let child_id = @ir.hot_child_get(func, node_id, 0)
      if child_id >= 0 && @ir.hot_node_is_live(func, child_id) {
        let child = @ir.hot_node_get(func, child_id)
        match child.op {
          @ir.HotOp::Heap if simplify_locals_is_discardable_default_gc_allocation(
              func, child_id,
            ) => {
            @ir.hot_replace_node(func, node_id, simplify_locals_make_nop(func))
            detached.push(child_id)
            changed = true
          }
          @ir.HotOp::LocalTee => {
            let value_id = @ir.hot_child_get(func, child_id, 0)
            let root_drop_tee = root_idx >= 0 &&
              root_idx < @ir.hot_root_count(func) &&
              node_id == @ir.hot_root_get(func, root_idx)
            if root_drop_tee &&
              simplify_locals_child_use_count_capped_at_two(func, value_id) == 1 {
              @ir.hot_replace_node(
                func,
                node_id,
                simplify_locals_make_local_set(func, child.imm0, value_id),
              )
              simplify_locals_queue_rehomed_wrapper_for_deletion(
                func, child_id, detached,
              )
              changed = true
            }
          }
          _ => ()
        }
      }
    }
    _ => ()
  }

  current = @ir.hot_node_get(func, node_id)
  match current.op {
    @ir.HotOp::LocalSet => {
      let value_id = @ir.hot_child_get(func, node_id, 0)
      if root_idx >= 0 &&
        get_counts[current.imm0] == 1 &&
        simplify_locals_value_is_child_inline_safe(func, value_id) {
        match parent_region {
          Some(parent_region) =>
            if simplify_locals_try_inline_following_local_get(
                func,
                parent_region,
                root_idx,
                current.imm0,
                value_id,
                policy.allow_nesting,
                detached,
              ) {
              simplify_locals_invalidate_sinkables(
                func,
                node_id,
                current,
                sinkables,
                skip_local=current.imm0,
              )
              simplify_locals_sinkables_clear_local(sinkables, current.imm0)
              @ir.hot_replace_node(
                func,
                node_id,
                simplify_locals_make_nop(func),
              )
              return true
            }
          None => ()
        }
      }
      simplify_locals_invalidate_sinkables(
        func,
        node_id,
        current,
        sinkables,
        skip_local=current.imm0,
      )
      let crosses_older_stack_value = policy.allow_sink_tee &&
        get_counts[current.imm0] > 1 &&
        !@ir.hot_node_kind_is_control(func, value_id) &&
        (match parent_region {
          Some(parent_region) =>
            simplify_locals_set_crosses_older_stack_value(
              func,
              parent_region,
              root_idx,
              node_id,
              current.imm0,
              minimums=simplify_locals_value_order_minimums_for_region(
                sinkables, func, parent_region,
              ),
            ) ||
            simplify_locals_set_first_use_crosses_intervening_local_write(
              func,
              parent_region,
              root_idx,
              current.imm0,
              minimums=simplify_locals_value_order_minimums_for_region(
                sinkables, func, parent_region,
              ),
            )
          None => false
        })
      if !crosses_older_stack_value {
        if simplify_locals_record_local_set(
            func,
            node_id,
            current,
            sinkables,
            first_cycle,
            policy.allow_sink_tee,
            get_counts,
            deferred_sets,
          ) {
          changed = true
        }
      } else {
        simplify_locals_sinkables_clear_local(sinkables, current.imm0)
      }
      changed
    }
    _ => {
      simplify_locals_invalidate_sinkables(func, node_id, current, sinkables)
      changed
    }
  }
}

///|
fn simplify_locals_collect_block_branch_exits(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  target_label : Int32,
  local_id : Int32,
  exits : Array[SimplifyLocalsBranchExit],
) -> Bool {
  let last_idx = simplify_locals_region_last_live_non_nop_root_idx(
    func, region_ref,
  )
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    if simplify_locals_node_targets_label(func, node_id, target_label) {
      let node = @ir.hot_node_get(func, node_id)
      match node.op {
        @ir.HotOp::Br => {
          if node.child_count != 0 || last_idx != Some(idx) {
            return false
          }
          match
            simplify_locals_find_branch_exit_set_for_local(
              func, region_ref, idx, target_label, local_id,
            ) {
            Some(exit) => exits.push(exit)
            None => return false
          }
        }
        @ir.HotOp::BrIf => {
          // An untyped br_if has only its condition child. The local write
          // immediately dominating the conditional exit can become the new
          // branch payload while preserving the condition as the final child.
          if simplify_locals_branch_payload_count(func, node_id) != 0 {
            return false
          }
          if node.child_count != 1 ||
            simplify_locals_node_reads_local(
              func,
              @ir.hot_child_get(func, node_id, 0),
              local_id,
            ) {
            return false
          }
          match
            simplify_locals_find_branch_exit_set_for_local(
              func, region_ref, idx, target_label, local_id,
            ) {
            Some(exit) => exits.push(exit)
            None => return false
          }
        }
        _ => return false
      }
      continue
    }
    let node = @ir.hot_node_get(func, node_id)
    if !@ir.hot_node_kind_is_control(func, node_id) {
      if simplify_locals_subtree_has_branch_to_label(
          func, node_id, target_label,
        ) {
        return false
      }
      continue
    }
    match node.op {
      @ir.HotOp::Block | @ir.HotOp::Loop => {
        let body_ref = @ir.hot_region_body_ref(func, node_id)
        match node.op {
          @ir.HotOp::Loop => {
            let input_count = @ir.hot_label_branch_arity(
              func,
              @ir.hot_control_node_label(func, node_id),
            )
            for child_idx = 0
                child_idx < input_count
                child_idx = child_idx + 1 {
              if simplify_locals_subtree_has_branch_to_label(
                  func,
                  @ir.hot_child_get(func, node_id, child_idx),
                  target_label,
                ) {
                return false
              }
            }
          }
          _ => ()
        }
        if simplify_locals_control_has_typed_branch_payload(func, node_id) &&
          simplify_locals_region_has_branch_to_label(
            func, body_ref, target_label,
          ) {
          return false
        }
        if !simplify_locals_collect_block_branch_exits(
            func, body_ref, target_label, local_id, exits,
          ) {
          return false
        }
      }
      @ir.HotOp::If => {
        if simplify_locals_subtree_has_branch_to_label(
            func,
            @ir.hot_child_get(func, node_id, 0),
            target_label,
          ) {
          return false
        }
        let then_ref = @ir.hot_region_then_ref(func, node_id)
        if simplify_locals_control_has_typed_branch_payload(func, node_id) &&
          simplify_locals_region_has_branch_to_label(
            func, then_ref, target_label,
          ) {
          return false
        }
        if !simplify_locals_collect_block_branch_exits(
            func, then_ref, target_label, local_id, exits,
          ) {
          return false
        }
        match @ir.hot_region_else_ref(func, node_id) {
          Some(else_ref) => {
            if simplify_locals_control_has_typed_branch_payload(func, node_id) &&
              simplify_locals_region_has_branch_to_label(
                func, else_ref, target_label,
              ) {
              return false
            }
            if !simplify_locals_collect_block_branch_exits(
                func, else_ref, target_label, local_id, exits,
              ) {
              return false
            }
          }
          None => ()
        }
      }
      @ir.HotOp::Try => {
        let body_ref = @ir.hot_region_body_ref(func, node_id)
        if simplify_locals_control_has_typed_branch_payload(func, node_id) &&
          simplify_locals_region_has_branch_to_label(
            func, body_ref, target_label,
          ) {
          return false
        }
        if !simplify_locals_collect_block_branch_exits(
            func, body_ref, target_label, local_id, exits,
          ) {
          return false
        }
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(catch_ref) => {
            if simplify_locals_control_has_typed_branch_payload(func, node_id) &&
              simplify_locals_region_has_branch_to_label(
                func, catch_ref, target_label,
              ) {
              return false
            }
            if !simplify_locals_collect_block_branch_exits(
                func, catch_ref, target_label, local_id, exits,
              ) {
              return false
            }
          }
          None => ()
        }
      }
      @ir.HotOp::TryTable => {
        let body_ref = @ir.hot_region_body_ref(func, node_id)
        let catch_list_ref = @ir.hot_region_catch_list_ref(func, node_id)
        if simplify_locals_control_has_typed_branch_payload(func, node_id) &&
          (
            simplify_locals_region_has_branch_to_label(
              func, body_ref, target_label,
            ) ||
            simplify_locals_region_has_branch_to_label(
              func, catch_list_ref, target_label,
            )
          ) {
          return false
        }
        if !simplify_locals_collect_block_branch_exits(
            func, body_ref, target_label, local_id, exits,
          ) ||
          !simplify_locals_collect_block_branch_exits(
            func, catch_list_ref, target_label, local_id, exits,
          ) {
          return false
        }
      }
      _ => ()
    }
  }
  true
}

///|
fn simplify_locals_try_apply_block_return_candidate(
  func : @ir.HotFunc,
  node_id : Int32,
  label_id : Int32,
  candidate : SimplifyLocalsTailSet,
  detached : Array[Int32],
) -> Bool {
  let body_ref = @ir.hot_region_body_ref(func, node_id)
  let exits : Array[SimplifyLocalsBranchExit] = []
  if !simplify_locals_collect_block_branch_exits(
      func,
      body_ref,
      label_id,
      candidate.local_id,
      exits,
    ) {
    return false
  }
  simplify_locals_replace_tail_set_with_result_value(func, body_ref, candidate)
  simplify_locals_queue_rehomed_wrapper_for_deletion(
    func,
    candidate.node_id,
    detached,
  )
  for exit in exits {
    ignore(
      @ir.hot_region_set(
        func,
        exit.region_ref,
        exit.set_idx,
        @ir.hot_build_nop(func),
      ),
    )
    let old_branch_children = @ir.hot_child_span(func, exit.br_id)
    let new_branch_children : Array[Int32] = [exit.value_id]
    for child_id in old_branch_children {
      new_branch_children.push(child_id)
    }
    ignore(@ir.hot_replace_child_span(func, exit.br_id, new_branch_children))
    // A conditional branch with a payload produces that payload when the
    // branch is not taken. The original untyped br_if was a statement, so
    // preserve that shape explicitly instead of leaving a typed region root
    // for lowering to spill through fresh temporary locals.
    match @ir.hot_node_get(func, exit.br_id).op {
      @ir.HotOp::BrIf =>
        ignore(
          @ir.hot_region_set(
            func,
            exit.region_ref,
            exit.br_idx,
            @ir.hot_build_drop(func, exit.br_id),
          ),
        )
      _ => ()
    }
    simplify_locals_queue_rehomed_wrapper_for_deletion(
      func,
      exit.set_id,
      detached,
    )
  }
  @ir.hot_control_result_type_set(
    func,
    node_id,
    simplify_locals_local_type_id(func, candidate.local_id, candidate.value_id),
  )
  true
}

///|
fn simplify_locals_region_has_branch_to_label(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  target_label : Int32,
) -> Bool {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    if simplify_locals_node_targets_label(func, node_id, target_label) {
      return true
    }
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::Block | @ir.HotOp::Loop =>
        if simplify_locals_region_has_branch_to_label(
            func,
            @ir.hot_region_body_ref(func, node_id),
            target_label,
          ) {
          return true
        }
      @ir.HotOp::If => {
        if simplify_locals_region_has_branch_to_label(
            func,
            @ir.hot_region_then_ref(func, node_id),
            target_label,
          ) {
          return true
        }
        match @ir.hot_region_else_ref(func, node_id) {
          Some(else_ref) =>
            if simplify_locals_region_has_branch_to_label(
                func, else_ref, target_label,
              ) {
              return true
            }
          None => ()
        }
      }
      @ir.HotOp::Try => {
        if simplify_locals_region_has_branch_to_label(
            func,
            @ir.hot_region_body_ref(func, node_id),
            target_label,
          ) {
          return true
        }
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(catch_ref) =>
            if simplify_locals_region_has_branch_to_label(
                func, catch_ref, target_label,
              ) {
              return true
            }
          None => ()
        }
      }
      @ir.HotOp::TryTable =>
        if simplify_locals_region_has_branch_to_label(
            func,
            @ir.hot_region_body_ref(func, node_id),
            target_label,
          ) ||
          simplify_locals_region_has_branch_to_label(
            func,
            @ir.hot_region_catch_list_ref(func, node_id),
            target_label,
          ) {
          return true
        }
      _ => ()
    }
  }
  false
}

///|
fn simplify_locals_subtree_has_branch_to_label(
  func : @ir.HotFunc,
  node_id : Int32,
  target_label : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  if simplify_locals_node_targets_label(func, node_id, target_label) {
    return true
  }
  for child_idx = 0
      child_idx < @ir.hot_child_count(func, node_id)
      child_idx = child_idx + 1 {
    if simplify_locals_subtree_has_branch_to_label(
        func,
        @ir.hot_child_get(func, node_id, child_idx),
        target_label,
      ) {
      return true
    }
  }
  false
}

///|
fn simplify_locals_subtree_has_typed_control(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  if @ir.hot_node_kind_is_control(func, node_id) &&
    @ir.hot_node_result_arity(func, node_id) != 0 {
    return true
  }
  for child_idx = 0
      child_idx < @ir.hot_child_count(func, node_id)
      child_idx = child_idx + 1 {
    if simplify_locals_subtree_has_typed_control(
        func,
        @ir.hot_child_get(func, node_id, child_idx),
      ) {
      return true
    }
  }
  false
}

///|
fn simplify_locals_try_rewrite_block_return(
  func : @ir.HotFunc,
  parent_region : @ir.HotRegionRef,
  root_idx : Int32,
  node_id : Int32,
  get_counts : Array[Int32],
  detached : Array[Int32],
  allow_multi_use~ : Bool = true,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Block => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, node_id) != 0 {
    return false
  }
  let body_ref = @ir.hot_region_body_ref(func, node_id)
  let label_id = @ir.hot_control_node_label(func, node_id)
  let candidates = simplify_locals_collect_block_return_candidates(
    func, body_ref, label_id, get_counts,
  )
  if candidates.is_empty() {
    return false
  }
  let preferred_locals = simplify_locals_following_root_local_get_order(
    func, parent_region, root_idx,
  )
  for preferred_local in preferred_locals {
    for candidate in candidates {
      if candidate.local_id != preferred_local ||
        (!allow_multi_use && get_counts[candidate.local_id] > 1) {
        continue
      }
      if simplify_locals_try_apply_block_return_candidate(
          func, node_id, label_id, candidate, detached,
        ) {
        if get_counts[candidate.local_id] == 1 &&
          (
            simplify_locals_try_consume_following_local_get(
              func,
              parent_region,
              root_idx,
              candidate.local_id,
              detached,
            ) ||
            simplify_locals_try_inline_following_local_get(
              func,
              parent_region,
              root_idx,
              candidate.local_id,
              node_id,
              true,
              detached,
            )
          ) {
          return true
        }
        ignore(
          @ir.hot_region_set(
            func,
            parent_region,
            root_idx,
            @ir.hot_build_local_set_from_value(
              func,
              candidate.local_id,
              node_id,
            ),
          ),
        )
        return true
      }
    }
  }
  let mut candidate_idx = candidates.length() - 1
  while candidate_idx >= 0 {
    let candidate = candidates[candidate_idx]
    if !allow_multi_use && get_counts[candidate.local_id] > 1 {
      candidate_idx = candidate_idx - 1
      continue
    }
    if simplify_locals_try_apply_block_return_candidate(
        func, node_id, label_id, candidate, detached,
      ) {
      if get_counts[candidate.local_id] == 1 &&
        (
          simplify_locals_try_consume_following_local_get(
            func,
            parent_region,
            root_idx,
            candidate.local_id,
            detached,
          ) ||
          simplify_locals_try_inline_following_local_get(
            func,
            parent_region,
            root_idx,
            candidate.local_id,
            node_id,
            true,
            detached,
          )
        ) {
        return true
      }
      ignore(
        @ir.hot_region_set(
          func,
          parent_region,
          root_idx,
          @ir.hot_build_local_set_from_value(func, candidate.local_id, node_id),
        ),
      )
      return true
    }
    candidate_idx = candidate_idx - 1
  }
  false
}

///|
fn simplify_locals_try_rewrite_if_return(
  func : @ir.HotFunc,
  parent_region : @ir.HotRegionRef,
  root_idx : Int32,
  node_id : Int32,
  get_counts : Array[Int32],
  detached : Array[Int32],
  allow_multi_use~ : Bool = true,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::If => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, node_id) != 0 {
    return false
  }
  let then_ref = @ir.hot_region_then_ref(func, node_id)
  let then_tail = simplify_locals_region_tail_local_set(func, then_ref)
  let then_unreachable = simplify_locals_region_is_unreachableish(
    func, then_ref,
  )
  match @ir.hot_region_else_ref(func, node_id) {
    Some(else_ref) => {
      let else_tail = simplify_locals_region_tail_local_set(func, else_ref)
      let else_unreachable = simplify_locals_region_is_unreachableish(
        func, else_ref,
      )
      let chosen_local = if then_unreachable {
        match else_tail {
          Some(tail) => tail.local_id
          None => return false
        }
      } else if else_unreachable {
        match then_tail {
          Some(tail) => tail.local_id
          None => return false
        }
      } else {
        match (then_tail, else_tail) {
          (Some(then_tail), Some(else_tail)) if then_tail.local_id ==
            else_tail.local_id => then_tail.local_id
          _ => return false
        }
      }
      if chosen_local < 0 ||
        chosen_local >= get_counts.length() ||
        get_counts[chosen_local] == 0 ||
        (!allow_multi_use && get_counts[chosen_local] > 1) {
        return false
      }
      let fallback_value = if then_unreachable {
        else_tail.unwrap().value_id
      } else {
        then_tail.unwrap().value_id
      }
      if (
          !then_unreachable &&
          simplify_locals_subtree_has_typed_control(
            func,
            then_tail.unwrap().value_id,
          )
        ) ||
        (
          !else_unreachable &&
          simplify_locals_subtree_has_typed_control(
            func,
            else_tail.unwrap().value_id,
          )
        ) {
        return false
      }
      if !then_unreachable {
        let tail = then_tail.unwrap()
        simplify_locals_replace_tail_set_with_result_value(func, then_ref, tail)
        simplify_locals_queue_rehomed_wrapper_for_deletion(
          func,
          tail.node_id,
          detached,
        )
      }
      if !else_unreachable {
        let tail = else_tail.unwrap()
        simplify_locals_replace_tail_set_with_result_value(func, else_ref, tail)
        simplify_locals_queue_rehomed_wrapper_for_deletion(
          func,
          tail.node_id,
          detached,
        )
      }
      @ir.hot_control_result_type_set(
        func,
        node_id,
        simplify_locals_local_type_id(func, chosen_local, fallback_value),
      )
      if get_counts[chosen_local] == 1 &&
        simplify_locals_try_consume_following_local_get(
          func, parent_region, root_idx, chosen_local, detached,
        ) {
        return true
      }
      ignore(
        @ir.hot_region_set(
          func,
          parent_region,
          root_idx,
          @ir.hot_build_local_set_from_value(func, chosen_local, node_id),
        ),
      )
      true
    }
    None => {
      let then_tail = match then_tail {
        Some(then_tail) => then_tail
        None => return false
      }
      if then_tail.local_id < 0 ||
        then_tail.local_id >= get_counts.length() ||
        (!allow_multi_use && get_counts[then_tail.local_id] > 1) ||
        !simplify_locals_local_type_is_defaultable(func, then_tail.local_id) {
        return false
      }
      simplify_locals_replace_tail_set_with_result_value(
        func, then_ref, then_tail,
      )
      simplify_locals_queue_rehomed_wrapper_for_deletion(
        func,
        then_tail.node_id,
        detached,
      )
      let result_block_type = match
        @ir.hot_local_type(func, then_tail.local_id) {
        Some(local_type) => @lib.BlockType::val_type(local_type)
        None => return false
      }
      let replacement_then = simplify_locals_build_one_armed_if_then_body(
        func,
        then_ref,
        then_tail.value_id,
      )
      if simplify_locals_region_has_branch_to_label(
          func,
          then_ref,
          @ir.hot_control_node_label(func, node_id),
        ) {
        // Existing void exits must skip the synthesized assignment. Keep their
        // original label on an outer block around the new result conditional.
        let result_if = @ir.hot_build_if(
          func,
          result_block_type,
          @ir.hot_child_get(func, node_id, 0),
          replacement_then,
          else_body=Some([@ir.hot_build_local_get(func, then_tail.local_id)]),
        )
        let capture = @ir.hot_build_local_set_from_value(
          func,
          then_tail.local_id,
          result_if,
        )
        @ir.hot_region_replace_body(func, then_ref, [])
        @ir.hot_control_demote_if_to_block(func, node_id, [capture])
        return true
      }
      let else_region = @ir.hot_build_region(
        func,
        [@ir.hot_build_local_get(func, then_tail.local_id)],
        1,
      )
      @ir.hot_child_set(func, node_id, 2, else_region)
      @ir.hot_region_replace_body(func, then_ref, replacement_then)
      @ir.hot_control_result_type_set(
        func,
        node_id,
        @ir.hot_type_intern_block_result(func, result_block_type),
      )
      if get_counts[then_tail.local_id] == 1 &&
        simplify_locals_try_consume_following_local_get(
          func,
          parent_region,
          root_idx,
          then_tail.local_id,
          detached,
        ) {
        return true
      }
      ignore(
        @ir.hot_region_set(
          func,
          parent_region,
          root_idx,
          @ir.hot_build_local_set_from_value(func, then_tail.local_id, node_id),
        ),
      )
      true
    }
  }
}

///|
fn simplify_locals_try_rewrite_loop_return(
  func : @ir.HotFunc,
  parent_region : @ir.HotRegionRef,
  root_idx : Int32,
  node_id : Int32,
  get_counts : Array[Int32],
  detached : Array[Int32],
  allow_multi_use~ : Bool = true,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Loop => ()
    _ => return false
  }
  let body_ref = @ir.hot_region_body_ref(func, node_id)
  let tail = match simplify_locals_region_tail_local_set(func, body_ref) {
    Some(tail) => tail
    None => return false
  }
  if tail.local_id < 0 ||
    tail.local_id >= get_counts.length() ||
    get_counts[tail.local_id] == 0 ||
    (!allow_multi_use && get_counts[tail.local_id] > 1) {
    return false
  }
  if simplify_locals_region_has_branch_to_label(
      func,
      body_ref,
      @ir.hot_control_node_label(func, node_id),
    ) {
    return false
  }
  if simplify_locals_region_next_live_non_nop_root_idx(
      func, parent_region, root_idx,
    )
    is None {
    return false
  }
  // The tail value still executes inside the loop. Export the loop's result,
  // rather than copying its producer into a following local.get.
  simplify_locals_replace_tail_set_with_result_value(func, body_ref, tail)
  simplify_locals_queue_rehomed_wrapper_for_deletion(
    func,
    tail.node_id,
    detached,
  )
  @ir.hot_control_result_type_set(
    func,
    node_id,
    simplify_locals_local_type_id(func, tail.local_id, tail.value_id),
  )
  if get_counts[tail.local_id] == 1 &&
    (
      simplify_locals_try_inline_following_local_get(
        func,
        parent_region,
        root_idx,
        tail.local_id,
        node_id,
        true,
        detached,
      ) ||
      simplify_locals_try_consume_following_local_get(
        func,
        parent_region,
        root_idx,
        tail.local_id,
        detached,
      )
    ) {
    return true
  }
  ignore(
    @ir.hot_region_set(
      func,
      parent_region,
      root_idx,
      @ir.hot_build_local_set_from_value(func, tail.local_id, node_id),
    ),
  )
  true
}

///|
fn simplify_locals_try_rewrite_nested_one_armed_if_child(
  func : @ir.HotFunc,
  parent_id : Int32,
  slot : Int32,
  child_id : Int32,
  get_counts : Array[Int32],
  detached : Array[Int32],
  allow_multi_use~ : Bool = true,
) -> Bool {
  if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
    return false
  }
  let child = @ir.hot_node_get(func, child_id)
  match child.op {
    @ir.HotOp::If => ()
    _ => return false
  }
  if @ir.hot_node_result_arity(func, child_id) != 0 {
    return false
  }
  match @ir.hot_region_else_ref(func, child_id) {
    Some(_) => return false
    None => ()
  }
  let then_ref = @ir.hot_region_then_ref(func, child_id)
  let then_tail = match simplify_locals_region_tail_local_set(func, then_ref) {
    Some(then_tail) => then_tail
    None => return false
  }
  if then_tail.local_id < 0 ||
    then_tail.local_id >= get_counts.length() ||
    get_counts[then_tail.local_id] == 0 ||
    (!allow_multi_use && get_counts[then_tail.local_id] > 1) ||
    !simplify_locals_local_type_is_defaultable(func, then_tail.local_id) {
    return false
  }
  simplify_locals_replace_tail_set_with_result_value(func, then_ref, then_tail)
  simplify_locals_queue_rehomed_wrapper_for_deletion(
    func,
    then_tail.node_id,
    detached,
  )
  let result_block_type = match @ir.hot_local_type(func, then_tail.local_id) {
    Some(local_type) => @lib.BlockType::val_type(local_type)
    None => return false
  }
  let replacement_then = simplify_locals_build_one_armed_if_then_body(
    func,
    then_ref,
    then_tail.value_id,
  )
  if simplify_locals_region_has_branch_to_label(
      func,
      then_ref,
      @ir.hot_control_node_label(func, child_id),
    ) {
    let result_if = @ir.hot_build_if(
      func,
      result_block_type,
      @ir.hot_child_get(func, child_id, 0),
      replacement_then,
      else_body=Some([@ir.hot_build_local_get(func, then_tail.local_id)]),
    )
    let capture = @ir.hot_build_local_set_from_value(
      func,
      then_tail.local_id,
      result_if,
    )
    @ir.hot_region_replace_body(func, then_ref, [])
    @ir.hot_control_demote_if_to_block(func, child_id, [capture])
    return true
  }
  // Rewrite in place so the conditional's source position and label remain
  // unchanged. A fresh conditional would place its write after old consumers.
  let else_region = @ir.hot_build_region(
    func,
    [@ir.hot_build_local_get(func, then_tail.local_id)],
    1,
  )
  @ir.hot_child_set(func, child_id, 2, else_region)
  @ir.hot_region_replace_body(func, then_ref, replacement_then)
  @ir.hot_control_result_type_set(
    func,
    child_id,
    @ir.hot_type_intern_block_result(func, result_block_type),
  )
  @ir.hot_child_set(
    func,
    parent_id,
    slot,
    @ir.hot_build_local_set_from_value(func, then_tail.local_id, child_id),
  )
  true
}

///|
fn simplify_locals_try_rewrite_nested_one_armed_if_children(
  func : @ir.HotFunc,
  node_id : Int32,
  get_counts : Array[Int32],
  detached : Array[Int32],
  allow_multi_use~ : Bool = true,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    let child_id = @ir.hot_child_get(func, node_id, slot)
    if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
      continue
    }
    if simplify_locals_try_rewrite_nested_one_armed_if_children(
        func,
        child_id,
        get_counts,
        detached,
        allow_multi_use~,
      ) ||
      simplify_locals_try_rewrite_nested_one_armed_if_child(
        func,
        node_id,
        slot,
        child_id,
        get_counts,
        detached,
        allow_multi_use~,
      ) {
      return true
    }
  }
  false
}

///|
fn simplify_locals_try_structure_rewrite_region(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  get_counts : Array[Int32],
  detached : Array[Int32],
  allow_multi_use~ : Bool = true,
) -> Bool {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    if simplify_locals_try_rewrite_split_local_set_wrapper_forwarder(
        func, node_id, detached,
      ) {
      return true
    }
    if simplify_locals_try_rewrite_nested_one_armed_if_children(
        func,
        node_id,
        get_counts,
        detached,
        allow_multi_use~,
      ) {
      return true
    }
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::Block =>
        if simplify_locals_try_structure_rewrite_region(
            func,
            @ir.hot_region_body_ref(func, node_id),
            get_counts,
            detached,
            allow_multi_use~,
          ) ||
          simplify_locals_try_rewrite_block_return(
            func,
            region_ref,
            idx,
            node_id,
            get_counts,
            detached,
            allow_multi_use~,
          ) {
          return true
        }
      @ir.HotOp::If => {
        if simplify_locals_try_structure_rewrite_region(
            func,
            @ir.hot_region_then_ref(func, node_id),
            get_counts,
            detached,
            allow_multi_use~,
          ) {
          return true
        }
        match @ir.hot_region_else_ref(func, node_id) {
          Some(else_ref) =>
            if simplify_locals_try_structure_rewrite_region(
                func,
                else_ref,
                get_counts,
                detached,
                allow_multi_use~,
              ) {
              return true
            }
          None => ()
        }
        if simplify_locals_try_rewrite_if_return(
            func,
            region_ref,
            idx,
            node_id,
            get_counts,
            detached,
            allow_multi_use~,
          ) {
          return true
        }
      }
      @ir.HotOp::Loop =>
        if simplify_locals_try_structure_rewrite_region(
            func,
            @ir.hot_region_body_ref(func, node_id),
            get_counts,
            detached,
            allow_multi_use~,
          ) ||
          simplify_locals_try_rewrite_loop_return(
            func,
            region_ref,
            idx,
            node_id,
            get_counts,
            detached,
            allow_multi_use~,
          ) {
          return true
        }
      @ir.HotOp::Try => {
        if simplify_locals_try_structure_rewrite_region(
            func,
            @ir.hot_region_body_ref(func, node_id),
            get_counts,
            detached,
            allow_multi_use~,
          ) {
          return true
        }
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(catch_ref) =>
            if simplify_locals_try_structure_rewrite_region(
                func,
                catch_ref,
                get_counts,
                detached,
                allow_multi_use~,
              ) {
              return true
            }
          None => ()
        }
      }
      @ir.HotOp::TryTable =>
        if simplify_locals_try_structure_rewrite_region(
            func,
            @ir.hot_region_body_ref(func, node_id),
            get_counts,
            detached,
            allow_multi_use~,
          ) ||
          simplify_locals_try_structure_rewrite_region(
            func,
            @ir.hot_region_catch_list_ref(func, node_id),
            get_counts,
            detached,
            allow_multi_use~,
          ) {
          return true
        }
      _ => ()
    }
  }
  false
}

///|
fn simplify_locals_run_structure_rewrites(
  func : @ir.HotFunc,
  detached : Array[Int32],
  allow_multi_use~ : Bool = true,
) -> Bool {
  simplify_locals_try_structure_rewrite_region(
    func,
    @ir.HotRegionRef::root(),
    simplify_locals_count_local_gets(func),
    detached,
    allow_multi_use~,
  )
}

///|
priv struct SimplifyLocalsEquivalentState {
  rep_of_local : Array[Int32]
  members_of_rep : Array[Array[Int32]]
  captures : PassSourceCaptures
  local_versions : Array[Int32]
  read_versions : Array[Int32]
  next_version : Array[Int32]
}

///|
fn simplify_locals_new_equivalences(
  func : @ir.HotFunc,
  captures~ : PassSourceCaptures? = None,
  read_versions~ : Array[Int32]? = None,
  next_version~ : Array[Int32]? = None,
) -> SimplifyLocalsEquivalentState {
  let captures = match captures {
    Some(captures) => captures
    None => PassSourceCaptures::new(func)
  }
  let read_versions = match read_versions {
    Some(versions) => versions
    None => Array::make(@ir.hot_node_count(func), -1)
  }
  let next_version = match next_version {
    Some(next) => next
    None => [0]
  }
  let local_versions = Array::makei(@ir.hot_local_count(func), fn(_) {
    let version = next_version[0]
    next_version[0] += 1
    version
  })
  let rep_of_local = Array::make(@ir.hot_local_count(func), 0)
  let members_of_rep : Array[Array[Int32]] = []
  for local_id = 0
      local_id < @ir.hot_local_count(func)
      local_id = local_id + 1 {
    rep_of_local[local_id] = local_id
    members_of_rep.push([local_id])
  }
  {
    rep_of_local,
    members_of_rep,
    captures,
    local_versions,
    read_versions,
    next_version,
  }
}

///|
fn simplify_locals_clone_equivalences(
  equivalences : SimplifyLocalsEquivalentState,
) -> SimplifyLocalsEquivalentState {
  let cloned_members : Array[Array[Int32]] = []
  for members in equivalences.members_of_rep {
    cloned_members.push(members.copy())
  }
  {
    rep_of_local: equivalences.rep_of_local.copy(),
    members_of_rep: cloned_members,
    captures: equivalences.captures,
    local_versions: equivalences.local_versions.copy(),
    read_versions: equivalences.read_versions,
    next_version: equivalences.next_version,
  }
}

///|
fn simplify_locals_clear_equivalences(
  equivalences : SimplifyLocalsEquivalentState,
) -> Unit {
  for local_id = 0
      local_id < equivalences.rep_of_local.length()
      local_id = local_id + 1 {
    equivalences.rep_of_local[local_id] = local_id
    equivalences.local_versions[local_id] = equivalences.next_version[0]
    equivalences.next_version[0] += 1
    equivalences.members_of_rep[local_id] = [local_id]
  }
}

///|
fn simplify_locals_reset_equivalent_local(
  equivalences : SimplifyLocalsEquivalentState,
  local_id : Int32,
) -> Unit {
  if local_id < 0 || local_id >= equivalences.rep_of_local.length() {
    return
  }
  let rep = equivalences.rep_of_local[local_id]
  if rep < 0 || rep >= equivalences.members_of_rep.length() {
    equivalences.rep_of_local[local_id] = local_id
    equivalences.members_of_rep[local_id] = [local_id]
    return
  }
  let remaining : Array[Int32] = []
  for member_id in equivalences.members_of_rep[rep] {
    if member_id != local_id {
      remaining.push(member_id)
    }
  }
  if rep == local_id && !remaining.is_empty() {
    let new_rep = remaining[0]
    equivalences.members_of_rep[new_rep] = remaining
    for member_id in remaining {
      equivalences.rep_of_local[member_id] = new_rep
    }
  } else if rep != local_id {
    equivalences.members_of_rep[rep] = remaining
  }
  equivalences.rep_of_local[local_id] = local_id
  equivalences.members_of_rep[local_id] = [local_id]
}

///|
fn simplify_locals_equivalent_check(
  equivalences : SimplifyLocalsEquivalentState,
  lhs : Int32,
  rhs : Int32,
) -> Bool {
  lhs >= 0 &&
  rhs >= 0 &&
  lhs < equivalences.rep_of_local.length() &&
  rhs < equivalences.rep_of_local.length() &&
  equivalences.rep_of_local[lhs] == equivalences.rep_of_local[rhs]
}

///|
fn simplify_locals_add_equivalence(
  equivalences : SimplifyLocalsEquivalentState,
  local_id : Int32,
  other_local : Int32,
) -> Unit {
  if local_id < 0 ||
    other_local < 0 ||
    local_id >= equivalences.rep_of_local.length() ||
    other_local >= equivalences.rep_of_local.length() ||
    local_id == other_local {
    return
  }
  let other_rep = equivalences.rep_of_local[other_local]
  if other_rep < 0 ||
    other_rep >= equivalences.members_of_rep.length() ||
    equivalences.rep_of_local[local_id] == other_rep {
    return
  }
  simplify_locals_reset_equivalent_local(equivalences, local_id)
  equivalences.rep_of_local[local_id] = other_rep
  equivalences.members_of_rep[other_rep].push(local_id)
}

///|
fn simplify_locals_direct_local_get_value(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Int32? {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalGet => Some(node.imm0)
    _ => None
  }
}

///|
fn simplify_locals_equivalent_local_get_value(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Int32? {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalGet => Some(node.imm0)
    _ =>
      // Binaryen's Properties::getFallthrough sees through this refining
      // wrapper. If it succeeds, the assigned local contains the same
      // reference as the source; if it traps, no following read is reached.
      match @ir.hot_node_exact_instr(func, node_id) {
        Some(@lib.Instruction::RefAsNonNull) if node.child_count == 1 =>
          simplify_locals_equivalent_local_get_value(
            func,
            @ir.hot_child_get(func, node_id, 0),
          )
        _ => None
      }
  }
}

///|
fn simplify_locals_equivalent_read_node(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Int32 {
  let mut current = node_id
  while current >= 0 && @ir.hot_node_is_live(func, current) {
    let node = @ir.hot_node_get(func, current)
    if node.op is @ir.HotOp::LocalGet {
      return current
    }
    match @ir.hot_node_exact_instr(func, current) {
      Some(@lib.Instruction::RefAsNonNull) if node.child_count == 1 =>
        current = @ir.hot_child_get(func, current, 0)
      _ => return -1
    }
  }
  -1
}

///|
fn simplify_locals_type_env(ctx : HotPassContext) -> @validate.Env? {
  match ctx.module_ctx {
    Some(module_ctx) =>
      Some(@validate.Env::new().with_types(module_ctx.subtypes))
    None => None
  }
}

///|
fn simplify_locals_local_type_is_subtype(
  env : @validate.Env?,
  func : @ir.HotFunc,
  lhs : Int32,
  rhs : Int32,
) -> Bool {
  let lhs_type = match @ir.hot_local_type(func, lhs) {
    Some(lhs_type) => lhs_type
    None => return false
  }
  let rhs_type = match @ir.hot_local_type(func, rhs) {
    Some(rhs_type) => rhs_type
    None => return false
  }
  if lhs_type == rhs_type {
    return true
  }
  match env {
    Some(env) => @validate.Match::matches(lhs_type, rhs_type, env)
    None => false
  }
}

///|
fn simplify_locals_get_count_ignoring_current(
  get_counts : Array[Int32],
  current_local : Int32,
  local_id : Int32,
) -> Int32 {
  let count = get_counts[local_id]
  if local_id == current_local && count > 0 {
    count - 1
  } else {
    count
  }
}

///|
fn simplify_locals_pick_best_equivalent_local(
  env : @validate.Env?,
  func : @ir.HotFunc,
  equivalences : SimplifyLocalsEquivalentState,
  get_counts : Array[Int32],
  local_id : Int32,
) -> Int32 {
  if local_id < 0 || local_id >= equivalences.rep_of_local.length() {
    return local_id
  }
  let mut best = local_id
  let rep = equivalences.rep_of_local[local_id]
  if rep < 0 || rep >= equivalences.members_of_rep.length() {
    return best
  }
  for candidate in equivalences.members_of_rep[rep] {
    if candidate == best ||
      !simplify_locals_local_type_is_subtype(env, func, candidate, best) {
      continue
    }
    let candidate_type = @ir.hot_local_type(func, candidate)
    let best_type = @ir.hot_local_type(func, best)
    let candidate_has_more_gets = simplify_locals_get_count_ignoring_current(
        get_counts, local_id, candidate,
      ) >
      simplify_locals_get_count_ignoring_current(get_counts, local_id, best)
    if candidate_type != best_type || candidate_has_more_gets {
      best = candidate
    }
  }
  best
}

///|
fn simplify_locals_scan_equivalent_region(
  env : @validate.Env?,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  equivalences : SimplifyLocalsEquivalentState,
  get_counts : Array[Int32],
  tee_defined_locals : Array[Bool],
) -> Bool {
  let capture_depth = equivalences.captures.touched.length()
  let mut changed = false
  let mut previous_root = -1
  let mut lane = 0
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    let arity = @ir.hot_node_result_arity(func, node_id)
    lane = if previous_root == node_id { lane + 1 } else { 0 }
    let force_eval = arity == 0 ||
      (
        equivalences.captures.is_region_capture(node_id, capture_depth) &&
        (lane == 0 || lane % arity == 0)
      )
    previous_root = node_id
    if simplify_locals_scan_equivalent_node(
        env,
        func,
        node_id,
        equivalences,
        get_counts,
        tee_defined_locals,
        false,
        force_eval~,
      ) {
      changed = true
    }
    equivalences.captures.mark(node_id)
  }
  equivalences.captures.rewind(capture_depth)
  changed
}

///|
fn simplify_locals_scan_equivalent_current(
  env : @validate.Env?,
  func : @ir.HotFunc,
  node_id : Int32,
  equivalences : SimplifyLocalsEquivalentState,
  get_counts : Array[Int32],
  tee_defined_locals : Array[Bool],
  protect_tee_defined_locals : Bool,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalSet | @ir.HotOp::LocalTee => {
      let local_id = node.imm0
      while equivalences.read_versions.length() <= node_id {
        equivalences.read_versions.push(-1)
      }
      // A later lane can revisit a captured producer after another write to
      // this local. Do not learn its old copy alias again. Still invalidate
      // aliases on every write visit, including actual repeated evaluation.
      let first_write_visit = equivalences.read_versions[node_id] < 0
      let value_id = @ir.hot_child_get(func, node_id, 0)
      let read_id = simplify_locals_equivalent_read_node(func, value_id)
      let source_local = simplify_locals_equivalent_local_get_value(
        func, value_id,
      )
      let source_is_current = first_write_visit &&
        (match source_local {
          Some(source) =>
            read_id >= 0 &&
            read_id < equivalences.read_versions.length() &&
            equivalences.read_versions[read_id] ==
            equivalences.local_versions[source]
          None => false
        })
      equivalences.local_versions[local_id] = equivalences.next_version[0]
      equivalences.next_version[0] += 1
      equivalences.read_versions[node_id] = equivalences.local_versions[local_id]
      match source_local {
        Some(value_local) if source_is_current =>
          if simplify_locals_equivalent_check(
              equivalences, local_id, value_local,
            ) {
            false
          } else {
            simplify_locals_reset_equivalent_local(equivalences, local_id)
            simplify_locals_add_equivalence(equivalences, local_id, value_local)
            false
          }
        _ => {
          simplify_locals_reset_equivalent_local(equivalences, local_id)
          false
        }
      }
    }
    @ir.HotOp::LocalGet => {
      let local_id = node.imm0
      while equivalences.read_versions.length() <= node_id {
        equivalences.read_versions.push(-1)
      }
      // Two lane consumers can visit the same captured producer. Its read was
      // evaluated before aliases learned inside that producer; rewriting it on
      // the later visit can turn an initializing tee into a self-copy.
      if equivalences.read_versions[node_id] >= 0 {
        return false
      }
      equivalences.read_versions[node_id] = equivalences.local_versions[local_id]
      let best = simplify_locals_pick_best_equivalent_local(
        env, func, equivalences, get_counts, local_id,
      )
      if protect_tee_defined_locals &&
        local_id >= 0 &&
        local_id < tee_defined_locals.length() &&
        tee_defined_locals[local_id] &&
        best != local_id {
        return false
      }
      if best == local_id ||
        !simplify_locals_local_type_is_subtype(env, func, best, local_id) {
        return false
      }
      let best_type = @ir.hot_local_type(func, best)
      let old_type = @ir.hot_local_type(func, local_id)
      let has_more_gets = simplify_locals_get_count_ignoring_current(
          get_counts, local_id, best,
        ) >
        simplify_locals_get_count_ignoring_current(
          get_counts, local_id, local_id,
        )
      if !has_more_gets && best_type == old_type {
        return false
      }
      @ir.hot_replace_node(
        func,
        node_id,
        simplify_locals_make_local_get(func, best),
      )
      equivalences.read_versions[node_id] = equivalences.local_versions[best]
      if best >= 0 && best < get_counts.length() {
        get_counts[best] = get_counts[best] + 1
      }
      if local_id >= 0 &&
        local_id < get_counts.length() &&
        get_counts[local_id] > 0 {
        get_counts[local_id] = get_counts[local_id] - 1
      }
      true
    }
    _ => false
  }
}

///|
fn simplify_locals_scan_equivalent_node(
  env : @validate.Env?,
  func : @ir.HotFunc,
  node_id : Int32,
  equivalences : SimplifyLocalsEquivalentState,
  get_counts : Array[Int32],
  tee_defined_locals : Array[Bool],
  protect_tee_defined_locals : Bool,
  force_eval~ : Bool = false,
) -> Bool {
  if !force_eval && equivalences.captures.contains(node_id) {
    return false
  }
  let capture_depth = equivalences.captures.touched.length()
  let changed = simplify_locals_scan_equivalent_node_uncaptured(
    env, func, node_id, equivalences, get_counts, tee_defined_locals, protect_tee_defined_locals,
  )
  equivalences.captures.rewind(capture_depth)
  changed
}

///|
fn simplify_locals_scan_equivalent_node_uncaptured(
  env : @validate.Env?,
  func : @ir.HotFunc,
  node_id : Int32,
  equivalences : SimplifyLocalsEquivalentState,
  get_counts : Array[Int32],
  tee_defined_locals : Array[Bool],
  protect_tee_defined_locals : Bool,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let mut changed = false
  let node = @ir.hot_node_get(func, node_id)
  let mut previous_input = -1
  let mut lane = 0
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop | @ir.HotOp::If =>
      for index = 0
          index < pass_value_operand_count(func, node_id)
          index = index + 1 {
        let slot = pass_value_operand_slot(node, index)
        let input = @ir.hot_child_get(func, node_id, slot)
        let arity = @ir.hot_node_result_arity(func, input)
        lane = if previous_input == input { lane + 1 } else { 0 }
        let force_eval = arity == 0 || (lane > 0 && lane % arity == 0)
        previous_input = input
        if simplify_locals_scan_equivalent_node(
            env,
            func,
            input,
            equivalences,
            get_counts,
            tee_defined_locals,
            false,
            force_eval~,
          ) {
          changed = true
        }
        if !(node.op is @ir.HotOp::If && slot == 0) {
          equivalences.captures.mark(input)
        }
      }
    _ => ()
  }
  match node.op {
    @ir.HotOp::Block => {
      if simplify_locals_scan_equivalent_region(
          env,
          func,
          @ir.hot_region_body_ref(func, node_id),
          equivalences,
          get_counts,
          tee_defined_locals,
        ) {
        changed = true
      }
      // Facts learned on the textual fallthrough path do not necessarily
      // hold at a branch target. Keep scanning branch-free blocks precisely,
      // but discard path-specific aliases when control can leave the region.
      if simplify_locals_region_has_any_branch(
          func,
          @ir.hot_region_body_ref(func, node_id),
        ) {
        simplify_locals_clear_equivalences(equivalences)
      }
      return changed
    }
    @ir.HotOp::If => {
      let then_equivalences = simplify_locals_clone_equivalences(equivalences)
      if simplify_locals_scan_equivalent_region(
          env,
          func,
          @ir.hot_region_then_ref(func, node_id),
          then_equivalences,
          get_counts,
          tee_defined_locals,
        ) {
        changed = true
      }
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_ref) => {
          let else_equivalences = simplify_locals_clone_equivalences(
            equivalences,
          )
          if simplify_locals_scan_equivalent_region(
              env, func, else_ref, else_equivalences, get_counts, tee_defined_locals,
            ) {
            changed = true
          }
        }
        None => ()
      }
      simplify_locals_clear_equivalences(equivalences)
      return changed
    }
    @ir.HotOp::Loop => {
      let loop_equivalences = simplify_locals_new_equivalences(
        func,
        captures=Some(equivalences.captures),
        read_versions=Some(equivalences.read_versions),
        next_version=Some(equivalences.next_version),
      )
      if simplify_locals_scan_equivalent_region(
          env,
          func,
          @ir.hot_region_body_ref(func, node_id),
          loop_equivalences,
          get_counts,
          tee_defined_locals,
        ) {
        changed = true
      }
      simplify_locals_clear_equivalences(equivalences)
      return changed
    }
    @ir.HotOp::Try => {
      let body_equivalences = simplify_locals_new_equivalences(
        func,
        captures=Some(equivalences.captures),
        read_versions=Some(equivalences.read_versions),
        next_version=Some(equivalences.next_version),
      )
      if simplify_locals_scan_equivalent_region(
          env,
          func,
          @ir.hot_region_body_ref(func, node_id),
          body_equivalences,
          get_counts,
          tee_defined_locals,
        ) {
        changed = true
      }
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_ref) => {
          let catch_equivalences = simplify_locals_new_equivalences(
            func,
            captures=Some(equivalences.captures),
            read_versions=Some(equivalences.read_versions),
            next_version=Some(equivalences.next_version),
          )
          if simplify_locals_scan_equivalent_region(
              env, func, catch_ref, catch_equivalences, get_counts, tee_defined_locals,
            ) {
            changed = true
          }
        }
        None => ()
      }
      simplify_locals_clear_equivalences(equivalences)
      return changed
    }
    @ir.HotOp::TryTable => {
      let body_equivalences = simplify_locals_new_equivalences(
        func,
        captures=Some(equivalences.captures),
        read_versions=Some(equivalences.read_versions),
        next_version=Some(equivalences.next_version),
      )
      if simplify_locals_scan_equivalent_region(
          env,
          func,
          @ir.hot_region_body_ref(func, node_id),
          body_equivalences,
          get_counts,
          tee_defined_locals,
        ) {
        changed = true
      }
      let catch_equivalences = simplify_locals_new_equivalences(
        func,
        captures=Some(equivalences.captures),
        read_versions=Some(equivalences.read_versions),
        next_version=Some(equivalences.next_version),
      )
      if simplify_locals_scan_equivalent_region(
          env,
          func,
          @ir.hot_region_catch_list_ref(func, node_id),
          catch_equivalences,
          get_counts,
          tee_defined_locals,
        ) {
        changed = true
      }
      simplify_locals_clear_equivalences(equivalences)
      return changed
    }
    _ =>
      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
        }
        if simplify_locals_scan_equivalent_node(
            env,
            func,
            child_id,
            equivalences,
            get_counts,
            tee_defined_locals,
            @ir.hot_node_kind_is_call(func, node_id),
          ) {
          changed = true
        }
      }
  }
  if simplify_locals_scan_equivalent_current(
      env, func, node_id, equivalences, get_counts, tee_defined_locals, protect_tee_defined_locals,
    ) {
    changed = true
  }
  changed
}

///|
fn simplify_locals_has_deferred_multi_use_local_set(
  func : @ir.HotFunc,
  get_counts : Array[Int32],
  deferred_sets : Array[Int32],
) -> Bool {
  for node_id in deferred_sets {
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::LocalSet => {
        let local_id = node.imm0
        if local_id >= 0 &&
          local_id < get_counts.length() &&
          get_counts[local_id] > 1 {
          return true
        }
      }
      _ => ()
    }
  }
  false
}

///|
fn simplify_locals_run_main_cycle(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  first_cycle : Bool,
  detached : Array[Int32],
  policy : SimplifyLocalsPolicy,
  protect_control_embedded_local_tee : Bool,
  get_count_cache~ : SimplifyLocalsGetCounts? = None,
  get_count_scans~ : Array[Int32]? = None,
) -> (Bool, Bool) {
  let sinkables = simplify_locals_new_sinkables(func, module_ctx=ctx.module_ctx)
  let count_gets_start_us = perf_start_timer_opt(
    ctx.perf,
    "detail:simplify-locals:count-local-gets",
  )
  let get_counts = simplify_locals_counts_for_cycle(
    func,
    get_count_cache,
    full_scans=get_count_scans,
  )
  perf_stop_timer_accumulate_opt(
    ctx.perf,
    "detail:simplify-locals:count-local-gets",
    count_gets_start_us,
  )
  let deferred_sets : Array[Int32] = []
  let scan_start_us = perf_start_timer_opt(
    ctx.perf,
    "detail:simplify-locals:scan-root-region",
  )
  let changed = simplify_locals_scan_region(
    func,
    @ir.HotRegionRef::root(),
    sinkables,
    first_cycle,
    get_counts,
    detached,
    policy,
    protect_control_embedded_local_tee,
    false,
    deferred_sets,
  )
  perf_stop_timer_accumulate_opt(
    ctx.perf,
    "detail:simplify-locals:scan-root-region",
    scan_start_us,
  )
  let has_deferred = first_cycle &&
    simplify_locals_has_deferred_multi_use_local_set(
      func, get_counts, deferred_sets,
    )
  (changed, has_deferred)
}

///|
fn simplify_locals_collect_equivalent_tee_defined_locals(
  func : @ir.HotFunc,
) -> Array[Bool] {
  let tee_defined = Array::make(@ir.hot_local_count(func), 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
    }
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::LocalTee => {
        let value_id = @ir.hot_child_get(func, node_id, 0)
        match simplify_locals_equivalent_local_get_value(func, value_id) {
          Some(_) =>
            if node.imm0 >= 0 && node.imm0 < tee_defined.length() {
              tee_defined[node.imm0] = true
            }
          None => ()
        }
      }
      _ => ()
    }
  }
  tee_defined
}

///|
fn simplify_locals_run_equivalent_cleanup(
  ctx : HotPassContext,
  func : @ir.HotFunc,
) -> Bool {
  let env = simplify_locals_type_env(ctx)
  let tee_defined_locals = simplify_locals_collect_equivalent_tee_defined_locals(
    func,
  )
  simplify_locals_scan_equivalent_region(
    env,
    func,
    @ir.HotRegionRef::root(),
    simplify_locals_new_equivalences(func),
    simplify_locals_count_local_gets(func),
    tee_defined_locals,
  )
}

///|
fn simplify_locals_equivalent_candidate_count(
  func : @ir.HotFunc,
  stop_after~ : Int32 = -1,
) -> Int32 {
  let mut count = 0
  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
    }
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::LocalSet | @ir.HotOp::LocalTee => {
        if @ir.hot_child_count(func, node_id) == 0 {
          continue
        }
        let value_id = @ir.hot_child_get(func, node_id, 0)
        match simplify_locals_equivalent_local_get_value(func, value_id) {
          Some(_) => {
            count = count + 1
            if stop_after >= 0 && count > stop_after {
              return count
            }
          }
          None => ()
        }
      }
      _ => ()
    }
  }
  count
}

///|
fn simplify_locals_fold_dead_set_copy_region(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  get_counts : Array[Int32],
  detached : Array[Int32],
  nop_scan_visits~ : Array[Int32]? = None,
) -> Bool {
  let mut changed = false
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    match @ir.hot_node_get(func, node_id).op {
      @ir.HotOp::Block | @ir.HotOp::Loop =>
        changed = simplify_locals_fold_dead_set_copy_region(
            ctx,
            func,
            @ir.hot_region_body_ref(func, node_id),
            get_counts,
            detached,
            nop_scan_visits~,
          ) ||
          changed
      @ir.HotOp::If => {
        changed = simplify_locals_fold_dead_set_copy_region(
            ctx,
            func,
            @ir.hot_region_then_ref(func, node_id),
            get_counts,
            detached,
            nop_scan_visits~,
          ) ||
          changed
        match @ir.hot_region_else_ref(func, node_id) {
          Some(else_ref) =>
            changed = simplify_locals_fold_dead_set_copy_region(
                ctx,
                func,
                else_ref,
                get_counts,
                detached,
                nop_scan_visits~,
              ) ||
              changed
          None => ()
        }
      }
      @ir.HotOp::Try => {
        changed = simplify_locals_fold_dead_set_copy_region(
            ctx,
            func,
            @ir.hot_region_body_ref(func, node_id),
            get_counts,
            detached,
            nop_scan_visits~,
          ) ||
          changed
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(catch_ref) =>
            changed = simplify_locals_fold_dead_set_copy_region(
                ctx,
                func,
                catch_ref,
                get_counts,
                detached,
                nop_scan_visits~,
              ) ||
              changed
          None => ()
        }
      }
      @ir.HotOp::TryTable => {
        changed = simplify_locals_fold_dead_set_copy_region(
            ctx,
            func,
            @ir.hot_region_body_ref(func, node_id),
            get_counts,
            detached,
            nop_scan_visits~,
          ) ||
          changed
        changed = simplify_locals_fold_dead_set_copy_region(
            ctx,
            func,
            @ir.hot_region_catch_list_ref(func, node_id),
            get_counts,
            detached,
            nop_scan_visits~,
          ) ||
          changed
      }
      _ => ()
    }
  }
  let mut idx = 0
  while idx + 3 < @ir.hot_region_root_count(func, region_ref) {
    let value_id = @ir.hot_region_get(func, region_ref, idx)
    if value_id >= 0 && @ir.hot_node_is_live(func, value_id) {
      match @ir.hot_node_get(func, value_id).op {
        @ir.HotOp::Nop => {
          match nop_scan_visits {
            Some(visits) => visits[0] += 1
            None => ()
          }
          idx += 1
          while idx < @ir.hot_region_root_count(func, region_ref) {
            let run_id = @ir.hot_region_get(func, region_ref, idx)
            if run_id < 0 || !@ir.hot_node_is_live(func, run_id) {
              break
            }
            match @ir.hot_node_get(func, run_id).op {
              @ir.HotOp::Nop => {
                match nop_scan_visits {
                  Some(visits) => visits[0] += 1
                  None => ()
                }
                idx += 1
              }
              _ => break
            }
          }
          continue
        }
        _ => ()
      }
    }
    let spill_set_id = @ir.hot_region_get(func, region_ref, idx + 1)
    let mut reload_idx = idx + 2
    while reload_idx < @ir.hot_region_root_count(func, region_ref) {
      let maybe_nop_id = @ir.hot_region_get(func, region_ref, reload_idx)
      if maybe_nop_id < 0 || !@ir.hot_node_is_live(func, maybe_nop_id) {
        break
      }
      match @ir.hot_node_get(func, maybe_nop_id).op {
        @ir.HotOp::Nop => {
          match nop_scan_visits {
            Some(visits) => visits[0] += 1
            None => ()
          }
          reload_idx += 1
        }
        _ => break
      }
    }
    let target_idx = reload_idx + 1
    if target_idx >= @ir.hot_region_root_count(func, region_ref) {
      idx += 1
      continue
    }
    let reload_id = @ir.hot_region_get(func, region_ref, reload_idx)
    let target_set_id = @ir.hot_region_get(func, region_ref, target_idx)
    if value_id >= 0 &&
      spill_set_id >= 0 &&
      reload_id >= 0 &&
      target_set_id >= 0 &&
      @ir.hot_node_is_live(func, value_id) &&
      @ir.hot_node_is_live(func, spill_set_id) &&
      @ir.hot_node_is_live(func, reload_id) &&
      @ir.hot_node_is_live(func, target_set_id) {
      let spill_set = @ir.hot_node_get(func, spill_set_id)
      let reload = @ir.hot_node_get(func, reload_id)
      let target_set = @ir.hot_node_get(func, target_set_id)
      match (spill_set.op, reload.op, target_set.op) {
        (@ir.HotOp::LocalSet, @ir.HotOp::LocalGet, @ir.HotOp::LocalSet) => {
          let scratch = spill_set.imm0
          if @ir.hot_node_result_arity(func, value_id) == 1 &&
            spill_set.child_count == 0 &&
            reload.child_count == 0 &&
            target_set.child_count == 0 &&
            scratch >= 0 &&
            scratch < get_counts.length() &&
            scratch != target_set.imm0 &&
            reload.imm0 == scratch &&
            get_counts[scratch] == 1 {
            let replacement_set = @ir.hot_build_local_set_from_value(
              func,
              target_set.imm0,
              value_id,
            )
            let removed = @ir.hot_region_splice(
              func,
              region_ref,
              idx,
              target_idx - idx + 1,
              [replacement_set],
            )
            for removed_id in removed {
              if removed_id != value_id {
                detached.push(removed_id)
              }
            }
            changed = true
            idx += 1
            continue
          }
        }
        _ => ()
      }
    }
    idx += 1
  }
  idx = 0
  while idx + 1 < @ir.hot_region_root_count(func, region_ref) {
    let first_id = @ir.hot_region_get(func, region_ref, idx)
    if first_id >= 0 && @ir.hot_node_is_live(func, first_id) {
      match @ir.hot_node_get(func, first_id).op {
        @ir.HotOp::Nop => {
          match nop_scan_visits {
            Some(visits) => visits[0] += 1
            None => ()
          }
          idx += 1
          while idx < @ir.hot_region_root_count(func, region_ref) {
            let run_id = @ir.hot_region_get(func, region_ref, idx)
            if run_id < 0 || !@ir.hot_node_is_live(func, run_id) {
              break
            }
            match @ir.hot_node_get(func, run_id).op {
              @ir.HotOp::Nop => {
                match nop_scan_visits {
                  Some(visits) => visits[0] += 1
                  None => ()
                }
                idx += 1
              }
              _ => break
            }
          }
          continue
        }
        _ => ()
      }
    }
    let mut second_idx = idx + 1
    while second_idx < @ir.hot_region_root_count(func, region_ref) {
      let maybe_nop_id = @ir.hot_region_get(func, region_ref, second_idx)
      if maybe_nop_id < 0 || !@ir.hot_node_is_live(func, maybe_nop_id) {
        break
      }
      match @ir.hot_node_get(func, maybe_nop_id).op {
        @ir.HotOp::Nop => {
          match nop_scan_visits {
            Some(visits) => visits[0] += 1
            None => ()
          }
          second_idx += 1
        }
        _ => break
      }
    }
    if second_idx >= @ir.hot_region_root_count(func, region_ref) {
      idx += 1
      continue
    }
    let second_id = @ir.hot_region_get(func, region_ref, second_idx)
    if first_id < 0 ||
      second_id < 0 ||
      !@ir.hot_node_is_live(func, first_id) ||
      !@ir.hot_node_is_live(func, second_id) {
      idx += 1
      continue
    }
    let first = @ir.hot_node_get(func, first_id)
    let second = @ir.hot_node_get(func, second_id)
    match first.op {
      @ir.HotOp::LocalSet => ()
      _ => {
        idx += 1
        continue
      }
    }
    match second.op {
      @ir.HotOp::LocalSet => ()
      _ => {
        idx += 1
        continue
      }
    }
    if first.child_count != 1 || second.child_count != 1 {
      idx += 1
      continue
    }
    let scratch = first.imm0
    if scratch < 0 ||
      scratch >= get_counts.length() ||
      scratch == second.imm0 ||
      get_counts[scratch] != 1 {
      idx += 1
      continue
    }
    let get_id = @ir.hot_child_get(func, second_id, 0)
    if get_id < 0 || !@ir.hot_node_is_live(func, get_id) {
      idx += 1
      continue
    }
    let get_node = @ir.hot_node_get(func, get_id)
    match get_node.op {
      @ir.HotOp::LocalGet => ()
      _ => {
        idx += 1
        continue
      }
    }
    if get_node.imm0 != scratch {
      idx += 1
      continue
    }
    if pass_node_use_count(ctx, func, get_id) != 1 {
      idx += 1
      continue
    }
    @ir.hot_replace_node(
      func,
      first_id,
      simplify_locals_make_local_set(
        func,
        second.imm0,
        @ir.hot_child_get(func, first_id, 0),
      ),
    )
    @ir.hot_replace_node(func, second_id, simplify_locals_make_nop(func))
    detached.push(get_id)
    changed = true
    idx = second_idx + 1
  }
  changed
}

///|
fn simplify_locals_mark_one_armed_if_tail_sets(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  preserved : Array[Bool],
) -> Unit {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::If => {
        let then_ref = @ir.hot_region_then_ref(func, node_id)
        match @ir.hot_region_else_ref(func, node_id) {
          None =>
            match simplify_locals_region_tail_local_set(func, then_ref) {
              Some(tail) =>
                if tail.node_id >= 0 && tail.node_id < preserved.length() {
                  preserved[tail.node_id] = true
                }
              None => ()
            }
          Some(else_ref) =>
            simplify_locals_mark_one_armed_if_tail_sets(
              func, else_ref, preserved,
            )
        }
        simplify_locals_mark_one_armed_if_tail_sets(func, then_ref, preserved)
      }
      @ir.HotOp::Block | @ir.HotOp::Loop =>
        simplify_locals_mark_one_armed_if_tail_sets(
          func,
          @ir.hot_region_body_ref(func, node_id),
          preserved,
        )
      @ir.HotOp::Try => {
        simplify_locals_mark_one_armed_if_tail_sets(
          func,
          @ir.hot_region_body_ref(func, node_id),
          preserved,
        )
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(catch_ref) =>
            simplify_locals_mark_one_armed_if_tail_sets(
              func, catch_ref, preserved,
            )
          None => ()
        }
      }
      @ir.HotOp::TryTable => {
        simplify_locals_mark_one_armed_if_tail_sets(
          func,
          @ir.hot_region_body_ref(func, node_id),
          preserved,
        )
        simplify_locals_mark_one_armed_if_tail_sets(
          func,
          @ir.hot_region_catch_list_ref(func, node_id),
          preserved,
        )
      }
      _ => ()
    }
  }
}

///|
fn simplify_locals_one_armed_if_tail_sets(func : @ir.HotFunc) -> Array[Bool] {
  let preserved = Array::make(@ir.hot_node_count(func), false)
  simplify_locals_mark_one_armed_if_tail_sets(
    func,
    @ir.HotRegionRef::root(),
    preserved,
  )
  preserved
}

///|
fn simplify_locals_run_dead_cleanup(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  detached : Array[Int32],
  rehomed_owners : Array[SimplifyLocalsDetachedControlOwner],
  discount_unreachable_tail_gets : Bool,
  preserve_self_copy_reads~ : Bool = false,
  get_count_cache~ : SimplifyLocalsGetCounts? = None,
  get_count_scans~ : Array[Int32]? = None,
) -> Bool {
  let get_counts = simplify_locals_counts_for_cycle(
    func,
    get_count_cache,
    full_scans=get_count_scans,
  )
  if discount_unreachable_tail_gets {
    simplify_locals_discount_unreachable_root_tail_gets(func, get_counts)
  }
  let one_armed_if_tail_sets = simplify_locals_one_armed_if_tail_sets(func)
  let mut changed = simplify_locals_fold_dead_set_copy_region(
    ctx,
    func,
    @ir.HotRegionRef::root(),
    get_counts,
    detached,
  )
  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
    }
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::LocalSet => {
        let value_id = @ir.hot_child_get(func, node_id, 0)
        if value_id >= 0 && @ir.hot_node_is_live(func, value_id) {
          let value = @ir.hot_node_get(func, value_id)
          match value.op {
            @ir.HotOp::LocalTee if value.imm0 == node.imm0 => {
              @ir.hot_replace_node(
                func,
                node_id,
                simplify_locals_make_local_set(
                  func,
                  node.imm0,
                  @ir.hot_child_get(func, value_id, 0),
                ),
              )
              detached.push(value_id)
              changed = true
              continue
            }
            @ir.HotOp::LocalGet if value.imm0 == node.imm0 => {
              if preserve_self_copy_reads {
                @ir.hot_replace_node(
                  func,
                  node_id,
                  simplify_locals_make_drop(func, value_id),
                )
              } else {
                @ir.hot_replace_node(
                  func,
                  node_id,
                  simplify_locals_make_nop(func),
                )
                detached.push(value_id)
              }
              changed = true
              continue
            }
            _ => ()
          }
        }
        if get_counts[node.imm0] != 0 {
          continue
        }
        if node_id >= 0 &&
          node_id < one_armed_if_tail_sets.length() &&
          one_armed_if_tail_sets[node_id] {
          continue
        }
        if simplify_locals_value_is_pure(func, value_id) {
          @ir.hot_replace_node(func, node_id, simplify_locals_make_nop(func))
          detached.push(value_id)
        } else {
          @ir.hot_replace_node(
            func,
            node_id,
            simplify_locals_make_drop(func, value_id),
          )
        }
        changed = true
      }
      @ir.HotOp::LocalTee => {
        let value_id = @ir.hot_child_get(func, node_id, 0)
        if value_id >= 0 && @ir.hot_node_is_live(func, value_id) {
          let value = @ir.hot_node_get(func, value_id)
          match value.op {
            @ir.HotOp::LocalGet if value.imm0 == node.imm0 => {
              @ir.hot_replace_node(
                func,
                node_id,
                @ir.hot_node_get(func, value_id),
              )
              detached.push(value_id)
              changed = true
              continue
            }
            _ => ()
          }
        }
        if get_counts[node.imm0] != 0 {
          continue
        }
        if value_id >= 0 &&
          @ir.hot_node_is_live(func, value_id) &&
          @ir.hot_node_kind_is_control(func, value_id) {
          rehomed_owners.push({
            replacement_id: node_id,
            detached_id: value_id,
          })
          @ir.hot_replace_node(func, node_id, @ir.hot_node_get(func, value_id))
          detached.push(value_id)
          changed = true
          continue
        }
        if simplify_locals_value_can_clone_node(func, value_id) {
          @ir.hot_replace_node(func, node_id, @ir.hot_node_get(func, value_id))
          detached.push(value_id)
          changed = true
        }
      }
      _ => ()
    }
  }
  changed
}

///|
fn simplify_locals_try_remove_nostructure_transparent_copy_chain(
  func : @ir.HotFunc,
  detached : Array[Int32],
) -> Bool {
  // Binaryen's tee-enabled no-structure cycles delete this exact transparent
  // copy component before its final dropped read. Keep the precleanup narrow:
  // two distinct scratch locals, one inner branch-free block, exact aggregate
  // use counts, matching defaultable types, and no effectful value producers.
  let region_ref = @ir.HotRegionRef::root()
  let get_counts = simplify_locals_count_local_gets(func)
  for first_idx = 0
      first_idx < @ir.hot_region_root_count(func, region_ref)
      first_idx = first_idx + 1 {
    let first_id = @ir.hot_region_get(func, region_ref, first_idx)
    if first_id < 0 || !@ir.hot_node_is_live(func, first_id) {
      continue
    }
    let first = @ir.hot_node_get(func, first_id)
    match first.op {
      @ir.HotOp::LocalSet => ()
      _ => continue
    }
    if first.child_count != 1 {
      continue
    }
    let first_get_id = @ir.hot_child_get(func, first_id, 0)
    if first_get_id < 0 || !@ir.hot_node_is_live(func, first_get_id) {
      continue
    }
    let first_get = @ir.hot_node_get(func, first_get_id)
    match first_get.op {
      @ir.HotOp::LocalGet => ()
      _ => continue
    }
    if first_get.child_count != 0 {
      continue
    }
    let source_local = first_get.imm0
    let target_local = first.imm0
    let block_idx = match
      simplify_locals_region_next_live_non_nop_root_idx(
        func, region_ref, first_idx,
      ) {
      Some(idx) => idx
      None => continue
    }
    let block_id = @ir.hot_region_get(func, region_ref, block_idx)
    if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
      continue
    }
    match @ir.hot_node_get(func, block_id).op {
      @ir.HotOp::Block => ()
      _ => continue
    }
    let block_body = @ir.hot_region_body_ref(func, block_id)
    let inner_idx = match
      simplify_locals_region_last_live_non_nop_root_idx(func, block_body) {
      Some(idx) => idx
      None => continue
    }
    let mut live_inner_count = 0
    for idx = 0
        idx < @ir.hot_region_root_count(func, block_body)
        idx = idx + 1 {
      let inner_root = @ir.hot_region_get(func, block_body, idx)
      if inner_root >= 0 && @ir.hot_node_is_live(func, inner_root) {
        match @ir.hot_node_get(func, inner_root).op {
          @ir.HotOp::Nop => ()
          _ => live_inner_count = live_inner_count + 1
        }
      }
    }
    if live_inner_count != 1 {
      continue
    }
    let inner_set_id = @ir.hot_region_get(func, block_body, inner_idx)
    let inner_set = @ir.hot_node_get(func, inner_set_id)
    match inner_set.op {
      @ir.HotOp::LocalSet => ()
      _ => continue
    }
    if inner_set.child_count != 1 {
      continue
    }
    let inner_get_id = @ir.hot_child_get(func, inner_set_id, 0)
    if inner_get_id < 0 || !@ir.hot_node_is_live(func, inner_get_id) {
      continue
    }
    let inner_get = @ir.hot_node_get(func, inner_get_id)
    match inner_get.op {
      @ir.HotOp::LocalGet => ()
      _ => continue
    }
    let scratch_local = inner_set.imm0
    if inner_get.imm0 != target_local ||
      scratch_local == target_local ||
      scratch_local == source_local {
      continue
    }
    let copy_back_idx = match
      simplify_locals_region_next_live_non_nop_root_idx(
        func, region_ref, block_idx,
      ) {
      Some(idx) => idx
      None => continue
    }
    let copy_back_id = @ir.hot_region_get(func, region_ref, copy_back_idx)
    let copy_back = @ir.hot_node_get(func, copy_back_id)
    match copy_back.op {
      @ir.HotOp::LocalSet => ()
      _ => continue
    }
    if copy_back.imm0 != target_local || copy_back.child_count != 1 {
      continue
    }
    let scratch_get_id = @ir.hot_child_get(func, copy_back_id, 0)
    if scratch_get_id < 0 || !@ir.hot_node_is_live(func, scratch_get_id) {
      continue
    }
    let scratch_get = @ir.hot_node_get(func, scratch_get_id)
    match scratch_get.op {
      @ir.HotOp::LocalGet => ()
      _ => continue
    }
    if scratch_get.imm0 != scratch_local {
      continue
    }
    let drop_idx = match
      simplify_locals_region_next_live_non_nop_root_idx(
        func, region_ref, copy_back_idx,
      ) {
      Some(idx) => idx
      None => continue
    }
    let drop_id = @ir.hot_region_get(func, region_ref, drop_idx)
    let drop_node = @ir.hot_node_get(func, drop_id)
    match drop_node.op {
      @ir.HotOp::Drop => ()
      _ => continue
    }
    if drop_node.child_count != 1 {
      continue
    }
    let target_get_id = @ir.hot_child_get(func, drop_id, 0)
    if target_get_id < 0 || !@ir.hot_node_is_live(func, target_get_id) {
      continue
    }
    let target_get = @ir.hot_node_get(func, target_get_id)
    match target_get.op {
      @ir.HotOp::LocalGet => ()
      _ => continue
    }
    if target_get.imm0 != target_local ||
      target_local < 0 ||
      target_local >= get_counts.length() ||
      scratch_local < 0 ||
      scratch_local >= get_counts.length() ||
      get_counts[target_local] != 2 ||
      get_counts[scratch_local] != 1 ||
      @ir.hot_local_type(func, source_local) !=
      @ir.hot_local_type(func, target_local) ||
      @ir.hot_local_type(func, target_local) !=
      @ir.hot_local_type(func, scratch_local) ||
      !simplify_locals_local_type_is_defaultable(func, target_local) {
      continue
    }
    for node_id in [first_id, inner_set_id, copy_back_id, drop_id] {
      @ir.hot_replace_node(func, node_id, simplify_locals_make_nop(func))
    }
    for node_id in [first_get_id, inner_get_id, scratch_get_id, target_get_id] {
      detached.push(node_id)
    }
    return true
  }
  false
}

///|
fn simplify_locals_run_with_options(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  policy : SimplifyLocalsPolicy,
  reuse_get_counts~ : Bool = true,
  get_count_scans~ : Array[Int32]? = None,
) -> HotPassResult {
  let get_count_cache = if reuse_get_counts {
    simplify_locals_new_get_count_cache(func)
  } else {
    None
  }
  let precleanup_detached : Array[Int32] = []
  let precleanup_changed = !policy.allow_structure_rewrites &&
    policy.allow_sink_tee &&
    policy.allow_nesting &&
    simplify_locals_try_remove_nostructure_transparent_copy_chain(
      func, precleanup_detached,
    )
  simplify_locals_delete_detached_nodes(func, precleanup_detached)
  let mut first_cycle = true
  let mut deferred_follow_up_cycle = false
  let mut changed = precleanup_changed
  while true {
    let current_is_deferred_follow_up = deferred_follow_up_cycle
    deferred_follow_up_cycle = false
    let main_cycle_start_us = perf_start_timer_opt(
      ctx.perf,
      "detail:simplify-locals:main-cycle",
    )
    let main_detached : Array[Int32] = []
    let (main_changed, has_deferred_main_cycle) = simplify_locals_run_main_cycle(
      ctx,
      func,
      first_cycle,
      main_detached,
      policy,
      false,
      get_count_cache~,
      get_count_scans~,
    )
    perf_stop_timer_opt(
      ctx.perf,
      "detail:simplify-locals:main-cycle",
      main_cycle_start_us,
    )
    let main_delete_start_us = perf_start_timer_opt(
      ctx.perf,
      "detail:simplify-locals:delete-main-detached",
    )
    simplify_locals_delete_detached_nodes(func, main_detached)
    perf_stop_timer_accumulate_opt(
      ctx.perf,
      "detail:simplify-locals:delete-main-detached",
      main_delete_start_us,
    )

    let cleanup_start_us = perf_start_timer_opt(
      ctx.perf,
      "detail:simplify-locals:dead-cleanup",
    )
    let cleanup_detached : Array[Int32] = []
    let cleanup_rehomed_owners : Array[SimplifyLocalsDetachedControlOwner] = []
    let cleanup_changed = simplify_locals_run_dead_cleanup(
      ctx,
      func,
      cleanup_detached,
      cleanup_rehomed_owners,
      policy.allow_nesting,
      preserve_self_copy_reads=policy.allow_structure_rewrites &&
        !policy.allow_sink_tee,
      get_count_cache~,
      get_count_scans~,
    )
    perf_stop_timer_accumulate_opt(
      ctx.perf,
      "detail:simplify-locals:dead-cleanup",
      cleanup_start_us,
    )
    let cleanup_delete_start_us = perf_start_timer_opt(
      ctx.perf,
      "detail:simplify-locals:delete-dead-detached",
    )
    simplify_locals_rehome_detached_control_owners(func, cleanup_rehomed_owners)
    simplify_locals_delete_detached_nodes(func, cleanup_detached)
    perf_stop_timer_accumulate_opt(
      ctx.perf,
      "detail:simplify-locals:delete-dead-detached",
      cleanup_delete_start_us,
    )

    if main_changed || cleanup_changed {
      changed = true
    }
    if first_cycle {
      first_cycle = false
      // Binaryen's initial follow-up main cycle is needed for deferred multi-use
      // local.set work that may become tee-shaped on the later cycle. No-tee
      // variants also need one follow-up after a main or dead-cleanup rewrite:
      // copy retargeting can expose a computed set/get only after the cycle's
      // get facts were collected, and leaving that carrier for the next pass is
      // non-idempotent.
      if has_deferred_main_cycle ||
        (policy.allow_structure_rewrites && main_changed) ||
        (!policy.allow_sink_tee && (main_changed || cleanup_changed)) {
        deferred_follow_up_cycle = true
        continue
      }
    } else if main_changed || cleanup_changed {
      // The deferred follow-up is the deliberate second main/dead pass for
      // multi-use tee/drop cleanup. Its changes do not require a third no-op
      // confirmation scan before the late no-structure cleanup gates below.
      if !current_is_deferred_follow_up {
        continue
      }
    }

    if policy.allow_structure_rewrites {
      let structure_start_us = perf_start_timer_opt(
        ctx.perf,
        "detail:simplify-locals:structure",
      )
      let structure_detached : Array[Int32] = []
      let structure_changed = simplify_locals_run_structure_rewrites(
        func,
        structure_detached,
        allow_multi_use=policy.allow_sink_tee,
      )
      perf_stop_timer_opt(
        ctx.perf,
        "detail:simplify-locals:structure",
        structure_start_us,
      )
      let structure_delete_start_us = perf_start_timer_opt(
        ctx.perf,
        "detail:simplify-locals:delete-structure-detached",
      )
      simplify_locals_delete_detached_nodes(func, structure_detached)
      perf_stop_timer_opt(
        ctx.perf,
        "detail:simplify-locals:delete-structure-detached",
        structure_delete_start_us,
      )

      if structure_changed {
        changed = true
        continue
      }
    }

    let local_count = @ir.hot_local_count(func)
    if local_count >= 96 {
      break
    }
    let sparse_equivalent_limit = if local_count >= 56 {
      local_count / 6
    } else {
      -1
    }
    let equivalent_candidate_count = simplify_locals_equivalent_candidate_count(
      func,
      stop_after=sparse_equivalent_limit,
    )
    if equivalent_candidate_count == 0 ||
      (
        sparse_equivalent_limit >= 0 &&
        equivalent_candidate_count <= sparse_equivalent_limit
      ) {
      break
    }

    let equivalent_start_us = perf_start_timer_opt(
      ctx.perf,
      "detail:simplify-locals:equivalent-cleanup",
    )
    let late_changed = simplify_locals_run_equivalent_cleanup(ctx, func)
    perf_stop_timer_opt(
      ctx.perf,
      "detail:simplify-locals:equivalent-cleanup",
      equivalent_start_us,
    )
    let late_cleanup_start_us = perf_start_timer_opt(
      ctx.perf,
      "detail:simplify-locals:late-dead-cleanup",
    )
    let late_cleanup_detached : Array[Int32] = []
    let late_cleanup_rehomed_owners : Array[SimplifyLocalsDetachedControlOwner] = []
    let late_cleanup_changed = simplify_locals_run_dead_cleanup(
      ctx,
      func,
      late_cleanup_detached,
      late_cleanup_rehomed_owners,
      policy.allow_nesting,
      preserve_self_copy_reads=policy.allow_structure_rewrites &&
        !policy.allow_sink_tee,
      get_count_cache~,
      get_count_scans~,
    )
    perf_stop_timer_opt(
      ctx.perf,
      "detail:simplify-locals:late-dead-cleanup",
      late_cleanup_start_us,
    )
    let late_delete_start_us = perf_start_timer_opt(
      ctx.perf,
      "detail:simplify-locals:delete-late-detached",
    )
    simplify_locals_rehome_detached_control_owners(
      func, late_cleanup_rehomed_owners,
    )
    simplify_locals_delete_detached_nodes(func, late_cleanup_detached)
    perf_stop_timer_opt(
      ctx.perf,
      "detail:simplify-locals:delete-late-detached",
      late_delete_start_us,
    )

    if late_changed || late_cleanup_changed {
      changed = true
      continue
    }
    break
  }
  if !changed {
    return HotPassResult::unchanged()
  }
  ignore(@ir.hot_repair_nondefaultable_local_scopes(func))
  pass_mark_mutated(ctx, func)
  HotPassResult::changed()
}

///|
fn simplify_locals_is_region_child_slot(
  func : @ir.HotFunc,
  node_id : Int32,
  slot : Int32,
) -> Bool {
  if @ir.hot_node_get(func, node_id).op is @ir.HotOp::If {
    slot == 1 || slot == 2
  } else {
    slot >= pass_value_operand_count(func, node_id)
  }
}

///|
fn simplify_locals_region_contains_node_before(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  before_node_id : Int32,
  seen : Array[Bool],
) -> Bool {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    if simplify_locals_node_contains_value_before(
        func,
        @ir.hot_region_get(func, region_ref, idx),
        before_node_id,
        seen,
      ) {
      return true
    }
  }
  false
}

///|
fn simplify_locals_node_contains_value_before(
  func : @ir.HotFunc,
  node_id : Int32,
  before_node_id : Int32,
  seen : Array[Bool],
) -> Bool {
  if node_id < 0 ||
    node_id >= seen.length() ||
    seen[node_id] ||
    !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  seen[node_id] = true
  if node_id < before_node_id && @ir.hot_node_result_arity(func, node_id) > 0 {
    return true
  }
  let node = @ir.hot_node_get(func, node_id)
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if !simplify_locals_is_region_child_slot(func, node_id, slot) &&
      simplify_locals_node_contains_value_before(
        func,
        @ir.hot_child_get(func, node_id, slot),
        before_node_id,
        seen,
      ) {
      return true
    }
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      simplify_locals_region_contains_node_before(
        func,
        @ir.hot_region_body_ref(func, node_id),
        before_node_id,
        seen,
      )
    @ir.HotOp::If => {
      if simplify_locals_region_contains_node_before(
          func,
          @ir.hot_region_then_ref(func, node_id),
          before_node_id,
          seen,
        ) {
        return true
      }
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_ref) =>
          simplify_locals_region_contains_node_before(
            func, else_ref, before_node_id, seen,
          )
        None => false
      }
    }
    @ir.HotOp::Try => {
      if simplify_locals_region_contains_node_before(
          func,
          @ir.hot_region_body_ref(func, node_id),
          before_node_id,
          seen,
        ) {
        return true
      }
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_ref) =>
          simplify_locals_region_contains_node_before(
            func, catch_ref, before_node_id, seen,
          )
        None => false
      }
    }
    @ir.HotOp::TryTable =>
      simplify_locals_region_contains_node_before(
        func,
        @ir.hot_region_body_ref(func, node_id),
        before_node_id,
        seen,
      ) ||
      simplify_locals_region_contains_node_before(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
        before_node_id,
        seen,
      )
    _ => false
  }
}

///|
fn simplify_locals_set_first_use_crosses_intervening_local_write(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  root_idx : Int32,
  local_id : Int32,
  minimums~ : SimplifyLocalsValueOrderMinimums? = None,
  later_seen_rows~ : Array[Int32]? = None,
) -> Bool {
  if root_idx < 0 {
    return false
  }
  let local_count = @ir.hot_local_count(func)
  let intervening_reads : Array[Int32] = []
  let intervening_writes : Array[Int32] = []
  let intervening_read_stamps = Array::make(local_count, -1)
  let intervening_write_stamps = Array::make(local_count, -1)
  for idx = root_idx + 1
      idx < @ir.hot_region_root_count(func, region_ref)
      idx = idx + 1 {
    let root_id = @ir.hot_region_get(func, region_ref, idx)
    if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
      continue
    }
    if simplify_locals_node_reads_local(func, root_id, local_id) {
      let use_reads : Array[Int32] = []
      let use_writes : Array[Int32] = []
      simplify_locals_collect_subtree_local_effects(
        func,
        root_id,
        use_reads,
        use_writes,
        Array::make(local_count, -1),
        Array::make(local_count, -1),
        0,
      )
      for read_local in use_reads {
        if intervening_writes.contains(read_local) {
          return true
        }
      }
      let use_root_writes_local = match @ir.hot_node_get(func, root_id).op {
        @ir.HotOp::LocalSet | @ir.HotOp::LocalTee => true
        _ => false
      }
      if use_root_writes_local && !use_writes.is_empty() {
        for later_idx = idx + 1
            later_idx < @ir.hot_region_root_count(func, region_ref)
            later_idx = later_idx + 1 {
          let later_root = @ir.hot_region_get(func, region_ref, later_idx)
          if later_root < 0 || !@ir.hot_node_is_live(func, later_root) {
            continue
          }
          if simplify_locals_contains_value_before_with_minimums(
              func,
              later_root,
              root_id,
              minimums,
              seen_rows=later_seen_rows,
            ) {
            let later_reads : Array[Int32] = []
            let later_writes : Array[Int32] = []
            simplify_locals_collect_subtree_local_effects(
              func,
              later_root,
              later_reads,
              later_writes,
              Array::make(local_count, -1),
              Array::make(local_count, -1),
              0,
            )
            for written_local in use_writes {
              if later_reads.contains(written_local) {
                return true
              }
            }
          }
        }
      }
      return false
    }
    simplify_locals_collect_subtree_local_effects(
      func, root_id, intervening_reads, intervening_writes, intervening_read_stamps,
      intervening_write_stamps, 0,
    )
    let root = @ir.hot_node_get(func, root_id)
    match root.op {
      @ir.HotOp::LocalSet | @ir.HotOp::LocalTee if root.imm0 == local_id =>
        return false
      _ => ()
    }
  }
  false
}

///|
fn simplify_locals_set_crosses_older_stack_value(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  root_idx : Int32,
  set_node_id : Int32,
  local_id : Int32,
  minimums~ : SimplifyLocalsValueOrderMinimums? = None,
) -> Bool {
  if root_idx < 0 {
    return false
  }
  for idx = root_idx + 1
      idx < @ir.hot_region_root_count(func, region_ref)
      idx = idx + 1 {
    let root_id = @ir.hot_region_get(func, region_ref, idx)
    if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
      continue
    }
    if simplify_locals_contains_value_before_with_minimums(
        func, root_id, set_node_id, minimums,
      ) {
      return true
    }
    if simplify_locals_node_reads_local(func, root_id, local_id) {
      return false
    }
    let root = @ir.hot_node_get(func, root_id)
    match root.op {
      @ir.HotOp::LocalSet | @ir.HotOp::LocalTee if root.imm0 == local_id =>
        return false
      _ => ()
    }
  }
  false
}

///|
fn simplify_locals_node_has_stack_effect_hazard(
  func : @ir.HotFunc,
  node_id : @ir.NodeId,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Call
    | @ir.HotOp::CallIndirect
    | @ir.HotOp::CallRef
    | @ir.HotOp::MemoryCopy
    | @ir.HotOp::MemoryFill => return true
    _ => ()
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if simplify_locals_is_region_child_slot(func, node_id, slot) {
      continue
    }
    if simplify_locals_node_has_stack_effect_hazard(
        func,
        @ir.hot_child_get(func, node_id, slot),
      ) {
      return true
    }
  }
  false
}

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

///|
fn simplify_locals_node_reads_local(
  func : @ir.HotFunc,
  node_id : @ir.NodeId,
  local_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalGet if node.imm0 == local_id => return true
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      if simplify_locals_region_reads_local(
          func,
          @ir.hot_region_body_ref(func, node_id),
          local_id,
        ) {
        return true
      }
    @ir.HotOp::If => {
      if simplify_locals_region_reads_local(
          func,
          @ir.hot_region_then_ref(func, node_id),
          local_id,
        ) {
        return true
      }
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_ref) =>
          if simplify_locals_region_reads_local(func, else_ref, local_id) {
            return true
          }
        None => ()
      }
    }
    @ir.HotOp::Try => {
      if simplify_locals_region_reads_local(
          func,
          @ir.hot_region_body_ref(func, node_id),
          local_id,
        ) {
        return true
      }
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_ref) =>
          if simplify_locals_region_reads_local(func, catch_ref, local_id) {
            return true
          }
        None => ()
      }
    }
    @ir.HotOp::TryTable =>
      if simplify_locals_region_reads_local(
          func,
          @ir.hot_region_body_ref(func, node_id),
          local_id,
        ) ||
        simplify_locals_region_reads_local(
          func,
          @ir.hot_region_catch_list_ref(func, node_id),
          local_id,
        ) {
        return true
      }
    _ => ()
  }
  let operand_node = @ir.hot_node_get(func, node_id)
  let operand_count = pass_value_operand_count(func, node_id)
  for slot = 0; slot < operand_count; slot = slot + 1 {
    if simplify_locals_node_reads_local(
        func,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, slot),
        ),
        local_id,
      ) {
      return true
    }
  }
  false
}

///|
priv struct SimplifyLocalsRootHazardScanState {
  pending_writers : Array[Bool]
  seen_stamps : Array[Int32]
  mut next_stamp : Int32
  mut pending_count : Int32
  mut visited_nodes : Int32
}

///|
fn SimplifyLocalsRootHazardScanState::new(
  func : @ir.HotFunc,
) -> SimplifyLocalsRootHazardScanState {
  {
    pending_writers: Array::make(@ir.hot_local_count(func), false),
    seen_stamps: Array::make(@ir.hot_node_count(func), 0),
    next_stamp: 0,
    pending_count: 0,
    visited_nodes: 0,
  }
}

///|
fn simplify_locals_region_reads_pending_root_hazard_local(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  state : SimplifyLocalsRootHazardScanState,
) -> Bool {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    if simplify_locals_node_reads_pending_root_hazard_local(
        func,
        @ir.hot_region_get(func, region_ref, idx),
        state,
      ) {
      return true
    }
  }
  false
}

///|
fn simplify_locals_node_reads_pending_root_hazard_local(
  func : @ir.HotFunc,
  node_id : @ir.NodeId,
  state : SimplifyLocalsRootHazardScanState,
) -> Bool {
  if node_id < 0 ||
    node_id >= state.seen_stamps.length() ||
    !@ir.hot_node_is_live(func, node_id) ||
    state.seen_stamps[node_id] == state.next_stamp {
    return false
  }
  state.seen_stamps[node_id] = state.next_stamp
  state.visited_nodes += 1
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalGet if node.imm0 >= 0 &&
      node.imm0 < state.pending_writers.length() &&
      state.pending_writers[node.imm0] => return true
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      if simplify_locals_region_reads_pending_root_hazard_local(
          func,
          @ir.hot_region_body_ref(func, node_id),
          state,
        ) {
        return true
      }
    @ir.HotOp::If => {
      if simplify_locals_region_reads_pending_root_hazard_local(
          func,
          @ir.hot_region_then_ref(func, node_id),
          state,
        ) {
        return true
      }
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_ref) =>
          if simplify_locals_region_reads_pending_root_hazard_local(
              func, else_ref, state,
            ) {
            return true
          }
        None => ()
      }
    }
    @ir.HotOp::Try => {
      if simplify_locals_region_reads_pending_root_hazard_local(
          func,
          @ir.hot_region_body_ref(func, node_id),
          state,
        ) {
        return true
      }
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_ref) =>
          if simplify_locals_region_reads_pending_root_hazard_local(
              func, catch_ref, state,
            ) {
            return true
          }
        None => ()
      }
    }
    @ir.HotOp::TryTable =>
      if simplify_locals_region_reads_pending_root_hazard_local(
          func,
          @ir.hot_region_body_ref(func, node_id),
          state,
        ) ||
        simplify_locals_region_reads_pending_root_hazard_local(
          func,
          @ir.hot_region_catch_list_ref(func, node_id),
          state,
        ) {
        return true
      }
    _ => ()
  }
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if simplify_locals_is_region_child_slot(func, node_id, slot) {
      continue
    }
    if simplify_locals_node_reads_pending_root_hazard_local(
        func,
        @ir.hot_child_get(func, node_id, slot),
        state,
      ) {
      return true
    }
  }
  false
}

///|
fn simplify_locals_root_local_set_stack_hazard_scan(
  func : @ir.HotFunc,
) -> (Bool, Int32) {
  let root_ref = @ir.HotRegionRef::root()
  if @ir.hot_region_root_count(func, root_ref) <= 1 {
    return (false, 0)
  }
  let state = SimplifyLocalsRootHazardScanState::new(func)
  for idx = 0; idx < @ir.hot_region_root_count(func, root_ref); idx = idx + 1 {
    let root_id = @ir.hot_region_get(func, root_ref, idx)
    if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
      continue
    }
    let has_stack_hazard = simplify_locals_node_has_stack_effect_hazard(
      func, root_id,
    )
    if has_stack_hazard && state.pending_count > 0 {
      state.next_stamp += 1
      if simplify_locals_node_reads_pending_root_hazard_local(
          func, root_id, state,
        ) {
        return (true, state.visited_nodes)
      }
    }
    let root = @ir.hot_node_get(func, root_id)
    match root.op {
      @ir.HotOp::LocalSet | @ir.HotOp::LocalTee if root.imm0 >= 0 &&
        root.imm0 < state.pending_writers.length() => {
        if state.pending_writers[root.imm0] {
          state.pending_count -= 1
        }
        state.pending_writers[root.imm0] = has_stack_hazard
        if has_stack_hazard {
          state.pending_count += 1
        }
      }
      _ => ()
    }
  }
  (false, state.visited_nodes)
}

///|
fn simplify_locals_should_skip_root_local_set_stack_hazard(
  func : @ir.HotFunc,
) -> Bool {
  simplify_locals_root_local_set_stack_hazard_scan(func).0
}

///|
fn simplify_locals_should_skip_root_local_set_stack_global_hazard(
  func : @ir.HotFunc,
) -> Bool {
  let root_ref = @ir.HotRegionRef::root()
  if @ir.hot_region_root_count(func, root_ref) <= 1 {
    return false
  }
  let mut has_local_set = false
  let mut has_stack_effect = false
  let mut has_global_state = false
  for idx = 0; idx < @ir.hot_region_root_count(func, root_ref); idx = idx + 1 {
    let root_id = @ir.hot_region_get(func, root_ref, idx)
    if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
      continue
    }
    match @ir.hot_node_get(func, root_id).op {
      @ir.HotOp::LocalSet => has_local_set = true
      _ => ()
    }
    if simplify_locals_node_has_stack_effect_hazard(func, root_id) {
      has_stack_effect = true
    }
    if simplify_locals_effects_has(
        simplify_locals_effects_mask_for_subtree(func, root_id),
        @ir.EFFECT_MASK_GLOBAL_STATE,
      ) {
      has_global_state = true
    }
  }
  has_local_set && has_stack_effect && has_global_state
}

///|
fn simplify_locals_memory_write_scan_push_region(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  pending : Array[Int32],
) -> Unit {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    pending.push(@ir.hot_region_get(func, region_ref, idx))
  }
}

///|
fn simplify_locals_root_memory_write_scan(func : @ir.HotFunc) -> (Bool, Int32) {
  let pending : Array[Int32] = []
  simplify_locals_memory_write_scan_push_region(
    func,
    @ir.HotRegionRef::root(),
    pending,
  )
  let seen = Array::make(@ir.hot_node_count(func), false)
  let mut visited_nodes = 0
  while !pending.is_empty() {
    let node_id = pending.pop().unwrap()
    if node_id < 0 ||
      node_id >= seen.length() ||
      seen[node_id] ||
      !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    seen[node_id] = true
    visited_nodes += 1
    if simplify_locals_effects_has(
        simplify_locals_effects_mask_for_exact_node(func, node_id),
        @ir.EFFECT_MASK_MEMORY_WRITE,
      ) {
      return (true, visited_nodes)
    }
    let node = @ir.hot_node_get(func, node_id)
    for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
      if !simplify_locals_is_region_child_slot(func, node_id, slot) {
        pending.push(@ir.hot_child_get(func, node_id, slot))
      }
    }
    match node.op {
      @ir.HotOp::Block | @ir.HotOp::Loop =>
        simplify_locals_memory_write_scan_push_region(
          func,
          @ir.hot_region_body_ref(func, node_id),
          pending,
        )
      @ir.HotOp::If => {
        simplify_locals_memory_write_scan_push_region(
          func,
          @ir.hot_region_then_ref(func, node_id),
          pending,
        )
        match @ir.hot_region_else_ref(func, node_id) {
          Some(else_ref) =>
            simplify_locals_memory_write_scan_push_region(
              func, else_ref, pending,
            )
          None => ()
        }
      }
      @ir.HotOp::Try => {
        simplify_locals_memory_write_scan_push_region(
          func,
          @ir.hot_region_body_ref(func, node_id),
          pending,
        )
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(catch_ref) =>
            simplify_locals_memory_write_scan_push_region(
              func, catch_ref, pending,
            )
          None => ()
        }
      }
      @ir.HotOp::TryTable => {
        simplify_locals_memory_write_scan_push_region(
          func,
          @ir.hot_region_body_ref(func, node_id),
          pending,
        )
        simplify_locals_memory_write_scan_push_region(
          func,
          @ir.hot_region_catch_list_ref(func, node_id),
          pending,
        )
      }
      _ => ()
    }
  }
  (false, visited_nodes)
}

///|
fn simplify_locals_large_local_tee_memory_write_hazard_scan(
  func : @ir.HotFunc,
) -> (Bool, Int32) {
  if @ir.hot_local_count(func) < 16 || !simplify_locals_has_local_tee(func) {
    return (false, 0)
  }
  simplify_locals_root_memory_write_scan(func)
}

///|
fn simplify_locals_should_skip_large_local_tee_memory_write_hazard(
  func : @ir.HotFunc,
) -> Bool {
  simplify_locals_large_local_tee_memory_write_hazard_scan(func).0
}

///|
fn simplify_locals_run(
  ctx : HotPassContext,
  func : @ir.HotFunc,
) -> HotPassResult {
  if simplify_locals_should_skip_large_local_tee_memory_write_hazard(func) {
    pipeline_trace_hook(
      ctx.trace,
      ctx.descriptor.name,
      "skip-hot",
      payload="reason=large-local-tee-memory-write-hazard-noop",
    )
    return HotPassResult::unchanged()
  }
  simplify_locals_run_with_options(
    ctx,
    func,
    SimplifyLocalsPolicy::new(true, true, true),
  )
}

///|
fn simplify_locals_notee_run(
  ctx : HotPassContext,
  func : @ir.HotFunc,
) -> HotPassResult {
  if simplify_locals_should_skip_large_local_tee_memory_write_hazard(func) {
    pipeline_trace_hook(
      ctx.trace,
      ctx.descriptor.name,
      "skip-hot",
      payload="reason=large-local-tee-memory-write-hazard-noop",
    )
    return HotPassResult::unchanged()
  }
  if (@ir.hot_local_count(func) >= 64 || @ir.hot_node_count(func) >= 512) &&
    simplify_locals_should_skip_root_local_set_stack_global_hazard(func) {
    pipeline_trace_hook(
      ctx.trace,
      ctx.descriptor.name,
      "skip-hot",
      payload="reason=root-local-set-stack-global-hazard-noop",
    )
    return HotPassResult::unchanged()
  }
  simplify_locals_run_with_options(
    ctx,
    func,
    SimplifyLocalsPolicy::new(true, false, true),
  )
}

///|
fn simplify_locals_has_local_tee(func : @ir.HotFunc) -> Bool {
  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
    }
    match @ir.hot_node_get(func, node_id).op {
      @ir.HotOp::LocalTee => return true
      _ => ()
    }
  }
  false
}

///|
fn simplify_locals_region_has_control_embedded_local_tee(
  func : @ir.HotFunc,
  region : @ir.HotRegionRef,
  inside_control : Bool,
) -> Bool {
  for idx = 0; idx < @ir.hot_region_root_count(func, region); idx = idx + 1 {
    if simplify_locals_node_has_control_embedded_local_tee(
        func,
        @ir.hot_region_get(func, region, idx),
        inside_control,
      ) {
      return true
    }
  }
  false
}

///|
fn simplify_locals_node_has_control_embedded_local_tee(
  func : @ir.HotFunc,
  node_id : Int32,
  inside_control : Bool,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::LocalTee => return inside_control
    _ => ()
  }
  let child_inside_control = inside_control ||
    (match node.op {
      @ir.HotOp::Block
      | @ir.HotOp::Loop
      | @ir.HotOp::If
      | @ir.HotOp::Try
      | @ir.HotOp::TryTable => true
      _ => false
    })
  for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
    if simplify_locals_is_region_child_slot(func, node_id, slot) {
      continue
    }
    if simplify_locals_node_has_control_embedded_local_tee(
        func,
        @ir.hot_child_get(func, node_id, slot),
        child_inside_control,
      ) {
      return true
    }
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      simplify_locals_region_has_control_embedded_local_tee(
        func,
        @ir.hot_region_body_ref(func, node_id),
        true,
      )
    @ir.HotOp::If => {
      if simplify_locals_region_has_control_embedded_local_tee(
          func,
          @ir.hot_region_then_ref(func, node_id),
          true,
        ) {
        return true
      }
      match @ir.hot_region_else_ref(func, node_id) {
        Some(else_region) =>
          simplify_locals_region_has_control_embedded_local_tee(
            func, else_region, true,
          )
        None => false
      }
    }
    @ir.HotOp::Try => {
      if simplify_locals_region_has_control_embedded_local_tee(
          func,
          @ir.hot_region_body_ref(func, node_id),
          true,
        ) {
        return true
      }
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(catch_region) =>
          simplify_locals_region_has_control_embedded_local_tee(
            func, catch_region, true,
          )
        None => false
      }
    }
    @ir.HotOp::TryTable =>
      simplify_locals_region_has_control_embedded_local_tee(
        func,
        @ir.hot_region_body_ref(func, node_id),
        true,
      ) ||
      simplify_locals_region_has_control_embedded_local_tee(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
        true,
      )
    _ => false
  }
}

///|
fn simplify_locals_has_control_embedded_local_tee(func : @ir.HotFunc) -> Bool {
  simplify_locals_region_has_control_embedded_local_tee(
    func,
    @ir.HotRegionRef::root(),
    false,
  )
}

///|
fn simplify_locals_lower_dropped_tees_in_protected_region(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  inside_control : Bool,
  get_counts : Array[Int32],
  one_armed_if_tail_sets : Array[Bool],
  detached : Array[Int32],
) -> Bool {
  let mut changed = false
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, region_ref, idx)
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::LocalSet if inside_control => {
        let value_id = @ir.hot_child_get(func, node_id, 0)
        if node.imm0 >= 0 &&
          node.imm0 < get_counts.length() &&
          get_counts[node.imm0] == 0 {
          if value_id >= 0 && @ir.hot_node_is_live(func, value_id) {
            match @ir.hot_node_get(func, value_id).op {
              @ir.HotOp::If => continue
              _ => ()
            }
          }
          if value_id < 0 {
            if idx > 0 {
              let previous_id = @ir.hot_region_get(func, region_ref, idx - 1)
              if previous_id >= 0 &&
                @ir.hot_node_is_live(func, previous_id) &&
                simplify_locals_value_is_pure(func, previous_id) {
                @ir.hot_replace_node(
                  func,
                  previous_id,
                  simplify_locals_make_nop(func),
                )
                @ir.hot_replace_node(
                  func,
                  node_id,
                  simplify_locals_make_nop(func),
                )
                changed = true
              }
            }
          } else if simplify_locals_value_is_pure(func, value_id) {
            @ir.hot_replace_node(func, node_id, simplify_locals_make_nop(func))
            detached.push(value_id)
            changed = true
          } else {
            @ir.hot_replace_node(
              func,
              node_id,
              simplify_locals_make_drop(func, value_id),
            )
            changed = true
          }
        }
      }
      @ir.HotOp::Drop if inside_control => {
        let child_id = @ir.hot_child_get(func, node_id, 0)
        if child_id >= 0 && @ir.hot_node_is_live(func, child_id) {
          let child = @ir.hot_node_get(func, child_id)
          match child.op {
            @ir.HotOp::LocalTee => {
              let value_id = @ir.hot_child_get(func, child_id, 0)
              if value_id >= 0 &&
                @ir.hot_node_is_live(func, value_id) &&
                simplify_locals_two_child_uses_are_unique(
                  func, child_id, value_id,
                ) {
                @ir.hot_replace_node(
                  func,
                  node_id,
                  simplify_locals_make_local_set(func, child.imm0, value_id),
                )
                detached.push(child_id)
                changed = true
              }
            }
            @ir.HotOp::Block | @ir.HotOp::Loop =>
              if simplify_locals_lower_dropped_tees_in_protected_region(
                  func,
                  @ir.hot_region_body_ref(func, child_id),
                  true,
                  get_counts,
                  one_armed_if_tail_sets,
                  detached,
                ) {
                changed = true
              }
            @ir.HotOp::If => {
              if simplify_locals_lower_dropped_tees_in_protected_region(
                  func,
                  @ir.hot_region_then_ref(func, child_id),
                  true,
                  get_counts,
                  one_armed_if_tail_sets,
                  detached,
                ) {
                changed = true
              }
              match @ir.hot_region_else_ref(func, child_id) {
                Some(else_ref) =>
                  if simplify_locals_lower_dropped_tees_in_protected_region(
                      func, else_ref, true, get_counts, one_armed_if_tail_sets, detached,
                    ) {
                    changed = true
                  }
                None => ()
              }
            }
            @ir.HotOp::Try => {
              if simplify_locals_lower_dropped_tees_in_protected_region(
                  func,
                  @ir.hot_region_body_ref(func, child_id),
                  true,
                  get_counts,
                  one_armed_if_tail_sets,
                  detached,
                ) {
                changed = true
              }
              match @ir.hot_region_catch_ref(func, child_id) {
                Some(catch_ref) =>
                  if simplify_locals_lower_dropped_tees_in_protected_region(
                      func, catch_ref, true, get_counts, one_armed_if_tail_sets,
                      detached,
                    ) {
                    changed = true
                  }
                None => ()
              }
            }
            @ir.HotOp::TryTable => {
              if simplify_locals_lower_dropped_tees_in_protected_region(
                  func,
                  @ir.hot_region_body_ref(func, child_id),
                  true,
                  get_counts,
                  one_armed_if_tail_sets,
                  detached,
                ) {
                changed = true
              }
              if simplify_locals_lower_dropped_tees_in_protected_region(
                  func,
                  @ir.hot_region_catch_list_ref(func, child_id),
                  true,
                  get_counts,
                  one_armed_if_tail_sets,
                  detached,
                ) {
                changed = true
              }
            }
            _ => ()
          }
        }
      }
      @ir.HotOp::Block | @ir.HotOp::Loop =>
        if simplify_locals_lower_dropped_tees_in_protected_region(
            func,
            @ir.hot_region_body_ref(func, node_id),
            true,
            get_counts,
            one_armed_if_tail_sets,
            detached,
          ) {
          changed = true
        }
      @ir.HotOp::If => {
        if simplify_locals_lower_dropped_tees_in_protected_region(
            func,
            @ir.hot_region_then_ref(func, node_id),
            true,
            get_counts,
            one_armed_if_tail_sets,
            detached,
          ) {
          changed = true
        }
        match @ir.hot_region_else_ref(func, node_id) {
          Some(else_ref) =>
            if simplify_locals_lower_dropped_tees_in_protected_region(
                func, else_ref, true, get_counts, one_armed_if_tail_sets, detached,
              ) {
              changed = true
            }
          None => ()
        }
      }
      @ir.HotOp::Try => {
        if simplify_locals_lower_dropped_tees_in_protected_region(
            func,
            @ir.hot_region_body_ref(func, node_id),
            true,
            get_counts,
            one_armed_if_tail_sets,
            detached,
          ) {
          changed = true
        }
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(catch_ref) =>
            if simplify_locals_lower_dropped_tees_in_protected_region(
                func, catch_ref, true, get_counts, one_armed_if_tail_sets, detached,
              ) {
              changed = true
            }
          None => ()
        }
      }
      @ir.HotOp::TryTable => {
        if simplify_locals_lower_dropped_tees_in_protected_region(
            func,
            @ir.hot_region_body_ref(func, node_id),
            true,
            get_counts,
            one_armed_if_tail_sets,
            detached,
          ) {
          changed = true
        }
        if simplify_locals_lower_dropped_tees_in_protected_region(
            func,
            @ir.hot_region_catch_list_ref(func, node_id),
            true,
            get_counts,
            one_armed_if_tail_sets,
            detached,
          ) {
          changed = true
        }
      }
      _ => ()
    }
  }
  changed
}

///|
fn simplify_locals_run_root_dead_cleanup(
  func : @ir.HotFunc,
  detached : Array[Int32],
  get_count_cache~ : SimplifyLocalsGetCounts? = None,
  get_count_scans~ : Array[Int32]? = None,
) -> Bool {
  let get_counts = simplify_locals_counts_for_cycle(
    func,
    get_count_cache,
    full_scans=get_count_scans,
  )
  let one_armed_if_tail_sets = simplify_locals_one_armed_if_tail_sets(func)
  let mut changed = false
  let root_ref = @ir.HotRegionRef::root()
  for idx = 0; idx < @ir.hot_region_root_count(func, root_ref); idx = idx + 1 {
    let node_id = @ir.hot_region_get(func, root_ref, idx)
    if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
      continue
    }
    let node = @ir.hot_node_get(func, node_id)
    match node.op {
      @ir.HotOp::LocalSet => {
        let value_id = @ir.hot_child_get(func, node_id, 0)
        if value_id >= 0 && @ir.hot_node_is_live(func, value_id) {
          let value = @ir.hot_node_get(func, value_id)
          match value.op {
            @ir.HotOp::LocalTee if value.imm0 == node.imm0 => {
              @ir.hot_replace_node(
                func,
                node_id,
                simplify_locals_make_local_set(
                  func,
                  node.imm0,
                  @ir.hot_child_get(func, value_id, 0),
                ),
              )
              detached.push(value_id)
              changed = true
              continue
            }
            @ir.HotOp::LocalGet if value.imm0 == node.imm0 => {
              @ir.hot_replace_node(
                func,
                node_id,
                simplify_locals_make_nop(func),
              )
              detached.push(value_id)
              changed = true
              continue
            }
            _ => ()
          }
        }
        if get_counts[node.imm0] != 0 {
          continue
        }
        if node_id >= 0 &&
          node_id < one_armed_if_tail_sets.length() &&
          one_armed_if_tail_sets[node_id] {
          continue
        }
        if value_id >= 0 && @ir.hot_node_is_live(func, value_id) {
          match @ir.hot_node_get(func, value_id).op {
            @ir.HotOp::If => continue
            _ => ()
          }
        }
        if simplify_locals_value_is_pure(func, value_id) {
          @ir.hot_replace_node(func, node_id, simplify_locals_make_nop(func))
          detached.push(value_id)
        } else {
          @ir.hot_replace_node(
            func,
            node_id,
            simplify_locals_make_drop(func, value_id),
          )
        }
        changed = true
      }
      @ir.HotOp::LocalTee => {
        let value_id = @ir.hot_child_get(func, node_id, 0)
        if value_id >= 0 && @ir.hot_node_is_live(func, value_id) {
          let value = @ir.hot_node_get(func, value_id)
          match value.op {
            @ir.HotOp::LocalGet if value.imm0 == node.imm0 => {
              @ir.hot_replace_node(
                func,
                node_id,
                @ir.hot_node_get(func, value_id),
              )
              detached.push(value_id)
              changed = true
              continue
            }
            _ => ()
          }
        }
        if get_counts[node.imm0] != 0 {
          continue
        }
        if simplify_locals_value_can_clone_node(func, value_id) {
          @ir.hot_replace_node(func, node_id, @ir.hot_node_get(func, value_id))
          detached.push(value_id)
          changed = true
        }
      }
      _ => ()
    }
  }
  changed
}

///|
fn simplify_locals_run_control_embedded_tee_root_only(
  ctx : HotPassContext,
  func : @ir.HotFunc,
  policy : SimplifyLocalsPolicy,
  reuse_get_counts~ : Bool = true,
  get_count_scans~ : Array[Int32]? = None,
) -> HotPassResult {
  let get_count_cache = if reuse_get_counts {
    simplify_locals_new_get_count_cache(func)
  } else {
    None
  }
  pipeline_trace_hook(
    ctx.trace,
    ctx.descriptor.name,
    "protect-hot",
    payload="reason=control-local-tee-hazard-root-only",
  )
  let mut first_cycle = true
  let mut changed = false
  while true {
    let main_detached : Array[Int32] = []
    let (main_changed, has_deferred_main_cycle) = simplify_locals_run_main_cycle(
      ctx,
      func,
      first_cycle,
      main_detached,
      policy,
      true,
      get_count_cache~,
      get_count_scans~,
    )
    simplify_locals_delete_detached_nodes(func, main_detached)

    let cleanup_detached : Array[Int32] = []
    let cleanup_changed = simplify_locals_run_root_dead_cleanup(
      func,
      cleanup_detached,
      get_count_cache~,
      get_count_scans~,
    )
    simplify_locals_delete_detached_nodes(func, cleanup_detached)

    let protected_detached : Array[Int32] = []
    let protected_get_counts = simplify_locals_counts_for_cycle(
      func,
      get_count_cache,
      full_scans=get_count_scans,
    )
    let protected_one_armed_if_tail_sets = simplify_locals_one_armed_if_tail_sets(
      func,
    )
    let protected_changed = simplify_locals_lower_dropped_tees_in_protected_region(
      func,
      @ir.HotRegionRef::root(),
      true,
      protected_get_counts,
      protected_one_armed_if_tail_sets,
      protected_detached,
    )
    simplify_locals_delete_detached_nodes(func, protected_detached)

    if main_changed || cleanup_changed || protected_changed {
      changed = true
    }
    if first_cycle {
      first_cycle = false
      if has_deferred_main_cycle || protected_changed {
        continue
      }
    } else if main_changed || cleanup_changed || protected_changed {
      continue
    }
    break
  }
  if !changed {
    return HotPassResult::unchanged()
  }
  ignore(@ir.hot_repair_nondefaultable_local_scopes(func))
  pass_mark_mutated(ctx, func)
  HotPassResult::changed()
}

///|
fn simplify_locals_nostructure_run(
  ctx : HotPassContext,
  func : @ir.HotFunc,
) -> HotPassResult {
  if simplify_locals_has_control_embedded_local_tee(func) {
    return simplify_locals_run_control_embedded_tee_root_only(
      ctx,
      func,
      SimplifyLocalsPolicy::new(false, true, true),
    )
  }
  simplify_locals_run_with_options(
    ctx,
    func,
    SimplifyLocalsPolicy::new(false, true, true),
  )
}

///|
fn simplify_locals_nonesting_run(
  ctx : HotPassContext,
  func : @ir.HotFunc,
) -> HotPassResult {
  let policy = SimplifyLocalsPolicy::new(false, false, false)
  if simplify_locals_should_skip_root_local_set_stack_hazard(func) {
    pipeline_trace_hook(
      ctx.trace,
      ctx.descriptor.name,
      "skip-hot",
      payload="reason=root-local-set-stack-hazard-noop",
    )
    return HotPassResult::unchanged()
  }
  if simplify_locals_has_control_embedded_local_tee(func) {
    return simplify_locals_run_control_embedded_tee_root_only(ctx, func, policy)
  }
  simplify_locals_run_with_options(ctx, func, policy)
}

///|
fn simplify_locals_notee_nostructure_run(
  ctx : HotPassContext,
  func : @ir.HotFunc,
) -> HotPassResult {
  // The no-tee spelling disables creating new tees, but Binaryen still cleans
  // existing dropped tees and optimizes unrelated locals. When a local.tee is
  // embedded under structured control, keep that child protected while allowing
  // unrelated root-level cleanup.
  if simplify_locals_has_control_embedded_local_tee(func) {
    return simplify_locals_run_control_embedded_tee_root_only(
      ctx,
      func,
      SimplifyLocalsPolicy::new(false, false, true),
    )
  }
  simplify_locals_run_with_options(
    ctx,
    func,
    SimplifyLocalsPolicy::new(false, false, true),
  )
}

///|
fn simplify_locals_grouping_descriptor_is_admitted(name : String) -> Bool {
  name == "simplify-locals" ||
  name == "simplify-locals-nostructure" ||
  name == "simplify-locals-no-structure"
}

///|
// Group only changed, selected scalar bodies, after writeback repair has settled
// their local indices. The cheap run scan keeps ordinary/no-change functions
// out of the shared encoded-cost and validation selector.
fn simplify_locals_group_numeric_locals_if_smaller(
  before : @lib.Module,
  mod_ : @lib.Module,
  touched~ : Array[Bool]? = None,
) -> @lib.Module {
  if physical_equal(before, mod_) {
    return mod_
  }
  let original = match before.code_sec {
    Some(@lib.CodeSec(funcs)) => funcs
    None => return mod_
  }
  let funcs = match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) => funcs
    None => return mod_
  }
  let mut selected : Array[Bool]? = None
  for index in 0..= original.length() ||
      physical_equal(original[index], funcs[index]) {
      continue
    }
    if touched is Some(mask) && (index >= mask.length() || !mask[index]) {
      continue
    }
    let @lib.Func(locals, _) = funcs[index]
    if locals.run_count() <= 1 {
      continue
    }
    let mut seen = 0
    let mut repeated = false
    let mut numeric = true
    for run in locals.runs() {
      let bit = match run.vt {
        @lib.NumTypeValType(@lib.I32NumType) => 1
        @lib.NumTypeValType(@lib.I64NumType) => 2
        @lib.NumTypeValType(@lib.F32NumType) => 4
        @lib.NumTypeValType(@lib.F64NumType) => 8
        _ => {
          numeric = false
          break
        }
      }
      repeated = repeated || (seen & bit) != 0
      seen = seen | bit
    }
    if numeric && repeated {
      if selected is None {
        selected = Some(Array::make(funcs.length(), false))
      }
      selected.unwrap()[index] = true
    }
  }
  match selected {
    Some(mask) =>
      oi_group_numeric_locals_if_smaller(
        mod_,
        touched=Some(mask),
        allow_frequency=false,
      )
    None => mod_
  }
}

///|
// Finalize module-wide type indices only after all HOT bodies are written back.
// Body-local grouping stays selected; a type remap also updates unselected
// typed instructions so their module references remain valid.
fn simplify_locals_cleanup_changed_module_encoding(
  before : @lib.Module,
  mod_ : @lib.Module,
  allow_grouping~ : Bool = true,
  touched~ : Array[Bool]? = None,
) -> @lib.Module {
  let cleaned = pass_cleanup_control_and_type_encoding(mod_, touched~)
  let grouped = if allow_grouping {
    simplify_locals_group_numeric_locals_if_smaller(before, cleaned, touched~)
  } else {
    cleaned
  }
  if physical_equal(before, grouped) ||
    !grouped.custom_secs.is_empty() ||
    pass_module_has_offset_sensitive_metadata(grouped) {
    return grouped
  }
  dfe_cleanup_simple_type_encoding(grouped)
}