// 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 2024 WebAssembly Community Group participants
// Counterpart: src/passes/HeapStoreOptimization.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 heap_store_optimization_descriptor() -> @ir.HotPassDescriptor {
  @ir.HotPassDescriptor::new(
    "heap-store-optimization",
    // Effects are required lazily after the tiny pure root fast path misses.
    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 heap_store_optimization_summary() -> String {
  "Fold exact struct.set writes into nearby struct.new allocations when local and effect ordering stays safe."
}

///|
priv struct HsoTypeCache {
  struct_field_counts : Array[Int32]
  default_field_counts : Array[Int32]
  default_field_types : Array[Array[@lib.ValType]?]
}

///|
priv struct HsoOrderEffects {
  mut shared_memory_read : Bool
  mut shared_memory_write : Bool
  mut shared_struct_read : Bool
  mut shared_struct_write : Bool
  mut shared_array_read : Bool
  mut shared_array_write : Bool
  mut acquire_read : Bool
  mut release_write : Bool
  mut seq_cst : Bool
  mut atomic_order_mask : @ir.EffectMask
}

///|
priv struct HsoPredicateCache {
  readonly_revision : Array[Int32]
  readonly_result : @bitset.BitSet
  skip_revision : Array[Int32]
  skip_result : @bitset.BitSet
  order_revision : Array[Int32]
  order_result : Array[HsoOrderEffects?]
}

///|
priv struct HsoExternalRoots {
  root_arrays : Array[Array[Int32]]
  starts : Array[Int32]
}

///|
priv struct HsoLocalOrderWorkspace {
  maxima : Array[Int32]
  stamps : Array[Int32]
  mut generation : Int32
}

///|
fn hso_new_local_order_workspace(local_count : Int32) -> HsoLocalOrderWorkspace {
  {
    maxima: Array::make(local_count, -1),
    stamps: Array::make(local_count, 0),
    generation: 0,
  }
}

///|
fn hso_local_order_workspace_begin(workspace : HsoLocalOrderWorkspace) -> Unit {
  workspace.generation += 1
}

///|
fn hso_local_order_workspace_note(
  workspace : HsoLocalOrderWorkspace,
  local_id : Int32,
  root_id : Int32,
) -> Unit {
  if local_id < 0 || local_id >= workspace.maxima.length() {
    return
  }
  if workspace.stamps[local_id] != workspace.generation {
    workspace.stamps[local_id] = workspace.generation
    workspace.maxima[local_id] = root_id
  } else {
    workspace.maxima[local_id] = Int32::max(workspace.maxima[local_id], root_id)
  }
}

///|
fn hso_local_order_workspace_get(
  workspace : HsoLocalOrderWorkspace,
  local_id : Int32,
) -> Int32 {
  if local_id < 0 ||
    local_id >= workspace.maxima.length() ||
    workspace.stamps[local_id] != workspace.generation {
    -1
  } else {
    workspace.maxima[local_id]
  }
}

///|
fn hso_new_order_effects() -> HsoOrderEffects {
  {
    shared_memory_read: false,
    shared_memory_write: false,
    shared_struct_read: false,
    shared_struct_write: false,
    shared_array_read: false,
    shared_array_write: false,
    acquire_read: false,
    release_write: false,
    seq_cst: false,
    atomic_order_mask: 0U,
  }
}

///|
fn hso_order_effects_accesses_shared(effects : HsoOrderEffects) -> Bool {
  effects.shared_memory_read ||
  effects.shared_memory_write ||
  effects.shared_struct_read ||
  effects.shared_struct_write ||
  effects.shared_array_read ||
  effects.shared_array_write
}

///|
fn hso_order_effects_merge(
  effects : HsoOrderEffects,
  other : HsoOrderEffects,
) -> Unit {
  effects.shared_memory_read = effects.shared_memory_read ||
    other.shared_memory_read
  effects.shared_memory_write = effects.shared_memory_write ||
    other.shared_memory_write
  effects.shared_struct_read = effects.shared_struct_read ||
    other.shared_struct_read
  effects.shared_struct_write = effects.shared_struct_write ||
    other.shared_struct_write
  effects.shared_array_read = effects.shared_array_read ||
    other.shared_array_read
  effects.shared_array_write = effects.shared_array_write ||
    other.shared_array_write
  effects.acquire_read = effects.acquire_read || other.acquire_read
  effects.release_write = effects.release_write || other.release_write
  effects.seq_cst = effects.seq_cst || other.seq_cst
  effects.atomic_order_mask = effects.atomic_order_mask |
    other.atomic_order_mask
}

///|
fn hso_order_effects_note_read_order(
  effects : HsoOrderEffects,
  order : @lib.AtomicOrder,
) -> Unit {
  effects.atomic_order_mask = effects.atomic_order_mask |
    @ir.effects_atomic_order_mask(order, read=true, write=false)
  match order {
    @lib.Relaxed => ()
    @lib.AcqRel => effects.acquire_read = true
    @lib.SeqCst => effects.seq_cst = true
  }
}

///|
fn hso_order_effects_note_write_order(
  effects : HsoOrderEffects,
  order : @lib.AtomicOrder,
) -> Unit {
  effects.atomic_order_mask = effects.atomic_order_mask |
    @ir.effects_atomic_order_mask(order, read=false, write=true)
  match order {
    @lib.Relaxed => ()
    @lib.AcqRel => effects.release_write = true
    @lib.SeqCst => effects.seq_cst = true
  }
}

///|
fn hso_type_is_shared(
  module_ctx : @ir.HotModuleContext?,
  type_idx : @lib.TypeIdx,
) -> Bool {
  match module_ctx {
    Some(ctx) =>
      match @ir.hot_module_resolve_subtype(ctx, type_idx) {
        Some(subtype) => subtype.metadata().is_shared()
        None => false
      }
    None => false
  }
}

///|
fn hso_order_effects_note_exact_instr(
  effects : HsoOrderEffects,
  module_ctx : @ir.HotModuleContext?,
  instr : @lib.Instruction,
) -> Unit {
  match instr {
    @lib.I32AtomicLoad(order, _)
    | @lib.I64AtomicLoad(order, _)
    | @lib.I32AtomicLoad8U(order, _)
    | @lib.I32AtomicLoad16U(order, _)
    | @lib.I64AtomicLoad8U(order, _)
    | @lib.I64AtomicLoad16U(order, _)
    | @lib.I64AtomicLoad32U(order, _) => {
      effects.shared_memory_read = true
      hso_order_effects_note_read_order(effects, order)
    }
    @lib.I32AtomicStore(order, _)
    | @lib.I64AtomicStore(order, _)
    | @lib.I32AtomicStore8(order, _)
    | @lib.I32AtomicStore16(order, _)
    | @lib.I64AtomicStore8(order, _)
    | @lib.I64AtomicStore16(order, _)
    | @lib.I64AtomicStore32(order, _) => {
      effects.shared_memory_write = true
      hso_order_effects_note_write_order(effects, order)
    }
    @lib.AtomicRmw(order, _, _) | @lib.AtomicCmpxchg(order, _, _) => {
      effects.shared_memory_read = true
      effects.shared_memory_write = true
      hso_order_effects_note_read_order(effects, order)
      hso_order_effects_note_write_order(effects, order)
    }
    @lib.WaitqueueNotify | @lib.StructWait(_, _) => {
      effects.shared_struct_read = true
      effects.shared_struct_write = true
      hso_order_effects_note_read_order(effects, @lib.AtomicOrder::seq_cst())
      hso_order_effects_note_write_order(effects, @lib.AtomicOrder::seq_cst())
    }
    @lib.MemoryAtomicWait32(_)
    | @lib.MemoryAtomicWait64(_)
    | @lib.MemoryAtomicNotify(_)
    | @lib.AtomicFence(_) => {
      effects.shared_memory_read = true
      effects.shared_memory_write = true
      effects.seq_cst = true
    }
    @lib.StructGet(type_idx, _)
    | @lib.StructGetS(type_idx, _)
    | @lib.StructGetU(type_idx, _) =>
      if hso_type_is_shared(module_ctx, type_idx) {
        effects.shared_struct_read = true
      }
    @lib.StructSet(type_idx, _) =>
      if hso_type_is_shared(module_ctx, type_idx) {
        effects.shared_struct_write = true
      }
    @lib.StructAtomicSet(order, _, _) => {
      effects.shared_struct_write = true
      hso_order_effects_note_write_order(effects, order)
    }
    @lib.StructAtomicGet(order, _, _)
    | @lib.StructAtomicGetS(order, _, _)
    | @lib.StructAtomicGetU(order, _, _) => {
      effects.shared_struct_read = true
      hso_order_effects_note_read_order(effects, order)
    }
    @lib.StructAtomicRmw(order, _, _, _)
    | @lib.StructAtomicCmpxchg(order, _, _) => {
      effects.shared_struct_read = true
      effects.shared_struct_write = true
      hso_order_effects_note_read_order(effects, order)
      hso_order_effects_note_write_order(effects, order)
    }
    @lib.ArrayGet(type_idx)
    | @lib.ArrayGetS(type_idx)
    | @lib.ArrayGetU(type_idx) =>
      if hso_type_is_shared(module_ctx, type_idx) {
        effects.shared_array_read = true
      }
    @lib.ArraySet(type_idx) | @lib.ArrayFill(type_idx) =>
      if hso_type_is_shared(module_ctx, type_idx) {
        effects.shared_array_write = true
      }
    @lib.ArrayAtomicSet(order, _) => {
      effects.shared_array_write = true
      hso_order_effects_note_write_order(effects, order)
    }
    @lib.ArrayAtomicGet(order, _)
    | @lib.ArrayAtomicGetS(order, _)
    | @lib.ArrayAtomicGetU(order, _) => {
      effects.shared_array_read = true
      hso_order_effects_note_read_order(effects, order)
    }
    @lib.ArrayAtomicRmw(order, _, _) | @lib.ArrayAtomicCmpxchg(order, _) => {
      effects.shared_array_read = true
      effects.shared_array_write = true
      hso_order_effects_note_read_order(effects, order)
      hso_order_effects_note_write_order(effects, order)
    }
    _ => ()
  }
}

///|
fn hso_collect_order_effects_region(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  region_ref : @ir.HotRegionRef,
  effects : HsoOrderEffects,
) -> Unit {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    hso_order_effects_merge(
      effects,
      hso_order_effects_for_node(
        cache,
        func,
        module_ctx,
        @ir.hot_region_get(func, region_ref, idx),
      ),
    )
  }
}

///|
fn hso_order_effects_for_node(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_id : Int32,
) -> HsoOrderEffects {
  if node_id < 0 ||
    node_id >= @ir.hot_node_count(func) ||
    !@ir.hot_node_is_live(func, node_id) {
    return hso_new_order_effects()
  }
  hso_ensure_predicate_cache_capacity(cache, node_id)
  let revision = @ir.hot_revision_current(func)
  if cache.order_revision[node_id] == revision {
    match cache.order_result[node_id] {
      Some(effects) => return effects
      None => return hso_new_order_effects()
    }
  }

  let effects = hso_new_order_effects()
  // Publish an empty in-progress summary before recursion so a malformed cyclic
  // HOT graph terminates; verified HOT graphs are structurally acyclic.
  cache.order_revision[node_id] = revision
  cache.order_result[node_id] = Some(effects)
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(instr) =>
      hso_order_effects_note_exact_instr(effects, module_ctx, instr)
    None => ()
  }
  // Descendant call barriers merge through the operand and region summaries
  // below. Only this node's flag is needed here; a subtree effect query would
  // repeat those walks and allocate arena-sized scratch for each non-leaf.
  if @ir.hot_node_has_flag(func, node_id, @ir.HOT_FLAG_CALL) {
    effects.shared_memory_read = true
    effects.shared_memory_write = true
    effects.shared_struct_read = true
    effects.shared_struct_write = true
    effects.shared_array_read = true
    effects.shared_array_write = true
  }
  let operand_node = @ir.hot_node_get(func, node_id)
  let operand_count = pass_value_operand_count(func, node_id)
  for idx = 0; idx < operand_count; idx = idx + 1 {
    hso_order_effects_merge(
      effects,
      hso_order_effects_for_node(
        cache,
        func,
        module_ctx,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, idx),
        ),
      ),
    )
  }
  match @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_collect_order_effects_region(
        cache,
        func,
        module_ctx,
        @ir.hot_region_body_ref(func, node_id),
        effects,
      )
    @ir.HotOp::If => {
      hso_collect_order_effects_region(
        cache,
        func,
        module_ctx,
        @ir.hot_region_then_ref(func, node_id),
        effects,
      )
      match @ir.hot_region_else_ref(func, node_id) {
        Some(region_ref) =>
          hso_collect_order_effects_region(
            cache, func, module_ctx, region_ref, effects,
          )
        None => ()
      }
    }
    @ir.HotOp::Try => {
      hso_collect_order_effects_region(
        cache,
        func,
        module_ctx,
        @ir.hot_region_body_ref(func, node_id),
        effects,
      )
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(region_ref) =>
          hso_collect_order_effects_region(
            cache, func, module_ctx, region_ref, effects,
          )
        None => ()
      }
    }
    @ir.HotOp::TryTable => {
      hso_collect_order_effects_region(
        cache,
        func,
        module_ctx,
        @ir.hot_region_body_ref(func, node_id),
        effects,
      )
      hso_collect_order_effects_region(
        cache,
        func,
        module_ctx,
        @ir.hot_region_catch_list_ref(func, node_id),
        effects,
      )
    }
    _ => ()
  }
  effects
}

///|
fn hso_order_effects_ordered_before(
  first : HsoOrderEffects,
  second : HsoOrderEffects,
) -> Bool {
  let first_shared = hso_order_effects_accesses_shared(first)
  let second_shared = hso_order_effects_accesses_shared(second)
  @ir.effects_atomic_orders_conflict(
    first.atomic_order_mask,
    second.atomic_order_mask,
    earlier_shared=first_shared,
    later_shared=second_shared,
  ) ||
  (second.release_write && first_shared) ||
  (first.acquire_read && second_shared) ||
  (first.seq_cst && second_shared) ||
  (second.seq_cst && first_shared)
}

///|
fn hso_release_memory_before_shared_gc_read_can_reorder(
  first : HsoOrderEffects,
  second : HsoOrderEffects,
) -> Bool {
  first.release_write &&
  first.shared_memory_write &&
  !first.shared_memory_read &&
  !first.shared_struct_read &&
  !first.shared_struct_write &&
  !first.shared_array_read &&
  !first.shared_array_write &&
  !first.acquire_read &&
  !first.seq_cst &&
  (second.shared_struct_read || second.shared_array_read) &&
  !second.shared_memory_read &&
  !second.shared_memory_write &&
  !second.shared_struct_write &&
  !second.shared_array_write &&
  !second.acquire_read &&
  !second.release_write &&
  !second.seq_cst
}

///|
fn hso_subtree_local_write_conflicts(
  func : @ir.HotFunc,
  node_id : Int32,
  other_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::LocalSet | @ir.HotOp::LocalTee =>
      if hso_subtree_touches_local(func, other_id, node.imm0) {
        return true
      }
    _ => ()
  }
  // HOT child spans already include structured regions; visit each edge once.
  for child in @ir.hot_child_span(func, node_id) {
    if hso_subtree_local_write_conflicts(func, child, other_id) {
      return true
    }
  }
  false
}

///|
fn hso_local_order_conflicts(
  func : @ir.HotFunc,
  first_id : Int32,
  second_id : Int32,
) -> Bool {
  hso_subtree_local_write_conflicts(func, first_id, second_id) ||
  hso_subtree_local_write_conflicts(func, second_id, first_id)
}

///|
fn hso_ordered_before(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  first_id : Int32,
  first_mask : UInt32,
  second_id : Int32,
  second_mask : UInt32,
) -> Bool {
  if hso_local_order_conflicts(func, first_id, second_id) ||
    hso_mask_ordered_before(first_mask, second_mask) {
    return true
  }
  hso_order_effects_ordered_before(
    hso_order_effects_for_node(cache, func, module_ctx, first_id),
    hso_order_effects_for_node(cache, func, module_ctx, second_id),
  )
}

///|
fn hso_new_predicate_cache() -> HsoPredicateCache {
  {
    readonly_revision: [],
    readonly_result: @bitset.BitSet::new(0),
    skip_revision: [],
    skip_result: @bitset.BitSet::new(0),
    order_revision: [],
    order_result: [],
  }
}

///|
fn hso_new_type_cache() -> HsoTypeCache {
  {
    struct_field_counts: [],
    default_field_counts: [],
    default_field_types: [],
  }
}

///|
fn hso_ensure_predicate_cache_capacity(
  cache : HsoPredicateCache,
  node_id : Int32,
) -> Unit {
  while cache.readonly_revision.length() <= node_id {
    cache.readonly_revision.push(-1)
    cache.skip_revision.push(-1)
    cache.order_revision.push(-1)
    cache.order_result.push(None)
  }
  cache.readonly_result.grow(cache.readonly_revision.length())
  cache.skip_result.grow(cache.skip_revision.length())
}

///|
fn hso_ensure_type_cache_capacity(
  cache : HsoTypeCache,
  type_slot : Int32,
) -> Unit {
  while cache.default_field_counts.length() <= type_slot {
    cache.struct_field_counts.push(-2)
    cache.default_field_counts.push(-2)
    cache.default_field_types.push(None)
  }
}

