// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// 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.

///|
// Field liveness is shared across a nominal hierarchy. Constructor writes do
// not prevent immutability; explicit accesses constrain the layouts instead.
fn gto_map_children(
  instr : @lib.Instruction,
  rewrite : (@lib.Expr) -> @lib.Expr,
) -> @lib.Instruction {
  match instr {
    @lib.Block(bt, body) => @lib.Instruction::block(bt, rewrite(body))
    @lib.Loop(bt, body) => @lib.Instruction::loop_(bt, rewrite(body))
    @lib.If(bt, yes, no) => {
      let @lib.Expr(yes) = rewrite(@lib.Expr::new(yes))
      let no = no.map(fn(body) { rewrite(@lib.Expr::new(body)).0 })
      @lib.Instruction::if_(bt, yes, no)
    }
    @lib.TryTable(bt, catches, body) =>
      @lib.Instruction::try_table(bt, catches, rewrite(body))
    @lib.Try(bt, body, catches, delegate) =>
      @lib.Instruction::try_(
        bt,
        rewrite(body),
        catches.map(fn(c) {
          match c {
            @lib.LegacyCatch(tag, expr) =>
              @lib.LegacyCatch::tagged(tag, rewrite(expr))
            @lib.LegacyCatchAll(expr) => @lib.LegacyCatch::all(rewrite(expr))
          }
        }),
        delegate~,
      )
    _ => instr
  }
}

///|
fn gto_visit_children(
  instr : @lib.Instruction,
  visit : (@lib.Expr) -> Unit,
) -> Unit {
  match instr {
    @lib.Block(_, body) | @lib.Loop(_, body) | @lib.TryTable(_, _, body) =>
      visit(body)
    @lib.If(_, yes, no) => {
      visit(@lib.Expr::new(yes))
      match no {
        Some(body) => visit(@lib.Expr::new(body))
        None => ()
      }
    }
    @lib.Try(_, body, catches, _) => {
      visit(body)
      for catch_ in catches {
        match catch_ {
          @lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => visit(expr)
        }
      }
    }
    _ => ()
  }
}

///|
fn gto_field_access(
  instr : @lib.Instruction,
) -> (@lib.TypeIdx, @lib.U32, Bool, Bool)? {
  match instr {
    @lib.StructGet(t, f)
    | @lib.StructGetS(t, f)
    | @lib.StructGetU(t, f)
    | @lib.StructAtomicGet(_, t, f)
    | @lib.StructAtomicGetS(_, t, f)
    | @lib.StructAtomicGetU(_, t, f)
    | @lib.StructWait(t, f) => Some((t, f, true, false))
    @lib.StructSet(t, f) | @lib.StructAtomicSet(_, t, f) =>
      Some((t, f, false, true))
    @lib.StructAtomicRmw(_, _, t, f) | @lib.StructAtomicCmpxchg(_, t, f) =>
      Some((t, f, true, true))
    _ => None
  }
}

///|
fn gto_scan_fields(
  expr : @lib.Expr,
  reads : Array[Array[Bool]],
  writes : Array[Array[Bool]],
) -> Unit {
  for instr in expr.0 {
    match gto_field_access(instr) {
      Some((@lib.TypeIdx(t), @lib.U32(f), read, write)) => {
        let t = t.to_int()
        let f = f.to_int()
        if t < reads.length() && f < reads[t].length() {
          let ordered_atomic_store = match instr {
            @lib.StructAtomicSet(order, _, _) => !(order is @lib.Relaxed)
            _ => false
          }
          // A non-relaxed atomic store is an ordering event, not only a value
          // write. Keep its location even when no value read is visible in the
          // module so field pruning cannot erase that synchronization event.
          reads[t][f] = reads[t][f] || read || ordered_atomic_store
          writes[t][f] = writes[t][f] || write
        }
      }
      _ => ()
    }
    gto_visit_children(instr, fn(body) { gto_scan_fields(body, reads, writes) })
  }
}

