// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Copyright 2023 WebAssembly Community Group participants.
// Retained historical upstream notice; current derivation remains unconfirmed.
// Conservative attribution from pinned Binaryen version_130 counterpart:
// Copyright 2016 WebAssembly Community Group participants
// Counterpart: src/passes/ReorderLocals.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 reorder_locals_summary() -> String {
  "Sort body locals by access frequency and drop unaccessed body locals."
}

///|
priv struct RLScanState {
  counts : Array[Int32]
  first_uses : Array[Int32]
  used_body_locals : Array[Int32]
  param_count : Int32
  mut next_rank : Int32
  mut valid : Bool
}

///|
fn RLScanState::new(local_count : Int32, param_count : Int32) -> RLScanState {
  {
    counts: Array::make(local_count, 0),
    first_uses: Array::make(local_count, 0),
    used_body_locals: Array::new(capacity=local_count - param_count),
    param_count,
    next_rank: 1,
    valid: true,
  }
}

///|
priv struct RLFuncRewrite {
  func : @lib.Func
  old_to_new : Array[Int32]
  changed : Bool
}

///|
fn rl_imported_func_count(mod_ : @lib.Module) -> Int32 {
  let mut count = 0
  match mod_.import_sec {
    Some(@lib.ImportSec(imports)) =>
      for import_ in imports {
        match import_ {
          @lib.Import(_, _, @lib.FuncExternType(_)) => count += 1
          _ => ()
        }
      }
    None => ()
  }
  count
}

///|
fn rl_local_idx_raw(idx : @lib.LocalIdx) -> Int32 {
  let @lib.LocalIdx(raw) = idx
  raw.reinterpret_as_int()
}

///|
fn rl_local_idx_from_int(idx : Int32) -> @lib.LocalIdx {
  @lib.LocalIdx::new(idx.reinterpret_as_uint())
}

///|

///|
fn rl_flatten_locals(
  locals : @lib.Locals,
  local_count : Int32,
) -> Array[@lib.ValType] {
  let out : Array[@lib.ValType] = Array::new(capacity=local_count)
  for run in locals.runs() {
    let ty = run.val_type()
    let run_len = run.count().reinterpret_as_int()
    for idx = 0; idx < run_len; idx = idx + 1 {
      out.push(ty)
    }
  }
  out
}

///|
fn rl_push_local_run(runs : Array[@lib.LocalRun], ty : @lib.ValType) -> Unit {
  match runs.last() {
    Some(last) if last.val_type() == ty =>
      runs[runs.length() - 1] = @lib.LocalRun::new(last.count() + 1U, ty)
    _ => runs.push(@lib.LocalRun::new(1U, ty))
  }
}

///|
fn rl_note_access(state : RLScanState, idx : @lib.LocalIdx) -> Unit {
  let raw = rl_local_idx_raw(idx)
  if raw < 0 || raw >= state.counts.length() {
    state.valid = false
    return
  }
  state.counts[raw] += 1
  if raw >= state.param_count && state.first_uses[raw] == 0 {
    state.first_uses[raw] = state.next_rank
    state.used_body_locals.push(raw)
    state.next_rank += 1
  }
}

///|
fn rl_scan_expr(expr : @lib.Expr, state : RLScanState) -> Unit {
  let @lib.Expr(instrs) = expr
  rl_scan_instrs(instrs, state)
}

///|
fn rl_scan_instrs(
  instrs : Array[@lib.Instruction],
  state : RLScanState,
) -> Unit {
  for instr in instrs {
    rl_scan_instruction(instr, state)
  }
}

///|
fn rl_scan_instruction(instr : @lib.Instruction, state : RLScanState) -> Unit {
  match instr {
    @lib.Block(_, body) => rl_scan_expr(body, state)
    @lib.Loop(_, body) => rl_scan_expr(body, state)
    @lib.If(_, then_body, else_body) => {
      rl_scan_instrs(then_body, state)
      match else_body {
        Some(body) => rl_scan_instrs(body, state)
        None => ()
      }
    }
    @lib.Try(_, body, catches, _) => {
      rl_scan_expr(body, state)
      for catch_ in catches {
        match catch_ {
          @lib.LegacyCatch(_, catch_body) | @lib.LegacyCatchAll(catch_body) =>
            rl_scan_expr(catch_body, state)
        }
      }
    }
    @lib.TryTable(_, _, body) => rl_scan_expr(body, state)
    @lib.LocalGet(idx) => rl_note_access(state, idx)
    @lib.LocalSet(idx) => rl_note_access(state, idx)
    @lib.LocalTee(idx) => rl_note_access(state, idx)
    _ => ()
  }
}