///|
fn hso_exact_instr_can_trap(instr : @lib.Instruction?) -> Bool {
  match instr {
    Some(@lib.Instruction::I32DivS)
    | Some(@lib.Instruction::I32DivU)
    | Some(@lib.Instruction::I32RemS)
    | Some(@lib.Instruction::I32RemU)
    | Some(@lib.Instruction::I64DivS)
    | Some(@lib.Instruction::I64DivU)
    | Some(@lib.Instruction::I64RemS)
    | Some(@lib.Instruction::I64RemU)
    | Some(@lib.Instruction::I32TruncF32S)
    | Some(@lib.Instruction::I32TruncF32U)
    | Some(@lib.Instruction::I32TruncF64S)
    | Some(@lib.Instruction::I32TruncF64U)
    | Some(@lib.Instruction::I64TruncF32S)
    | Some(@lib.Instruction::I64TruncF32U)
    | Some(@lib.Instruction::I64TruncF64S)
    | Some(@lib.Instruction::I64TruncF64U) => true
    _ => false
  }
}

///|
fn hso_i32_const_at(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::Const =>
      match @ir.hot_const_get(func, node.imm0) {
        @ir.HotConstPayload::I32Const(@lib.I32(raw)) => Some(raw)
        _ => None
      }
    _ => None
  }
}

///|
fn hso_i64_const_at(func : @ir.HotFunc, node_id : Int32) -> Int64? {
  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::Const =>
      match @ir.hot_const_get(func, node.imm0) {
        @ir.HotConstPayload::I64Const(@lib.I64(raw)) => Some(raw)
        _ => None
      }
    _ => None
  }
}

///|
fn hso_exact_div_rem_is_known_safe(func : @ir.HotFunc, node_id : Int32) -> Bool {
  if @ir.hot_child_count(func, node_id) != 2 {
    return false
  }
  let lhs_id = @ir.hot_child_get(func, node_id, 0)
  let rhs_id = @ir.hot_child_get(func, node_id, 1)
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.Instruction::I32DivS) =>
      match (hso_i32_const_at(func, lhs_id), hso_i32_const_at(func, rhs_id)) {
        (Some(lhs), Some(rhs)) =>
          rhs != 0 && !(lhs == -2147483647 - 1 && rhs == -1)
        _ => false
      }
    Some(@lib.Instruction::I32DivU)
    | Some(@lib.Instruction::I32RemS)
    | Some(@lib.Instruction::I32RemU) =>
      match hso_i32_const_at(func, rhs_id) {
        Some(rhs) => rhs != 0
        None => false
      }
    Some(@lib.Instruction::I64DivS) =>
      match (hso_i64_const_at(func, lhs_id), hso_i64_const_at(func, rhs_id)) {
        (Some(lhs), Some(rhs)) =>
          rhs != 0L && !(lhs == -9223372036854775808L && rhs == -1L)
        _ => false
      }
    Some(@lib.Instruction::I64DivU)
    | Some(@lib.Instruction::I64RemS)
    | Some(@lib.Instruction::I64RemU) =>
      match hso_i64_const_at(func, rhs_id) {
        Some(rhs) => rhs != 0L
        None => false
      }
    _ => false
  }
}

///|
fn hso_node_may_trap(func : @ir.HotFunc, node_id : Int32) -> Bool {
  if hso_exact_div_rem_is_known_safe(func, node_id) {
    false
  } else {
    @ir.hot_may_trap(func, node_id) ||
    hso_exact_instr_can_trap(@ir.hot_node_exact_instr(func, node_id))
  }
}

///|
fn hso_effects_without_descendant_trap(
  func : @ir.HotFunc,
  node_id : Int32,
  mask : UInt32,
) -> UInt32 {
  let descendants_checked_separately = match
    @ir.hot_node_get(func, node_id).op {
    @ir.HotOp::Drop
    | @ir.HotOp::Block
    | @ir.HotOp::Loop
    | @ir.HotOp::If
    | @ir.HotOp::Try
    | @ir.HotOp::TryTable => true
    _ => false
  }
  if descendants_checked_separately {
    mask ^ (mask & @ir.EFFECT_MASK_TRAP)
  } else {
    mask
  }
}

///|
fn hso_effects_for_node(
  func : @ir.HotFunc,
  node_masks : Array[UInt32],
  node_id : Int32,
) -> UInt32 {
  let base_mask = if node_id >= 0 && node_id < node_masks.length() {
    node_masks[node_id]
  } else {
    @ir.effects_for_node(func, node_id)
  }
  if node_id >= 0 &&
    node_id < @ir.hot_node_count(func) &&
    hso_exact_div_rem_is_known_safe(func, node_id) {
    base_mask ^ (base_mask & @ir.EFFECT_MASK_TRAP)
  } else if node_id >= 0 &&
    node_id < @ir.hot_node_count(func) &&
    hso_exact_instr_can_trap(@ir.hot_node_exact_instr(func, node_id)) {
    base_mask | @ir.EFFECT_MASK_TRAP
  } else {
    base_mask
  }
}

///|
fn hso_reorder_effect_mask(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  node_masks : Array[UInt32],
  node_id : Int32,
) -> UInt32 {
  let mask = hso_effects_for_node(func, node_masks, node_id)
  if (mask & @ir.EFFECT_MASK_CONTROL) == 0U {
    return mask
  }
  if hso_subtree_may_skip_local_set(cache, func, node_id) {
    mask
  } else {
    mask ^ (mask & @ir.EFFECT_MASK_CONTROL)
  }
}

///|
fn hso_mask_has_control_transfer(mask : UInt32) -> Bool {
  (
    mask &
    (@ir.EFFECT_MASK_CONTROL | @ir.EFFECT_MASK_CALL | @ir.EFFECT_MASK_THROW)
  ) !=
  0U
}

///|
fn hso_mask_ordered_before(barrier_mask : UInt32, moved_mask : UInt32) -> Bool {
  if hso_mask_is_pure(barrier_mask) || hso_mask_is_pure(moved_mask) {
    return false
  }
  if hso_mask_has_control_transfer(barrier_mask) ||
    hso_mask_has_control_transfer(moved_mask) {
    return true
  }
  let nonlocal_write_mask = @ir.EFFECT_MASK_GLOBAL_STATE |
    @ir.EFFECT_MASK_MEMORY_WRITE |
    @ir.EFFECT_MASK_TABLE_WRITE
  let barrier_writes_nonlocal_state = (barrier_mask & nonlocal_write_mask) != 0U
  let moved_writes_nonlocal_state = (moved_mask & nonlocal_write_mask) != 0U
  if (
      (barrier_mask & @ir.EFFECT_MASK_TRAP) != 0U && moved_writes_nonlocal_state
    ) ||
    ((moved_mask & @ir.EFFECT_MASK_TRAP) != 0U && barrier_writes_nonlocal_state) {
    return true
  }
  if (barrier_mask & @ir.EFFECT_MASK_LOCAL_STATE) != 0U &&
    (moved_mask & @ir.EFFECT_MASK_LOCAL_STATE) != 0U {
    return true
  }
  if (barrier_mask & @ir.EFFECT_MASK_GLOBAL_STATE) != 0U &&
    (moved_mask & @ir.EFFECT_MASK_GLOBAL_STATE) != 0U {
    return true
  }
  let barrier_reads_memory = (barrier_mask & @ir.EFFECT_MASK_MEMORY_READ) != 0U
  let barrier_writes_memory = (barrier_mask & @ir.EFFECT_MASK_MEMORY_WRITE) !=
    0U
  let moved_reads_memory = (moved_mask & @ir.EFFECT_MASK_MEMORY_READ) != 0U
  let moved_writes_memory = (moved_mask & @ir.EFFECT_MASK_MEMORY_WRITE) != 0U
  if barrier_writes_memory && (moved_reads_memory || moved_writes_memory) {
    return true
  }
  if barrier_reads_memory && moved_writes_memory {
    return true
  }
  let barrier_reads_table = (barrier_mask & @ir.EFFECT_MASK_TABLE_READ) != 0U
  let barrier_writes_table = (barrier_mask & @ir.EFFECT_MASK_TABLE_WRITE) != 0U
  let moved_reads_table = (moved_mask & @ir.EFFECT_MASK_TABLE_READ) != 0U
  let moved_writes_table = (moved_mask & @ir.EFFECT_MASK_TABLE_WRITE) != 0U
  if barrier_writes_table && (moved_reads_table || moved_writes_table) {
    return true
  }
  if barrier_reads_table && moved_writes_table {
    return true
  }
  false
}

///|
fn hso_mask_is_pure(mask : UInt32) -> Bool {
  mask == 0U
}

///|
fn hso_new_external_roots() -> HsoExternalRoots {
  { root_arrays: [], starts: [], }
}

///|
fn hso_external_roots_push(
  external_roots : HsoExternalRoots,
  roots : Array[Int32],
  start : Int32,
) -> Unit {
  external_roots.root_arrays.push(roots)
  external_roots.starts.push(start)
}

///|
fn hso_external_roots_pop(external_roots : HsoExternalRoots) -> Unit {
  ignore(external_roots.root_arrays.pop())
  ignore(external_roots.starts.pop())
}

///|
fn hso_external_roots_touch_local(
  func : @ir.HotFunc,
  external_roots : HsoExternalRoots,
  local_id : Int32,
) -> Bool {
  for segment_idx = 0
      segment_idx < external_roots.root_arrays.length()
      segment_idx = segment_idx + 1 {
    let roots = external_roots.root_arrays[segment_idx]
    let start = external_roots.starts[segment_idx]
    for root_idx = start; root_idx < roots.length(); root_idx = root_idx + 1 {
      if hso_subtree_touches_local(func, roots[root_idx], local_id) {
        return true
      }
    }
  }
  false
}

///|
fn hso_region_touches_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 hso_subtree_touches_local(
        func,
        @ir.hot_region_get(func, region_ref, idx),
        local_id,
      ) {
      return true
    }
  }
  false
}

///|
fn hso_region_accesses_global(
  module_ctx : @ir.HotModuleContext?,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  global_id : Int32,
  want_write : Bool,
) -> Bool {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    if hso_subtree_accesses_global(
        module_ctx,
        func,
        @ir.hot_region_get(func, region_ref, idx),
        global_id,
        want_write,
      ) {
      return true
    }
  }
  false
}

///|
fn hso_subtree_accesses_global(
  module_ctx : @ir.HotModuleContext?,
  func : @ir.HotFunc,
  node_id : Int32,
  global_id : Int32,
  want_write : 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::GlobalGet =>
      if !want_write &&
        !globals_are_provably_disjoint(module_ctx, node.imm0, global_id) {
        return true
      }
    @ir.HotOp::GlobalSet =>
      if want_write &&
        !globals_are_provably_disjoint(module_ctx, node.imm0, global_id) {
        return true
      }
    _ => ()
  }
  let operand_node = @ir.hot_node_get(func, node_id)
  let operand_count = pass_value_operand_count(func, node_id)
  for idx = 0; idx < operand_count; idx = idx + 1 {
    if hso_subtree_accesses_global(
        module_ctx,
        func,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, idx),
        ),
        global_id,
        want_write,
      ) {
      return true
    }
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_accesses_global(
        module_ctx,
        func,
        @ir.hot_region_body_ref(func, node_id),
        global_id,
        want_write,
      )
    @ir.HotOp::If =>
      if hso_region_accesses_global(
          module_ctx,
          func,
          @ir.hot_region_then_ref(func, node_id),
          global_id,
          want_write,
        ) {
        true
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_accesses_global(
              module_ctx, func, region_ref, global_id, want_write,
            )
          None => false
        }
      }
    @ir.HotOp::Try =>
      if hso_region_accesses_global(
          module_ctx,
          func,
          @ir.hot_region_body_ref(func, node_id),
          global_id,
          want_write,
        ) {
        true
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_accesses_global(
              module_ctx, func, region_ref, global_id, want_write,
            )
          None => false
        }
      }
    @ir.HotOp::TryTable =>
      hso_region_accesses_global(
        module_ctx,
        func,
        @ir.hot_region_body_ref(func, node_id),
        global_id,
        want_write,
      ) ||
      hso_region_accesses_global(
        module_ctx,
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
        global_id,
        want_write,
      )
    _ => false
  }
}

///|
fn hso_subtree_global_order_conflicts(
  module_ctx : @ir.HotModuleContext?,
  func : @ir.HotFunc,
  barrier_id : Int32,
  moved_id : Int32,
) -> Bool {
  if barrier_id < 0 || !@ir.hot_node_is_live(func, barrier_id) {
    return false
  }
  let barrier = @ir.hot_node_get(func, barrier_id)
  match barrier.op {
    @ir.HotOp::GlobalGet =>
      if hso_subtree_accesses_global(
          module_ctx,
          func,
          moved_id,
          barrier.imm0,
          true,
        ) {
        return true
      }
    @ir.HotOp::GlobalSet =>
      if hso_subtree_accesses_global(
          module_ctx,
          func,
          moved_id,
          barrier.imm0,
          false,
        ) ||
        hso_subtree_accesses_global(
          module_ctx,
          func,
          moved_id,
          barrier.imm0,
          true,
        ) {
        return true
      }
    _ => ()
  }
  let operand_node = @ir.hot_node_get(func, barrier_id)
  let operand_count = pass_value_operand_count(func, barrier_id)
  for idx = 0; idx < operand_count; idx = idx + 1 {
    if hso_subtree_global_order_conflicts(
        module_ctx,
        func,
        @ir.hot_child_get(
          func,
          barrier_id,
          pass_value_operand_slot(operand_node, idx),
        ),
        moved_id,
      ) {
      return true
    }
  }
  match barrier.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_global_order_conflicts(
        module_ctx,
        func,
        @ir.hot_region_body_ref(func, barrier_id),
        moved_id,
      )
    @ir.HotOp::If =>
      hso_region_global_order_conflicts(
        module_ctx,
        func,
        @ir.hot_region_then_ref(func, barrier_id),
        moved_id,
      ) ||
      (match @ir.hot_region_else_ref(func, barrier_id) {
        Some(region_ref) =>
          hso_region_global_order_conflicts(
            module_ctx, func, region_ref, moved_id,
          )
        None => false
      })
    @ir.HotOp::Try =>
      hso_region_global_order_conflicts(
        module_ctx,
        func,
        @ir.hot_region_body_ref(func, barrier_id),
        moved_id,
      ) ||
      (match @ir.hot_region_catch_ref(func, barrier_id) {
        Some(region_ref) =>
          hso_region_global_order_conflicts(
            module_ctx, func, region_ref, moved_id,
          )
        None => false
      })
    @ir.HotOp::TryTable =>
      hso_region_global_order_conflicts(
        module_ctx,
        func,
        @ir.hot_region_body_ref(func, barrier_id),
        moved_id,
      ) ||
      hso_region_global_order_conflicts(
        module_ctx,
        func,
        @ir.hot_region_catch_list_ref(func, barrier_id),
        moved_id,
      )
    _ => false
  }
}

///|
fn hso_region_global_order_conflicts(
  module_ctx : @ir.HotModuleContext?,
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  moved_id : Int32,
) -> Bool {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    if hso_subtree_global_order_conflicts(
        module_ctx,
        func,
        @ir.hot_region_get(func, region_ref, idx),
        moved_id,
      ) {
      return true
    }
  }
  false
}

///|
fn hso_subtree_has_global_write(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::GlobalSet => return true
    _ => ()
  }
  let operand_node = @ir.hot_node_get(func, node_id)
  let operand_count = pass_value_operand_count(func, node_id)
  for idx = 0; idx < operand_count; idx = idx + 1 {
    if hso_subtree_has_global_write(
        func,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, idx),
        ),
      ) {
      return true
    }
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_has_global_write(func, @ir.hot_region_body_ref(func, node_id))
    @ir.HotOp::If =>
      hso_region_has_global_write(func, @ir.hot_region_then_ref(func, node_id)) ||
      (match @ir.hot_region_else_ref(func, node_id) {
        Some(region_ref) => hso_region_has_global_write(func, region_ref)
        None => false
      })
    @ir.HotOp::Try =>
      hso_region_has_global_write(func, @ir.hot_region_body_ref(func, node_id)) ||
      (match @ir.hot_region_catch_ref(func, node_id) {
        Some(region_ref) => hso_region_has_global_write(func, region_ref)
        None => false
      })
    @ir.HotOp::TryTable =>
      hso_region_has_global_write(func, @ir.hot_region_body_ref(func, node_id)) ||
      hso_region_has_global_write(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
      )
    _ => false
  }
}

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

///|
fn hso_subtree_touches_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
      }
    _ => ()
  }
  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 hso_subtree_touches_local(
        func,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, slot),
        ),
        local_id,
      ) {
      return true
    }
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_touches_local(
        func,
        @ir.hot_region_body_ref(func, node_id),
        local_id,
      )
    @ir.HotOp::If => {
      let then_touches = hso_region_touches_local(
        func,
        @ir.hot_region_then_ref(func, node_id),
        local_id,
      )
      if then_touches {
        true
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_touches_local(func, region_ref, local_id)
          None => false
        }
      }
    }
    @ir.HotOp::Try => {
      let body_touches = hso_region_touches_local(
        func,
        @ir.hot_region_body_ref(func, node_id),
        local_id,
      )
      if body_touches {
        true
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_touches_local(func, region_ref, local_id)
          None => false
        }
      }
    }
    @ir.HotOp::TryTable =>
      hso_region_touches_local(
        func,
        @ir.hot_region_body_ref(func, node_id),
        local_id,
      ) ||
      hso_region_touches_local(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
        local_id,
      )
    _ => false
  }
}

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

///|
fn hso_active_owners_contains(
  active_owners : Array[Int32],
  owner_id : Int32,
) -> Bool {
  for active_owner_id in active_owners {
    if active_owner_id == owner_id {
      return true
    }
  }
  false
}