///|
fn gto_visit_initializers(
  mod_ : @lib.Module,
  visit : (@lib.Expr) -> Unit,
) -> Unit {
  match mod_.global_sec {
    Some(@lib.GlobalSec(globals)) =>
      for @lib.Global(_, init) in globals {
        visit(init)
      }
    None => ()
  }
  match mod_.table_sec {
    Some(@lib.TableSec(tables)) =>
      for @lib.Table(_, init) in tables {
        match init {
          Some(expr) => visit(expr)
          None => ()
        }
      }
    None => ()
  }
  match mod_.elem_sec {
    Some(@lib.ElemSec(elems)) =>
      for @lib.Elem(_, kind) in elems {
        match kind {
          @lib.FuncExprsElemKind(exprs) | @lib.TypedExprsElemKind(_, exprs) =>
            for expr in exprs {
              visit(expr)
            }
          _ => ()
        }
      }
    None => ()
  }
}

///|
fn gto_map_initializers(
  mod_ : @lib.Module,
  rewrite : (@lib.Expr) -> @lib.Expr,
) -> @lib.Module {
  let mut next = mod_
  match mod_.global_sec {
    Some(@lib.GlobalSec(globals)) =>
      next = next.with_global_sec(
        @lib.GlobalSec::new(
          globals.map(fn(g) {
            let @lib.Global(ty, init) = g
            @lib.Global::new(ty, rewrite(init))
          }),
        ),
      )
    None => ()
  }
  match mod_.table_sec {
    Some(@lib.TableSec(tables)) =>
      next = next.with_table_sec(
        @lib.TableSec::new(
          tables.map(fn(t) {
            let @lib.Table(ty, init) = t
            @lib.Table::new(ty, init.map(rewrite))
          }),
        ),
      )
    None => ()
  }
  match mod_.elem_sec {
    Some(@lib.ElemSec(elems)) =>
      next = next.with_elem_sec(
        @lib.ElemSec::new(
          elems.map(fn(e) {
            let @lib.Elem(mode, kind) = e
            let kind = match kind {
              @lib.FuncExprsElemKind(exprs) =>
                @lib.ElemKind::func_exprs(exprs.map(rewrite))
              @lib.TypedExprsElemKind(ty, exprs) =>
                @lib.ElemKind::typed_exprs(ty, exprs.map(rewrite))
              _ => kind
            }
            @lib.Elem::new(mode, kind)
          }),
        ),
      )
    None => ()
  }
  next
}