///|
fn rl_sort_used_body_locals(
  used_body_locals : Array[Int32],
  counts : Array[Int32],
  first_uses : Array[Int32],
) -> Array[Int32] {
  used_body_locals.sort_by(fn(lhs, rhs) {
    let lhs_count = counts[lhs]
    let rhs_count = counts[rhs]
    if lhs_count == rhs_count {
      if first_uses[lhs] == first_uses[rhs] {
        lhs - rhs
      } else {
        first_uses[lhs] - first_uses[rhs]
      }
    } else {
      rhs_count - lhs_count
    }
  })
  used_body_locals
}

///|
fn rl_grouping_abs_key(abs : @lib.AbsHeapType) -> UInt64 {
  match abs {
    @lib.ExnAbsHeapType => 0UL
    @lib.StringAbsHeapType => 1UL
    @lib.ArrayAbsHeapType => 2UL
    @lib.StructAbsHeapType => 3UL
    @lib.I31AbsHeapType => 4UL
    @lib.EqAbsHeapType => 5UL
    @lib.AnyAbsHeapType => 6UL
    @lib.ExternAbsHeapType => 7UL
    @lib.FuncAbsHeapType => 8UL
    @lib.ContAbsHeapType => 9UL
    @lib.NoneAbsHeapType => 10UL
    @lib.NoExternAbsHeapType => 11UL
    @lib.NoFuncAbsHeapType => 12UL
    @lib.NoExnAbsHeapType => 13UL
    @lib.NoContAbsHeapType => 14UL
    @lib.WaitqueueAbsHeapType => 15UL
    @lib.NoWaitqueueAbsHeapType => 16UL
  }
}

///|
// Scalar/heap-kind/reference flags occupy disjoint high bits; the payload keeps
// the full UInt32 index. No derived ValType graph hash or equality is needed.
fn rl_grouping_type_key(ty : @lib.ValType) -> UInt64? {
  match ty {
    @lib.NumTypeValType(@lib.I32NumType) => Some(0UL)
    @lib.NumTypeValType(@lib.I64NumType) => Some(1UL)
    @lib.NumTypeValType(@lib.F32NumType) => Some(2UL)
    @lib.NumTypeValType(@lib.F64NumType) => Some(3UL)
    @lib.VecTypeValType => Some(4UL)
    @lib.BotValType => None
    @lib.RefTypeValType(@lib.AbsHeapTypeRefType(abs)) =>
      Some(0x1100000000000000UL | rl_grouping_abs_key(abs))
    @lib.RefTypeValType(@lib.HeapTypeRefType(nullable, exact, heap)) => {
      let payload = match heap {
        @lib.AbsHeapTypeHeapType(abs) => rl_grouping_abs_key(abs)
        @lib.SharedAbsHeapTypeHeapType(abs) =>
          0x100000000UL | rl_grouping_abs_key(abs)
        @lib.HeapType(@lib.TypeIdx(idx)) => 0x200000000UL | idx.to_uint64()
        @lib.HeapType(@lib.RecIdx(idx)) => 0x300000000UL | idx.to_uint64()
        @lib.DefTypeHeapType(_) => return None
      }
      let nullable_flag = if nullable { 0x0100000000000000UL } else { 0UL }
      let exact_flag = if exact { 0x0200000000000000UL } else { 0UL }
      Some(0x1000000000000000UL | nullable_flag | exact_flag | payload)
    }
  }
}