///|
fn hso_branch_escapes_active_owners(
  func : @ir.HotFunc,
  active_owners : Array[Int32],
  node_id : Int32,
) -> Bool {
  let targets : Array[Int32] = []
  match @ir.hot_branch_target(func, node_id) {
    Some(label_id) => targets.push(label_id)
    None => ()
  }
  for label_id in @ir.hot_branch_table_targets_for_node(func, node_id) {
    targets.push(label_id)
  }
  if targets.is_empty() {
    return true
  }
  for label_id in targets {
    let owner_id = @ir.hot_label_owner(func, label_id)
    if !hso_active_owners_contains(active_owners, owner_id) {
      return true
    }
  }
  false
}

///|
fn hso_subtree_may_skip_local_set(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  hso_ensure_predicate_cache_capacity(cache, node_id)
  let revision = @ir.hot_revision_current(func)
  if cache.skip_revision[node_id] == revision {
    return cache.skip_result[node_id]
  }
  let active_owners : Array[Int32] = []
  let result = hso_subtree_may_skip_local_set_from_root(
    func, node_id, active_owners,
  )
  cache.skip_revision[node_id] = revision
  cache.skip_result[node_id] = result
  result
}

///|
fn hso_subtree_may_escape_to_active_catch(
  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::Call
    | @ir.HotOp::CallIndirect
    | @ir.HotOp::CallRef
    | @ir.HotOp::Throw
    | @ir.HotOp::Rethrow
    | @ir.HotOp::ThrowRef
    | @ir.HotOp::Delegate => 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 hso_subtree_may_escape_to_active_catch(
        func,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, slot),
        ),
      ) {
      return true
    }
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_may_escape_to_active_catch(
        func,
        @ir.hot_region_body_ref(func, node_id),
      )
    @ir.HotOp::If => {
      let then_escapes = hso_region_may_escape_to_active_catch(
        func,
        @ir.hot_region_then_ref(func, node_id),
      )
      if then_escapes {
        true
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_may_escape_to_active_catch(func, region_ref)
          None => false
        }
      }
    }
    @ir.HotOp::Try => {
      let body_escapes = hso_region_may_escape_to_active_catch(
        func,
        @ir.hot_region_body_ref(func, node_id),
      )
      if body_escapes {
        true
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_may_escape_to_active_catch(func, region_ref)
          None => false
        }
      }
    }
    @ir.HotOp::TryTable =>
      hso_region_may_escape_to_active_catch(
        func,
        @ir.hot_region_body_ref(func, node_id),
      ) ||
      hso_region_may_escape_to_active_catch(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
      )
    _ => false
  }
}

///|
fn hso_subtree_contains_try_table(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::TryTable => 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 hso_subtree_contains_try_table(
        func,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, slot),
        ),
      ) {
      return true
    }
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_contains_try_table(
        func,
        @ir.hot_region_body_ref(func, node_id),
      )
    @ir.HotOp::If => {
      let then_has_try_table = hso_region_contains_try_table(
        func,
        @ir.hot_region_then_ref(func, node_id),
      )
      if then_has_try_table {
        true
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) => hso_region_contains_try_table(func, region_ref)
          None => false
        }
      }
    }
    @ir.HotOp::Try => {
      let body_has_try_table = hso_region_contains_try_table(
        func,
        @ir.hot_region_body_ref(func, node_id),
      )
      if body_has_try_table {
        true
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) => hso_region_contains_try_table(func, region_ref)
          None => false
        }
      }
    }
    _ => false
  }
}

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

///|
fn hso_subtree_has_catchable_try_table_escape(
  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::TryTable =>
      if hso_region_may_escape_to_active_catch(
          func,
          @ir.hot_region_body_ref(func, node_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 hso_subtree_has_catchable_try_table_escape(
        func,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, slot),
        ),
      ) {
      return true
    }
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_has_catchable_try_table_escape(
        func,
        @ir.hot_region_body_ref(func, node_id),
      )
    @ir.HotOp::If => {
      let then_has_escape = hso_region_has_catchable_try_table_escape(
        func,
        @ir.hot_region_then_ref(func, node_id),
      )
      if then_has_escape {
        true
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_has_catchable_try_table_escape(func, region_ref)
          None => false
        }
      }
    }
    @ir.HotOp::Try => {
      let body_has_escape = hso_region_has_catchable_try_table_escape(
        func,
        @ir.hot_region_body_ref(func, node_id),
      )
      if body_has_escape {
        true
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_has_catchable_try_table_escape(func, region_ref)
          None => false
        }
      }
    }
    @ir.HotOp::TryTable =>
      hso_region_has_catchable_try_table_escape(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
      )
    _ => false
  }
}

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

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

///|
fn hso_subtree_skip_is_branch_only(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 @ir.hot_is_branch(func, node_id) {
    if @ir.hot_branch_target(func, node_id).is_none() &&
      @ir.hot_branch_table_targets_for_node(func, node_id).is_empty() {
      return false
    }
  }
  match node.op {
    @ir.HotOp::ReturnCall
    | @ir.HotOp::ReturnCallIndirect
    | @ir.HotOp::ReturnCallRef
    | @ir.HotOp::Throw
    | @ir.HotOp::Rethrow
    | @ir.HotOp::ThrowRef
    | @ir.HotOp::Delegate
    | @ir.HotOp::Unreachable => return false
    _ => ()
  }
  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 !hso_subtree_skip_is_branch_only(
        func,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, slot),
        ),
      ) {
      return false
    }
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_skip_is_branch_only(
        func,
        @ir.hot_region_body_ref(func, node_id),
      )
    @ir.HotOp::If => {
      let then_ok = hso_region_skip_is_branch_only(
        func,
        @ir.hot_region_then_ref(func, node_id),
      )
      if !then_ok {
        false
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) => hso_region_skip_is_branch_only(func, region_ref)
          None => true
        }
      }
    }
    @ir.HotOp::Try => {
      let body_ok = hso_region_skip_is_branch_only(
        func,
        @ir.hot_region_body_ref(func, node_id),
      )
      if !body_ok {
        false
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) => hso_region_skip_is_branch_only(func, region_ref)
          None => true
        }
      }
    }
    @ir.HotOp::TryTable =>
      hso_region_skip_is_branch_only(
        func,
        @ir.hot_region_body_ref(func, node_id),
      ) &&
      hso_region_skip_is_branch_only(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
      )
    _ => true
  }
}

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

///|
fn hso_subtree_may_skip_local_set_from_root(
  func : @ir.HotFunc,
  node_id : Int32,
  active_owners : 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)
  if @ir.hot_is_branch(func, node_id) &&
    hso_branch_escapes_active_owners(func, active_owners, node_id) {
    return true
  }
  match node.op {
    @ir.HotOp::Return
    | @ir.HotOp::ReturnCall
    | @ir.HotOp::ReturnCallIndirect
    | @ir.HotOp::ReturnCallRef
    | @ir.HotOp::Throw
    | @ir.HotOp::Rethrow
    | @ir.HotOp::ThrowRef
    | @ir.HotOp::Delegate => return false
    @ir.HotOp::Unreachable => return true
    _ => ()
  }
  let pushes_owner = match node.op {
    @ir.HotOp::Block
    | @ir.HotOp::Loop
    | @ir.HotOp::If
    | @ir.HotOp::Try
    | @ir.HotOp::TryTable => true
    _ => false
  }
  if pushes_owner {
    active_owners.push(node_id)
  }
  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 hso_subtree_may_skip_local_set_from_root(
        func,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, slot),
        ),
        active_owners,
      ) {
      if pushes_owner {
        ignore(active_owners.pop())
      }
      return true
    }
  }
  let result = match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_may_skip_local_set(
        func,
        @ir.hot_region_body_ref(func, node_id),
        active_owners,
      )
    @ir.HotOp::If => {
      let then_skips = hso_region_may_skip_local_set(
        func,
        @ir.hot_region_then_ref(func, node_id),
        active_owners,
      )
      if then_skips {
        true
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_may_skip_local_set(func, region_ref, active_owners)
          None => false
        }
      }
    }
    @ir.HotOp::Try => {
      let body_skips = hso_region_may_skip_local_set(
        func,
        @ir.hot_region_body_ref(func, node_id),
        active_owners,
      )
      if body_skips {
        true
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_may_skip_local_set(func, region_ref, active_owners)
          None => false
        }
      }
    }
    @ir.HotOp::TryTable =>
      hso_region_may_skip_local_set(
        func,
        @ir.hot_region_body_ref(func, node_id),
        active_owners,
      ) ||
      hso_region_may_skip_local_set(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
        active_owners,
      )
    _ => false
  }
  if pushes_owner {
    ignore(active_owners.pop())
  }
  result
}

///|
fn hso_region_is_trapless_readonly(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  node_masks : Array[UInt32],
  region_ref : @ir.HotRegionRef,
) -> Bool {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    if !hso_subtree_is_trapless_readonly(
        cache,
        func,
        node_masks,
        @ir.hot_region_get(func, region_ref, idx),
      ) {
      return false
    }
  }
  true
}

///|
fn hso_compute_subtree_is_trapless_readonly(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  node_masks : Array[UInt32],
  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)
  let mask = hso_effects_without_descendant_trap(
    func,
    node_id,
    hso_reorder_effect_mask(cache, func, node_masks, node_id),
  )
  let allowed_effects = @ir.EFFECT_MASK_LOCAL_STATE |
    @ir.EFFECT_MASK_GLOBAL_STATE |
    @ir.EFFECT_MASK_MEMORY_READ |
    @ir.EFFECT_MASK_TABLE_READ
  let disallowed_effects = mask ^ (mask & allowed_effects)
  let allows_side_effect = match node.op {
    @ir.HotOp::Drop => true
    _ => false
  }
  if disallowed_effects != 0U ||
    (!allows_side_effect && @ir.hot_has_side_effect(func, node_id)) ||
    hso_node_may_trap(func, node_id) ||
    @ir.hot_is_branch(func, node_id) ||
    @ir.hot_is_terminator_node(func, node_id) ||
    @ir.hot_node_may_throw_exception(func, node_id) {
    return false
  }
  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 !hso_subtree_is_trapless_readonly(
        cache,
        func,
        node_masks,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, slot),
        ),
      ) {
      return false
    }
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_is_trapless_readonly(
        cache,
        func,
        node_masks,
        @ir.hot_region_body_ref(func, node_id),
      )
    @ir.HotOp::If => {
      let then_is_readonly = hso_region_is_trapless_readonly(
        cache,
        func,
        node_masks,
        @ir.hot_region_then_ref(func, node_id),
      )
      if !then_is_readonly {
        false
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_is_trapless_readonly(cache, func, node_masks, region_ref)
          None => true
        }
      }
    }
    @ir.HotOp::Try => {
      let body_is_readonly = hso_region_is_trapless_readonly(
        cache,
        func,
        node_masks,
        @ir.hot_region_body_ref(func, node_id),
      )
      if !body_is_readonly {
        false
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_is_trapless_readonly(cache, func, node_masks, region_ref)
          None => true
        }
      }
    }
    @ir.HotOp::TryTable =>
      hso_region_is_trapless_readonly(
        cache,
        func,
        node_masks,
        @ir.hot_region_body_ref(func, node_id),
      ) &&
      hso_region_is_trapless_readonly(
        cache,
        func,
        node_masks,
        @ir.hot_region_catch_list_ref(func, node_id),
      )
    _ => true
  }
}

///|
fn hso_subtree_is_trapless_readonly(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  node_masks : Array[UInt32],
  node_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  hso_ensure_predicate_cache_capacity(cache, node_id)
  let revision = @ir.hot_revision_current(func)
  if cache.readonly_revision[node_id] == revision {
    return cache.readonly_result[node_id]
  }
  let result = hso_compute_subtree_is_trapless_readonly(
    cache, func, node_masks, node_id,
  )
  cache.readonly_revision[node_id] = revision
  cache.readonly_result[node_id] = result
  result
}

///|
fn hso_region_is_trapless_reorderable(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  node_masks : Array[UInt32],
  region_ref : @ir.HotRegionRef,
  active_owners : Array[Int32],
) -> Bool {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    if !hso_subtree_is_trapless_reorderable(
        cache,
        func,
        node_masks,
        @ir.hot_region_get(func, region_ref, idx),
        active_owners,
      ) {
      return false
    }
  }
  true
}

///|
fn hso_subtree_is_trapless_reorderable(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  node_masks : Array[UInt32],
  node_id : Int32,
  active_owners : 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)
  if @ir.hot_is_branch(func, node_id) &&
    hso_branch_escapes_active_owners(func, active_owners, node_id) {
    return false
  }
  let mask = hso_effects_without_descendant_trap(
    func,
    node_id,
    hso_effects_for_node(func, node_masks, node_id),
  )
  let allowed_effects = @ir.EFFECT_MASK_CONTROL |
    @ir.EFFECT_MASK_LOCAL_STATE |
    @ir.EFFECT_MASK_GLOBAL_STATE |
    @ir.EFFECT_MASK_MEMORY_READ |
    @ir.EFFECT_MASK_TABLE_READ
  let disallowed_effects = mask ^ (mask & allowed_effects)
  let allows_side_effect = match node.op {
    @ir.HotOp::Drop
    | @ir.HotOp::Block
    | @ir.HotOp::Loop
    | @ir.HotOp::If
    | @ir.HotOp::Try
    | @ir.HotOp::TryTable => true
    _ => @ir.hot_is_branch(func, node_id)
  }
  if disallowed_effects != 0U ||
    (!allows_side_effect && @ir.hot_has_side_effect(func, node_id)) ||
    hso_node_may_trap(func, node_id) ||
    @ir.hot_node_may_throw_exception(func, node_id) ||
    (
      !@ir.hot_is_branch(func, node_id) &&
      @ir.hot_is_terminator_node(func, node_id)
    ) {
    return false
  }
  let pushes_owner = match node.op {
    @ir.HotOp::Block
    | @ir.HotOp::Loop
    | @ir.HotOp::If
    | @ir.HotOp::Try
    | @ir.HotOp::TryTable => true
    _ => false
  }
  if pushes_owner {
    active_owners.push(node_id)
  }
  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 !hso_subtree_is_trapless_reorderable(
        cache,
        func,
        node_masks,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, slot),
        ),
        active_owners,
      ) {
      if pushes_owner {
        ignore(active_owners.pop())
      }
      return false
    }
  }
  let regions_ok = match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_is_trapless_reorderable(
        cache,
        func,
        node_masks,
        @ir.hot_region_body_ref(func, node_id),
        active_owners,
      )
    @ir.HotOp::If => {
      let then_is_reorderable = hso_region_is_trapless_reorderable(
        cache,
        func,
        node_masks,
        @ir.hot_region_then_ref(func, node_id),
        active_owners,
      )
      if !then_is_reorderable {
        false
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_is_trapless_reorderable(
              cache, func, node_masks, region_ref, active_owners,
            )
          None => true
        }
      }
    }
    @ir.HotOp::Try => {
      let body_is_reorderable = hso_region_is_trapless_reorderable(
        cache,
        func,
        node_masks,
        @ir.hot_region_body_ref(func, node_id),
        active_owners,
      )
      if !body_is_reorderable {
        false
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_is_trapless_reorderable(
              cache, func, node_masks, region_ref, active_owners,
            )
          None => true
        }
      }
    }
    @ir.HotOp::TryTable =>
      hso_region_is_trapless_reorderable(
        cache,
        func,
        node_masks,
        @ir.hot_region_body_ref(func, node_id),
        active_owners,
      ) &&
      hso_region_is_trapless_reorderable(
        cache,
        func,
        node_masks,
        @ir.hot_region_catch_list_ref(func, node_id),
        active_owners,
      )
    _ => true
  }
  if pushes_owner {
    ignore(active_owners.pop())
  }
  regions_ok
}

///|
fn hso_region_is_trapless_readonly_contained_control(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  node_masks : Array[UInt32],
  region_ref : @ir.HotRegionRef,
  active_owners : Array[Int32],
) -> Bool {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    if !hso_subtree_is_trapless_readonly_contained_control(
        cache,
        func,
        node_masks,
        @ir.hot_region_get(func, region_ref, idx),
        active_owners,
      ) {
      return false
    }
  }
  true
}