///|
fn gto_public_types(
  mod_ : @lib.Module,
  context : @ir.HotModuleContext,
  bases : Array[Int32],
) -> Array[Bool] {
  let graph = Dae2Graph::new()
  for _ in context.subtypes {
    ignore(graph.location())
  }
  let embedded_types_root = [-1]
  for i, subtype in context.subtypes {
    let base = bases[i]
    graph.edge(i, base)
    graph.edge(base, i)
    for parent in subtype.super_types() {
      graph.edge(i, dae2_type_slot(parent, base))
    }
    for target in [subtype.describes_type(), subtype.descriptor_type()] {
      if target is Some(t) {
        graph.edge(i, dae2_type_slot(t, base))
      }
    }
    match subtype.get_comptype() {
      @lib.StructCompType(fields) =>
        for f in fields {
          dae2_type_field_edges(graph, i, f, base, embedded_types_root)
        }
      @lib.ArrayCompType(f) =>
        dae2_type_field_edges(graph, i, f, base, embedded_types_root)
      @lib.FuncCompType(params, results) =>
        for vt in params + results {
          dae2_type_ref_edges(graph, i, vt, base, embedded_types_root)
        }
      @lib.ContCompType(t) => graph.edge(i, dae2_type_slot(t, base))
    }
  }
  let exposed = Array::make(context.subtypes.length(), false)
  // Binaryen's getIgnorablePublicTypes preserves the canonical mutable
  // byte/UTF-16 arrays used for strings across Web host boundaries.
  for i, subtype in context.subtypes {
    let singleton = bases[i] == i &&
      (i + 1 == bases.length() || bases[i + 1] != i)
    if singleton &&
      !subtype.metadata().is_shared() &&
      subtype.super_types().is_empty() &&
      subtype.descriptor_type() is None &&
      subtype.describes_type() is None &&
      (
        subtype is @lib.SubType(true, _, _, _) ||
        subtype is @lib.CompTypeSubType(_, _)
      ) {
      match subtype.get_comptype() {
        @lib.ArrayCompType(
          @lib.FieldType(@lib.PackTypeStorageType(_), @lib.Var)
        ) => exposed[i] = true
        _ => ()
      }
    }
  }
  match mod_.import_sec {
    Some(@lib.ImportSec(imports)) =>
      for @lib.Import(_, _, kind) in imports {
        match kind {
          @lib.FuncExternType(t) => dae_mark_used_type_idx(t, exposed)
          @lib.TableExternType(t) => dae_mark_used_table_type(t, exposed)
          @lib.GlobalExternType(t) => dae_mark_used_global_type(t, exposed)
          @lib.TagExternType(@lib.TagType(t)) =>
            dae_mark_used_type_idx(t, exposed)
          _ => ()
        }
      }
    None => ()
  }
  match mod_.export_sec {
    Some(@lib.ExportSec(exports)) =>
      for @lib.Export(_, kind) in exports {
        match kind {
          @lib.FuncExternIdx(@lib.FuncIdx(i)) =>
            dae_mark_used_type_idx(context.func_type_idxs[i.to_int()], exposed)
          @lib.TableExternIdx(@lib.TableIdx(i)) =>
            dae_mark_used_table_type(context.tables[i.to_int()], exposed)
          @lib.GlobalExternIdx(@lib.GlobalIdx(i)) =>
            dae_mark_used_global_type(context.globals[i.to_int()], exposed)
          @lib.TagExternIdx(@lib.TagIdx(i)) => {
            let @lib.TagType(t) = context.tags[i.to_int()]
            dae_mark_used_type_idx(t, exposed)
          }
          _ => ()
        }
      }
    None => ()
  }
  let external_table_types : Array[@lib.RefType] = []
  let external_tables = Array::make(context.tables.length(), false)
  let mut imported_table_idx = 0
  match mod_.import_sec {
    Some(@lib.ImportSec(imports)) =>
      for @lib.Import(_, _, kind) in imports {
        match kind {
          @lib.TableExternType(@lib.TableType(ref_type, _)) => {
            external_table_types.push(ref_type)
            external_tables[imported_table_idx] = true
            imported_table_idx += 1
          }
          _ => ()
        }
      }
    None => ()
  }
  match mod_.export_sec {
    Some(@lib.ExportSec(exports)) =>
      for @lib.Export(_, kind) in exports {
        match kind {
          @lib.TableExternIdx(@lib.TableIdx(index)) => {
            let @lib.TableType(ref_type, _) = context.tables[index.to_int()]
            external_table_types.push(ref_type)
            external_tables[index.to_int()] = true
          }
          _ => ()
        }
      }
    None => ()
  }
  if !external_table_types.is_empty() {
    let env = @validate.Env::new().with_module(mod_)
    for raw_idx, type_idx in context.func_type_idxs {
      let func_idx = @lib.FuncIdx::new(raw_idx.reinterpret_as_uint())
      if external_table_types.any(fn(table_type) {
          gsi_func_ref_matches_table(env, func_idx, table_type)
        }) {
        dae_mark_used_type_idx(type_idx, exposed)
      }
    }
    match mod_.code_sec {
      Some(@lib.CodeSec(funcs)) =>
        for @lib.Func(_, @lib.Expr(body)) in funcs {
          dae2_mark_host_table_call_types(body, external_tables, exposed)
        }
      None => ()
    }
  }
  for i, visible in exposed {
    if visible {
      graph.observe(i)
    }
  }
  graph.solve()
  graph.used
}