///|
// Equal-frequency permutations preserve the weighted local-index ULEB size,
// including width boundaries. Compress declaration runs only when their actual
// encoded bytes strictly improve; retain first-use ordering on ties/no gain.
fn rl_group_equal_frequency_locals(
  order : Array[Int32],
  counts : Array[Int32],
  body_types : Array[@lib.ValType],
  first_uses : Array[Int32],
  param_count : Int32,
) -> Array[Int32] {
  if order.length() <= 2 {
    return order
  }
  let type_ranks : Map[UInt64, Int32] = Map([])
  let ranks = Array::make(body_types.length(), 0)
  for old_idx in order {
    let key = match rl_grouping_type_key(body_types[old_idx - param_count]) {
      Some(key) => key
      None => return order
    }
    let rank = match type_ranks.get(key) {
      Some(rank) => rank
      None => {
        let rank = type_ranks.length()
        type_ranks[key] = rank
        rank
      }
    }
    ranks[old_idx - param_count] = rank
  }
  if type_ranks.length() <= 1 {
    return order
  }
  let grouped = order.copy()
  grouped.sort_by(fn(lhs, rhs) {
    if counts[lhs] != counts[rhs] {
      counts[rhs] - counts[lhs]
    } else if ranks[lhs - param_count] != ranks[rhs - param_count] {
      ranks[lhs - param_count] - ranks[rhs - param_count]
    } else {
      first_uses[lhs] - first_uses[rhs]
    }
  })
  let current_runs : Array[@lib.LocalRun] = []
  let grouped_runs : Array[@lib.LocalRun] = []
  for old_idx in order {
    rl_push_local_run(current_runs, body_types[old_idx - param_count])
  }
  for old_idx in grouped {
    rl_push_local_run(grouped_runs, body_types[old_idx - param_count])
  }
  let current_bytes = @buffer.new()
  let grouped_bytes = @buffer.new()
  match
    (
      @binary.Encode::encode(@lib.Locals::new(current_runs), current_bytes),
      @binary.Encode::encode(@lib.Locals::new(grouped_runs), grouped_bytes),
    ) {
    (Ok(_), Ok(_)) if grouped_bytes.length() < current_bytes.length() => grouped
    _ => order
  }
}

///|
fn rl_rewrite_local_idx(
  idx : @lib.LocalIdx,
  old_to_new : Array[Int32],
) -> @lib.LocalIdx {
  let raw = rl_local_idx_raw(idx)
  if raw < 0 || raw >= old_to_new.length() {
    abort("reorder-locals local remap out of range")
  }
  let mapped = old_to_new[raw]
  if mapped < 0 {
    abort("reorder-locals attempted to rewrite dropped local access")
  }
  rl_local_idx_from_int(mapped)
}

///|
fn rl_rewrite_expr(expr : @lib.Expr, old_to_new : Array[Int32]) -> @lib.Expr {
  let @lib.Expr(instrs) = expr
  let rewritten = instrs.copy()
  rl_rewrite_instrs_in_place(rewritten, old_to_new)
  @lib.Expr::new(rewritten)
}

///|
fn rl_rewrite_instrs_in_place(
  instrs : Array[@lib.Instruction],
  old_to_new : Array[Int32],
) -> Unit {
  for idx = 0; idx < instrs.length(); idx = idx + 1 {
    match instrs[idx] {
      @lib.Block(block_type, @lib.Expr(body)) => {
        let rewritten_body = body.copy()
        rl_rewrite_instrs_in_place(rewritten_body, old_to_new)
        instrs[idx] = @lib.Instruction::block(
          block_type,
          @lib.Expr::new(rewritten_body),
        )
      }
      @lib.Loop(block_type, @lib.Expr(body)) => {
        let rewritten_body = body.copy()
        rl_rewrite_instrs_in_place(rewritten_body, old_to_new)
        instrs[idx] = @lib.Instruction::loop_(
          block_type,
          @lib.Expr::new(rewritten_body),
        )
      }
      @lib.If(block_type, then_body, else_body) => {
        let rewritten_then = then_body.copy()
        rl_rewrite_instrs_in_place(rewritten_then, old_to_new)
        let rewritten_else = match else_body {
          Some(else_instrs) => {
            let next_else = else_instrs.copy()
            rl_rewrite_instrs_in_place(next_else, old_to_new)
            Some(next_else)
          }
          None => None
        }
        instrs[idx] = @lib.Instruction::if_(
          block_type, rewritten_then, rewritten_else,
        )
      }
      @lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
        let rewritten_body = body.copy()
        rl_rewrite_instrs_in_place(rewritten_body, old_to_new)
        let rewritten_catches : Array[@lib.LegacyCatch] = []
        for catch_ in catches {
          match catch_ {
            @lib.LegacyCatch(tag_idx, @lib.Expr(catch_body)) => {
              let rewritten_catch_body = catch_body.copy()
              rl_rewrite_instrs_in_place(rewritten_catch_body, old_to_new)
              rewritten_catches.push(
                @lib.LegacyCatch::tagged(
                  tag_idx,
                  @lib.Expr::new(rewritten_catch_body),
                ),
              )
            }
            @lib.LegacyCatchAll(@lib.Expr(catch_body)) => {
              let rewritten_catch_body = catch_body.copy()
              rl_rewrite_instrs_in_place(rewritten_catch_body, old_to_new)
              rewritten_catches.push(
                @lib.LegacyCatch::all(@lib.Expr::new(rewritten_catch_body)),
              )
            }
          }
        }
        instrs[idx] = @lib.Instruction::try_(
          block_type,
          @lib.Expr::new(rewritten_body),
          rewritten_catches,
          delegate~,
        )
      }
      @lib.TryTable(block_type, catches, @lib.Expr(body)) => {
        let rewritten_body = body.copy()
        rl_rewrite_instrs_in_place(rewritten_body, old_to_new)
        instrs[idx] = @lib.Instruction::try_table(
          block_type,
          catches,
          @lib.Expr::new(rewritten_body),
        )
      }
      @lib.LocalGet(local_idx) => {
        let next_idx = rl_rewrite_local_idx(local_idx, old_to_new)
        if next_idx != local_idx {
          instrs[idx] = @lib.Instruction::local_get(next_idx)
        }
      }
      @lib.LocalSet(local_idx) => {
        let next_idx = rl_rewrite_local_idx(local_idx, old_to_new)
        if next_idx != local_idx {
          instrs[idx] = @lib.Instruction::local_set(next_idx)
        }
      }
      @lib.LocalTee(local_idx) => {
        let next_idx = rl_rewrite_local_idx(local_idx, old_to_new)
        if next_idx != local_idx {
          instrs[idx] = @lib.Instruction::local_tee(next_idx)
        }
      }
      _ => ()
    }
  }
}