///|
fn hso_subtree_is_trapless_readonly_contained_control(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  node_masks : Array[UInt32],
  node_id : Int32,
  active_owners : 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)
  if node.op is @ir.HotOp::Loop &&
    hso_region_targets_owner_label(
      func,
      @ir.hot_region_body_ref(func, node_id),
      node_id,
    ) {
    return false
  }
  if @ir.hot_is_branch(func, node_id) &&
    hso_branch_escapes_active_owners(func, active_owners, node_id) {
    return false
  }
  let mask = hso_effects_without_descendant_trap(
    func,
    node_id,
    hso_reorder_effect_mask(cache, func, node_masks, node_id),
  )
  let allowed_effects = @ir.EFFECT_MASK_CONTROL |
    @ir.EFFECT_MASK_LOCAL_STATE |
    @ir.EFFECT_MASK_GLOBAL_STATE |
    @ir.EFFECT_MASK_MEMORY_READ |
    @ir.EFFECT_MASK_TABLE_READ
  let disallowed_effects = mask ^ (mask & allowed_effects)
  let allows_side_effect = match node.op {
    @ir.HotOp::Drop
    | @ir.HotOp::Block
    | @ir.HotOp::Loop
    | @ir.HotOp::If
    | @ir.HotOp::Try
    | @ir.HotOp::TryTable => true
    _ => @ir.hot_is_branch(func, node_id)
  }
  if disallowed_effects != 0U ||
    (!allows_side_effect && @ir.hot_has_side_effect(func, node_id)) ||
    hso_node_may_trap(func, node_id) ||
    @ir.hot_node_may_throw_exception(func, node_id) ||
    (
      !@ir.hot_is_branch(func, node_id) &&
      @ir.hot_is_terminator_node(func, node_id)
    ) {
    return false
  }
  let pushes_owner = match node.op {
    @ir.HotOp::Block
    | @ir.HotOp::Loop
    | @ir.HotOp::If
    | @ir.HotOp::Try
    | @ir.HotOp::TryTable => true
    _ => false
  }
  if pushes_owner {
    active_owners.push(node_id)
  }
  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 !hso_subtree_is_trapless_readonly_contained_control(
        cache,
        func,
        node_masks,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, slot),
        ),
        active_owners,
      ) {
      if pushes_owner {
        ignore(active_owners.pop())
      }
      return false
    }
  }
  let regions_ok = match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_is_trapless_readonly_contained_control(
        cache,
        func,
        node_masks,
        @ir.hot_region_body_ref(func, node_id),
        active_owners,
      )
    @ir.HotOp::If => {
      let then_is_readonly = hso_region_is_trapless_readonly_contained_control(
        cache,
        func,
        node_masks,
        @ir.hot_region_then_ref(func, node_id),
        active_owners,
      )
      if !then_is_readonly {
        false
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_is_trapless_readonly_contained_control(
              cache, func, node_masks, region_ref, active_owners,
            )
          None => true
        }
      }
    }
    @ir.HotOp::Try => {
      let body_is_readonly = hso_region_is_trapless_readonly_contained_control(
        cache,
        func,
        node_masks,
        @ir.hot_region_body_ref(func, node_id),
        active_owners,
      )
      if !body_is_readonly {
        false
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_is_trapless_readonly_contained_control(
              cache, func, node_masks, region_ref, active_owners,
            )
          None => true
        }
      }
    }
    @ir.HotOp::TryTable =>
      hso_region_is_trapless_readonly_contained_control(
        cache,
        func,
        node_masks,
        @ir.hot_region_body_ref(func, node_id),
        active_owners,
      ) &&
      hso_region_is_trapless_readonly_contained_control(
        cache,
        func,
        node_masks,
        @ir.hot_region_catch_list_ref(func, node_id),
        active_owners,
      )
    _ => true
  }
  if pushes_owner {
    ignore(active_owners.pop())
  }
  regions_ok
}

///|
fn hso_nodes_can_reorder(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_masks : Array[UInt32],
  barrier_id : Int32,
  barrier_mask : UInt32,
  moved_id : Int32,
  moved_mask : UInt32,
) -> Bool {
  // Descriptor operand masks deliberately omit local reads. Keep their real
  // read/write dependencies before applying the coarser effect relaxations.
  if hso_local_order_conflicts(func, barrier_id, moved_id) {
    return false
  }
  if ((barrier_mask | moved_mask) & @ir.EFFECT_MASK_GLOBAL_STATE) != 0U &&
    hso_subtree_global_order_conflicts(module_ctx, func, barrier_id, moved_id) {
    return false
  }
  let barrier_reorder_mask = if (barrier_mask & @ir.EFFECT_MASK_CONTROL) == 0U {
    barrier_mask
  } else if hso_subtree_may_skip_local_set(cache, func, barrier_id) {
    barrier_mask
  } else {
    barrier_mask ^ (barrier_mask & @ir.EFFECT_MASK_CONTROL)
  }
  let moved_reorder_mask = if (moved_mask & @ir.EFFECT_MASK_CONTROL) == 0U {
    moved_mask
  } else if hso_subtree_may_skip_local_set(cache, func, moved_id) {
    moved_mask
  } else {
    moved_mask ^ (moved_mask & @ir.EFFECT_MASK_CONTROL)
  }
  let barrier_without_global = barrier_reorder_mask ^
    (barrier_reorder_mask & @ir.EFFECT_MASK_GLOBAL_STATE)
  let moved_without_global = moved_reorder_mask ^
    (moved_reorder_mask & @ir.EFFECT_MASK_GLOBAL_STATE)
  if barrier_reorder_mask != 0U &&
    moved_reorder_mask != 0U &&
    barrier_without_global == 0U &&
    moved_without_global == 0U {
    return true
  }
  let barrier_order = hso_order_effects_for_node(
    cache, func, module_ctx, barrier_id,
  )
  let moved_order = hso_order_effects_for_node(
    cache, func, module_ctx, moved_id,
  )
  let release_gc_allowed_mask = @ir.EFFECT_MASK_LOCAL_STATE |
    @ir.EFFECT_MASK_MEMORY_READ |
    @ir.EFFECT_MASK_MEMORY_WRITE |
    @ir.EFFECT_MASK_ATOMIC_READ |
    @ir.EFFECT_MASK_ATOMIC_WRITE |
    @ir.EFFECT_MASK_RELEASE |
    @ir.EFFECT_MASK_TRAP
  if (
      (barrier_reorder_mask | moved_reorder_mask) ^
      ((barrier_reorder_mask | moved_reorder_mask) & release_gc_allowed_mask)
    ) ==
    0U &&
    hso_release_memory_before_shared_gc_read_can_reorder(
      barrier_order, moved_order,
    ) {
    return true
  }
  if hso_mask_ordered_before(barrier_reorder_mask, moved_reorder_mask) ||
    hso_order_effects_ordered_before(barrier_order, moved_order) {
    ()
  } else {
    return true
  }
  let local_control_barrier_mask = @ir.EFFECT_MASK_LOCAL_STATE |
    @ir.EFFECT_MASK_CONTROL
  let local_only_barrier = (
      barrier_reorder_mask ^ (barrier_reorder_mask & local_control_barrier_mask)
    ) ==
    0U
  if local_only_barrier &&
    (moved_reorder_mask & @ir.EFFECT_MASK_LOCAL_STATE) == 0U {
    return true
  }
  hso_subtree_is_trapless_readonly(cache, func, node_masks, barrier_id) &&
  hso_subtree_is_trapless_readonly(cache, func, node_masks, moved_id)
}

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

///|
fn hso_desc_node_has_loop_control_barrier(
  func : @ir.HotFunc,
  node_id : Int32,
  active_loop_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::BrOnNull
    | @ir.HotOp::BrOnNonNull
    | @ir.HotOp::BrOnCast
    | @ir.HotOp::BrOnCastFail =>
      return active_loop_id >= 0 && node.imm0 == active_loop_id
    @ir.HotOp::BrTable => return active_loop_id >= 0
    _ => ()
  }
  if @ir.hot_is_terminator_node(func, node_id) {
    return true
  }
  let operand_node = @ir.hot_node_get(func, node_id)
  let operand_count = pass_value_operand_count(func, node_id)
  for idx = 0; idx < operand_count; idx = idx + 1 {
    if hso_desc_node_has_loop_control_barrier(
        func,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, idx),
        ),
        active_loop_id,
      ) {
      return true
    }
  }
  match node.op {
    @ir.HotOp::Block =>
      hso_desc_region_has_loop_control_barrier(
        func,
        @ir.hot_region_body_ref(func, node_id),
        active_loop_id,
      )
    @ir.HotOp::Loop =>
      hso_desc_region_has_loop_control_barrier(
        func,
        @ir.hot_region_body_ref(func, node_id),
        node.imm0,
      )
    @ir.HotOp::If =>
      if hso_desc_region_has_loop_control_barrier(
          func,
          @ir.hot_region_then_ref(func, node_id),
          active_loop_id,
        ) {
        true
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) =>
            hso_desc_region_has_loop_control_barrier(
              func, region_ref, active_loop_id,
            )
          None => false
        }
      }
    @ir.HotOp::Try =>
      if hso_desc_region_has_loop_control_barrier(
          func,
          @ir.hot_region_body_ref(func, node_id),
          active_loop_id,
        ) {
        true
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) =>
            hso_desc_region_has_loop_control_barrier(
              func, region_ref, active_loop_id,
            )
          None => false
        }
      }
    @ir.HotOp::TryTable =>
      hso_desc_region_has_loop_control_barrier(
        func,
        @ir.hot_region_body_ref(func, node_id),
        active_loop_id,
      ) ||
      hso_desc_region_has_loop_control_barrier(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
        active_loop_id,
      )
    _ => false
  }
}

///|
fn hso_desc_operand_effects_for_region(
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_masks : Array[UInt32],
  region_ref : @ir.HotRegionRef,
) -> UInt32 {
  let mut mask = 0U
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    mask = mask |
      hso_desc_operand_effects_for_node(
        func,
        module_ctx,
        node_masks,
        @ir.hot_region_get(func, region_ref, idx),
      )
  }
  mask
}

///|
fn hso_desc_operand_effects_for_node(
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_masks : Array[UInt32],
  node_id : Int32,
) -> UInt32 {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return 0U
  }
  let node = @ir.hot_node_get(func, node_id)
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.Instruction::StructNew(_))
    | Some(@lib.Instruction::StructNewDefault(_))
    | Some(@lib.Instruction::StructNewDesc(_))
    | Some(@lib.Instruction::StructNewDefaultDesc(_)) => {
      let mut mask = hso_struct_new_shallow_effects(func, node_id)
      let operand_node = @ir.hot_node_get(func, node_id)
      let operand_count = pass_value_operand_count(func, node_id)
      for idx = 0; idx < operand_count; idx = idx + 1 {
        mask = mask |
          hso_desc_operand_effects_for_node(
            func,
            module_ctx,
            node_masks,
            @ir.hot_child_get(
              func,
              node_id,
              pass_value_operand_slot(operand_node, idx),
            ),
          )
      }
      return mask
    }
    _ => ()
  }
  if @ir.hot_is_branch(func, node_id) {
    let mut mask = 0U
    let operand_node = @ir.hot_node_get(func, node_id)
    let operand_count = pass_value_operand_count(func, node_id)
    for idx = 0; idx < operand_count; idx = idx + 1 {
      mask = mask |
        hso_desc_operand_effects_for_node(
          func,
          module_ctx,
          node_masks,
          @ir.hot_child_get(
            func,
            node_id,
            pass_value_operand_slot(operand_node, idx),
          ),
        )
    }
    return mask
  }
  match node.op {
    @ir.HotOp::LocalGet => return 0U
    @ir.HotOp::Drop => {
      let mut mask = 0U
      let operand_node = @ir.hot_node_get(func, node_id)
      let operand_count = pass_value_operand_count(func, node_id)
      for idx = 0; idx < operand_count; idx = idx + 1 {
        mask = mask |
          hso_desc_operand_effects_for_node(
            func,
            module_ctx,
            node_masks,
            @ir.hot_child_get(
              func,
              node_id,
              pass_value_operand_slot(operand_node, idx),
            ),
          )
      }
      return mask
    }
    @ir.HotOp::GlobalGet =>
      match module_ctx {
        Some(ctx) =>
          match
            @ir.hot_module_resolve_global_type(
              ctx,
              @lib.GlobalIdx::new(node.imm0.reinterpret_as_uint()),
            ) {
            Some(@lib.GlobalType(_, false)) => return 0U
            _ => ()
          }
        None => ()
      }
    @ir.HotOp::Block => {
      let region_ref = @ir.hot_region_body_ref(func, node_id)
      let mut mask = hso_desc_operand_effects_for_region(
        func, module_ctx, node_masks, region_ref,
      )
      mask = mask ^ (mask & @ir.EFFECT_MASK_CONTROL)
      return mask
    }
    @ir.HotOp::Loop => {
      let region_ref = @ir.hot_region_body_ref(func, node_id)
      if hso_desc_region_has_loop_control_barrier(func, region_ref, node.imm0) {
        return hso_effects_for_node(func, node_masks, node_id)
      }
      let mut mask = hso_desc_operand_effects_for_region(
        func, module_ctx, node_masks, region_ref,
      )
      mask = mask ^ (mask & @ir.EFFECT_MASK_CONTROL)
      return mask
    }
    @ir.HotOp::If => {
      let mut mask = 0U
      let operand_node = @ir.hot_node_get(func, node_id)
      let operand_count = pass_value_operand_count(func, node_id)
      for idx = 0; idx < operand_count; idx = idx + 1 {
        mask = mask |
          hso_desc_operand_effects_for_node(
            func,
            module_ctx,
            node_masks,
            @ir.hot_child_get(
              func,
              node_id,
              pass_value_operand_slot(operand_node, idx),
            ),
          )
      }
      mask = mask |
        hso_desc_operand_effects_for_region(
          func,
          module_ctx,
          node_masks,
          @ir.hot_region_then_ref(func, node_id),
        )
      match @ir.hot_region_else_ref(func, node_id) {
        Some(region_ref) =>
          mask = mask |
            hso_desc_operand_effects_for_region(
              func, module_ctx, node_masks, region_ref,
            )
        None => ()
      }
      return mask
    }
    @ir.HotOp::Select => {
      let mut mask = 0U
      let operand_node = @ir.hot_node_get(func, node_id)
      let operand_count = pass_value_operand_count(func, node_id)
      for idx = 0; idx < operand_count; idx = idx + 1 {
        mask = mask |
          hso_desc_operand_effects_for_node(
            func,
            module_ctx,
            node_masks,
            @ir.hot_child_get(
              func,
              node_id,
              pass_value_operand_slot(operand_node, idx),
            ),
          )
      }
      return mask
    }
    _ => ()
  }
  hso_effects_for_node(func, node_masks, node_id)
}

///|
fn hso_struct_new_operand_effect_mask(
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_masks : Array[UInt32],
  struct_new_id : Int32,
) -> UInt32 {
  let mut mask = 0U
  if struct_new_id < 0 || !@ir.hot_node_is_live(func, struct_new_id) {
    return mask
  }
  for idx = 0; idx < @ir.hot_child_count(func, struct_new_id); idx = idx + 1 {
    mask = mask |
      hso_desc_operand_effects_for_node(
        func,
        module_ctx,
        node_masks,
        @ir.hot_child_get(func, struct_new_id, idx),
      )
  }
  mask
}

///|
fn hso_struct_new_global_read_conflicts_with_write(
  module_ctx : @ir.HotModuleContext?,
  func : @ir.HotFunc,
  struct_new_id : Int32,
  blocker_id : Int32,
) -> Bool {
  if struct_new_id < 0 || !@ir.hot_node_is_live(func, struct_new_id) {
    return false
  }
  if blocker_id < 0 || !@ir.hot_node_is_live(func, blocker_id) {
    return false
  }
  let blocker = @ir.hot_node_get(func, blocker_id)
  match blocker.op {
    @ir.HotOp::GlobalSet => {
      for idx = 0; idx < @ir.hot_child_count(func, struct_new_id); idx = idx + 1 {
        if hso_subtree_accesses_global(
            module_ctx,
            func,
            @ir.hot_child_get(func, struct_new_id, idx),
            blocker.imm0,
            false,
          ) {
          return true
        }
      }
      false
    }
    _ => false
  }
}

///|
fn hso_global_only_ordering_can_be_ignored(
  module_ctx : @ir.HotModuleContext?,
  func : @ir.HotFunc,
  moved_struct_new_id : Int32,
  moved_mask : UInt32,
  blocker_id : Int32,
  blocker_mask : UInt32,
) -> Bool {
  let moved_without_global = moved_mask ^
    (moved_mask & @ir.EFFECT_MASK_GLOBAL_STATE)
  let blocker_without_global = blocker_mask ^
    (blocker_mask & @ir.EFFECT_MASK_GLOBAL_STATE)
  if moved_without_global != 0U || blocker_without_global != 0U {
    return false
  }
  !hso_subtree_global_order_conflicts(
    module_ctx, func, blocker_id, moved_struct_new_id,
  ) &&
  !hso_struct_new_global_read_conflicts_with_write(
    module_ctx, func, moved_struct_new_id, blocker_id,
  )
}

///|
fn hso_subtree_has_tail_or_throw_escape(
  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::Return
    | @ir.HotOp::ReturnCall
    | @ir.HotOp::ReturnCallIndirect
    | @ir.HotOp::ReturnCallRef
    | @ir.HotOp::Throw
    | @ir.HotOp::Rethrow
    | @ir.HotOp::ThrowRef
    | @ir.HotOp::Delegate
    | @ir.HotOp::Unreachable => 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 hso_subtree_has_tail_or_throw_escape(
        func,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, slot),
        ),
      ) {
      return true
    }
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_has_tail_or_throw_escape(
        func,
        @ir.hot_region_body_ref(func, node_id),
      )
    @ir.HotOp::If =>
      if hso_region_has_tail_or_throw_escape(
          func,
          @ir.hot_region_then_ref(func, node_id),
        ) {
        true
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_has_tail_or_throw_escape(func, region_ref)
          None => false
        }
      }
    @ir.HotOp::Try =>
      if hso_region_has_tail_or_throw_escape(
          func,
          @ir.hot_region_body_ref(func, node_id),
        ) {
        true
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_has_tail_or_throw_escape(func, region_ref)
          None => false
        }
      }
    @ir.HotOp::TryTable =>
      hso_region_has_tail_or_throw_escape(
        func,
        @ir.hot_region_body_ref(func, node_id),
      ) ||
      hso_region_has_tail_or_throw_escape(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
      )
    _ => false
  }
}

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

///|
fn hso_try_table_allows_swap(func : @ir.HotFunc, try_id : Int32) -> Bool {
  if try_id < 0 || !@ir.hot_node_is_live(func, try_id) {
    return false
  }
  match @ir.hot_node_get(func, try_id).op {
    @ir.HotOp::TryTable => ()
    _ => return false
  }
  !hso_region_has_tail_or_throw_escape(
    func,
    @ir.hot_region_body_ref(func, try_id),
  )
}