///|
fn gto_remap_field(instr : @lib.Instruction, f : @lib.U32) -> @lib.Instruction {
  match instr {
    @lib.StructGet(t, _) => @lib.Instruction::struct_get(t, f)
    @lib.StructGetS(t, _) => @lib.Instruction::struct_get_s(t, f)
    @lib.StructGetU(t, _) => @lib.Instruction::struct_get_u(t, f)
    @lib.StructSet(t, _) => @lib.Instruction::struct_set(t, f)
    @lib.StructAtomicGet(o, t, _) =>
      @lib.Instruction::struct_atomic_get(o, t, f)
    @lib.StructAtomicGetS(o, t, _) =>
      @lib.Instruction::struct_atomic_get_s(o, t, f)
    @lib.StructAtomicGetU(o, t, _) =>
      @lib.Instruction::struct_atomic_get_u(o, t, f)
    @lib.StructAtomicSet(o, t, _) =>
      @lib.Instruction::struct_atomic_set(o, t, f)
    @lib.StructAtomicRmw(o, op, t, _) =>
      @lib.Instruction::struct_atomic_rmw(op, t, f, order=o)
    @lib.StructAtomicCmpxchg(o, t, _) =>
      @lib.Instruction::struct_atomic_cmpxchg(t, f, order=o)
    @lib.StructWait(t, _) => @lib.Instruction::struct_wait(t, f)
    _ => instr
  }
}

///|
fn gto_operand_start(
  mod_ : @lib.Module,
  funcs : Array[@lib.TypeIdx],
  instrs : Array[@lib.Instruction],
  end : Int32,
) -> Int32 {
  let mut needed = 1
  for i = end; i >= 0; i = i - 1 {
    match dae_stack_effect_evidence(instrs[i], mod_, funcs) {
      Some(e) => {
        if e.outputs > needed {
          return -1
        }
        needed = needed - e.outputs + e.inputs
        if needed == 0 {
          return i
        }
      }
      None => return -1
    }
  }
  -1
}

///|
fn gto_droppable(instrs : Array[@lib.Instruction]) -> Bool {
  instrs.all(fn(i) {
    dae_instr_is_droppable_pure_leaf(i) ||
    dae_instr_is_nontrapping_pure_unary(i) ||
    dae_instr_is_nontrapping_pure_binary(i) ||
    i is @lib.RefFunc(_) ||
    i is @lib.StructNew(_) ||
    i is @lib.StructNewDefault(_)
  })
}