///|
fn rl_module_subtypes(mod_ : @lib.Module) -> Array[@lib.SubType] {
  let subtypes : Array[@lib.SubType] = match mod_.type_sec {
    Some(@lib.TypeSec(rec_types)) => Array::new(capacity=rec_types.length())
    None => []
  }
  match mod_.type_sec {
    Some(@lib.TypeSec(rec_types)) =>
      for rec_type in rec_types {
        match rec_type {
          @lib.SingleRecType(subtype) => subtypes.push(subtype)
          @lib.GroupRecType(group) =>
            for subtype in group {
              subtypes.push(subtype)
            }
        }
      }
    None => abort("reorder-locals missing type section")
  }
  subtypes
}

///|
fn rl_params_for_type_idx(
  subtypes : Array[@lib.SubType],
  type_idx : @lib.TypeIdx,
) -> Array[@lib.ValType] {
  let subtype = match type_idx {
    @lib.TypeIdx(type_pos) =>
      match subtypes.get(type_pos.reinterpret_as_int()) {
        Some(subtype) => subtype
        None => abort("reorder-locals missing function subtype")
      }
    @lib.RecIdx(_) =>
      // Function-section type references are module-global TypeIdx values after
      // decode/validation. A RecIdx here would mean a rec-group-local type
      // escaped into the module function declaration table, so keep this as an
      // invariant assertion instead of trying to reinterpret it.
      abort("reorder-locals unsupported recursive function type index")
  }
  match subtype.get_comptype() {
    @lib.FuncCompType(params, _) => params
    _ => abort("reorder-locals expected function type")
  }
}

///|
fn rl_defined_func_param_cache(
  mod_ : @lib.Module,
) -> Array[Array[@lib.ValType]] {
  let subtypes = rl_module_subtypes(mod_)
  let out : Array[Array[@lib.ValType]] = match mod_.func_sec {
    Some(@lib.FuncSec(type_idxs)) => Array::new(capacity=type_idxs.length())
    None => []
  }
  match mod_.func_sec {
    Some(@lib.FuncSec(type_idxs)) =>
      for type_idx in type_idxs {
        out.push(rl_params_for_type_idx(subtypes, type_idx))
      }
    None => abort("reorder-locals missing function section")
  }
  out
}

///|
fn rl_used_body_locals_are_original_prefix(
  used_body_locals : Array[Int32],
  param_count : Int32,
) -> Bool {
  for idx = 0; idx < used_body_locals.length(); idx = idx + 1 {
    if used_body_locals[idx] != param_count + idx {
      return false
    }
  }
  true
}