///|
fn hso_result_try_table_allows_drop_swap(
  func : @ir.HotFunc,
  try_id : Int32,
) -> Bool {
  hso_try_table_allows_swap(func, try_id) &&
  hso_sequence_result_type(func, try_id) != None
}

///|
fn hso_is_void_try_table_block(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::Block => ()
    _ => return false
  }
  if hso_sequence_result_type(func, node_id) != None {
    return false
  }
  let body_ref = @ir.hot_region_body_ref(func, node_id)
  if @ir.hot_region_root_count(func, body_ref) != 1 {
    return false
  }
  let try_id = @ir.hot_region_get(func, body_ref, 0)
  if hso_sequence_result_type(func, try_id) != None {
    return false
  }
  hso_try_table_allows_swap(func, try_id)
}

///|
fn hso_is_dropped_result_try_table_block(
  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::Block => ()
    _ => return false
  }
  if hso_sequence_result_type(func, node_id) != None {
    return false
  }
  let body_ref = @ir.hot_region_body_ref(func, node_id)
  let root_count = @ir.hot_region_root_count(func, body_ref)
  if root_count == 1 {
    let drop_id = @ir.hot_region_get(func, body_ref, 0)
    if drop_id < 0 || !@ir.hot_node_is_live(func, drop_id) {
      return false
    }
    match @ir.hot_node_get(func, drop_id).op {
      @ir.HotOp::Drop => ()
      _ => return false
    }
    if @ir.hot_child_count(func, drop_id) != 1 {
      return false
    }
    let try_id = @ir.hot_child_get(func, drop_id, 0)
    if try_id < 0 || !@ir.hot_node_is_live(func, try_id) {
      return false
    }
    hso_result_try_table_allows_drop_swap(func, try_id)
  } else if root_count == 2 {
    let try_id = @ir.hot_region_get(func, body_ref, 0)
    let drop_id = @ir.hot_region_get(func, body_ref, 1)
    if try_id < 0 ||
      drop_id < 0 ||
      !@ir.hot_node_is_live(func, try_id) ||
      !@ir.hot_node_is_live(func, drop_id) {
      return false
    }
    if !hso_result_try_table_allows_drop_swap(func, try_id) {
      return false
    }
    match @ir.hot_node_get(func, drop_id).op {
      @ir.HotOp::Drop => true
      _ => false
    }
  } else {
    false
  }
}

///|
fn hso_wrapped_try_table_swap_allowed(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_masks : Array[UInt32],
  moved_struct_new_id : Int32,
  blocker_id : Int32,
  target_local_id : Int32,
) -> Bool {
  if !hso_is_dropped_result_try_table_block(func, blocker_id) &&
    !hso_is_void_try_table_block(func, blocker_id) {
    return false
  }
  let raw_blocker_mask = hso_reorder_effect_mask(
    cache, func, node_masks, blocker_id,
  )
  let blocker_mask = raw_blocker_mask ^
    (raw_blocker_mask & @ir.EFFECT_MASK_CONTROL)
  let moved_mask = hso_struct_new_operand_effect_mask(
      func, module_ctx, node_masks, moved_struct_new_id,
    ) |
    hso_struct_new_shallow_effects(func, moved_struct_new_id)
  if hso_ordered_before(
      cache, func, module_ctx, moved_struct_new_id, moved_mask, blocker_id, blocker_mask,
    ) &&
    !hso_global_only_ordering_can_be_ignored(
      module_ctx, func, moved_struct_new_id, moved_mask, blocker_id, blocker_mask,
    ) {
    return false
  }
  if (blocker_mask & @ir.EFFECT_MASK_LOCAL_STATE) != 0U &&
    hso_subtree_touches_local(func, blocker_id, target_local_id) {
    return false
  }
  true
}

///|
fn hso_root_can_swap_before_local_struct_new(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_masks : Array[UInt32],
  moved_struct_new_id : Int32,
  blocker_id : Int32,
  target_local_id : Int32,
) -> Bool {
  if blocker_id < 0 || !@ir.hot_node_is_live(func, blocker_id) {
    return false
  }
  // Binaryen optimizes tee-wrapped struct.set action roots before block-local
  // chain swapping sees them. Treat an unprocessed tee-root as a constructor
  // local-set barrier here so one fresh struct chain does not move across and
  // consume a separate tee chain in the same region.
  match hso_tee_wrapped_struct_set_local_id(func, blocker_id) {
    Some(_) => return false
    None => ()
  }
  let blocker_node = @ir.hot_node_get(func, blocker_id)
  match blocker_node.op {
    @ir.HotOp::LocalSet =>
      if @ir.hot_child_count(func, blocker_id) == 1 {
        match
          @ir.hot_node_exact_instr(func, @ir.hot_child_get(func, blocker_id, 0)) {
          Some(@lib.Instruction::StructNew(_))
          | Some(@lib.Instruction::StructNewDefault(_))
          | Some(@lib.Instruction::StructNewDesc(_))
          | Some(@lib.Instruction::StructNewDefaultDesc(_)) => return false
          _ => ()
        }
      }
    _ => ()
  }
  let wrapped_try_table_swap = hso_wrapped_try_table_swap_allowed(
    cache, func, module_ctx, node_masks, moved_struct_new_id, blocker_id, target_local_id,
  )
  if hso_subtree_may_escape_to_active_catch(func, blocker_id) &&
    !wrapped_try_table_swap {
    return false
  }
  if wrapped_try_table_swap {
    return true
  }
  let blocker_mask = hso_reorder_effect_mask(
    cache, func, node_masks, blocker_id,
  )
  let moved_mask = hso_struct_new_operand_effect_mask(
      func, module_ctx, node_masks, moved_struct_new_id,
    ) |
    hso_struct_new_shallow_effects(func, moved_struct_new_id)
  if hso_ordered_before(
      cache, func, module_ctx, moved_struct_new_id, moved_mask, blocker_id, blocker_mask,
    ) &&
    !hso_global_only_ordering_can_be_ignored(
      module_ctx, func, moved_struct_new_id, moved_mask, blocker_id, blocker_mask,
    ) {
    return false
  }
  let disallowed_effects = @ir.EFFECT_MASK_CONTROL |
    @ir.EFFECT_MASK_CALL |
    @ir.EFFECT_MASK_THROW
  if (blocker_mask & @ir.EFFECT_MASK_LOCAL_STATE) != 0U &&
    hso_subtree_touches_local(func, blocker_id, target_local_id) {
    return false
  }
  if moved_mask == 0U {
    match blocker_node.op {
      @ir.HotOp::Call | @ir.HotOp::CallIndirect | @ir.HotOp::CallRef =>
        return true
      _ => ()
    }
  }
  if (blocker_mask & disallowed_effects) == 0U &&
    !@ir.hot_is_terminator_node(func, blocker_id) {
    return true
  }
  hso_subtree_is_trapless_reorderable(cache, func, node_masks, blocker_id, [])
}

///|
fn hso_swapped_readonly_roots_respect_ref_order(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  node_masks : Array[UInt32],
  swapped_roots : Array[Int32],
  ref_id : Int32,
) -> Bool {
  let ref_node = @ir.hot_node_get(func, ref_id)
  let ref_local = match ref_node.op {
    @ir.HotOp::LocalGet => ref_node.imm0
    _ => return false
  }
  for root_id in swapped_roots {
    if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
      continue
    }
    if ref_id < root_id &&
      hso_subtree_is_trapless_reorderable(cache, func, node_masks, root_id, []) &&
      hso_subtree_touches_local(func, root_id, ref_local) {
      return false
    }
  }
  true
}

///|
fn hso_note_region_local_max_root(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  root_id : Int32,
  local_order : HsoLocalOrderWorkspace,
) -> Unit {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    hso_note_subtree_local_max_root(
      func,
      @ir.hot_region_get(func, region_ref, idx),
      root_id,
      local_order,
    )
  }
}

///|
fn hso_note_subtree_local_max_root(
  func : @ir.HotFunc,
  node_id : Int32,
  root_id : Int32,
  local_order : HsoLocalOrderWorkspace,
) -> 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 | @ir.HotOp::LocalSet | @ir.HotOp::LocalTee =>
      hso_local_order_workspace_note(local_order, node.imm0, root_id)
    _ => ()
  }
  let operand_count = pass_value_operand_count(func, node_id)
  for slot = 0; slot < operand_count; slot = slot + 1 {
    hso_note_subtree_local_max_root(
      func,
      @ir.hot_child_get(func, node_id, pass_value_operand_slot(node, slot)),
      root_id,
      local_order,
    )
  }
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_note_region_local_max_root(
        func,
        @ir.hot_region_body_ref(func, node_id),
        root_id,
        local_order,
      )
    @ir.HotOp::If => {
      hso_note_region_local_max_root(
        func,
        @ir.hot_region_then_ref(func, node_id),
        root_id,
        local_order,
      )
      match @ir.hot_region_else_ref(func, node_id) {
        Some(region_ref) =>
          hso_note_region_local_max_root(func, region_ref, root_id, local_order)
        None => ()
      }
    }
    @ir.HotOp::Try => {
      hso_note_region_local_max_root(
        func,
        @ir.hot_region_body_ref(func, node_id),
        root_id,
        local_order,
      )
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(region_ref) =>
          hso_note_region_local_max_root(func, region_ref, root_id, local_order)
        None => ()
      }
    }
    @ir.HotOp::TryTable => {
      hso_note_region_local_max_root(
        func,
        @ir.hot_region_body_ref(func, node_id),
        root_id,
        local_order,
      )
      hso_note_region_local_max_root(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
        root_id,
        local_order,
      )
    }
    _ => ()
  }
}

///|
fn hso_note_swapped_readonly_root(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  node_masks : Array[UInt32],
  root_id : Int32,
  local_order : HsoLocalOrderWorkspace,
) -> Unit {
  if hso_subtree_is_trapless_reorderable(cache, func, node_masks, root_id, []) {
    hso_note_subtree_local_max_root(func, root_id, root_id, local_order)
  }
}

///|
fn hso_build_const_f32(func : @ir.HotFunc) -> Int32 {
  @ir.hot_build_const_f32(func, @lib.F32(0.0))
}

///|
fn hso_build_const_f64(func : @ir.HotFunc) -> Int32 {
  @ir.hot_build_const_f64(func, @lib.F64(0.0))
}

///|
fn hso_build_const_v128_zero(func : @ir.HotFunc) -> Int32 {
  @ir.hot_build_const_v128_zero(func)
}

///|
fn hso_build_default_value(func : @ir.HotFunc, vt : @lib.ValType) -> Int32? {
  match vt {
    @lib.NumTypeValType(@lib.I32NumType) =>
      Some(@ir.hot_build_const_i32(func, @lib.I32(0)))
    @lib.NumTypeValType(@lib.I64NumType) =>
      Some(@ir.hot_build_const_i64(func, @lib.I64(0L)))
    @lib.NumTypeValType(@lib.F32NumType) => Some(hso_build_const_f32(func))
    @lib.NumTypeValType(@lib.F64NumType) => Some(hso_build_const_f64(func))
    @lib.VecTypeValType => Some(hso_build_const_v128_zero(func))
    @lib.RefTypeValType(ref_type) =>
      if ref_type.is_nullable() {
        Some(@ir.hot_build_ref_null(func, ref_type))
      } else {
        None
      }
    @lib.BotValType => None
  }
}

///|
fn hso_type_struct_field_count(
  type_cache : HsoTypeCache,
  module_ctx : @ir.HotModuleContext,
  type_idx : @lib.TypeIdx,
) -> Int32? {
  let type_slot = match type_idx {
    @lib.TypeIdx(raw_idx) | @lib.RecIdx(raw_idx) => raw_idx.reinterpret_as_int()
  }
  hso_ensure_type_cache_capacity(type_cache, type_slot)
  match type_cache.struct_field_counts[type_slot] {
    count if count >= 0 => return Some(count)
    -1 => return None
    _ => ()
  }
  let fields = match
    @ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
    Some(fields) => fields
    None => {
      type_cache.struct_field_counts[type_slot] = -1
      return None
    }
  }
  type_cache.struct_field_counts[type_slot] = fields.length()
  Some(fields.length())
}

///|
fn hso_type_defaultable_struct_field_types(
  type_cache : HsoTypeCache,
  module_ctx : @ir.HotModuleContext,
  type_idx : @lib.TypeIdx,
) -> Array[@lib.ValType]? {
  let type_slot = match type_idx {
    @lib.TypeIdx(raw_idx) | @lib.RecIdx(raw_idx) => raw_idx.reinterpret_as_int()
  }
  hso_ensure_type_cache_capacity(type_cache, type_slot)
  match type_cache.default_field_counts[type_slot] {
    count if count >= 0 =>
      match type_cache.default_field_types[type_slot] {
        Some(field_types) => return Some(field_types)
        None => ()
      }
    -1 => return None
    _ => ()
  }
  let fields = match
    @ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
    Some(fields) => fields
    None => {
      type_cache.struct_field_counts[type_slot] = -1
      return None
    }
  }
  type_cache.struct_field_counts[type_slot] = fields.length()
  let field_types : Array[@lib.ValType] = []
  for field in fields {
    let @lib.FieldType(stor, _) = field
    let field_type = stor.unpack()
    match field_type {
      @lib.RefTypeValType(ref_type) =>
        if !ref_type.is_nullable() {
          type_cache.default_field_counts[type_slot] = -1
          type_cache.default_field_types[type_slot] = None
          return None
        }
      _ => ()
    }
    field_types.push(field_type)
  }
  type_cache.default_field_counts[type_slot] = field_types.length()
  type_cache.default_field_types[type_slot] = Some(field_types)
  Some(field_types)
}

///|
fn hso_type_has_defaultable_struct_fields(
  type_cache : HsoTypeCache,
  module_ctx : @ir.HotModuleContext,
  type_idx : @lib.TypeIdx,
) -> Int32? {
  match
    hso_type_defaultable_struct_field_types(type_cache, module_ctx, type_idx) {
    Some(field_types) => Some(field_types.length())
    None => None
  }
}

///|
fn hso_build_default_struct_new_children(
  type_cache : HsoTypeCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext,
  type_idx : @lib.TypeIdx,
  field_idx : Int32,
  replacement_id : Int32,
  desc_id : Int32?,
) -> Array[Int32]? {
  let field_types = match
    hso_type_defaultable_struct_field_types(type_cache, module_ctx, type_idx) {
    Some(field_types) => field_types
    None => return None
  }
  let children : Array[Int32] = []
  for idx = 0; idx < field_types.length(); idx = idx + 1 {
    if idx == field_idx {
      children.push(replacement_id)
      continue
    }
    match hso_build_default_value(func, field_types[idx]) {
      Some(value_id) => children.push(value_id)
      None => return None
    }
  }
  match desc_id {
    Some(desc_id) => children.push(desc_id)
    None => ()
  }
  Some(children)
}

///|
fn hso_supported_struct_new(
  type_cache : HsoTypeCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_id : Int32,
) -> (@lib.TypeIdx, Int32, Int32?, Bool)? {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  let child_count = @ir.hot_child_count(func, node_id)
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.Instruction::StructNew(type_idx)) =>
      Some((type_idx, child_count, None, false))
    Some(@lib.Instruction::StructNewDesc(type_idx)) =>
      if child_count > 0 {
        Some(
          (
            type_idx,
            child_count - 1,
            Some(@ir.hot_child_get(func, node_id, child_count - 1)),
            false,
          ),
        )
      } else {
        None
      }
    Some(@lib.Instruction::StructNewDefault(type_idx)) =>
      match module_ctx {
        Some(module_ctx) =>
          match
            hso_type_has_defaultable_struct_fields(
              type_cache, module_ctx, type_idx,
            ) {
            Some(field_count) => Some((type_idx, field_count, None, true))
            None => None
          }
        None => None
      }
    Some(@lib.Instruction::StructNewDefaultDesc(type_idx)) =>
      if child_count > 0 {
        let desc_id = @ir.hot_child_get(func, node_id, child_count - 1)
        match module_ctx {
          Some(module_ctx) =>
            match
              hso_type_has_defaultable_struct_fields(
                type_cache, module_ctx, type_idx,
              ) {
              Some(field_count) =>
                Some((type_idx, field_count, Some(desc_id), true))
              None => None
            }
          None => None
        }
      } else {
        None
      }
    _ => None
  }
}

///|
fn hso_struct_new_shallow_effects(
  func : @ir.HotFunc,
  node_id : Int32,
) -> UInt32 {
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.Instruction::StructNew(_))
    | Some(@lib.Instruction::StructNewDefault(_)) => 0U
    Some(@lib.Instruction::StructNewDesc(_))
    | Some(@lib.Instruction::StructNewDefaultDesc(_)) => {
      let child_count = @ir.hot_child_count(func, node_id)
      if child_count <= 0 {
        return @ir.EFFECT_MASK_TRAP
      }
      let desc_id = @ir.hot_child_get(func, node_id, child_count - 1)
      let results = @ir.hot_type_results(
        func,
        @ir.hot_node_get(func, desc_id).ty,
      )
      if results.length() != 1 {
        return @ir.EFFECT_MASK_TRAP
      }
      match results[0] {
        @lib.RefTypeValType(ref_type) =>
          if ref_type.is_nullable() {
            @ir.EFFECT_MASK_TRAP
          } else {
            0U
          }
        _ => @ir.EFFECT_MASK_TRAP
      }
    }
    _ => 0U
  }
}