///|
fn gto_layout_expr(
  expr : @lib.Expr,
  maps : Array[Array[Int32]],
  mod_ : @lib.Module,
  context : @ir.HotModuleContext,
  locals : @lib.Locals?,
  param_count : Int32,
  removed_inits : Array[@lib.Global],
  failed : Array[Bool],
) -> @lib.Expr {
  let out : Array[@lib.Instruction] = []
  let starts : Array[Int32] = []
  let instrs = expr.0
  for pos, original in instrs {
    starts.push(out.length())
    let instr = gto_map_children(original, fn(body) {
      gto_layout_expr(
        body, maps, mod_, context, locals, param_count, removed_inits, failed,
      )
    })
    match gto_field_access(instr) {
      Some((@lib.TypeIdx(t), @lib.U32(f), read, _)) => {
        let next = maps[t.to_int()][f.to_int()]
        if next < 0 && !read {
          out.push(@lib.Instruction::drop())
          out.push(@lib.Instruction::ref_as_non_null())
          out.push(@lib.Instruction::drop())
        } else if next >= 0 {
          out.push(gto_remap_field(instr, @lib.U32::new(next.to_uint())))
        } else {
          failed[0] = true
          out.push(instr)
        }
        continue
      }
      _ => ()
    }
    match instr {
      @lib.StructNew(@lib.TypeIdx(t)) => {
        let map = maps[t.to_int()]
        let mut changed = false
        let mut reordered = false
        let mut prev = -1
        for i, n in map {
          if i != n {
            changed = true
          }
          if n >= 0 {
            if n < prev {
              reordered = true
            }
            prev = n
          }
        }
        if !changed {
          out.push(instr)
          continue
        }
        let ranges : Array[(Int32, Int32)] = []
        let mut end = pos
        for _ in map {
          let start = gto_operand_start(
            mod_,
            context.func_type_idxs,
            instrs,
            end - 1,
          )
          if start < 0 {
            failed[0] = true
            return expr
          }
          ranges.push((start, end))
          end = start
        }
        let ranges = ranges.rev()
        while out.length() > starts[end] {
          ignore(out.pop())
        }
        let kept : Array[Array[@lib.Instruction]] = Array::make(
          map.filter(fn(n) { n >= 0 }).length(),
          [],
        )
        for i, range in ranges {
          let (start, end) = range
          let old = gsi_instr_slice(instrs, start, end)
          let body = gto_layout_expr(
              @lib.Expr::new(old),
              maps,
              mod_,
              context,
              locals,
              param_count,
              removed_inits,
              failed,
            ).0
          if map[i] < 0 {
            if !gto_droppable(body) {
              if locals is None {
                let fields = match context.subtypes[t.to_int()].get_comptype() {
                  @lib.StructCompType(f) => f
                  _ => {
                    failed[0] = true
                    return expr
                  }
                }
                removed_inits.push(
                  @lib.Global::new(
                    @lib.GlobalType::new(fields[i].unpack(), false),
                    @lib.Expr::new(body),
                  ),
                )
              } else {
                out.append(body)
                out.push(@lib.Instruction::drop())
              }
            }
          } else if reordered {
            if gto_droppable(body) &&
              body.all(fn(i) {
                !(i is @lib.LocalGet(_) ||
                i is @lib.GlobalGet(_) ||
                i is @lib.MemorySize(_) ||
                i is @lib.TableSize(_))
              }) {
              kept[map[i]] = body
            } else {
              let local_types = match locals {
                Some(l) => l
                None => {
                  failed[0] = true
                  return expr
                }
              }
              let fields = match context.subtypes[t.to_int()].get_comptype() {
                @lib.StructCompType(f) => f
                _ => {
                  failed[0] = true
                  return expr
                }
              }
              let vt = fields[i].unpack()
              let local_type = match vt {
                @lib.RefTypeValType(r) =>
                  @lib.ValType::ref_type(r.make_nullable())
                _ => vt
              }
              let slot = @lib.LocalIdx::new(
                (param_count + local_types.length()).to_uint(),
              )
              local_types.push(local_type)
              out.append(body)
              out.push(@lib.Instruction::local_set(slot))
              let reload = [@lib.Instruction::local_get(slot)]
              if local_type != vt {
                reload.push(@lib.Instruction::ref_as_non_null())
              }
              kept[map[i]] = reload
            }
          } else {
            out.append(body)
          }
        }
        if reordered {
          for body in kept {
            out.append(body)
          }
        }
        if kept.is_empty() {
          out.push(@lib.Instruction::struct_new_default(@lib.TypeIdx::new(t)))
        } else {
          out.push(instr)
        }
      }
      _ => out.push(instr)
    }
  }
  @lib.Expr::new(out)
}