///|
fn rl_rewrite_func_type_stable(
  func : @lib.Func,
  params : Array[@lib.ValType],
) -> RLFuncRewrite {
  let @lib.Func(body_locals, body) = func
  let body_local_count = body_locals.length()
  if body_local_count == 0 {
    return { func, old_to_new: [], changed: false, }
  }

  let total_locals = params.length() + body_local_count
  let scan = RLScanState::new(total_locals, params.length())
  rl_scan_expr(body, scan)
  if !scan.valid {
    return { func, old_to_new: [], changed: false, }
  }
  let body_local_types = rl_flatten_locals(body_locals, body_local_count)
  let old_to_new = Array::make(total_locals, -1)
  for idx = 0; idx < params.length(); idx = idx + 1 {
    old_to_new[idx] = idx
  }

  let reordered_body_runs : Array[@lib.LocalRun] = []
  let mut old_body_start = 0
  let mut next_body_idx = 0
  let mut remapped = false
  while old_body_start < body_local_count {
    let ty = body_local_types[old_body_start]
    let mut old_body_end = old_body_start + 1
    while old_body_end < body_local_count &&
          body_local_types[old_body_end] == ty {
      old_body_end += 1
    }
    let used_in_run : Array[Int32] = []
    for old_body_idx = old_body_start
        old_body_idx < old_body_end
        old_body_idx = old_body_idx + 1 {
      let old_idx = params.length() + old_body_idx
      if scan.counts[old_idx] > 0 {
        used_in_run.push(old_idx)
      }
    }
    ignore(rl_sort_used_body_locals(used_in_run, scan.counts, scan.first_uses))
    for old_idx in used_in_run {
      let new_idx = params.length() + next_body_idx
      old_to_new[old_idx] = new_idx
      if old_idx != new_idx {
        remapped = true
      }
      rl_push_local_run(reordered_body_runs, ty)
      next_body_idx += 1
    }
    old_body_start = old_body_end
  }

  if next_body_idx == body_local_count && !remapped {
    return { func, old_to_new: [], changed: false, }
  }
  let rewritten_locals = @lib.Locals::new(reordered_body_runs)
  let rewritten_body = if remapped {
    rl_rewrite_expr(body, old_to_new)
  } else {
    body
  }
  {
    func: @lib.Func::new(rewritten_locals, rewritten_body),
    old_to_new,
    changed: true,
  }
}

///|
fn rl_rewrite_func(
  func : @lib.Func,
  params : Array[@lib.ValType],
) -> RLFuncRewrite {
  let @lib.Func(body_locals, body) = func
  let body_local_count = body_locals.length()
  if body_local_count == 0 {
    return { func, old_to_new: [], changed: false, }
  }

  let total_locals = params.length() + body_local_count
  let scan = RLScanState::new(total_locals, params.length())
  rl_scan_expr(body, scan)
  if !scan.valid {
    // Fail closed for invalid/stale intermediate modules. Optimizing nested
    // cleanup callers can then validate-or-fallback instead of crashing while
    // probing a cleanup candidate.
    return { func, old_to_new: [], changed: false, }
  }

  let frequency_order = rl_sort_used_body_locals(
    scan.used_body_locals,
    scan.counts,
    scan.first_uses,
  )
  if body_locals.runs().length() <= 1 &&
    frequency_order.length() == body_local_count &&
    rl_used_body_locals_are_original_prefix(frequency_order, params.length()) {
    return { func, old_to_new: [], changed: false, }
  }
  let body_local_types = rl_flatten_locals(body_locals, body_local_count)
  let used_body_locals = rl_group_equal_frequency_locals(
    frequency_order,
    scan.counts,
    body_local_types,
    scan.first_uses,
    params.length(),
  )
  let used_body_locals_are_original_prefix = rl_used_body_locals_are_original_prefix(
    used_body_locals,
    params.length(),
  )
  if used_body_locals.length() == body_local_count &&
    used_body_locals_are_original_prefix {
    return { func, old_to_new: [], changed: false, }
  }

  let old_to_new = Array::make(total_locals, -1)
  for idx = 0; idx < params.length(); idx = idx + 1 {
    old_to_new[idx] = idx
  }

  let reordered_body_runs : Array[@lib.LocalRun] = Array::new(
    capacity=used_body_locals.length(),
  )
  for body_idx = 0
      body_idx < used_body_locals.length()
      body_idx = body_idx + 1 {
    let old_idx = used_body_locals[body_idx]
    old_to_new[old_idx] = params.length() + body_idx
    rl_push_local_run(
      reordered_body_runs,
      body_local_types[old_idx - params.length()],
    )
  }

  let rewritten_locals = @lib.Locals::new(reordered_body_runs)
  if used_body_locals_are_original_prefix {
    let rewritten_func = @lib.Func::new(rewritten_locals, body)
    return { func: rewritten_func, old_to_new, changed: true, }
  }

  let rewritten_body = rl_rewrite_expr(body, old_to_new)
  let rewritten_func = @lib.Func::new(rewritten_locals, rewritten_body)
  { func: rewritten_func, old_to_new, changed: true, }
}