///|
fn hso_struct_set_parts(
  func : @ir.HotFunc,
  node_id : Int32,
) -> (@lib.TypeIdx, Int32, Int32, Int32)? {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return None
  }
  if @ir.hot_child_count(func, node_id) != 2 {
    return None
  }
  match @ir.hot_node_exact_instr(func, node_id) {
    Some(@lib.Instruction::StructSet(type_idx, field_idx))
    | Some(@lib.Instruction::StructAtomicSet(@lib.Relaxed, type_idx, field_idx)) => {
      let @lib.U32(raw_field_idx) = field_idx
      Some(
        (
          type_idx,
          raw_field_idx.reinterpret_as_int(),
          @ir.hot_child_get(func, node_id, 0),
          @ir.hot_child_get(func, node_id, 1),
        ),
      )
    }
    // A release or sequentially-consistent store to a fresh object can
    // synchronize with a later acquire after another thread receives the
    // object. Constructor initialization preserves the value but not that
    // atomic ordering event. Relaxed stores carry no such ordering edge and
    // remain foldable while the object is proven not to have escaped.
    _ => None
  }
}

///|
fn hso_sequence_result_type(
  func : @ir.HotFunc,
  value_id : Int32,
) -> @lib.BlockType? {
  let results = @ir.hot_type_results(func, @ir.hot_node_get(func, value_id).ty)
  if results.length() != 1 {
    return None
  }
  Some(@lib.BlockType::val_type(results[0]))
}

///|
fn hso_build_preserved_replacement(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  node_masks : Array[UInt32],
  old_value_id : Int32,
  new_value_id : Int32,
) -> Int32? {
  if hso_subtree_is_trapless_readonly(cache, func, node_masks, old_value_id) ||
    hso_subtree_is_trapless_readonly_contained_control(
      cache,
      func,
      node_masks,
      old_value_id,
      [],
    ) {
    return Some(new_value_id)
  }
  match hso_sequence_result_type(func, new_value_id) {
    Some(block_type) => {
      let drop_old = @ir.hot_build_drop(func, old_value_id)
      Some(@ir.hot_build_block(func, block_type, [drop_old, new_value_id]))
    }
    None => None
  }
}

///|
fn hso_branch_exits_owner(
  func : @ir.HotFunc,
  owner_id : Int32,
  active_owners : Array[Int32],
  node_id : Int32,
) -> Bool {
  let targets : Array[Int32] = []
  match @ir.hot_branch_target(func, node_id) {
    Some(label_id) => targets.push(label_id)
    None => ()
  }
  for label_id in @ir.hot_branch_table_targets_for_node(func, node_id) {
    targets.push(label_id)
  }
  if targets.is_empty() {
    return false
  }
  for label_id in targets {
    let target_owner_id = @ir.hot_label_owner(func, label_id)
    if target_owner_id != owner_id &&
      hso_active_owners_contains(active_owners, target_owner_id) {
      return false
    }
  }
  true
}

///|
fn hso_region_exits_owner(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  owner_id : Int32,
  active_owners : Array[Int32],
) -> Bool {
  for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
    let root_id = @ir.hot_region_get(func, region_ref, idx)
    if hso_subtree_exits_owner(func, root_id, owner_id, active_owners) {
      return true
    }
  }
  false
}

///|
fn hso_subtree_exits_owner(
  func : @ir.HotFunc,
  node_id : Int32,
  owner_id : Int32,
  active_owners : 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)
  if @ir.hot_is_branch(func, node_id) &&
    @ir.hot_is_terminator_node(func, node_id) {
    return hso_branch_exits_owner(func, owner_id, active_owners, node_id)
  }
  match node.op {
    @ir.HotOp::Return
    | @ir.HotOp::ReturnCall
    | @ir.HotOp::ReturnCallIndirect
    | @ir.HotOp::ReturnCallRef
    | @ir.HotOp::Throw
    | @ir.HotOp::Rethrow
    | @ir.HotOp::ThrowRef
    | @ir.HotOp::Delegate
    | @ir.HotOp::Unreachable => return true
    _ => ()
  }
  let pushes_owner = match node.op {
    @ir.HotOp::Block
    | @ir.HotOp::Loop
    | @ir.HotOp::If
    | @ir.HotOp::Try
    | @ir.HotOp::TryTable => true
    _ => false
  }
  if pushes_owner {
    active_owners.push(node_id)
  }
  let exits_owner = match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_exits_owner(
        func,
        @ir.hot_region_body_ref(func, node_id),
        owner_id,
        active_owners,
      )
    @ir.HotOp::If => {
      let then_exits_owner = hso_region_exits_owner(
        func,
        @ir.hot_region_then_ref(func, node_id),
        owner_id,
        active_owners,
      )
      if !then_exits_owner {
        false
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_exits_owner(func, region_ref, owner_id, active_owners)
          None => false
        }
      }
    }
    @ir.HotOp::Try => {
      let body_exits_owner = hso_region_exits_owner(
        func,
        @ir.hot_region_body_ref(func, node_id),
        owner_id,
        active_owners,
      )
      if !body_exits_owner {
        false
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_exits_owner(func, region_ref, owner_id, active_owners)
          None => false
        }
      }
    }
    @ir.HotOp::TryTable =>
      hso_region_exits_owner(
        func,
        @ir.hot_region_body_ref(func, node_id),
        owner_id,
        active_owners,
      ) &&
      hso_region_exits_owner(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
        owner_id,
        active_owners,
      )
    _ => false
  }
  if pushes_owner {
    ignore(active_owners.pop())
  }
  exits_owner
}

///|
fn hso_subtree_targets_owner_label(
  func : @ir.HotFunc,
  node_id : Int32,
  owner_id : Int32,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  match @ir.hot_branch_target(func, node_id) {
    Some(label_id) =>
      if @ir.hot_label_owner(func, label_id) == owner_id {
        return true
      }
    None => ()
  }
  for label_id in @ir.hot_branch_table_targets_for_node(func, node_id) {
    if @ir.hot_label_owner(func, label_id) == owner_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 hso_subtree_targets_owner_label(
        func,
        @ir.hot_child_get(
          func,
          node_id,
          pass_value_operand_slot(operand_node, slot),
        ),
        owner_id,
      ) {
      return true
    }
  }
  let node = @ir.hot_node_get(func, node_id)
  match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_region_targets_owner_label(
        func,
        @ir.hot_region_body_ref(func, node_id),
        owner_id,
      )
    @ir.HotOp::If => {
      let then_targets_owner = hso_region_targets_owner_label(
        func,
        @ir.hot_region_then_ref(func, node_id),
        owner_id,
      )
      if then_targets_owner {
        true
      } else {
        match @ir.hot_region_else_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_targets_owner_label(func, region_ref, owner_id)
          None => false
        }
      }
    }
    @ir.HotOp::Try => {
      let body_targets_owner = hso_region_targets_owner_label(
        func,
        @ir.hot_region_body_ref(func, node_id),
        owner_id,
      )
      if body_targets_owner {
        true
      } else {
        match @ir.hot_region_catch_ref(func, node_id) {
          Some(region_ref) =>
            hso_region_targets_owner_label(func, region_ref, owner_id)
          None => false
        }
      }
    }
    @ir.HotOp::TryTable =>
      hso_region_targets_owner_label(
        func,
        @ir.hot_region_body_ref(func, node_id),
        owner_id,
      ) ||
      hso_region_targets_owner_label(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
        owner_id,
      )
    _ => false
  }
}

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

///|
fn hso_try_flatten_block_wrapper(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::Block => ()
    _ => return false
  }
  let body_ref = @ir.hot_region_body_ref(func, node_id)
  if @ir.hot_region_root_count(func, body_ref) != 1 {
    return false
  }
  let block_type = match hso_sequence_result_type(func, node_id) {
    Some(block_type) => block_type
    None => return false
  }
  let root_id = @ir.hot_region_get(func, body_ref, 0)
  if root_id < 0 ||
    !@ir.hot_node_is_live(func, root_id) ||
    Some(block_type) != hso_sequence_result_type(func, root_id) {
    return false
  }
  let root_node = @ir.hot_node_get(func, root_id)
  if root_node.op is @ir.HotOp::Block {
    // Keep the outer block and its entry operands. Peeling an inner block with
    // entry operands would lose their evaluation, and changing a shared inner
    // body would also change its other occurrences.
    if pass_value_operand_count(func, root_id) != 0 {
      return false
    }
    if !hso_wrapper_has_one_reference_bounded(func, root_id) {
      return false
    }
    let inner_ref = @ir.hot_region_body_ref(func, root_id)
    let roots = Array::makei(@ir.hot_region_root_count(func, inner_ref), fn(
      index,
    ) {
      @ir.hot_region_get(func, inner_ref, index)
    })
    let old_label_id = @ir.hot_control_node_label(func, root_id)
    let new_label_id = @ir.hot_control_node_label(func, node_id)
    if old_label_id < 0 || old_label_id >= 1024 {
      return false
    }
    hso_retarget_subtree_label(func, root_id, old_label_id, new_label_id)
    @ir.hot_region_replace_body(func, body_ref, roots)
    @ir.hot_region_replace_body(func, inner_ref, [])
    return true
  }
  // Forwarding a non-block root cannot discard the typed outer prefix.
  if pass_value_operand_count(func, node_id) != 0 ||
    hso_subtree_targets_owner_label(func, root_id, node_id) ||
    hso_subtree_targets_owner_label(func, root_id, root_id) {
    return false
  }
  @ir.hot_replace_node(func, node_id, root_node)
  true
}

///|
// This new peel path requires an exclusive inner owner. Keep the proof bounded
// instead of building a whole-function use-count overlay for every wrapper.
// Incomplete ownership scans reject before any mutation.
fn hso_wrapper_has_one_reference_bounded(
  func : @ir.HotFunc,
  target : Int32,
) -> Bool {
  let node_count = @ir.hot_node_count(func)
  let root_count = @ir.hot_root_count(func)
  if node_count > 1024 || root_count > 1024 - node_count {
    return false
  }
  let mut budget = 1024 - node_count - root_count
  let mut references = 0
  for index in 0.. 1 {
        return false
      }
    }
  }
  for owner in 0.. budget {
      return false
    }
    budget -= child_count
    let node = @ir.hot_node_get(func, owner)
    let target_count = match node.op {
      @ir.HotOp::BrTable =>
        @ir.hot_branch_table_targets(func, node.imm0).length() + 1
      @ir.HotOp::TryTable =>
        @ir.hot_catch_info_get(func, node.imm1).catches.length()
      @ir.HotOp::Continuation =>
        @ir.hot_branch_table_targets(func, node.imm1).length()
      _ => 0
    }
    if target_count > budget {
      return false
    }
    budget -= target_count
    for slot in 0.. 1 {
          return false
        }
      }
    }
  }
  references == 1
}

///|
fn hso_retarget_subtree_label(
  func : @ir.HotFunc,
  node_id : Int32,
  from_label_id : Int32,
  to_label_id : Int32,
) -> Unit {
  if node_id < 0 ||
    !@ir.hot_node_is_live(func, node_id) ||
    from_label_id == to_label_id {
    return
  }
  let removed_labels = @bitset.BitSet::new(from_label_id + 1)
  removed_labels[from_label_id] = true
  let seen = @bitset.BitSet::new(@ir.hot_node_count(func))
  let pending = [node_id]
  while !pending.is_empty() {
    let current = pending.pop().unwrap()
    if current < 0 || !@ir.hot_node_is_live(func, current) || seen[current] {
      continue
    }
    seen[current] = true
    // Use the existing complete node-label remap for branches, tables,
    // continuation handlers, and try_table catch destinations.
    ignore(
      remove_unused_names_retarget_node_labels(
        func, current, removed_labels, to_label_id,
      ),
    )
    for slot in 0..<@ir.hot_child_count(func, current) {
      pending.push(@ir.hot_child_get(func, current, slot))
    }
  }
}

///|
fn hso_trim_unreachable_subtree(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::Block => {
      let changed = hso_trim_unreachable_region_tails(
        func,
        @ir.hot_region_body_ref(func, node_id),
        node_id,
      )
      hso_try_flatten_block_wrapper(func, node_id) || changed
    }
    @ir.HotOp::Loop =>
      hso_trim_unreachable_region_tails(
        func,
        @ir.hot_region_body_ref(func, node_id),
        node_id,
      )
    @ir.HotOp::If => {
      let mut changed = hso_trim_unreachable_region_tails(
        func,
        @ir.hot_region_then_ref(func, node_id),
        node_id,
      )
      match @ir.hot_region_else_ref(func, node_id) {
        Some(region_ref) =>
          changed = hso_trim_unreachable_region_tails(func, region_ref, node_id) ||
            changed
        None => ()
      }
      changed
    }
    @ir.HotOp::Try => {
      let mut changed = hso_trim_unreachable_region_tails(
        func,
        @ir.hot_region_body_ref(func, node_id),
        node_id,
      )
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(region_ref) =>
          changed = hso_trim_unreachable_region_tails(func, region_ref, node_id) ||
            changed
        None => ()
      }
      changed
    }
    @ir.HotOp::TryTable =>
      hso_trim_unreachable_region_tails(
        func,
        @ir.hot_region_body_ref(func, node_id),
        node_id,
      ) ||
      hso_trim_unreachable_region_tails(
        func,
        @ir.hot_region_catch_list_ref(func, node_id),
        node_id,
      )
    _ => false
  }
}

///|
fn hso_trim_unreachable_region_tails(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
  owner_id : Int32,
) -> Bool {
  let mut changed = false
  let root_count = @ir.hot_region_root_count(func, region_ref)
  let mut idx = 0
  while idx < root_count {
    let root_id = @ir.hot_region_get(func, region_ref, idx)
    changed = hso_trim_unreachable_subtree(func, root_id) || changed
    if @ir.hot_is_terminator_node(func, root_id) ||
      hso_subtree_exits_owner(func, root_id, owner_id, [owner_id]) {
      let removed = root_count - idx - 1
      if removed > 0 {
        ignore(@ir.hot_region_splice(func, region_ref, idx + 1, removed, []))
        changed = true
      }
      return changed
    }
    idx = idx + 1
  }
  changed
}

///|
fn hso_array_insert_prefix_roots(
  roots : Array[Int32],
  idx : Int32,
  inserted : Array[Int32],
  count : Int32,
) -> Unit {
  if count <= 0 {
    return
  }
  let suffix_count = roots.length() - idx
  let suffix : Array[Int32] = Array::make(suffix_count, 0)
  for suffix_idx = 0; suffix_idx < suffix_count; suffix_idx = suffix_idx + 1 {
    suffix[suffix_idx] = roots[idx + suffix_idx]
  }
  while roots.length() > idx {
    ignore(roots.pop())
  }
  for inserted_idx = 0; inserted_idx < count; inserted_idx = inserted_idx + 1 {
    roots.push(inserted[inserted_idx])
  }
  for suffix_root_id in suffix {
    roots.push(suffix_root_id)
  }
}

///|
fn hso_liftable_swapped_block_roots(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_masks : Array[UInt32],
  moved_struct_new_id : Int32,
  node_id : Int32,
  local_id : Int32,
) -> Array[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::Block => ()
    _ => return None
  }
  if hso_sequence_result_type(func, node_id) != None {
    return None
  }
  let body_ref = @ir.hot_region_body_ref(func, node_id)
  let roots = hso_region_roots(func, body_ref)
  if roots.is_empty() {
    return None
  }
  for root_id in roots {
    if hso_subtree_contains_try_table(func, root_id) {
      return None
    }
    if !hso_root_can_swap_before_local_struct_new(
        cache, func, module_ctx, node_masks, moved_struct_new_id, root_id, local_id,
      ) {
      return None
    }
  }
  Some(roots)
}

///|
fn hso_peel_reorderable_value_prefix_roots(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_masks : Array[UInt32],
  struct_new_id : Int32,
  field_idx : Int32,
  field_count : Int32,
  desc_id : Int32?,
  needs_default_materialization : Bool,
  value_id : Int32,
  local_id : Int32,
  active_catch_depth : Int32,
) -> Array[Int32]? {
  if value_id < 0 || !@ir.hot_node_is_live(func, value_id) {
    return None
  }
  let node = @ir.hot_node_get(func, value_id)
  match node.op {
    @ir.HotOp::Block => ()
    _ => return Some([])
  }
  let block_type = match hso_sequence_result_type(func, value_id) {
    Some(block_type) => block_type
    None => return Some([])
  }
  let body_ref = @ir.hot_region_body_ref(func, value_id)
  let roots = hso_region_roots(func, body_ref)
  if roots.length() < 2 {
    return Some([])
  }
  let final_root_id = roots[roots.length() - 1]
  if hso_sequence_result_type(func, final_root_id) != Some(block_type) {
    return Some([])
  }
  let prefix_roots : Array[Int32] = Array::new(capacity=roots.length() - 1)
  for idx = 0; idx < roots.length() - 1; idx = idx + 1 {
    let root_id = roots[idx]
    let uncaught_call_can_lift = if active_catch_depth == 0 &&
      !hso_subtree_touches_local(func, root_id, local_id) {
      match @ir.hot_node_get(func, root_id).op {
        @ir.HotOp::Call | @ir.HotOp::CallIndirect | @ir.HotOp::CallRef => true
        _ => false
      }
    } else {
      false
    }
    if !uncaught_call_can_lift &&
      !hso_root_can_swap_before_local_struct_new(
        cache, func, module_ctx, node_masks, final_root_id, root_id, local_id,
      ) {
      return Some([])
    }
    prefix_roots.push(root_id)
  }
  if prefix_roots.is_empty() {
    return Some([])
  }
  let struct_new_shallow_mask = hso_struct_new_shallow_effects(
    func, struct_new_id,
  )
  for prefix_id in prefix_roots {
    if hso_ordered_before(
        cache,
        func,
        module_ctx,
        struct_new_id,
        struct_new_shallow_mask,
        prefix_id,
        hso_reorder_effect_mask(cache, func, node_masks, prefix_id),
      ) &&
      !hso_subtree_is_trapless_reorderable(cache, func, node_masks, prefix_id, []) {
      return Some([])
    }
  }
  for idx = field_idx + 1; idx < field_count; idx = idx + 1 {
    let later_id = if needs_default_materialization {
      -1
    } else {
      @ir.hot_child_get(func, struct_new_id, idx)
    }
    if later_id >= 0 {
      if hso_subtree_has_tail_or_throw_escape(func, later_id) {
        return Some([])
      }
      let later_mask = hso_effects_for_node(func, node_masks, later_id)
      for prefix_id in prefix_roots {
        if !hso_nodes_can_reorder(
            cache,
            func,
            module_ctx,
            node_masks,
            later_id,
            later_mask,
            prefix_id,
            hso_reorder_effect_mask(cache, func, node_masks, prefix_id),
          ) {
          return Some([])
        }
      }
    }
  }
  match desc_id {
    Some(desc_id) => {
      let desc_mask = hso_desc_operand_effects_for_node(
        func, module_ctx, node_masks, desc_id,
      )
      for prefix_id in prefix_roots {
        if !hso_nodes_can_reorder(
            cache,
            func,
            module_ctx,
            node_masks,
            desc_id,
            desc_mask,
            prefix_id,
            hso_reorder_effect_mask(cache, func, node_masks, prefix_id),
          ) {
          return Some([])
        }
      }
    }
    None => ()
  }
  ignore(
    @ir.hot_region_splice(func, body_ref, 0, roots.length(), [final_root_id]),
  )
  ignore(hso_try_flatten_block_wrapper(func, value_id))
  Some(prefix_roots)
}