///|
fn gto_optimize_fields(mod_ : @lib.Module) -> @lib.Module {
  let mod_ = pass_cleanup_unused_type_groups(gto_canonicalize_types(mod_))
  let recs = match mod_.type_sec {
    Some(@lib.TypeSec(t)) => t
    None => return mod_
  }
  let context = @ir.hot_module_context_from_module(mod_)
  let types = context.subtypes
  let bases : Array[Int32] = []
  for rec in recs {
    let base = bases.length()
    match rec {
      @lib.SingleRecType(_) => bases.push(base)
      @lib.GroupRecType(g) =>
        for _ in g {
          bases.push(base)
        }
    }
  }
  let public_ = gto_public_types(mod_, context, bases)
  let fields = types.map(fn(t) {
    match t.get_comptype() {
      @lib.StructCompType(f) => f
      _ => []
    }
  })
  let reads = fields.map(fn(f) { Array::make(f.length(), false) })
  let writes = fields.map(fn(f) { Array::make(f.length(), false) })
  match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) =>
      for @lib.Func(_, body) in funcs {
        gto_scan_fields(body, reads, writes)
      }
    None => ()
  }
  gto_visit_initializers(mod_, fn(body) { gto_scan_fields(body, reads, writes) })
  // Descriptor prototype handling has additional host constraints. The
  // descriptor-specific stage handles those groups after ordinary structs.
  for i, t in types {
    if t.describes_type() is Some(_) || t.descriptor_type() is Some(_) {
      public_[i] = true
    }
  }
  let parents = types.map(fn(t) {
    match t.super_types() {
      [@lib.TypeIdx(i), ..] => i.to_int()
      _ => -1
    }
  })
  let children : Array[Array[Int32]] = parents.map(fn(_) { [] })
  for i, p in parents {
    if p >= 0 {
      children[p].push(i)
    }
  }
  let all_reads = fields.map(fn(f) { Array::make(f.length(), false) })
  let all_writes = fields.map(fn(f) { Array::make(f.length(), false) })
  let pending : Array[(Int32, Int32, Bool)] = []
  for i, type_fields in fields {
    for f = 0; f < type_fields.length(); f = f + 1 {
      if reads[i][f] || public_[i] {
        all_reads[i][f] = true
        pending.push((i, f, false))
      }
      if writes[i][f] || public_[i] {
        all_writes[i][f] = true
        pending.push((i, f, true))
      }
    }
  }
  let mut pending_cursor = 0
  while pending_cursor < pending.length() {
    let (type_slot, field_slot, is_write) = pending[pending_cursor]
    pending_cursor += 1
    let parent = parents[type_slot]
    if parent >= 0 && field_slot < fields[parent].length() {
      let target = if is_write { all_writes[parent] } else { all_reads[parent] }
      if !target[field_slot] {
        target[field_slot] = true
        pending.push((parent, field_slot, is_write))
      }
    }
    for child in children[type_slot] {
      if field_slot < fields[child].length() {
        let target = if is_write { all_writes[child] } else { all_reads[child] }
        if !target[field_slot] {
          target[field_slot] = true
          pending.push((child, field_slot, is_write))
        }
      }
    }
  }
  let maps = fields.map(fn(f) { Array::make(f.length(), -1) })
  let immutable = fields.map(fn(f) { Array::make(f.length(), false) })
  let next_types : Array[@lib.SubType] = []
  let mut any_change = false
  for i, subtype in types {
    let p = parents[i]
    if p >= 0 {
      for f = 0; f < fields[p].length(); f = f + 1 {
        reads[i][f] = reads[i][f] || reads[p][f]
        writes[i][f] = writes[i][f] || writes[p][f]
      }
    }
    let mut next = 0
    let mut inherited = 0
    if p >= 0 {
      inherited = fields[p].length()
      for f = 0; f < inherited; f = f + 1 {
        if maps[p][f] >= 0 {
          maps[i][f] = maps[p][f]
          next = Int32::max(next, maps[p][f] + 1)
        }
      }
    }
    for f = 0; f < fields[i].length(); f = f + 1 {
      let removable = !public_[i] &&
        (!all_reads[i][f] || (!reads[i][f] && !writes[i][f]))
      if maps[i][f] < 0 && !removable {
        maps[i][f] = next
        next += 1
      }
      immutable[i][f] = !public_[i] &&
        !all_writes[i][f] &&
        (f >= inherited || immutable[p][f])
      if maps[i][f] != f ||
        (immutable[i][f] && fields[i][f] is @lib.FieldType(_, @lib.Var)) {
        any_change = true
      }
    }
    let comp = match subtype.get_comptype() {
      @lib.StructCompType(_) => {
        let out = Array::make(next, gto_placeholder_field())
        for f, field in fields[i] {
          if maps[i][f] >= 0 {
            let @lib.FieldType(storage, mutability) = field
            out[maps[i][f]] = @lib.FieldType::new(
              storage,
              if immutable[i][f] {
                @lib.Mut::const_()
              } else {
                mutability
              },
            )
          }
        }
        @lib.CompType::struct_(out)
      }
      comp => comp
    }
    next_types.push(
      match subtype {
        @lib.SubType(final_, supers, metadata, _) =>
          @lib.SubType::new(final_, supers, comp, metadata~)
        @lib.CompTypeSubType(metadata, _) =>
          @lib.SubType::comp_type(comp, metadata~)
      },
    )
  }
  if !any_change {
    return mod_
  }
  let failed = [false]
  let removed_inits : Array[@lib.Global] = []
  let mut candidate = gto_map_initializers(mod_, fn(body) {
    gto_layout_expr(body, maps, mod_, context, None, 0, removed_inits, failed)
  })
  match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) => {
      let imported = context.func_type_idxs.length() - funcs.length()
      let next_funcs : Array[@lib.Func] = []
      for i, @lib.Func(locals, body) in funcs {
        let locals = locals.copy()
        let @lib.FuncType(params, _) = context.func_types[imported + i]
        let body = gto_layout_expr(
          body,
          maps,
          mod_,
          context,
          Some(locals),
          params.length(),
          removed_inits,
          failed,
        )
        next_funcs.push(@lib.Func::new(locals, body))
      }
      candidate = candidate.with_code_sec(@lib.CodeSec::new(next_funcs))
    }
    None => ()
  }
  if failed[0] {
    return mod_
  }
  if !removed_inits.is_empty() {
    let globals = match candidate.global_sec {
      Some(@lib.GlobalSec(gs)) => gs.copy()
      None => []
    }
    globals.append(removed_inits)
    candidate = candidate.with_global_sec(@lib.GlobalSec::new(globals))
  }
  let next_recs : Array[@lib.RecType] = []
  let mut index = 0
  for rec in recs {
    match rec {
      @lib.SingleRecType(_) => {
        next_recs.push(@lib.RecType::new(next_types[index]))
        index += 1
      }
      @lib.GroupRecType(group) => {
        let next : Array[@lib.SubType] = []
        for _ in group {
          next.push(next_types[index])
          index += 1
        }
        next_recs.push(@lib.RecType::group(next))
      }
    }
  }
  candidate = candidate.with_type_sec(@lib.TypeSec::new(next_recs))
  match mod_.name_sec {
    Some(names) => {
      let field_names = names.field_names.map(fn(name_map) {
        let @lib.IndirectNameMap(entries) = name_map
        @lib.IndirectNameMap::new(
          entries.map(fn(entry) {
            let @lib.IndirectNameAssoc(t, @lib.NameMap(names)) = entry
            let retained : Array[@lib.NameAssoc] = []
            for @lib.NameAssoc(f, name) in names {
              let n = maps[t.to_int()][f.to_int()]
              if n >= 0 {
                retained.push(@lib.NameAssoc::new(n.to_uint(), name))
              }
            }
            retained.sort_by(fn(a, b) {
              let @lib.NameAssoc(a, _) = a
              let @lib.NameAssoc(b, _) = b
              a.compare(b)
            })
            @lib.IndirectNameAssoc::new(t, @lib.NameMap::new(retained))
          }),
        )
      })
      candidate = candidate.with_name_sec(
        @lib.NameSec::new(
          module_name=names.module_name,
          func_names=names.func_names,
          local_names=names.local_names,
          label_names=names.label_names,
          type_names=names.type_names,
          field_names~,
          table_names=names.table_names,
          memory_names=names.memory_names,
          global_names=names.global_names,
          elem_names=names.elem_names,
          data_names=names.data_names,
          tag_names=names.tag_names,
        ),
      )
    }
    None => ()
  }
  let type_order = gto_collect_type_order(
    candidate.with_type_sec(mod_.type_sec.unwrap()),
  )
  let candidate = gto_regroup_private_types(candidate, public_, type_order)
  if @validate.validate_module(candidate).is_ok() {
    candidate
  } else {
    mod_
  }
}