///|
fn rl_rewrite_name_map(
  names : @lib.NameMap,
  old_to_new : Array[Int32],
) -> @lib.NameMap? {
  let @lib.NameMap(entries) = names
  let rewritten : Array[@lib.NameAssoc] = Array::new(capacity=entries.length())
  let mut sorted = true
  let mut last_mapped = -1
  for entry in entries {
    let @lib.NameAssoc(idx, name) = entry
    let raw = idx.reinterpret_as_int()
    if raw < 0 || raw >= old_to_new.length() {
      continue
    }
    let mapped = old_to_new[raw]
    if mapped >= 0 {
      if mapped < last_mapped {
        sorted = false
      } else {
        last_mapped = mapped
      }
      rewritten.push(@lib.NameAssoc::new(mapped.reinterpret_as_uint(), name))
    }
  }
  if rewritten.length() == 0 {
    return None
  }
  if !sorted {
    rewritten.sort_by(fn(lhs, rhs) {
      let @lib.NameAssoc(lhs_idx, _) = lhs
      let @lib.NameAssoc(rhs_idx, _) = rhs
      lhs_idx.reinterpret_as_int() - rhs_idx.reinterpret_as_int()
    })
  }
  Some(@lib.NameMap::new(rewritten))
}

///|
fn rl_rewrite_local_names(
  names : @lib.IndirectNameMap?,
  imported_func_count : Int32,
  func_remaps : Array[Array[Int32]?],
) -> @lib.IndirectNameMap? {
  match names {
    Some(@lib.IndirectNameMap(entries)) => {
      let rewritten : Array[@lib.IndirectNameAssoc] = Array::new(
        capacity=entries.length(),
      )
      for entry in entries {
        let @lib.IndirectNameAssoc(func_idx, local_names) = entry
        let raw_func_idx = func_idx.reinterpret_as_int()
        if raw_func_idx < imported_func_count {
          rewritten.push(entry)
          continue
        }
        let code_idx = raw_func_idx - imported_func_count
        match func_remaps.get(code_idx) {
          Some(Some(old_to_new)) =>
            match rl_rewrite_name_map(local_names, old_to_new) {
              Some(next_names) =>
                rewritten.push(
                  @lib.IndirectNameAssoc::new(func_idx, next_names),
                )
              None => ()
            }
          _ => rewritten.push(entry)
        }
      }
      if rewritten.length() == 0 {
        None
      } else {
        Some(@lib.IndirectNameMap::new(rewritten))
      }
    }
    None => None
  }
}

///|
fn rl_name_sec_is_empty(name_sec : @lib.NameSec) -> Bool {
  name_sec.module_name is None &&
  name_sec.func_names is None &&
  name_sec.local_names is None &&
  name_sec.label_names is None &&
  name_sec.type_names is None &&
  name_sec.table_names is None &&
  name_sec.memory_names is None &&
  name_sec.global_names is None &&
  name_sec.elem_names is None &&
  name_sec.data_names is None &&
  name_sec.field_names is None &&
  name_sec.tag_names is None
}

///|
fn rl_rewrite_name_sec(
  name_sec : @lib.NameSec?,
  imported_func_count : Int32,
  func_remaps : Array[Array[Int32]?],
) -> @lib.NameSec? {
  match name_sec {
    Some(sec) => {
      let {
        module_name,
        func_names,
        label_names,
        type_names,
        table_names,
        memory_names,
        global_names,
        elem_names,
        data_names,
        field_names,
        tag_names,
        ..
      } = sec
      let next = @lib.NameSec::new(
        module_name~,
        func_names~,
        local_names=rl_rewrite_local_names(
          sec.local_names,
          imported_func_count,
          func_remaps,
        ),
        label_names~,
        type_names~,
        table_names~,
        memory_names~,
        global_names~,
        elem_names~,
        data_names~,
        field_names~,
        tag_names~,
      )
      if rl_name_sec_is_empty(next) {
        None
      } else {
        Some(next)
      }
    }
    None => None
  }
}