///|
fn hso_try_fold_into_struct_new(
  cache : HsoPredicateCache,
  type_cache : HsoTypeCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_masks : Array[UInt32],
  struct_new_id : Int32,
  type_idx : @lib.TypeIdx,
  field_idx : Int32,
  value_id : Int32,
  local_id : Int32,
  active_catch_depth : Int32,
  skip_owner_id : Int32?,
  external_roots : HsoExternalRoots,
) -> Array[Int32]? {
  let (new_type_idx, field_count, desc_id, needs_default_materialization) = match
    hso_supported_struct_new(type_cache, func, module_ctx, struct_new_id) {
    Some(parts) => parts
    _ => return None
  }
  if new_type_idx != type_idx || field_idx < 0 || field_idx >= field_count {
    return None
  }
  for idx = field_idx + 1; idx < field_count; idx = idx + 1 {
    if !needs_default_materialization {
      let later_id = @ir.hot_child_get(func, struct_new_id, idx)
      if hso_subtree_global_order_conflicts(
          module_ctx, func, later_id, value_id,
        ) {
        return None
      }
    }
  }
  match desc_id {
    Some(desc_id) =>
      if hso_subtree_global_order_conflicts(module_ctx, func, desc_id, value_id) {
        return None
      }
    None => ()
  }
  if hso_subtree_has_catchable_try_table_escape(func, value_id) {
    return None
  }
  ignore(hso_trim_unreachable_subtree(func, value_id))
  let lifted_prefix_roots = match
    hso_peel_reorderable_value_prefix_roots(
      cache, func, module_ctx, node_masks, struct_new_id, field_idx, field_count,
      desc_id, needs_default_materialization, value_id, local_id, active_catch_depth,
    ) {
    Some(roots) => roots
    None => return None
  }

  let value_mask = hso_effects_for_node(func, node_masks, value_id)
  let value_reorder_mask = hso_reorder_effect_mask(
    cache, func, node_masks, value_id,
  )
  if (value_mask & @ir.EFFECT_MASK_LOCAL_STATE) != 0U &&
    hso_subtree_touches_local(func, value_id, local_id) {
    return None
  }
  if (value_mask & @ir.EFFECT_MASK_CONTROL) != 0U &&
    hso_subtree_may_skip_local_set(cache, func, value_id) {
    let skip_is_safe = match skip_owner_id {
      Some(_) =>
        !hso_external_roots_touch_local(func, external_roots, local_id) &&
        hso_subtree_skip_is_branch_only(func, value_id)
      None => false
    }
    if !skip_is_safe {
      return None
    }
  }
  if active_catch_depth > 0 &&
    hso_subtree_may_escape_to_active_catch(func, value_id) {
    return None
  }
  let struct_new_shallow_mask = hso_struct_new_shallow_effects(
    func, struct_new_id,
  )
  if hso_ordered_before(
      cache, func, module_ctx, struct_new_id, struct_new_shallow_mask, value_id,
      value_reorder_mask,
    ) &&
    !hso_subtree_is_trapless_reorderable(cache, func, node_masks, value_id, []) {
    return None
  }
  for prefix_id in lifted_prefix_roots {
    if hso_ordered_before(
        cache,
        func,
        module_ctx,
        struct_new_id,
        struct_new_shallow_mask,
        prefix_id,
        hso_reorder_effect_mask(cache, func, node_masks, prefix_id),
      ) &&
      !hso_subtree_is_trapless_reorderable(cache, func, node_masks, prefix_id, []) {
      return None
    }
  }

  for idx = field_idx + 1; idx < field_count; idx = idx + 1 {
    let later_id = if needs_default_materialization {
      -1
    } else {
      @ir.hot_child_get(func, struct_new_id, idx)
    }
    let later_mask = if later_id >= 0 {
      hso_effects_for_node(func, node_masks, later_id)
    } else {
      0U
    }
    if later_id >= 0 && hso_subtree_has_tail_or_throw_escape(func, later_id) {
      return None
    }
    if !hso_nodes_can_reorder(
        cache, func, module_ctx, node_masks, later_id, later_mask, value_id, value_reorder_mask,
      ) {
      return None
    }
    for prefix_id in lifted_prefix_roots {
      if !hso_nodes_can_reorder(
          cache,
          func,
          module_ctx,
          node_masks,
          later_id,
          later_mask,
          prefix_id,
          hso_reorder_effect_mask(cache, func, node_masks, prefix_id),
        ) {
        return None
      }
    }
  }
  match desc_id {
    Some(desc_id) => {
      let desc_mask = hso_desc_operand_effects_for_node(
        func, module_ctx, node_masks, desc_id,
      )
      if !hso_nodes_can_reorder(
          cache, func, module_ctx, node_masks, desc_id, desc_mask, value_id, value_reorder_mask,
        ) {
        return None
      }
      for prefix_id in lifted_prefix_roots {
        if !hso_nodes_can_reorder(
            cache,
            func,
            module_ctx,
            node_masks,
            desc_id,
            desc_mask,
            prefix_id,
            hso_reorder_effect_mask(cache, func, node_masks, prefix_id),
          ) {
          return None
        }
      }
    }
    None => ()
  }

  if needs_default_materialization {
    let module_ctx = match module_ctx {
      Some(module_ctx) => module_ctx
      None => return None
    }
    let children = match
      hso_build_default_struct_new_children(
        type_cache, func, module_ctx, type_idx, field_idx, value_id, desc_id,
      ) {
      Some(children) => children
      None => return None
    }
    let instr = match desc_id {
      Some(_) => @lib.Instruction::struct_new_desc(type_idx)
      None => @lib.Instruction::struct_new(type_idx)
    }
    @ir.hot_node_exact_instr_set(func, struct_new_id, instr)
    ignore(@ir.hot_replace_child_span(func, struct_new_id, children))
  } else {
    let old_value_id = @ir.hot_child_get(func, struct_new_id, field_idx)
    let replacement = match
      hso_build_preserved_replacement(
        cache, func, node_masks, old_value_id, value_id,
      ) {
      Some(replacement) => replacement
      None => return None
    }
    @ir.hot_child_set(func, struct_new_id, field_idx, replacement)
  }
  Some(lifted_prefix_roots)
}

///|
fn hso_try_fold_tee_wrapped_struct_set(
  cache : HsoPredicateCache,
  type_cache : HsoTypeCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_masks : Array[UInt32],
  node_id : Int32,
  active_catch_depth : Int32,
) -> Array[Int32]? {
  let (type_idx, field_idx, ref_id, value_id) = match
    hso_struct_set_parts(func, node_id) {
    Some(parts) => parts
    None => return None
  }
  if !@ir.hot_node_is_live(func, ref_id) {
    return None
  }
  let ref_node = @ir.hot_node_get(func, ref_id)
  match ref_node.op {
    @ir.HotOp::LocalTee => ()
    _ => return None
  }
  if @ir.hot_child_count(func, ref_id) != 1 {
    return None
  }
  let struct_new_id = @ir.hot_child_get(func, ref_id, 0)
  let lifted_prefix_roots = match
    hso_try_fold_into_struct_new(
      cache,
      type_cache,
      func,
      module_ctx,
      node_masks,
      struct_new_id,
      type_idx,
      field_idx,
      value_id,
      ref_node.imm0,
      active_catch_depth,
      None,
      hso_new_external_roots(),
    ) {
    Some(roots) => roots
    None => return None
  }
  let local_set_id = @ir.hot_build_local_set(func, ref_node.imm0, struct_new_id)
  let replacement_count = lifted_prefix_roots.length() + 1
  let replacement_roots : Array[Int32] = Array::make(replacement_count, 0)
  for idx = 0; idx < lifted_prefix_roots.length(); idx = idx + 1 {
    replacement_roots[idx] = lifted_prefix_roots[idx]
  }
  replacement_roots[replacement_count - 1] = local_set_id
  Some(replacement_roots)
}

///|
fn hso_tee_wrapped_struct_set_local_id(
  func : @ir.HotFunc,
  node_id : Int32,
) -> Int32? {
  let (_, _, ref_id, _) = match hso_struct_set_parts(func, node_id) {
    Some(parts) => parts
    None => return None
  }
  if !@ir.hot_node_is_live(func, ref_id) {
    return None
  }
  let ref_node = @ir.hot_node_get(func, ref_id)
  match ref_node.op {
    @ir.HotOp::LocalTee => Some(ref_node.imm0)
    _ => None
  }
}

///|
fn hso_tee_fold_has_adjacent_readonly_prefix(
  cache : HsoPredicateCache,
  func : @ir.HotFunc,
  node_masks : Array[UInt32],
  roots : Array[Int32],
  idx : Int32,
  local_id : Int32,
) -> Bool {
  if idx <= 0 || idx + 1 >= roots.length() {
    return false
  }
  let prefix_id = roots[idx - 1]
  if !hso_subtree_is_trapless_readonly(cache, func, node_masks, prefix_id) {
    return false
  }
  let next_id = roots[idx + 1]
  if next_id < 0 || !@ir.hot_node_is_live(func, next_id) {
    return false
  }
  let next_node = @ir.hot_node_get(func, next_id)
  match next_node.op {
    @ir.HotOp::LocalGet => next_node.imm0 == local_id
    _ => false
  }
}

///|
fn hso_local_set_struct_new(
  type_cache : HsoTypeCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_id : Int32,
) -> (Int32, Int32, @lib.TypeIdx)? {
  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 => ()
    _ => return None
  }
  if @ir.hot_child_count(func, node_id) != 1 {
    return None
  }
  let struct_new_id = @ir.hot_child_get(func, node_id, 0)
  match hso_supported_struct_new(type_cache, func, module_ctx, struct_new_id) {
    Some((type_idx, _, _, _)) => Some((node.imm0, struct_new_id, type_idx))
    None => None
  }
}

///|
fn hso_struct_set_for_local(
  func : @ir.HotFunc,
  node_id : Int32,
  local_id : Int32,
  expected_type_idx : @lib.TypeIdx,
) -> (Int32, Int32, Int32)? {
  let (type_idx, field_idx, ref_id, value_id) = match
    hso_struct_set_parts(func, node_id) {
    Some(parts) => parts
    None => return None
  }
  if type_idx != expected_type_idx || !@ir.hot_node_is_live(func, ref_id) {
    return None
  }
  let ref_node = @ir.hot_node_get(func, ref_id)
  match ref_node.op {
    @ir.HotOp::LocalGet => if ref_node.imm0 != local_id { return None }
    _ => return None
  }
  Some((field_idx, ref_id, value_id))
}

///|
fn hso_process_node_regions(
  cache : HsoPredicateCache,
  type_cache : HsoTypeCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_masks : Array[UInt32],
  node_id : Int32,
  active_catch_depth : Int32,
  external_roots : HsoExternalRoots,
  local_order : HsoLocalOrderWorkspace,
) -> Bool {
  if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
    return false
  }
  // Nested HSO opportunities can only live below HOT control/region nodes.
  // Avoid recursively walking straight-line value trees during the pre-pass
  // region descent; the later root-chain scan still handles LocalSet/StructSet
  // candidates in the current region.
  if (hso_effects_for_node(func, node_masks, node_id) & @ir.EFFECT_MASK_CONTROL) ==
    0U {
    return false
  }
  let node = @ir.hot_node_get(func, node_id)
  let mut child_changed = false
  if !hso_node_may_have_regions(node.op) {
    for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
      child_changed = hso_process_node_regions(
          cache,
          type_cache,
          func,
          module_ctx,
          node_masks,
          @ir.hot_child_get(func, node_id, slot),
          active_catch_depth,
          external_roots,
          local_order,
        ) ||
        child_changed
    }
  }
  let region_changed = match node.op {
    @ir.HotOp::Block | @ir.HotOp::Loop =>
      hso_process_region(
        cache,
        type_cache,
        func,
        module_ctx,
        node_masks,
        @ir.hot_region_body_ref(func, node_id),
        active_catch_depth,
        Some(node_id),
        external_roots,
        local_order,
      )
    @ir.HotOp::If => {
      let mut changed = hso_process_region(
        cache,
        type_cache,
        func,
        module_ctx,
        node_masks,
        @ir.hot_region_then_ref(func, node_id),
        active_catch_depth,
        Some(node_id),
        external_roots,
        local_order,
      )
      match @ir.hot_region_else_ref(func, node_id) {
        Some(region_ref) =>
          changed = hso_process_region(
              cache,
              type_cache,
              func,
              module_ctx,
              node_masks,
              region_ref,
              active_catch_depth,
              Some(node_id),
              external_roots,
              local_order,
            ) ||
            changed
        None => ()
      }
      changed
    }
    @ir.HotOp::Try => {
      let body_catch_depth = if @ir.hot_region_catch_ref(func, node_id).is_some() {
        active_catch_depth + 1
      } else {
        active_catch_depth
      }
      let mut changed = hso_process_region(
        cache,
        type_cache,
        func,
        module_ctx,
        node_masks,
        @ir.hot_region_body_ref(func, node_id),
        body_catch_depth,
        Some(node_id),
        external_roots,
        local_order,
      )
      match @ir.hot_region_catch_ref(func, node_id) {
        Some(region_ref) =>
          changed = hso_process_region(
              cache,
              type_cache,
              func,
              module_ctx,
              node_masks,
              region_ref,
              active_catch_depth,
              Some(node_id),
              external_roots,
              local_order,
            ) ||
            changed
        None => ()
      }
      changed
    }
    @ir.HotOp::TryTable =>
      hso_process_region(
        cache,
        type_cache,
        func,
        module_ctx,
        node_masks,
        @ir.hot_region_body_ref(func, node_id),
        active_catch_depth + 1,
        Some(node_id),
        external_roots,
        local_order,
      ) ||
      hso_process_region(
        cache,
        type_cache,
        func,
        module_ctx,
        node_masks,
        @ir.hot_region_catch_list_ref(func, node_id),
        active_catch_depth,
        Some(node_id),
        external_roots,
        local_order,
      )
    _ => false
  }
  child_changed || region_changed
}

///|
fn hso_node_may_have_regions(op : @ir.HotOp) -> Bool {
  match op {
    @ir.HotOp::Block
    | @ir.HotOp::Loop
    | @ir.HotOp::If
    | @ir.HotOp::Try
    | @ir.HotOp::TryTable => true
    _ => false
  }
}

///|
fn hso_region_roots(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
) -> Array[Int32] {
  let root_count = @ir.hot_region_root_count(func, region_ref)
  let roots : Array[Int32] = Array::make(root_count, 0)
  for idx = 0; idx < root_count; idx = idx + 1 {
    roots[idx] = @ir.hot_region_get(func, region_ref, idx)
  }
  roots
}

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

///|
fn hso_try_fold_pure_plain_struct_set_chain(
  func : @ir.HotFunc,
  roots : Array[Int32],
  idx : Int32,
  root_id : Int32,
  local_id : Int32,
  struct_new_id : Int32,
  type_idx : @lib.TypeIdx,
) -> (Array[Int32], Int32)? {
  let exact_type_idx = match @ir.hot_node_exact_instr(func, struct_new_id) {
    Some(@lib.Instruction::StructNew(exact_type_idx)) => exact_type_idx
    _ => return None
  }
  if exact_type_idx != type_idx {
    return None
  }
  let field_count = @ir.hot_child_count(func, struct_new_id)
  if field_count == 0 {
    return None
  }
  for field_idx = 0; field_idx < field_count; field_idx = field_idx + 1 {
    if !hso_fast_chain_leaf(
        func,
        @ir.hot_child_get(func, struct_new_id, field_idx),
      ) {
      return None
    }
  }
  let replacements : Array[Int32] = Array::make(field_count, -1)
  let mut next_idx = idx + 1
  let mut set_count = 0
  while next_idx < roots.length() {
    let (field_idx, _, value_id) = match
      hso_struct_set_for_local(func, roots[next_idx], local_id, type_idx) {
      Some(parts) => parts
      None => break
    }
    if field_idx < 0 || field_idx >= field_count {
      return None
    }
    if !hso_fast_chain_leaf(func, value_id) {
      return None
    }
    replacements[field_idx] = value_id
    set_count = set_count + 1
    next_idx = next_idx + 1
  }
  if set_count < 2 {
    return None
  }
  for field_idx = 0; field_idx < field_count; field_idx = field_idx + 1 {
    if replacements[field_idx] >= 0 {
      @ir.hot_child_set(func, struct_new_id, field_idx, replacements[field_idx])
    }
  }
  Some(([root_id], next_idx))
}

///|
fn hso_try_fold_pure_default_struct_set_chain(
  type_cache : HsoTypeCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  roots : Array[Int32],
  idx : Int32,
  root_id : Int32,
  local_id : Int32,
  struct_new_id : Int32,
  type_idx : @lib.TypeIdx,
) -> (Array[Int32], Int32)? {
  let module_ctx = match module_ctx {
    Some(module_ctx) => module_ctx
    None => return None
  }
  let exact_type_idx = match @ir.hot_node_exact_instr(func, struct_new_id) {
    Some(@lib.Instruction::StructNewDefault(exact_type_idx)) => exact_type_idx
    _ => return None
  }
  if exact_type_idx != type_idx {
    return None
  }
  let field_count = match
    hso_type_struct_field_count(type_cache, module_ctx, type_idx) {
    Some(field_count) => field_count
    None => return None
  }
  let replacements : Array[Int32] = Array::make(field_count, -1)
  let mut next_idx = idx + 1
  let mut set_count = 0
  let mut missing_count = field_count
  while next_idx < roots.length() {
    let (field_idx, _, value_id) = match
      hso_struct_set_for_local(func, roots[next_idx], local_id, type_idx) {
      Some(parts) => parts
      None => break
    }
    if field_idx < 0 || field_idx >= field_count {
      return None
    }
    if !hso_fast_chain_leaf(func, value_id) {
      return None
    }
    if replacements[field_idx] < 0 {
      missing_count = missing_count - 1
    }
    replacements[field_idx] = value_id
    set_count = set_count + 1
    next_idx = next_idx + 1
  }
  if set_count < 2 {
    return None
  }
  let children : Array[Int32] = if missing_count == 0 {
    replacements
  } else {
    let field_types = match
      hso_type_defaultable_struct_field_types(type_cache, module_ctx, type_idx) {
      Some(field_types) => field_types
      None => return None
    }
    let children : Array[Int32] = Array::make(field_count, 0)
    for field_idx = 0; field_idx < field_count; field_idx = field_idx + 1 {
      if replacements[field_idx] >= 0 {
        children[field_idx] = replacements[field_idx]
      } else {
        match hso_build_default_value(func, field_types[field_idx]) {
          Some(default_id) => children[field_idx] = default_id
          None => return None
        }
      }
    }
    children
  }
  @ir.hot_node_exact_instr_set(
    func,
    struct_new_id,
    @lib.Instruction::struct_new(type_idx),
  )
  ignore(@ir.hot_replace_child_span(func, struct_new_id, children))
  Some(([root_id], next_idx))
}

///|
fn hso_plain_local_set_struct_new_fast(
  func : @ir.HotFunc,
  node_id : Int32,
) -> (Int32, Int32, @lib.TypeIdx, 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::LocalSet => ()
    _ => return None
  }
  if @ir.hot_child_count(func, node_id) != 1 {
    return None
  }
  let struct_new_id = @ir.hot_child_get(func, node_id, 0)
  let type_idx = match @ir.hot_node_exact_instr(func, struct_new_id) {
    Some(@lib.Instruction::StructNew(type_idx)) => type_idx
    _ => return None
  }
  let field_count = @ir.hot_child_count(func, struct_new_id)
  if field_count == 0 {
    return None
  }
  for field_idx = 0; field_idx < field_count; field_idx = field_idx + 1 {
    if !hso_fast_chain_leaf(
        func,
        @ir.hot_child_get(func, struct_new_id, field_idx),
      ) {
      return None
    }
  }
  Some((node.imm0, struct_new_id, type_idx, field_count))
}

///|
fn hso_try_fold_tiny_pure_plain_region(
  func : @ir.HotFunc,
  region_ref : @ir.HotRegionRef,
) -> Bool {
  if @ir.hot_region_root_count(func, region_ref) != 3 {
    return false
  }
  let root0 = @ir.hot_region_get(func, region_ref, 0)
  let root1 = @ir.hot_region_get(func, region_ref, 1)
  let root2 = @ir.hot_region_get(func, region_ref, 2)
  let (local_id, struct_new_id, type_idx, field_count) = match
    hso_plain_local_set_struct_new_fast(func, root0) {
    Some(parts) => parts
    None => return false
  }
  let (field_idx1, _, value_id1) = match
    hso_struct_set_for_local(func, root1, local_id, type_idx) {
    Some(parts) => parts
    None => return false
  }
  let (field_idx2, _, value_id2) = match
    hso_struct_set_for_local(func, root2, local_id, type_idx) {
    Some(parts) => parts
    None => return false
  }
  if field_idx1 < 0 ||
    field_idx1 >= field_count ||
    field_idx2 < 0 ||
    field_idx2 >= field_count {
    return false
  }
  if !hso_fast_chain_leaf(func, value_id1) ||
    !hso_fast_chain_leaf(func, value_id2) {
    return false
  }
  @ir.hot_child_set(func, struct_new_id, field_idx1, value_id1)
  @ir.hot_child_set(func, struct_new_id, field_idx2, value_id2)
  ignore(@ir.hot_region_splice(func, region_ref, 1, 2, []))
  true
}

///|
fn hso_chain_scratch_capacity(remaining_root_count : Int32) -> Int32 {
  if remaining_root_count <= 8 {
    remaining_root_count
  } else {
    8
  }
}

///|
fn hso_process_local_set_chain(
  cache : HsoPredicateCache,
  type_cache : HsoTypeCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_masks : Array[UInt32],
  roots : Array[Int32],
  root_id : Int32,
  idx : Int32,
  local_id : Int32,
  struct_new_id : Int32,
  type_idx : @lib.TypeIdx,
  active_catch_depth : Int32,
  owner_id : Int32?,
  external_roots : HsoExternalRoots,
  local_order : HsoLocalOrderWorkspace,
) -> (Array[Int32], Int32, Bool) {
  match
    hso_try_fold_pure_plain_struct_set_chain(
      func, roots, idx, root_id, local_id, struct_new_id, type_idx,
    ) {
    Some((segment_roots, next_idx)) => return (segment_roots, next_idx, true)
    None => ()
  }
  match
    hso_try_fold_pure_default_struct_set_chain(
      type_cache, func, module_ctx, roots, idx, root_id, local_id, struct_new_id,
      type_idx,
    ) {
    Some((segment_roots, next_idx)) => return (segment_roots, next_idx, true)
    None => ()
  }
  let scratch_capacity = hso_chain_scratch_capacity(roots.length() - idx)
  let segment_roots : Array[Int32] = Array::new(capacity=scratch_capacity)
  segment_roots.push(root_id)
  hso_local_order_workspace_begin(local_order)
  let mut segment_local_set_idx = 0
  let mut segment_has_swapped_prefix = false
  let mut changed = false
  let mut next_idx = idx + 1
  while next_idx < roots.length() {
    match hso_struct_set_for_local(func, roots[next_idx], local_id, type_idx) {
      Some((field_idx, ref_id, value_id)) => {
        if segment_has_swapped_prefix {
          let ref_node = @ir.hot_node_get(func, ref_id)
          let ref_local = match ref_node.op {
            @ir.HotOp::LocalGet => ref_node.imm0
            _ => break
          }
          if ref_local < 0 ||
            ref_local >= local_order.maxima.length() ||
            hso_local_order_workspace_get(local_order, ref_local) > ref_id {
            break
          }
        }
        match
          hso_try_fold_into_struct_new(
            cache, type_cache, func, module_ctx, node_masks, struct_new_id, type_idx,
            field_idx, value_id, local_id, active_catch_depth, owner_id, external_roots,
          ) {
          Some(lifted_prefix_roots) => {
            let nop_id = @ir.hot_build_nop(func)
            if segment_has_swapped_prefix {
              hso_array_insert_prefix_roots(
                segment_roots,
                segment_local_set_idx,
                [nop_id],
                1,
              )
              segment_local_set_idx = segment_local_set_idx + 1
            }
            if !lifted_prefix_roots.is_empty() {
              hso_array_insert_prefix_roots(
                segment_roots,
                segment_local_set_idx,
                lifted_prefix_roots,
                lifted_prefix_roots.length(),
              )
              segment_local_set_idx = segment_local_set_idx +
                lifted_prefix_roots.length()
            }
            changed = true
            if !segment_has_swapped_prefix {
              segment_roots.push(nop_id)
            }
            next_idx = next_idx + 1
            continue
          }
          None => ()
        }
      }
      None => ()
    }
    if next_idx < roots.length() - 1 {
      let branch_skips_owner = match owner_id {
        Some(_) =>
          hso_subtree_skip_is_branch_only(func, roots[next_idx]) &&
          !hso_subtree_has_catchable_try_table_escape(func, roots[next_idx]) &&
          !hso_external_roots_touch_local(func, external_roots, local_id)
        None => false
      }
      let next_is_matching_set = match
        hso_struct_set_for_local(func, roots[next_idx + 1], local_id, type_idx) {
        Some(_) => true
        None => false
      }
      // The owner-skipping shortcut may bypass constructor evaluation. Only
      // effect-free operands can bypass the ordinary dependency checks.
      if branch_skips_owner &&
        next_is_matching_set &&
        (
          (
            hso_struct_new_operand_effect_mask(
              func, module_ctx, node_masks, struct_new_id,
            ) |
            hso_struct_new_shallow_effects(func, struct_new_id)
          ) ==
          0U ||
          hso_root_can_swap_before_local_struct_new(
            cache,
            func,
            module_ctx,
            node_masks,
            struct_new_id,
            roots[next_idx],
            local_id,
          )
        ) {
        segment_roots.push(roots[next_idx])
        let last_idx = segment_roots.length() - 1
        let moved_local_set_id = segment_roots[segment_local_set_idx]
        segment_roots[segment_local_set_idx] = segment_roots[last_idx]
        segment_roots[last_idx] = moved_local_set_id
        hso_note_swapped_readonly_root(
          cache,
          func,
          node_masks,
          roots[next_idx],
          local_order,
        )
        segment_local_set_idx = last_idx
        segment_has_swapped_prefix = true
        changed = true
        next_idx = next_idx + 1
        continue
      }
      match
        hso_liftable_swapped_block_roots(
          cache,
          func,
          module_ctx,
          node_masks,
          struct_new_id,
          roots[next_idx],
          local_id,
        ) {
        Some(lifted_roots) => {
          hso_array_insert_prefix_roots(
            segment_roots,
            segment_local_set_idx,
            lifted_roots,
            lifted_roots.length(),
          )
          for lifted_root_id in lifted_roots {
            hso_note_swapped_readonly_root(
              cache, func, node_masks, lifted_root_id, local_order,
            )
          }
          segment_local_set_idx = segment_local_set_idx + lifted_roots.length()
          segment_has_swapped_prefix = true
          changed = true
          next_idx = next_idx + 1
          continue
        }
        None =>
          if hso_root_can_swap_before_local_struct_new(
              cache,
              func,
              module_ctx,
              node_masks,
              struct_new_id,
              roots[next_idx],
              local_id,
            ) {
            segment_roots.push(roots[next_idx])
            let last_idx = segment_roots.length() - 1
            let moved_local_set_id = segment_roots[segment_local_set_idx]
            segment_roots[segment_local_set_idx] = segment_roots[last_idx]
            segment_roots[last_idx] = moved_local_set_id
            hso_note_swapped_readonly_root(
              cache,
              func,
              node_masks,
              roots[next_idx],
              local_order,
            )
            segment_local_set_idx = last_idx
            segment_has_swapped_prefix = true
            changed = true
            next_idx = next_idx + 1
            continue
          }
      }
    }
    break
  }
  (segment_roots, next_idx, changed)
}

///|
fn hso_process_region(
  cache : HsoPredicateCache,
  type_cache : HsoTypeCache,
  func : @ir.HotFunc,
  module_ctx : @ir.HotModuleContext?,
  node_masks : Array[UInt32],
  region_ref : @ir.HotRegionRef,
  active_catch_depth : Int32,
  owner_id : Int32?,
  external_roots : HsoExternalRoots,
  local_order : HsoLocalOrderWorkspace,
) -> Bool {
  if hso_try_fold_tiny_pure_plain_region(func, region_ref) {
    return true
  }
  let roots = hso_region_roots(func, region_ref)
  let mut changed = false

  for idx = 0; idx < roots.length(); idx = idx + 1 {
    let root_id = roots[idx]
    // The nested-region descent can only do work for control-containing roots.
    // Keep straight-line roots out before constructing suffix/external context.
    if (
        hso_effects_for_node(func, node_masks, root_id) &
        @ir.EFFECT_MASK_CONTROL
      ) !=
      0U {
      hso_external_roots_push(external_roots, roots, idx + 1)
      changed = hso_process_node_regions(
          cache, type_cache, func, module_ctx, node_masks, root_id, active_catch_depth,
          external_roots, local_order,
        ) ||
        changed
      hso_external_roots_pop(external_roots)
    }
  }

  let next_roots : Array[Int32] = Array::new(capacity=roots.length())
  let mut idx = 0
  while idx < roots.length() {
    let root_id = roots[idx]
    let root_node = @ir.hot_node_get(func, root_id)
    match root_node.op {
      @ir.HotOp::Heap | @ir.HotOp::Atomic => {
        let tee_local_id = hso_tee_wrapped_struct_set_local_id(func, root_id)
        let skip_tee_fold = match tee_local_id {
          Some(local_id) =>
            hso_tee_fold_has_adjacent_readonly_prefix(
              cache, func, node_masks, roots, idx, local_id,
            )
          None => false
        }
        if skip_tee_fold {
          next_roots.push(root_id)
        } else {
          match
            hso_try_fold_tee_wrapped_struct_set(
              cache, type_cache, func, module_ctx, node_masks, root_id, active_catch_depth,
            ) {
            Some(replacement_roots) => {
              changed = true
              let replacement_last_idx = replacement_roots.length() - 1
              let replacement_local_set_id = replacement_roots[replacement_last_idx]
              for replacement_idx = 0
                  replacement_idx < replacement_last_idx
                  replacement_idx = replacement_idx + 1 {
                next_roots.push(replacement_roots[replacement_idx])
              }
              match
                hso_local_set_struct_new(
                  type_cache, func, module_ctx, replacement_local_set_id,
                ) {
                Some((local_id, struct_new_id, type_idx)) => {
                  let (segment_roots, next_idx, segment_changed) = hso_process_local_set_chain(
                    cache, type_cache, func, module_ctx, node_masks, roots, replacement_local_set_id,
                    idx, local_id, struct_new_id, type_idx, active_catch_depth, owner_id,
                    external_roots, local_order,
                  )
                  changed = segment_changed || changed
                  for segment_root_id in segment_roots {
                    next_roots.push(segment_root_id)
                  }
                  idx = next_idx - 1
                }
                None => next_roots.push(replacement_local_set_id)
              }
            }
            None => next_roots.push(root_id)
          }
        }
        idx = idx + 1
      }
      @ir.HotOp::LocalSet =>
        match hso_local_set_struct_new(type_cache, func, module_ctx, root_id) {
          Some((local_id, struct_new_id, type_idx)) => {
            let (segment_roots, next_idx, segment_changed) = hso_process_local_set_chain(
              cache, type_cache, func, module_ctx, node_masks, roots, root_id, idx,
              local_id, struct_new_id, type_idx, active_catch_depth, owner_id, external_roots,
              local_order,
            )
            changed = segment_changed || changed
            for segment_root_id in segment_roots {
              next_roots.push(segment_root_id)
            }
            idx = next_idx
          }
          None => {
            next_roots.push(root_id)
            idx = idx + 1
          }
        }
      _ => {
        next_roots.push(root_id)
        idx = idx + 1
      }
    }
  }

  if !@lib.equals(roots, next_roots) {
    ignore(
      @ir.hot_region_splice(func, region_ref, 0, roots.length(), next_roots),
    )
    changed = true
  }
  changed
}

///|
fn heap_store_optimization_run(
  ctx : HotPassContext,
  func : @ir.HotFunc,
) -> HotPassResult {
  if hso_try_fold_tiny_pure_plain_region(func, @ir.HotRegionRef::root()) {
    pass_mark_mutated(ctx, func)
    return HotPassResult::changed()
  }
  let effects = pass_require_effects(ctx, func)
  let predicate_cache = hso_new_predicate_cache()
  let type_cache = hso_new_type_cache()
  let revision = @ir.hot_revision_current(func)
  let changed = hso_process_region(
    predicate_cache,
    type_cache,
    func,
    ctx.module_ctx,
    effects.node_masks,
    @ir.HotRegionRef::root(),
    0,
    None,
    hso_new_external_roots(),
    hso_new_local_order_workspace(@ir.hot_local_count(func)),
  )
  if changed || @ir.hot_revision_current(func) != revision {
    pass_mark_mutated(ctx, func)
    HotPassResult::changed()
  } else {
    HotPassResult::unchanged()
  }
}