///|
fn rl_rebuild_module(
  mod_ : @lib.Module,
  next_code_sec : @lib.CodeSec?,
  next_name_sec : @lib.NameSec?,
) -> @lib.Module {
  @lib.Module::new(
    custom_secs=mod_.custom_secs,
    type_sec=mod_.type_sec,
    import_sec=mod_.import_sec,
    func_annotation_sec=mod_.func_annotation_sec,
    func_sec=mod_.func_sec,
    table_sec=mod_.table_sec,
    mem_sec=mod_.mem_sec,
    tag_sec=mod_.tag_sec,
    stringrefs_sec=mod_.stringrefs_sec,
    global_sec=mod_.global_sec,
    export_sec=mod_.export_sec,
    start_sec=mod_.start_sec,
    elem_sec=mod_.elem_sec,
    data_cnt_sec=mod_.data_cnt_sec,
    code_sec=next_code_sec,
    data_sec=mod_.data_sec,
    name_sec=next_name_sec,
  )
}

///|
pub fn reorder_locals_run_module_pass(mod_ : @lib.Module) -> @lib.Module {
  if pass_module_has_offset_sensitive_metadata(mod_) {
    return mod_
  }
  let imported_func_count = rl_imported_func_count(mod_)
  match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) => {
      if funcs.is_empty() {
        return mod_
      }
      let defined_func_params = rl_defined_func_param_cache(mod_)
      let rewritten_funcs : Array[@lib.Func] = Array::new(
        capacity=funcs.length(),
      )
      let func_remaps : Array[Array[Int32]?] = Array::new(
        capacity=funcs.length(),
      )
      let mut changed = false
      for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
        let func = funcs[code_idx]
        let params = match defined_func_params.get(code_idx) {
          Some(params) => params
          None => abort("reorder-locals missing defined function params")
        }
        let rewrite = rl_rewrite_func(func, params)
        rewritten_funcs.push(rewrite.func)
        if rewrite.changed {
          func_remaps.push(Some(rewrite.old_to_new))
          changed = true
        } else {
          func_remaps.push(None)
        }
      }
      if !changed {
        return mod_
      }
      rl_rebuild_module(
        mod_,
        Some(@lib.CodeSec::new(rewritten_funcs)),
        rl_rewrite_name_sec(mod_.name_sec, imported_func_count, func_remaps),
      )
    }
    None => mod_
  }
}

///|
fn run_hot_pipeline_apply_reorder_locals_to_touched(
  mod_ : @lib.Module,
  touched : Array[Bool],
  params_by_func~ : Array[Array[@lib.ValType]]? = None,
) -> Result[@lib.Module, String] {
  let funcs = match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) => funcs
    None => return Ok(mod_)
  }
  let params = match params_by_func {
    Some(params) => params
    None => rl_defined_func_param_cache(mod_)
  }
  let func_remaps : Array[Array[Int32]?] = Array::make(funcs.length(), None)
  let mut rewritten : Array[@lib.Func]? = None
  for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
    if code_idx >= touched.length() || !touched[code_idx] {
      continue
    }
    let func_params = match params.get(code_idx) {
      Some(params) => params
      None =>
        return Err("missing defined function params for code index \{code_idx}")
    }
    let rewrite = rl_rewrite_func(funcs[code_idx], func_params)
    if rewrite.changed {
      func_remaps[code_idx] = Some(rewrite.old_to_new)
      match rewritten {
        Some(copy) => copy[code_idx] = rewrite.func
        None => {
          let copy = funcs.copy()
          copy[code_idx] = rewrite.func
          rewritten = Some(copy)
        }
      }
    }
  }
  match rewritten {
    Some(copy) => {
      let next = mod_.with_code_sec(@lib.CodeSec::new(copy))
      match
        rl_rewrite_name_sec(
          mod_.name_sec,
          rl_imported_func_count(mod_),
          func_remaps,
        ) {
        Some(name_sec) => Ok(next.with_name_sec(name_sec))
        None => Ok(next.without_name_sec())
      }
    }
    None => Ok(mod_)
  }
}