// 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 2022 WebAssembly Community Group participants
// Counterpart: src/passes/GlobalStructInference.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 global_struct_inference_summary() -> String {
  "Fold immutable struct field reads from direct immutable global instances when their values are globally fixed."
}

///|
pub fn global_struct_inference_desc_cast_summary() -> String {
  "Run global-struct-inference and rewrite eligible casts to descriptor-equality casts."
}

///|
fn gsi_type_slot(type_idx : @lib.TypeIdx, type_count : Int32) -> Int32 {
  match type_idx {
    @lib.TypeIdx(idx) => {
      let slot = idx.reinterpret_as_int()
      if slot < 0 || slot >= type_count {
        -1
      } else {
        slot
      }
    }
    @lib.RecIdx(_) => -1
  }
}

///|
priv struct GsiClosedWorldFacts {
  poisoned_type_slots : Array[Bool]
  candidate_globals_by_type_slot : Array[Array[@lib.GlobalIdx]]
}

///|
priv struct GsiValueGroup {
  value : @lib.Expr
  globals : Array[@lib.GlobalIdx]
}

///|
priv struct GsiSplitOperand {
  start : Int32
  end_exclusive : Int32
  expr : @lib.Expr
}

///|
priv struct GsiDirectFieldReadRequest {
  global_idx : @lib.GlobalIdx
  type_idx : @lib.TypeIdx
  field_idx : Int32
}

///|
priv struct GsiTypedFieldReadRequest {
  type_idx : @lib.TypeIdx
  field_idx : Int32
}

///|
priv struct GsiUnnestReadRequests {
  direct_reads : Array[GsiDirectFieldReadRequest]
  typed_reads : Array[GsiTypedFieldReadRequest]
}

///|
fn gsi_closed_world_facts_has_candidates(facts : GsiClosedWorldFacts) -> Bool {
  for slot = 0
      slot < facts.candidate_globals_by_type_slot.length()
      slot = slot + 1 {
    if !facts.poisoned_type_slots[slot] &&
      facts.candidate_globals_by_type_slot[slot].length() > 0 {
      return true
    }
  }
  false
}

///|
fn gsi_struct_new_type(instr : @lib.Instruction) -> @lib.TypeIdx? {
  match instr {
    @lib.StructNew(type_idx)
    | @lib.StructNewDefault(type_idx)
    | @lib.StructNewDesc(type_idx)
    | @lib.StructNewDefaultDesc(type_idx) => Some(type_idx)
    _ => None
  }
}

///|
fn gsi_mark_poisoned_type(
  poisoned_type_slots : Array[Bool],
  type_idx : @lib.TypeIdx,
) -> Unit {
  let slot = gsi_type_slot(type_idx, poisoned_type_slots.length())
  if slot >= 0 {
    poisoned_type_slots[slot] = true
  }
}

///|
fn gsi_mark_all_struct_allocs(
  instrs : Array[@lib.Instruction],
  poisoned_type_slots : Array[Bool],
) -> Unit {
  for instr in instrs {
    match gsi_struct_new_type(instr) {
      Some(type_idx) => gsi_mark_poisoned_type(poisoned_type_slots, type_idx)
      None => ()
    }
    match instr {
      @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
        gsi_mark_all_struct_allocs(body, poisoned_type_slots)
      @lib.If(_, then_body, else_body) => {
        gsi_mark_all_struct_allocs(then_body, poisoned_type_slots)
        match else_body {
          Some(else_body) =>
            gsi_mark_all_struct_allocs(else_body, poisoned_type_slots)
          None => ()
        }
      }
      @lib.TryTable(_, _, @lib.Expr(body)) =>
        gsi_mark_all_struct_allocs(body, poisoned_type_slots)
      _ => ()
    }
  }
}

///|
fn gsi_mark_nested_global_struct_allocs(
  instrs : Array[@lib.Instruction],
  poisoned_type_slots : Array[Bool],
) -> Unit {
  for idx = 0; idx < instrs.length(); idx = idx + 1 {
    let instr = instrs[idx]
    match gsi_struct_new_type(instr) {
      Some(type_idx) =>
        if idx + 1 != instrs.length() {
          gsi_mark_poisoned_type(poisoned_type_slots, type_idx)
        }
      None => ()
    }
    match instr {
      @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
        gsi_mark_all_struct_allocs(body, poisoned_type_slots)
      @lib.If(_, then_body, else_body) => {
        gsi_mark_all_struct_allocs(then_body, poisoned_type_slots)
        match else_body {
          Some(else_body) =>
            gsi_mark_all_struct_allocs(else_body, poisoned_type_slots)
          None => ()
        }
      }
      @lib.TryTable(_, _, @lib.Expr(body)) =>
        gsi_mark_all_struct_allocs(body, poisoned_type_slots)
      _ => ()
    }
  }
}

///|
fn gsi_global_idx_order(global_idx : @lib.GlobalIdx) -> Int32 {
  let @lib.GlobalIdx(raw) = global_idx
  raw.reinterpret_as_int()
}

///|
fn gsi_candidate_list_contains(
  candidates : Array[@lib.GlobalIdx],
  candidate : @lib.GlobalIdx,
) -> Bool {
  for existing in candidates {
    if existing == candidate {
      return true
    }
  }
  false
}

///|
fn gsi_sort_candidate_globals(candidates : Array[@lib.GlobalIdx]) -> Unit {
  candidates.sort_by(fn(lhs, rhs) {
    gsi_global_idx_order(lhs) - gsi_global_idx_order(rhs)
  })
}

///|
fn gsi_propagate_closed_world_subtype_facts(
  env : @validate.Env,
  poisoned_type_slots : Array[Bool],
  candidate_globals_by_type_slot : Array[Array[@lib.GlobalIdx]],
) -> Unit {
  let type_count = poisoned_type_slots.length()
  let poison_queue : Array[Int32] = []
  let mut has_candidates = false
  for slot = 0; slot < type_count; slot = slot + 1 {
    if poisoned_type_slots[slot] {
      poison_queue.push(slot)
    }
    if !candidate_globals_by_type_slot[slot].is_empty() {
      has_candidates = true
    }
  }
  let mut poison_cursor = 0
  while poison_cursor < poison_queue.length() {
    let child_slot = poison_queue[poison_cursor]
    poison_cursor += 1
    for super_type in env.global_types[child_slot].super_types() {
      let parent_slot = gsi_type_slot(super_type, type_count)
      if parent_slot >= 0 && !poisoned_type_slots[parent_slot] {
        poisoned_type_slots[parent_slot] = true
        poison_queue.push(parent_slot)
      }
    }
  }
  if !has_candidates {
    return
  }
  let candidate_slots : Array[Int32] = []
  let candidate_values : Array[@lib.GlobalIdx] = []
  let seen : Array[@hashmap.HashMap[@lib.GlobalIdx, Bool]] = []
  for slot = 0; slot < type_count; slot = slot + 1 {
    let slot_seen : @hashmap.HashMap[@lib.GlobalIdx, Bool] = @hashmap.new()
    for candidate in candidate_globals_by_type_slot[slot] {
      if slot_seen.get(candidate) is None {
        slot_seen.set(candidate, true)
        candidate_slots.push(slot)
        candidate_values.push(candidate)
      }
    }
    seen.push(slot_seen)
  }
  let mut candidate_cursor = 0
  while candidate_cursor < candidate_slots.length() {
    let child_slot = candidate_slots[candidate_cursor]
    let candidate = candidate_values[candidate_cursor]
    candidate_cursor += 1
    for super_type in env.global_types[child_slot].super_types() {
      let parent_slot = gsi_type_slot(super_type, type_count)
      if parent_slot >= 0 && seen[parent_slot].get(candidate) is None {
        seen[parent_slot].set(candidate, true)
        candidate_globals_by_type_slot[parent_slot].push(candidate)
        candidate_slots.push(parent_slot)
        candidate_values.push(candidate)
      }
    }
  }
  for slot = 0; slot < type_count; slot = slot + 1 {
    if poisoned_type_slots[slot] {
      candidate_globals_by_type_slot[slot] = []
    } else {
      gsi_sort_candidate_globals(candidate_globals_by_type_slot[slot])
    }
  }
}

///|
fn gsi_global_decl_is_eq_comparable(
  env : @validate.Env,
  global_type : @lib.GlobalType,
) -> Bool {
  match global_type {
    @lib.GlobalType(value_type, _) =>
      @validate.Match::matches(value_type, @lib.ValType::eqref_null(), env)
  }
}

///|
fn gsi_ref_type_matches_exact_type(
  ref_type : @lib.RefType,
  type_idx : @lib.TypeIdx,
) -> Bool {
  match ref_type.get_heap_type() {
    @lib.HeapType(inner_type_idx) => inner_type_idx == type_idx
    _ => false
  }
}

///|
fn gsi_val_type_ref_matches_exact_type(
  value_type : @lib.ValType,
  type_idx : @lib.TypeIdx,
) -> @lib.RefType? {
  match value_type {
    @lib.RefTypeValType(ref_type) =>
      if gsi_ref_type_matches_exact_type(ref_type, type_idx) {
        Some(ref_type)
      } else {
        None
      }
    _ => None
  }
}

///|
fn gsi_global_value_type_for_exact_struct(
  env : @validate.Env,
  global_idx : @lib.GlobalIdx,
  type_idx : @lib.TypeIdx,
) -> @lib.ValType? {
  match env.get_global_type(global_idx) {
    Some(@lib.GlobalType(value_type, _)) =>
      match gsi_val_type_ref_matches_exact_type(value_type, type_idx) {
        Some(_) => Some(value_type)
        None => None
      }
    None => None
  }
}

///|
fn gsi_struct_field_is_immutable(
  env : @validate.Env,
  type_idx : @lib.TypeIdx,
  field_idx : @lib.U32,
) -> Bool {
  let fields = match env.resolve_struct_fields(type_idx) {
    Ok(fields) => fields
    Err(_) => return false
  }
  let @lib.U32(raw_idx) = field_idx
  let idx = raw_idx.reinterpret_as_int()
  idx >= 0 && idx < fields.length() && !fields[idx].is_mutable()
}

///|
fn gsi_candidate_lists_equal(
  lhs : Array[@lib.GlobalIdx],
  rhs : Array[@lib.GlobalIdx],
) -> Bool {
  if lhs.length() != rhs.length() {
    return false
  }
  for idx = 0; idx < lhs.length(); idx = idx + 1 {
    if lhs[idx] != rhs[idx] {
      return false
    }
  }
  true
}

///|
fn gsi_exact_direct_candidate_globals(
  env : @validate.Env,
  mod_ : @lib.Module,
  facts : GsiClosedWorldFacts,
) -> Array[Array[@lib.GlobalIdx]] {
  let type_count = facts.poisoned_type_slots.length()
  let candidates_by_type_slot : Array[Array[@lib.GlobalIdx]] = []
  for slot = 0; slot < type_count; slot = slot + 1 {
    ignore(slot)
    candidates_by_type_slot.push([])
  }
  let globals = match mod_.global_sec {
    Some(@lib.GlobalSec(globals)) => globals
    None => return candidates_by_type_slot
  }
  let total_globals = env.globals.length()
  let imported_global_count = total_globals - globals.length()
  for defined_idx = 0
      defined_idx < globals.length()
      defined_idx = defined_idx + 1 {
    let global_ = globals[defined_idx]
    let @lib.Global(global_type, init) = global_
    match global_type {
      @lib.GlobalType(_, true) => continue
      _ => ()
    }
    if !gsi_global_decl_is_eq_comparable(env, global_type) {
      continue
    }
    match gsi_candidate_global_values(env, init) {
      Some((type_idx, _)) => {
        let slot = gsi_type_slot(type_idx, type_count)
        if slot < 0 || facts.poisoned_type_slots[slot] {
          continue
        }
        let candidate = @lib.GlobalIdx::new(
          (imported_global_count + defined_idx).reinterpret_as_uint(),
        )
        if gsi_global_value_type_for_exact_struct(env, candidate, type_idx)
          is None {
          continue
        }
        candidates_by_type_slot[slot].push(candidate)
      }
      None => ()
    }
  }
  for slot = 0; slot < type_count; slot = slot + 1 {
    gsi_sort_candidate_globals(candidates_by_type_slot[slot])
    if !gsi_candidate_lists_equal(
        candidates_by_type_slot[slot],
        facts.candidate_globals_by_type_slot[slot],
      ) {
      candidates_by_type_slot[slot] = []
    }
  }
  candidates_by_type_slot
}

///|
fn gsi_exact_single_candidate_globals(
  env : @validate.Env,
  mod_ : @lib.Module,
  facts : GsiClosedWorldFacts,
) -> Array[@lib.GlobalIdx?] {
  let type_count = facts.poisoned_type_slots.length()
  let first_candidate_by_type_slot : Array[@lib.GlobalIdx?] = Array::make(
    type_count,
    None,
  )
  let candidate_count_by_type_slot : Array[Int32] = Array::make(type_count, 0)
  let direct_candidates_by_type_slot = gsi_exact_direct_candidate_globals(
    env, mod_, facts,
  )
  for slot = 0; slot < type_count; slot = slot + 1 {
    for candidate in direct_candidates_by_type_slot[slot] {
      if candidate_count_by_type_slot[slot] == 0 {
        first_candidate_by_type_slot[slot] = Some(candidate)
      }
      candidate_count_by_type_slot[slot] += 1
    }
    if candidate_count_by_type_slot[slot] != 1 {
      first_candidate_by_type_slot[slot] = None
    }
  }
  first_candidate_by_type_slot
}

///|
fn gsi_mark_external_struct_origins(
  env : @validate.Env,
  value_type : @lib.ValType,
  poisoned_type_slots : Array[Bool],
  cached_value_types : Array[@lib.ValType],
  cached_slots : Array[Array[Int32]],
) -> Unit {
  let external_type = match value_type {
    @lib.RefTypeValType(_) => value_type
    _ => return
  }
  for cache_idx, cached_type in cached_value_types {
    if cached_type == external_type {
      for slot in cached_slots[cache_idx] {
        poisoned_type_slots[slot] = true
      }
      return
    }
  }
  let compatible_slots : Array[Int32] = []
  let type_count = poisoned_type_slots.length()
  for slot = 0; slot < type_count; slot = slot + 1 {
    let type_idx = gsi_type_idx_from_slot(slot)
    if env.resolve_struct_fields(type_idx) is Err(_) &&
      env.resolve_array_field(type_idx) is Err(_) {
      continue
    }
    let exact_value_type = @lib.ValType::ref_type(
      @lib.RefType::new(false, @lib.HeapType::new(type_idx), exact=true),
    )
    if @validate.Match::matches(exact_value_type, external_type, env) {
      poisoned_type_slots[slot] = true
      compatible_slots.push(slot)
    }
  }
  cached_value_types.push(external_type)
  cached_slots.push(compatible_slots)
  for slot in compatible_slots {
    let type_idx = gsi_type_idx_from_slot(slot)
    match env.resolve_struct_fields(type_idx) {
      Ok(fields) =>
        for field in fields {
          gsi_mark_external_struct_origins(
            env,
            field.unpack(),
            poisoned_type_slots,
            cached_value_types,
            cached_slots,
          )
        }
      Err(_) =>
        match env.resolve_array_field(type_idx) {
          Ok(field) =>
            gsi_mark_external_struct_origins(
              env,
              field.unpack(),
              poisoned_type_slots,
              cached_value_types,
              cached_slots,
            )
          Err(_) => ()
        }
    }
  }
}

///|
fn gsi_mark_imported_struct_origins(
  env : @validate.Env,
  mod_ : @lib.Module,
  poisoned_type_slots : Array[Bool],
  cached_value_types : Array[@lib.ValType],
  cached_slots : Array[Array[Int32]],
) -> Unit {
  let imports = match mod_.import_sec {
    Some(@lib.ImportSec(imports)) => imports
    None => return
  }
  for import_ in imports {
    let @lib.Import(_, _, external_type) = import_
    match external_type {
      @lib.GlobalExternType(@lib.GlobalType(value_type, _)) =>
        gsi_mark_external_struct_origins(
          env, value_type, poisoned_type_slots, cached_value_types, cached_slots,
        )
      @lib.TableExternType(@lib.TableType(ref_type, _)) =>
        gsi_mark_external_struct_origins(
          env,
          @lib.ValType::ref_type(ref_type),
          poisoned_type_slots,
          cached_value_types,
          cached_slots,
        )
      @lib.FuncExternType(type_idx) =>
        match env.resolve_functype(type_idx) {
          Some(@lib.FuncType(_, results)) =>
            for result in results {
              gsi_mark_external_struct_origins(
                env, result, poisoned_type_slots, cached_value_types, cached_slots,
              )
            }
          None => ()
        }
      @lib.TagExternType(@lib.TagType(type_idx)) =>
        match env.resolve_functype(type_idx) {
          Some(@lib.FuncType(params, _)) =>
            for param in params {
              gsi_mark_external_struct_origins(
                env, param, poisoned_type_slots, cached_value_types, cached_slots,
              )
            }
          None => ()
        }
      @lib.MemExternType(_) => ()
    }
  }
}

///|
priv struct GsiExportedOriginRoots {
  funcs : Array[Int32]
  globals : Array[Int32]
}

///|
fn gsi_exported_origin_roots(
  mod_ : @lib.Module,
  first_defined_func : Int32,
  defined_func_count : Int32,
  first_defined_global : Int32,
  defined_global_count : Int32,
) -> GsiExportedOriginRoots {
  // One entity may be exported under many names. Plan each body or initializer
  // once so export aliases do not multiply closed-world scanning work.
  let funcs : Array[Int32] = []
  let globals : Array[Int32] = []
  let func_seen = Array::make(defined_func_count, false)
  let global_seen = Array::make(defined_global_count, false)
  match mod_.export_sec {
    Some(@lib.ExportSec(exports)) =>
      for export_ in exports {
        match export_ {
          @lib.Export(_, @lib.FuncExternIdx(@lib.FuncIdx(raw_idx))) => {
            let defined_idx = raw_idx.reinterpret_as_int() - first_defined_func
            if defined_idx >= 0 &&
              defined_idx < defined_func_count &&
              !func_seen[defined_idx] {
              func_seen[defined_idx] = true
              funcs.push(defined_idx)
            }
          }
          @lib.Export(_, @lib.GlobalExternIdx(@lib.GlobalIdx(raw_idx))) => {
            let defined_idx = raw_idx.reinterpret_as_int() -
              first_defined_global
            if defined_idx >= 0 &&
              defined_idx < defined_global_count &&
              !global_seen[defined_idx] {
              global_seen[defined_idx] = true
              globals.push(defined_idx)
            }
          }
          _ => ()
        }
      }
    None => ()
  }
  { funcs, globals, }
}

///|
priv struct GsiGlobalVisitStamps {
  marks : Array[Int32]
  mut generation : Int32
}

///|
fn GsiGlobalVisitStamps::new() -> GsiGlobalVisitStamps {
  { marks: [], generation: 0, }
}

///|
fn GsiGlobalVisitStamps::begin_root(self : GsiGlobalVisitStamps) -> Unit {
  if self.generation >= 0x7FFFFFFF {
    for idx = 0; idx < self.marks.length(); idx = idx + 1 {
      self.marks[idx] = 0
    }
    self.generation = 1
  } else {
    self.generation += 1
  }
}

///|
fn GsiGlobalVisitStamps::visit(
  self : GsiGlobalVisitStamps,
  slot : Int32,
) -> Bool {
  if slot < 0 {
    return false
  }
  while self.marks.length() <= slot {
    // Grow only when an immutable-global alias is actually traversed. Modules
    // with exported functions but no global aliases retain an empty scratch.
    self.marks.push(0)
  }
  if self.marks[slot] == self.generation {
    return false
  }
  self.marks[slot] = self.generation
  true
}

///|
fn gsi_collect_returned_ref_funcs(
  env : @validate.Env,
  expr : @lib.Expr,
  results : Array[@lib.ValType],
  callable_funcs : Array[@lib.FuncIdx],
  defined_globals : Array[@lib.Global],
  first_defined_global : Int32,
  visited_globals : GsiGlobalVisitStamps,
) -> Unit {
  let @lib.Expr(instrs) = expr
  for instr in instrs {
    match instr {
      @lib.RefFunc(target) =>
        for result in results {
          match result {
            @lib.RefTypeValType(ref_type) =>
              if gsi_func_ref_matches_table(env, target, ref_type) {
                callable_funcs.push(target)
              }
            _ => ()
          }
        }
      @lib.GlobalGet(@lib.GlobalIdx(raw_idx)) => {
        let defined_idx = raw_idx.reinterpret_as_int() - first_defined_global
        if defined_idx >= 0 && defined_idx < defined_globals.length() {
          let @lib.Global(@lib.GlobalType(_, mutable_), init) = defined_globals[defined_idx]
          if !mutable_ && visited_globals.visit(defined_idx) {
            gsi_collect_returned_ref_funcs(
              env, init, results, callable_funcs, defined_globals, first_defined_global,
              visited_globals,
            )
          }
        }
      }
      @lib.Block(_, body) | @lib.Loop(_, body) | @lib.TryTable(_, _, body) =>
        gsi_collect_returned_ref_funcs(
          env, body, results, callable_funcs, defined_globals, first_defined_global,
          visited_globals,
        )
      @lib.If(_, yes, no) => {
        gsi_collect_returned_ref_funcs(
          env,
          @lib.Expr::new(yes),
          results,
          callable_funcs,
          defined_globals,
          first_defined_global,
          visited_globals,
        )
        match no {
          Some(body) =>
            gsi_collect_returned_ref_funcs(
              env,
              @lib.Expr::new(body),
              results,
              callable_funcs,
              defined_globals,
              first_defined_global,
              visited_globals,
            )
          None => ()
        }
      }
      @lib.Try(_, body, catches, _) => {
        gsi_collect_returned_ref_funcs(
          env, body, results, callable_funcs, defined_globals, first_defined_global,
          visited_globals,
        )
        for catch_ in catches {
          match catch_ {
            @lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) =>
              gsi_collect_returned_ref_funcs(
                env, body, results, callable_funcs, defined_globals, first_defined_global,
                visited_globals,
              )
          }
        }
      }
      _ => ()
    }
  }
}

///|
fn gsi_mark_exported_param_struct_origins(
  env : @validate.Env,
  mod_ : @lib.Module,
  poisoned_type_slots : Array[Bool],
  cached_value_types : Array[@lib.ValType],
  cached_slots : Array[Array[Int32]],
) -> Unit {
  if mod_.export_sec is None {
    return
  }
  let defined_funcs = match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) => funcs
    None => []
  }
  let first_defined_func = env.funcs.length() - defined_funcs.length()
  let defined_globals = match mod_.global_sec {
    Some(@lib.GlobalSec(globals)) => globals
    None => []
  }
  let first_defined_global = env.globals.length() - defined_globals.length()
  let roots = gsi_exported_origin_roots(
    mod_,
    first_defined_func,
    defined_funcs.length(),
    first_defined_global,
    defined_globals.length(),
  )
  if roots.funcs.is_empty() && roots.globals.is_empty() {
    return
  }
  let callable_funcs : Array[@lib.FuncIdx] = []
  let visited_globals = GsiGlobalVisitStamps::new()
  for defined_idx in roots.funcs {
    let func_idx = @lib.FuncIdx::new(
      (first_defined_func + defined_idx).reinterpret_as_uint(),
    )
    callable_funcs.push(func_idx)
    visited_globals.begin_root()
    match
      (defined_funcs.get(defined_idx), env.get_functype_by_funcidx(func_idx)) {
      (Some(@lib.Func(_, @lib.Expr(body))), Some(@lib.FuncType(_, results))) =>
        gsi_collect_returned_ref_funcs(
          env,
          @lib.Expr::new(body),
          results,
          callable_funcs,
          defined_globals,
          first_defined_global,
          visited_globals,
        )
      _ => ()
    }
  }
  for defined_idx in roots.globals {
    match defined_globals.get(defined_idx) {
      Some(@lib.Global(@lib.GlobalType(value_type, _), init)) => {
        // Mutable exported globals expose their initial ref.func just as
        // immutable globals do. Recursive alias traversal remains restricted
        // to immutable globals inside gsi_collect_returned_ref_funcs.
        visited_globals.begin_root()
        gsi_collect_returned_ref_funcs(
          env,
          init,
          [value_type],
          callable_funcs,
          defined_globals,
          first_defined_global,
          visited_globals,
        )
      }
      None => ()
    }
  }
  let callable_seen = Array::make(env.funcs.length(), false)
  for func_idx in callable_funcs {
    let @lib.FuncIdx(raw_idx) = func_idx
    let slot = raw_idx.reinterpret_as_int()
    if slot < first_defined_func ||
      slot < 0 ||
      slot >= callable_seen.length() ||
      callable_seen[slot] {
      continue
    }
    callable_seen[slot] = true
    match env.get_functype_by_funcidx(func_idx) {
      Some(@lib.FuncType(params, _)) =>
        for param in params {
          gsi_mark_external_struct_origins(
            env, param, poisoned_type_slots, cached_value_types, cached_slots,
          )
        }
      None => ()
    }
  }
}

///|
fn gsi_func_ref_matches_table(
  env : @validate.Env,
  func_idx : @lib.FuncIdx,
  table_ref_type : @lib.RefType,
) -> Bool {
  let type_idx = match env.get_functypeidx_by_funcidx(func_idx) {
    Some(type_idx) => type_idx
    None => return false
  }
  for exact in [false, true] {
    let func_ref_type = @lib.ValType::ref_type(
      @lib.RefType::new(false, @lib.HeapType::new(type_idx), exact~),
    )
    if @validate.Match::matches(
        func_ref_type,
        @lib.ValType::ref_type(table_ref_type),
        env,
      ) {
      return true
    }
  }
  false
}

///|
fn gsi_mark_external_table_callable_param_struct_origins(
  env : @validate.Env,
  mod_ : @lib.Module,
  poisoned_type_slots : Array[Bool],
  cached_value_types : Array[@lib.ValType],
  cached_slots : Array[Array[Int32]],
) -> Unit {
  let external_table_types : Array[@lib.RefType] = []
  match mod_.import_sec {
    Some(@lib.ImportSec(imports)) =>
      for import_ in imports {
        match import_ {
          @lib.Import(_, _, @lib.TableExternType(@lib.TableType(ref_type, _))) =>
            external_table_types.push(ref_type)
          _ => ()
        }
      }
    None => ()
  }
  match mod_.export_sec {
    Some(@lib.ExportSec(exports)) =>
      for export_ in exports {
        let table_idx = match export_ {
          @lib.Export(_, @lib.TableExternIdx(table_idx)) => table_idx
          _ => continue
        }
        match env.get_table_type(table_idx) {
          Some(@lib.TableType(ref_type, _)) =>
            external_table_types.push(ref_type)
          None => ()
        }
      }
    None => ()
  }
  if external_table_types.is_empty() {
    return
  }
  let defined_func_count = match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) => funcs.length()
    None => 0
  }
  let first_defined_func = env.funcs.length() - defined_func_count
  for raw_idx = first_defined_func
      raw_idx < env.funcs.length()
      raw_idx = raw_idx + 1 {
    let func_idx = @lib.FuncIdx::new(raw_idx.reinterpret_as_uint())
    let mut callable = false
    for table_ref_type in external_table_types {
      if gsi_func_ref_matches_table(env, func_idx, table_ref_type) {
        callable = true
        break
      }
    }
    if !callable {
      continue
    }
    match env.get_functype_by_funcidx(func_idx) {
      Some(@lib.FuncType(params, _)) =>
        for param in params {
          gsi_mark_external_struct_origins(
            env, param, poisoned_type_slots, cached_value_types, cached_slots,
          )
        }
      None => ()
    }
  }
}

///|
fn gsi_build_closed_world_facts(
  env : @validate.Env,
  mod_ : @lib.Module,
) -> GsiClosedWorldFacts {
  let type_count = env.global_types.length()
  let poisoned_type_slots : Array[Bool] = Array::make(type_count, false)
  let cached_external_value_types : Array[@lib.ValType] = []
  let cached_external_type_slots : Array[Array[Int32]] = []
  let candidate_globals_by_type_slot : Array[Array[@lib.GlobalIdx]] = []
  for slot = 0; slot < type_count; slot = slot + 1 {
    ignore(slot)
    candidate_globals_by_type_slot.push([])
  }
  gsi_mark_imported_struct_origins(
    env, mod_, poisoned_type_slots, cached_external_value_types, cached_external_type_slots,
  )
  gsi_mark_exported_param_struct_origins(
    env, mod_, poisoned_type_slots, cached_external_value_types, cached_external_type_slots,
  )
  gsi_mark_external_table_callable_param_struct_origins(
    env, mod_, poisoned_type_slots, cached_external_value_types, cached_external_type_slots,
  )

  match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) =>
      for func in funcs {
        let @lib.Func(_, @lib.Expr(instrs)) = func
        gsi_mark_all_struct_allocs(instrs, poisoned_type_slots)
      }
    None => ()
  }

  match mod_.table_sec {
    Some(@lib.TableSec(tables)) =>
      for table in tables {
        let @lib.Table(_, init) = table
        match init {
          Some(@lib.Expr(body)) =>
            gsi_mark_all_struct_allocs(body, poisoned_type_slots)
          None => ()
        }
      }
    None => ()
  }
  match mod_.elem_sec {
    Some(@lib.ElemSec(elems)) =>
      for elem in elems {
        let @lib.Elem(_, kind) = elem
        match kind {
          @lib.FuncExprsElemKind(exprs) | @lib.TypedExprsElemKind(_, exprs) =>
            for expr in exprs {
              let @lib.Expr(body) = expr
              gsi_mark_all_struct_allocs(body, poisoned_type_slots)
            }
          @lib.FuncsElemKind(_) => ()
        }
      }
    None => ()
  }

  let globals = match mod_.global_sec {
    Some(@lib.GlobalSec(globals)) => globals
    None => {
      gsi_propagate_closed_world_subtype_facts(
        env, poisoned_type_slots, candidate_globals_by_type_slot,
      )
      return { poisoned_type_slots, candidate_globals_by_type_slot, }
    }
  }
  let total_globals = env.globals.length()
  let imported_global_count = total_globals - globals.length()
  for defined_idx = 0
      defined_idx < globals.length()
      defined_idx = defined_idx + 1 {
    let global_ = globals[defined_idx]
    let @lib.Global(global_type, init) = global_
    let @lib.Expr(init_instrs) = init
    gsi_mark_nested_global_struct_allocs(init_instrs, poisoned_type_slots)
    match global_type {
      @lib.GlobalType(_, true) => {
        gsi_mark_all_struct_allocs(init_instrs, poisoned_type_slots)
        continue
      }
      _ => ()
    }
    if !gsi_global_decl_is_eq_comparable(env, global_type) {
      gsi_mark_all_struct_allocs(init_instrs, poisoned_type_slots)
      continue
    }
    match gsi_candidate_global_values(env, init) {
      Some((type_idx, _)) => {
        let slot = gsi_type_slot(type_idx, type_count)
        if slot >= 0 && !poisoned_type_slots[slot] {
          candidate_globals_by_type_slot[slot].push(
            @lib.GlobalIdx::new(
              (imported_global_count + defined_idx).reinterpret_as_uint(),
            ),
          )
        }
      }
      None => gsi_mark_all_struct_allocs(init_instrs, poisoned_type_slots)
    }
  }
  gsi_merge_equivalent_struct_type_facts(
    env, poisoned_type_slots, candidate_globals_by_type_slot,
  )
  gsi_propagate_closed_world_subtype_facts(
    env, poisoned_type_slots, candidate_globals_by_type_slot,
  )
  { poisoned_type_slots, candidate_globals_by_type_slot, }
}

///|
fn gsi_merge_equivalent_struct_type_facts(
  env : @validate.Env,
  poisoned_type_slots : Array[Bool],
  candidates_by_type_slot : Array[Array[@lib.GlobalIdx]],
) -> Unit {
  let type_count = poisoned_type_slots.length()
  for lhs = 0; lhs < type_count; lhs = lhs + 1 {
    if env.resolve_struct_fields(gsi_type_idx_from_slot(lhs)) is Err(_) {
      continue
    }
    for rhs = lhs + 1; rhs < type_count; rhs = rhs + 1 {
      if !poisoned_type_slots[lhs] &&
        !poisoned_type_slots[rhs] &&
        candidates_by_type_slot[lhs].is_empty() &&
        candidates_by_type_slot[rhs].is_empty() {
        continue
      }
      if env.resolve_struct_fields(gsi_type_idx_from_slot(rhs)) is Err(_) {
        continue
      }
      let lhs_type = @lib.ValType::ref_type(
        @lib.RefType::new(
          false,
          @lib.HeapType::new(gsi_type_idx_from_slot(lhs)),
        ),
      )
      let rhs_type = @lib.ValType::ref_type(
        @lib.RefType::new(
          false,
          @lib.HeapType::new(gsi_type_idx_from_slot(rhs)),
        ),
      )
      if !@validate.Match::matches(lhs_type, rhs_type, env) ||
        !@validate.Match::matches(rhs_type, lhs_type, env) {
        continue
      }
      let poisoned = poisoned_type_slots[lhs] || poisoned_type_slots[rhs]
      poisoned_type_slots[lhs] = poisoned
      poisoned_type_slots[rhs] = poisoned
      let lhs_candidates = candidates_by_type_slot[lhs].copy()
      let rhs_candidates = candidates_by_type_slot[rhs].copy()
      for candidate in rhs_candidates {
        if !gsi_candidate_list_contains(candidates_by_type_slot[lhs], candidate) {
          candidates_by_type_slot[lhs].push(candidate)
        }
      }
      for candidate in lhs_candidates {
        if !gsi_candidate_list_contains(candidates_by_type_slot[rhs], candidate) {
          candidates_by_type_slot[rhs].push(candidate)
        }
      }
    }
  }
}

///|
fn gsi_default_expr_for_valtype(vt : @lib.ValType) -> @lib.Expr? {
  match vt {
    @lib.NumTypeValType(@lib.I32NumType) =>
      Some(@lib.Expr::new([@lib.Instruction::i32_const(@lib.I32::new(0))]))
    @lib.NumTypeValType(@lib.I64NumType) =>
      Some(@lib.Expr::new([@lib.Instruction::i64_const(@lib.I64::new(0L))]))
    @lib.NumTypeValType(@lib.F32NumType) =>
      Some(@lib.Expr::new([@lib.Instruction::f32_const(@lib.F32::new(0.0))]))
    @lib.NumTypeValType(@lib.F64NumType) =>
      Some(@lib.Expr::new([@lib.Instruction::f64_const(@lib.F64::new(0.0))]))
    @lib.VecTypeValType =>
      Some(
        @lib.Expr::new([
          @lib.Instruction::v128_const(
            b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00',
            b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00',
          ),
        ]),
      )
    @lib.RefTypeValType(ref_type) =>
      if ref_type.is_nullable() {
        Some(@lib.Expr::new([@lib.Instruction::ref_null_type(ref_type)]))
      } else {
        None
      }
    @lib.BotValType => None
  }
}

///|
fn gsi_simple_instr_result_type(
  env : @validate.Env,
  instr : @lib.Instruction,
) -> @lib.ValType? {
  match instr {
    @lib.I32Const(_) => Some(@lib.ValType::i32())
    @lib.I64Const(_) => Some(@lib.ValType::i64())
    @lib.F32Const(_) => Some(@lib.ValType::f32())
    @lib.F64Const(_) => Some(@lib.ValType::f64())
    @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
      Some(@lib.ValType::v128())
    @lib.RefNull(ref_type) => Some(@lib.ValType::ref_null_type(ref_type))
    @lib.RefFunc(func_idx) =>
      match env.get_functypeidx_by_funcidx(func_idx) {
        Some(type_idx) =>
          Some(
            @lib.ValType::ref_type(
              @lib.RefType::new(false, @lib.HeapType::new(type_idx)),
            ),
          )
        None => None
      }
    @lib.GlobalGet(global_idx) =>
      match env.get_global_type(global_idx) {
        Some(@lib.GlobalType(value_type, false)) => Some(value_type)
        _ => None
      }
    @lib.StringConst(_) => Some(@lib.ValType::stringref())
    _ => None
  }
}

///|
fn gsi_const_expr_for_field(
  env : @validate.Env,
  field_type : @lib.FieldType,
  instr : @lib.Instruction,
) -> @lib.Expr? {
  match field_type.get_storage_type() {
    @lib.PackTypeStorageType(_) =>
      match gsi_simple_instr_result_type(env, instr) {
        Some(instr_type) =>
          if @validate.Match::matches(instr_type, @lib.ValType::i32(), env) {
            Some(@lib.Expr::new([instr]))
          } else {
            None
          }
        None => None
      }
    @lib.ValTypeStorageType(value_type) =>
      match gsi_simple_instr_result_type(env, instr) {
        Some(instr_type) =>
          if @validate.Match::matches(instr_type, value_type, env) {
            Some(@lib.Expr::new([instr]))
          } else {
            None
          }
        None => None
      }
  }
}

///|
fn gsi_instr_slice(
  instrs : Array[@lib.Instruction],
  start : Int32,
  end_exclusive : Int32,
) -> Array[@lib.Instruction] {
  let out : Array[@lib.Instruction] = []
  for idx = start; idx < end_exclusive; idx = idx + 1 {
    out.push(instrs[idx])
  }
  out
}

///|
fn gsi_unnestable_stack_operand_count(
  env : @validate.Env,
  instr : @lib.Instruction,
) -> Int32? {
  match instr {
    @lib.I32Const(_)
    | @lib.I64Const(_)
    | @lib.F32Const(_)
    | @lib.F64Const(_)
    | @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)
    | @lib.RefNull(_)
    | @lib.RefFunc(_)
    | @lib.GlobalGet(_)
    | @lib.StringConst(_)
    | @lib.StructNewDefault(_) => Some(0)
    @lib.I32Eqz
    | @lib.I64Eqz
    | @lib.I32Clz
    | @lib.I32Ctz
    | @lib.I32Popcnt
    | @lib.I64Clz
    | @lib.I64Ctz
    | @lib.I64Popcnt
    | @lib.I32Extend8S
    | @lib.I32Extend16S
    | @lib.I64Extend8S
    | @lib.I64Extend16S
    | @lib.I64Extend32S
    | @lib.F32Abs
    | @lib.F32Neg
    | @lib.F32Ceil
    | @lib.F32Floor
    | @lib.F32Trunc
    | @lib.F32Nearest
    | @lib.F32Sqrt
    | @lib.F64Abs
    | @lib.F64Neg
    | @lib.F64Ceil
    | @lib.F64Floor
    | @lib.F64Trunc
    | @lib.F64Nearest
    | @lib.F64Sqrt => Some(1)
    @lib.ExternConvertAny | @lib.AnyConvertExtern => Some(1)
    @lib.StructNew(type_idx) =>
      match env.resolve_struct_fields(type_idx) {
        Ok(fields) => Some(fields.length())
        Err(_) => None
      }
    @lib.StructNewDesc(type_idx) =>
      match env.resolve_struct_fields(type_idx) {
        Ok(fields) => Some(fields.length() + 1)
        Err(_) => None
      }
    @lib.StructNewDefaultDesc(_) => Some(1)
    @lib.ArrayNew(_) => Some(2)
    @lib.ArrayNewDefault(_) => Some(1)
    @lib.ArrayNewFixed(_, @lib.U32(raw_len)) => {
      let len = raw_len.reinterpret_as_int()
      if len < 0 {
        None
      } else {
        Some(len)
      }
    }
    @lib.ArrayNewData(_, _) | @lib.ArrayNewElem(_, _) => Some(2)
    @lib.I32Add
    | @lib.I32Sub
    | @lib.I32Mul
    | @lib.I32And
    | @lib.I32Or
    | @lib.I32Xor
    | @lib.I32Shl
    | @lib.I32ShrS
    | @lib.I32ShrU
    | @lib.I32Rotl
    | @lib.I32Rotr
    | @lib.I64Add
    | @lib.I64Sub
    | @lib.I64Mul
    | @lib.I64And
    | @lib.I64Or
    | @lib.I64Xor
    | @lib.I64Shl
    | @lib.I64ShrS
    | @lib.I64ShrU
    | @lib.I64Rotl
    | @lib.I64Rotr
    | @lib.F32Add
    | @lib.F32Sub
    | @lib.F32Mul
    | @lib.F32Div
    | @lib.F32Min
    | @lib.F32Max
    | @lib.F32Copysign
    | @lib.F64Add
    | @lib.F64Sub
    | @lib.F64Mul
    | @lib.F64Div
    | @lib.F64Min
    | @lib.F64Max
    | @lib.F64Copysign => Some(2)
    _ => None
  }
}

///|
fn gsi_unnestable_instr_result_type(
  env : @validate.Env,
  instr : @lib.Instruction,
) -> @lib.ValType? {
  match instr {
    @lib.I32Eqz
    | @lib.I64Eqz
    | @lib.I32Clz
    | @lib.I32Ctz
    | @lib.I32Popcnt
    | @lib.I32Extend8S
    | @lib.I32Extend16S
    | @lib.I32Add
    | @lib.I32Sub
    | @lib.I32Mul
    | @lib.I32And
    | @lib.I32Or
    | @lib.I32Xor
    | @lib.I32Shl
    | @lib.I32ShrS
    | @lib.I32ShrU
    | @lib.I32Rotl
    | @lib.I32Rotr => Some(@lib.ValType::i32())
    @lib.I64Clz
    | @lib.I64Ctz
    | @lib.I64Popcnt
    | @lib.I64Extend8S
    | @lib.I64Extend16S
    | @lib.I64Extend32S
    | @lib.I64Add
    | @lib.I64Sub
    | @lib.I64Mul
    | @lib.I64And
    | @lib.I64Or
    | @lib.I64Xor
    | @lib.I64Shl
    | @lib.I64ShrS
    | @lib.I64ShrU
    | @lib.I64Rotl
    | @lib.I64Rotr => Some(@lib.ValType::i64())
    @lib.F32Abs
    | @lib.F32Neg
    | @lib.F32Ceil
    | @lib.F32Floor
    | @lib.F32Trunc
    | @lib.F32Nearest
    | @lib.F32Sqrt
    | @lib.F32Add
    | @lib.F32Sub
    | @lib.F32Mul
    | @lib.F32Div
    | @lib.F32Min
    | @lib.F32Max
    | @lib.F32Copysign => Some(@lib.ValType::f32())
    @lib.F64Abs
    | @lib.F64Neg
    | @lib.F64Ceil
    | @lib.F64Floor
    | @lib.F64Trunc
    | @lib.F64Nearest
    | @lib.F64Sqrt
    | @lib.F64Add
    | @lib.F64Sub
    | @lib.F64Mul
    | @lib.F64Div
    | @lib.F64Min
    | @lib.F64Max
    | @lib.F64Copysign => Some(@lib.ValType::f64())
    @lib.StructNew(type_idx)
    | @lib.StructNewDefault(type_idx)
    | @lib.StructNewDesc(type_idx)
    | @lib.StructNewDefaultDesc(type_idx)
    | @lib.ArrayNew(type_idx)
    | @lib.ArrayNewDefault(type_idx)
    | @lib.ArrayNewFixed(type_idx, _)
    | @lib.ArrayNewData(type_idx, _)
    | @lib.ArrayNewElem(type_idx, _) =>
      Some(
        @lib.ValType::ref_type(
          @lib.RefType::new(
            false,
            @lib.HeapType::new(type_idx),
            exact=env.infer_exact_references,
          ),
        ),
      )
    _ => gsi_simple_instr_result_type(env, instr)
  }
}

///|
fn gsi_find_operand_start(
  env : @validate.Env,
  instrs : Array[@lib.Instruction],
  end_inclusive : Int32,
) -> Int32 {
  let mut needed = 1
  let mut idx = end_inclusive
  while idx >= 0 {
    let operands = match gsi_unnestable_stack_operand_count(env, instrs[idx]) {
      Some(operands) => operands
      None => return -1
    }
    needed -= 1
    if needed < 0 {
      return -1
    }
    needed += operands
    if needed == 0 {
      return idx
    }
    idx -= 1
  }
  -1
}

///|
fn gsi_split_constructor_operands(
  env : @validate.Env,
  instrs : Array[@lib.Instruction],
  operand_count : Int32,
) -> Array[GsiSplitOperand]? {
  if operand_count < 0 || instrs.length() < operand_count + 1 {
    return None
  }
  let rev : Array[GsiSplitOperand] = []
  let mut end_inclusive = instrs.length() - 2
  for operand = 0; operand < operand_count; operand = operand + 1 {
    ignore(operand)
    let start = gsi_find_operand_start(env, instrs, end_inclusive)
    if start < 0 {
      return None
    }
    rev.push({
      start,
      end_exclusive: end_inclusive + 1,
      expr: @lib.Expr::new(gsi_instr_slice(instrs, start, end_inclusive + 1)),
    })
    end_inclusive = start - 1
  }
  if end_inclusive != -1 {
    return None
  }
  let out : Array[GsiSplitOperand] = []
  for idx = rev.length() - 1; idx >= 0; idx = idx - 1 {
    out.push(rev[idx])
  }
  Some(out)
}

///|
fn gsi_expr_is_materializable_field_value(
  env : @validate.Env,
  field_type : @lib.FieldType,
  expr : @lib.Expr,
) -> Bool {
  match expr {
    @lib.Expr([instr]) =>
      gsi_const_expr_for_field(env, field_type, instr) is Some(_)
    _ => false
  }
}

///|
fn gsi_expr_is_unnestable_field_value(
  env : @validate.Env,
  field_type : @lib.FieldType,
  expr : @lib.Expr,
) -> Bool {
  if gsi_expr_is_materializable_field_value(env, field_type, expr) {
    return false
  }
  let @lib.Expr(instrs) = expr
  if instrs.length() <= 1 {
    return false
  }
  for instr in instrs {
    if gsi_unnestable_stack_operand_count(env, instr) is None {
      return false
    }
  }
  match gsi_unnestable_instr_result_type(env, instrs[instrs.length() - 1]) {
    Some(actual_type) =>
      @validate.Match::matches(actual_type, field_type.unpack(), env)
    None => false
  }
}

///|
fn gsi_append_operand_expr(
  out : Array[@lib.Instruction],
  operand : @lib.Expr,
) -> Unit {
  let @lib.Expr(instrs) = operand
  for instr in instrs {
    out.push(instr)
  }
}

///|
fn gsi_instr_field_read_type_and_index(
  instr : @lib.Instruction,
) -> (@lib.TypeIdx, Int32)? {
  match instr {
    @lib.StructGet(read_type, @lib.U32(raw_idx))
    | @lib.StructGetS(read_type, @lib.U32(raw_idx))
    | @lib.StructGetU(read_type, @lib.U32(raw_idx)) =>
      Some((read_type, raw_idx.reinterpret_as_int()))
    _ => None
  }
}

///|
fn gsi_facts_include_candidate(
  facts : GsiClosedWorldFacts,
  read_type : @lib.TypeIdx,
  candidate : @lib.GlobalIdx,
) -> Bool {
  let slot = gsi_type_slot(
    read_type,
    facts.candidate_globals_by_type_slot.length(),
  )
  if slot < 0 || facts.poisoned_type_slots[slot] {
    return false
  }
  gsi_candidate_list_contains(
    facts.candidate_globals_by_type_slot[slot],
    candidate,
  )
}

///|
fn gsi_collect_unnest_read_requests_instrs(
  instrs : Array[@lib.Instruction],
  closed_world : Bool,
  requests : GsiUnnestReadRequests,
) -> Unit {
  let mut idx = 0
  while idx < instrs.length() {
    let instr = instrs[idx]
    if idx + 1 < instrs.length() {
      match instr {
        @lib.GlobalGet(global_idx) =>
          match gsi_instr_field_read_type_and_index(instrs[idx + 1]) {
            Some((type_idx, field_idx)) =>
              requests.direct_reads.push({ global_idx, type_idx, field_idx, })
            None => ()
          }
        @lib.LocalGet(_) =>
          if closed_world {
            match gsi_instr_field_read_type_and_index(instrs[idx + 1]) {
              Some((type_idx, field_idx)) =>
                requests.typed_reads.push({ type_idx, field_idx, })
              None => ()
            }
          }
        _ => ()
      }
    }
    match instr {
      @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
        gsi_collect_unnest_read_requests_instrs(body, closed_world, requests)
      @lib.If(_, then_body, else_body) => {
        gsi_collect_unnest_read_requests_instrs(
          then_body, closed_world, requests,
        )
        match else_body {
          Some(else_body) =>
            gsi_collect_unnest_read_requests_instrs(
              else_body, closed_world, requests,
            )
          None => ()
        }
      }
      @lib.TryTable(_, _, @lib.Expr(body)) =>
        gsi_collect_unnest_read_requests_instrs(body, closed_world, requests)
      _ => ()
    }
    idx += 1
  }
}

///|
fn gsi_collect_unnest_read_requests(
  mod_ : @lib.Module,
  closed_world : Bool,
) -> GsiUnnestReadRequests {
  let requests : GsiUnnestReadRequests = { direct_reads: [], typed_reads: [], }
  match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) =>
      for func in funcs {
        let @lib.Func(_, @lib.Expr(instrs)) = func
        gsi_collect_unnest_read_requests_instrs(instrs, closed_world, requests)
      }
    None => ()
  }
  requests
}

///|
fn gsi_requests_may_need_unnested_candidate_field(
  requests : GsiUnnestReadRequests,
  env : @validate.Env,
  facts : GsiClosedWorldFacts?,
  candidate : @lib.GlobalIdx,
  candidate_type : @lib.TypeIdx,
  field_idx : Int32,
) -> Bool {
  for request in requests.direct_reads {
    if request.global_idx == candidate &&
      request.type_idx == candidate_type &&
      request.field_idx == field_idx {
      return true
    }
  }
  match facts {
    Some(facts) =>
      for request in requests.typed_reads {
        if request.field_idx == field_idx &&
          gsi_candidate_type_matches_read_type(
            env,
            candidate_type,
            request.type_idx,
          ) &&
          gsi_facts_include_candidate(facts, request.type_idx, candidate) {
          return true
        }
      }
    None => ()
  }
  false
}

///|
fn gsi_rebuild_struct_new_init(
  operands : Array[GsiSplitOperand],
  fields : Array[@lib.FieldType],
  type_idx : @lib.TypeIdx,
  ctor_instr : @lib.Instruction,
) -> @lib.Expr {
  let out : Array[@lib.Instruction] = []
  for idx = 0; idx < operands.length(); idx = idx + 1 {
    gsi_append_operand_expr(out, operands[idx].expr)
  }
  match ctor_instr {
    @lib.StructNew(_) => out.push(@lib.Instruction::struct_new(type_idx))
    @lib.StructNewDesc(_) =>
      out.push(@lib.Instruction::struct_new_desc(type_idx))
    _ => ignore(fields)
  }
  @lib.Expr::new(out)
}

///|
fn gsi_try_unnest_global_init(
  requests : GsiUnnestReadRequests,
  env : @validate.Env,
  facts : GsiClosedWorldFacts?,
  global_idx : @lib.GlobalIdx,
  global_ : @lib.Global,
  next_fresh_abs_idx : Int32,
  fresh_globals : Array[@lib.Global],
) -> @lib.Global? {
  let @lib.Global(global_type, init) = global_
  match global_type {
    @lib.GlobalType(_, true) => return None
    _ => ()
  }
  let @lib.Expr(instrs) = init
  if instrs.length() == 0 {
    return None
  }
  let (type_idx, extra_operands, ctor_instr) = match
    instrs[instrs.length() - 1] {
    @lib.StructNew(type_idx) =>
      (type_idx, 0, @lib.Instruction::struct_new(type_idx))
    @lib.StructNewDesc(type_idx) =>
      (type_idx, 1, @lib.Instruction::struct_new_desc(type_idx))
    _ => return None
  }
  let fields = match env.resolve_struct_fields(type_idx) {
    Ok(fields) => fields
    Err(_) => return None
  }
  let operands = match
    gsi_split_constructor_operands(
      env,
      instrs,
      fields.length() + extra_operands,
    ) {
    Some(operands) => operands
    None => return None
  }
  let mut changed = false
  let next_operands = operands.copy()
  for field_idx = 0; field_idx < fields.length(); field_idx = field_idx + 1 {
    if fields[field_idx].is_mutable() {
      continue
    }
    let operand = operands[field_idx].expr
    if !gsi_expr_is_unnestable_field_value(env, fields[field_idx], operand) {
      continue
    }
    if !gsi_requests_may_need_unnested_candidate_field(
        requests, env, facts, global_idx, type_idx, field_idx,
      ) {
      continue
    }
    let fresh_abs_idx = next_fresh_abs_idx + fresh_globals.length()
    fresh_globals.push(
      @lib.Global::new(
        @lib.GlobalType::new(fields[field_idx].unpack(), false),
        operand,
      ),
    )
    next_operands[field_idx] = {
      start: operands[field_idx].start,
      end_exclusive: operands[field_idx].end_exclusive,
      expr: @lib.Expr::new([
        @lib.Instruction::global_get(
          @lib.GlobalIdx::new(fresh_abs_idx.reinterpret_as_uint()),
        ),
      ]),
    }
    changed = true
  }
  if changed {
    let next_init = gsi_rebuild_struct_new_init(
      next_operands, fields, type_idx, ctor_instr,
    )
    Some(@lib.Global::new(global_type, next_init))
  } else {
    None
  }
}

///|
fn gsi_instrs_have_ref_get_desc(instrs : Array[@lib.Instruction]) -> Bool {
  for instr in instrs {
    match instr {
      @lib.RefGetDesc(_) => return true
      @lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
        if gsi_instrs_have_ref_get_desc(body) {
          return true
        }
      @lib.If(_, then_body, else_body) => {
        if gsi_instrs_have_ref_get_desc(then_body) {
          return true
        }
        match else_body {
          Some(else_body) =>
            if gsi_instrs_have_ref_get_desc(else_body) {
              return true
            }
          None => ()
        }
      }
      @lib.TryTable(_, _, @lib.Expr(body)) =>
        if gsi_instrs_have_ref_get_desc(body) {
          return true
        }
      _ => ()
    }
  }
  false
}

///|
fn gsi_module_has_ref_get_desc(mod_ : @lib.Module) -> Bool {
  match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) =>
      for func in funcs {
        let @lib.Func(_, @lib.Expr(instrs)) = func
        if gsi_instrs_have_ref_get_desc(instrs) {
          return true
        }
      }
    None => ()
  }
  false
}

///|
fn gsi_unnest_global_operands(
  mod_ : @lib.Module,
  closed_world : Bool,
) -> (@lib.Module, Bool) {
  let globals = match mod_.global_sec {
    Some(@lib.GlobalSec(globals)) => globals
    None => return (mod_, false)
  }
  let env = @validate.Env::new().with_module(mod_)
  let facts = if closed_world {
    Some(gsi_build_closed_world_facts(env, mod_))
  } else {
    None
  }
  let total_globals = env.globals.length()
  let imported_global_count = total_globals - globals.length()
  let requests = gsi_collect_unnest_read_requests(mod_, closed_world)
  let next_globals = globals.copy()
  let fresh_globals : Array[@lib.Global] = []
  let mut changed = false
  for defined_idx = 0
      defined_idx < globals.length()
      defined_idx = defined_idx + 1 {
    let global_idx = @lib.GlobalIdx::new(
      (imported_global_count + defined_idx).reinterpret_as_uint(),
    )
    match
      gsi_try_unnest_global_init(
        requests,
        env,
        facts,
        global_idx,
        globals[defined_idx],
        total_globals,
        fresh_globals,
      ) {
      Some(next_global) => {
        next_globals[defined_idx] = next_global
        changed = true
      }
      None => ()
    }
  }
  if !changed {
    return (mod_, false)
  }
  for fresh in fresh_globals {
    next_globals.push(fresh)
  }
  (
    reorder_globals_run_module_pass_always(
      mod_.with_global_sec(@lib.GlobalSec::new(next_globals)),
    ),
    true,
  )
}

///|
fn gsi_candidate_field_values(
  env : @validate.Env,
  instrs : Array[@lib.Instruction],
  type_idx : @lib.TypeIdx,
  field_operand_start : Int32,
  expected_extra_operands : Int32,
  defaults_only : Bool,
) -> Array[@lib.Expr?]? {
  let fields = match env.resolve_struct_fields(type_idx) {
    Ok(fields) => fields
    Err(_) => return None
  }
  let values : Array[@lib.Expr?] = Array::make(fields.length(), None)
  if defaults_only {
    if instrs.length() != expected_extra_operands + 1 {
      return Some(values)
    }
    for idx = 0; idx < fields.length(); idx = idx + 1 {
      if fields[idx].is_mutable() {
        continue
      }
      values[idx] = gsi_default_expr_for_valtype(fields[idx].unpack())
    }
    return Some(values)
  }
  if instrs.length() != expected_extra_operands + fields.length() + 1 {
    return Some(values)
  }
  for idx = 0; idx < fields.length(); idx = idx + 1 {
    if fields[idx].is_mutable() {
      continue
    }
    values[idx] = gsi_const_expr_for_field(
      env,
      fields[idx],
      instrs[field_operand_start + idx],
    )
  }
  Some(values)
}

///|
fn gsi_candidate_global_values(
  env : @validate.Env,
  expr : @lib.Expr,
) -> (@lib.TypeIdx, Array[@lib.Expr?])? {
  let @lib.Expr(instrs) = expr
  if instrs.length() == 0 {
    return None
  }
  match instrs[instrs.length() - 1] {
    @lib.StructNew(type_idx) =>
      gsi_candidate_field_values(env, instrs, type_idx, 0, 0, false).map(fn(
        values,
      ) {
        (type_idx, values)
      })
    @lib.StructNewDefault(type_idx) =>
      gsi_candidate_field_values(env, instrs, type_idx, 0, 0, true).map(fn(
        values,
      ) {
        (type_idx, values)
      })
    @lib.StructNewDesc(type_idx) =>
      gsi_candidate_field_values(env, instrs, type_idx, 0, 1, false).map(fn(
        values,
      ) {
        (type_idx, values)
      })
    @lib.StructNewDefaultDesc(type_idx) =>
      gsi_candidate_field_values(env, instrs, type_idx, 0, 1, true).map(fn(
        values,
      ) {
        (type_idx, values)
      })
    _ => None
  }
}

///|
fn gsi_pack_unsigned(value : Int32, bits : Int32) -> Int32 {
  let mask = if bits == 8 { 0xFF } else { 0xFFFF }
  value & mask
}

///|
fn gsi_pack_signed(value : Int32, bits : Int32) -> Int32 {
  let masked = gsi_pack_unsigned(value, bits)
  let sign_bit = 1 << (bits - 1)
  if (masked & sign_bit) != 0 {
    masked - (1 << bits)
  } else {
    masked
  }
}

///|
fn gsi_packed_expr(expr : @lib.Expr, bits : Int32, signed : Bool) -> @lib.Expr? {
  match expr {
    @lib.Expr([@lib.I32Const(@lib.I32(value))]) =>
      Some(
        @lib.Expr::new([
          @lib.Instruction::i32_const(
            @lib.I32::new(
              if signed {
                gsi_pack_signed(value, bits)
              } else {
                gsi_pack_unsigned(value, bits)
              },
            ),
          ),
        ]),
      )
    @lib.Expr(instrs) => {
      let out = instrs.copy()
      if signed {
        out.push(
          if bits == 8 {
            @lib.Instruction::i32_extend8s()
          } else {
            @lib.Instruction::i32_extend16s()
          },
        )
      } else {
        out.push(
          @lib.Instruction::i32_const(
            @lib.I32::new(if bits == 8 { 0xFF } else { 0xFFFF }),
          ),
        )
        out.push(@lib.Instruction::i32_and())
      }
      Some(@lib.Expr::new(out))
    }
  }
}

///|
fn gsi_append_expr_instrs(
  out : Array[@lib.Instruction],
  expr : @lib.Expr,
) -> Unit {
  let @lib.Expr(instrs) = expr
  for instr in instrs {
    out.push(instr)
  }
}

///|
fn gsi_global_ref_type(
  env : @validate.Env,
  global_idx : @lib.GlobalIdx,
) -> @lib.RefType? {
  match env.get_global_type(global_idx) {
    Some(@lib.GlobalType(@lib.RefTypeValType(ref_type), _)) => Some(ref_type)
    _ => None
  }
}

///|
fn gsi_exact_single_candidate_for_type(
  exact_single_candidate_by_type_slot : Array[@lib.GlobalIdx?],
  type_idx : @lib.TypeIdx,
) -> @lib.GlobalIdx? {
  let slot = gsi_type_slot(
    type_idx,
    exact_single_candidate_by_type_slot.length(),
  )
  if slot < 0 {
    None
  } else {
    exact_single_candidate_by_type_slot[slot]
  }
}

///|
fn gsi_local_get_ref_type_for_exact_struct(
  local_env : @validate.Env,
  local_idx : @lib.LocalIdx,
  type_idx : @lib.TypeIdx,
) -> @lib.RefType? {
  match local_env.get_local_type(local_idx) {
    Some(value_type) =>
      gsi_val_type_ref_matches_exact_type(value_type, type_idx)
    None => None
  }
}

///|
fn gsi_single_candidate_local_origin_block(
  env : @validate.Env,
  local_env : @validate.Env,
  exact_single_candidate_by_type_slot : Array[@lib.GlobalIdx?],
  local_idx : @lib.LocalIdx,
  type_idx : @lib.TypeIdx,
  field_idx : @lib.U32,
) -> @lib.Instruction? {
  if !gsi_struct_field_is_immutable(env, type_idx, field_idx) {
    return None
  }
  let local_ref_type = match
    gsi_local_get_ref_type_for_exact_struct(local_env, local_idx, type_idx) {
    Some(ref_type) => ref_type
    None => return None
  }
  let candidate_global_idx = match
    gsi_exact_single_candidate_for_type(
      exact_single_candidate_by_type_slot, type_idx,
    ) {
    Some(candidate) => candidate
    None => return None
  }
  let candidate_value_type = match
    gsi_global_value_type_for_exact_struct(env, candidate_global_idx, type_idx) {
    Some(value_type) => value_type
    None => return None
  }
  let block_body : Array[@lib.Instruction] = [
    @lib.Instruction::local_get(local_idx),
  ]
  if local_ref_type.is_nullable() {
    block_body.push(@lib.Instruction::ref_as_non_null())
  }
  block_body.push(@lib.Instruction::drop())
  block_body.push(@lib.Instruction::global_get(candidate_global_idx))
  Some(
    @lib.Instruction::block(
      @lib.BlockType::val_type(candidate_value_type),
      @lib.Expr::new(block_body),
    ),
  )
}

///|
fn gsi_single_propagated_candidate_for_type(
  rewrite_candidates_by_type_slot : Array[Array[@lib.GlobalIdx]],
  type_idx : @lib.TypeIdx,
) -> @lib.GlobalIdx? {
  let slot = gsi_type_slot(type_idx, rewrite_candidates_by_type_slot.length())
  if slot < 0 {
    return None
  }
  let candidates = rewrite_candidates_by_type_slot[slot]
  if candidates.length() == 1 {
    Some(candidates[0])
  } else {
    None
  }
}

///|
fn gsi_global_value_type_for_candidate_origin(
  env : @validate.Env,
  global_idx : @lib.GlobalIdx,
  read_type_idx : @lib.TypeIdx,
) -> @lib.ValType? {
  match env.get_global_type(global_idx) {
    Some(@lib.GlobalType(value_type, false)) =>
      match value_type {
        @lib.RefTypeValType(ref_type) =>
          match ref_type.get_heap_type() {
            @lib.HeapType(candidate_type_idx) =>
              if gsi_candidate_type_matches_read_type(
                  env, candidate_type_idx, read_type_idx,
                ) {
                Some(value_type)
              } else {
                None
              }
            _ => None
          }
        _ => None
      }
    _ => None
  }
}

///|
fn gsi_propagated_single_candidate_local_origin_block(
  env : @validate.Env,
  local_env : @validate.Env,
  rewrite_candidates_by_type_slot : Array[Array[@lib.GlobalIdx]],
  local_idx : @lib.LocalIdx,
  type_idx : @lib.TypeIdx,
  field_idx : @lib.U32,
) -> @lib.Instruction? {
  if !gsi_struct_field_is_immutable(env, type_idx, field_idx) {
    return None
  }
  let local_ref_type = match
    gsi_local_get_ref_type_for_exact_struct(local_env, local_idx, type_idx) {
    Some(ref_type) => ref_type
    None => return None
  }
  let candidate_global_idx = match
    gsi_single_propagated_candidate_for_type(
      rewrite_candidates_by_type_slot, type_idx,
    ) {
    Some(candidate) => candidate
    None => return None
  }
  let candidate_value_type = match
    gsi_global_value_type_for_candidate_origin(
      env, candidate_global_idx, type_idx,
    ) {
    Some(value_type) => value_type
    None => return None
  }
  let block_body : Array[@lib.Instruction] = [
    @lib.Instruction::local_get(local_idx),
  ]
  if local_ref_type.is_nullable() {
    block_body.push(@lib.Instruction::ref_as_non_null())
  }
  block_body.push(@lib.Instruction::drop())
  block_body.push(@lib.Instruction::global_get(candidate_global_idx))
  Some(
    @lib.Instruction::block(
      @lib.BlockType::val_type(candidate_value_type),
      @lib.Expr::new(block_body),
    ),
  )
}

///|
fn gsi_single_instr_expr_result_type(
  env : @validate.Env,
  expr : @lib.Expr,
) -> @lib.ValType? {
  match expr {
    @lib.Expr([instr]) => gsi_simple_instr_result_type(env, instr)
    _ => None
  }
}

///|
fn gsi_one_value_local_fold_block(
  env : @validate.Env,
  local_env : @validate.Env,
  rewrite_candidates_by_type_slot : Array[Array[@lib.GlobalIdx]],
  fields_by_type : Array[Array[@lib.FieldType]?],
  global_values_by_abs_idx : Array[Array[@lib.Expr?]?],
  global_type_slots : Array[Int32],
  local_idx : @lib.LocalIdx,
  type_idx : @lib.TypeIdx,
  field_idx : @lib.U32,
  signed : Bool,
  unsigned : Bool,
) -> @lib.Instruction? {
  if !gsi_struct_field_is_immutable(env, type_idx, field_idx) {
    return None
  }
  let local_ref_type = match
    gsi_local_get_ref_type_for_exact_struct(local_env, local_idx, type_idx) {
    Some(ref_type) => ref_type
    None => return None
  }
  let slot = gsi_type_slot(type_idx, rewrite_candidates_by_type_slot.length())
  if slot < 0 {
    return None
  }
  let candidates = rewrite_candidates_by_type_slot[slot]
  if candidates.length() < 1 {
    return None
  }
  let result_type = match
    gsi_field_read_result_type(
      fields_by_type, type_idx, field_idx, signed, unsigned,
    ) {
    Some(result_type) => result_type
    None => return None
  }
  let mut folded_value : @lib.Expr? = None
  for candidate in candidates {
    let value = match
      gsi_folded_candidate_global_field_expr(
        env, fields_by_type, global_values_by_abs_idx, global_type_slots, candidate,
        type_idx, field_idx, signed, unsigned,
      ) {
      Some(value) => value
      None => return None
    }
    if !gsi_expr_matches_result_type(env, value, result_type) {
      return None
    }
    match folded_value {
      None => folded_value = Some(value)
      Some(first_value) =>
        if !pass_const_instruction_vectors_bits_equal(value.0, first_value.0) {
          return None
        }
    }
  }
  let value = match folded_value {
    Some(value) => value
    None => return None
  }
  let block_body : Array[@lib.Instruction] = [
    @lib.Instruction::local_get(local_idx),
  ]
  if local_ref_type.is_nullable() {
    block_body.push(@lib.Instruction::ref_as_non_null())
  }
  block_body.push(@lib.Instruction::drop())
  gsi_append_expr_instrs(block_body, value)
  Some(
    @lib.Instruction::block(
      @lib.BlockType::val_type(result_type),
      @lib.Expr::new(block_body),
    ),
  )
}

///|
fn gsi_field_read_result_type(
  fields_by_type : Array[Array[@lib.FieldType]?],
  type_idx : @lib.TypeIdx,
  field_idx : @lib.U32,
  signed : Bool,
  unsigned : Bool,
) -> @lib.ValType? {
  let type_slot = gsi_type_slot(type_idx, fields_by_type.length())
  if type_slot < 0 {
    return None
  }
  let fields = match fields_by_type[type_slot] {
    Some(fields) => fields
    None => return None
  }
  let @lib.U32(raw_idx) = field_idx
  let idx = raw_idx.reinterpret_as_int()
  if idx < 0 || idx >= fields.length() {
    return None
  }
  match fields[idx].get_storage_type() {
    @lib.ValTypeStorageType(value_type) => Some(value_type)
    @lib.PackTypeStorageType(_) =>
      if signed || unsigned {
        Some(@lib.ValType::i32())
      } else {
        None
      }
  }
}

///|
fn gsi_packed_repair_expr_matches_i32(
  env : @validate.Env,
  expr : @lib.Expr,
) -> Bool {
  match expr {
    @lib.Expr([value, @lib.I32Extend8S])
    | @lib.Expr([value, @lib.I32Extend16S]) =>
      match gsi_simple_instr_result_type(env, value) {
        Some(actual_type) =>
          @validate.Match::matches(actual_type, @lib.ValType::i32(), env)
        None => false
      }
    @lib.Expr([value, @lib.I32Const(_), @lib.I32And]) =>
      match gsi_simple_instr_result_type(env, value) {
        Some(actual_type) =>
          @validate.Match::matches(actual_type, @lib.ValType::i32(), env)
        None => false
      }
    _ => false
  }
}

///|
fn gsi_expr_matches_result_type(
  env : @validate.Env,
  expr : @lib.Expr,
  result_type : @lib.ValType,
) -> Bool {
  match gsi_single_instr_expr_result_type(env, expr) {
    Some(actual_type) => @validate.Match::matches(actual_type, result_type, env)
    None =>
      @validate.Match::matches(@lib.ValType::i32(), result_type, env) &&
      gsi_packed_repair_expr_matches_i32(env, expr)
  }
}

///|
fn gsi_descriptor_read_result_type(
  env : @validate.Env,
  type_idx : @lib.TypeIdx,
  operand_ref_type : @lib.RefType?,
) -> @lib.ValType? {
  match env.descriptor_result_type(type_idx, operand_ref_type) {
    Ok(result_type) => Some(result_type)
    Err(_) => None
  }
}

///|
fn gsi_candidate_global_descriptor_expr(
  env : @validate.Env,
  expr : @lib.Expr,
) -> (@lib.TypeIdx, @lib.Expr)? {
  let @lib.Expr(instrs) = expr
  if instrs.length() == 0 {
    return None
  }
  let (type_idx, operand_count, desc_operand_idx) = match
    instrs[instrs.length() - 1] {
    @lib.StructNewDesc(type_idx) => {
      let fields = match env.resolve_struct_fields(type_idx) {
        Ok(fields) => fields
        Err(_) => return None
      }
      (type_idx, fields.length() + 1, fields.length())
    }
    @lib.StructNewDefaultDesc(type_idx) => (type_idx, 1, 0)
    _ => return None
  }
  let operands = match
    gsi_split_constructor_operands(env, instrs, operand_count) {
    Some(operands) => operands
    None => return None
  }
  let desc_expr = operands[desc_operand_idx].expr
  let result_type = match gsi_descriptor_read_result_type(env, type_idx, None) {
    Some(result_type) => result_type
    None => return None
  }
  if gsi_expr_matches_result_type(env, desc_expr, result_type) {
    Some((type_idx, desc_expr))
  } else {
    None
  }
}

///|
fn gsi_type_idx_from_slot(slot : Int32) -> @lib.TypeIdx {
  @lib.TypeIdx::new(slot.reinterpret_as_uint())
}

///|
fn gsi_candidate_type_matches_read_type(
  env : @validate.Env,
  candidate_type_idx : @lib.TypeIdx,
  read_type_idx : @lib.TypeIdx,
) -> Bool {
  let candidate_type = @lib.ValType::ref_type(
    @lib.RefType::new(false, @lib.HeapType::new(candidate_type_idx)),
  )
  let read_type = @lib.ValType::ref_type(
    @lib.RefType::new(false, @lib.HeapType::new(read_type_idx)),
  )
  @validate.Match::matches(candidate_type, read_type, env)
}

///|
fn gsi_folded_global_descriptor_expr(
  env : @validate.Env,
  global_descriptor_values_by_abs_idx : Array[@lib.Expr?],
  global_type_slots : Array[Int32],
  global_idx : @lib.GlobalIdx,
  type_idx : @lib.TypeIdx,
  operand_ref_type : @lib.RefType?,
) -> @lib.Expr? {
  let @lib.GlobalIdx(abs_idx_u) = global_idx
  let abs_idx = abs_idx_u.reinterpret_as_int()
  if abs_idx < 0 ||
    abs_idx >= global_descriptor_values_by_abs_idx.length() ||
    abs_idx >= global_type_slots.length() {
    return None
  }
  let type_slot = gsi_type_slot(type_idx, global_type_slots.length())
  if type_slot < 0 || global_type_slots[abs_idx] != type_slot {
    return None
  }
  let value = match global_descriptor_values_by_abs_idx[abs_idx] {
    Some(value) => value
    None => return None
  }
  let result_type = match
    gsi_descriptor_read_result_type(env, type_idx, operand_ref_type) {
    Some(result_type) => result_type
    None => return None
  }
  if gsi_expr_matches_result_type(env, value, result_type) {
    Some(value)
  } else {
    None
  }
}

///|
fn gsi_folded_candidate_global_descriptor_expr(
  env : @validate.Env,
  global_descriptor_values_by_abs_idx : Array[@lib.Expr?],
  global_type_slots : Array[Int32],
  global_idx : @lib.GlobalIdx,
  read_type_idx : @lib.TypeIdx,
  result_type : @lib.ValType,
) -> @lib.Expr? {
  let @lib.GlobalIdx(abs_idx_u) = global_idx
  let abs_idx = abs_idx_u.reinterpret_as_int()
  if abs_idx < 0 ||
    abs_idx >= global_descriptor_values_by_abs_idx.length() ||
    abs_idx >= global_type_slots.length() {
    return None
  }
  let candidate_slot = global_type_slots[abs_idx]
  if candidate_slot < 0 {
    return None
  }
  let candidate_type_idx = gsi_type_idx_from_slot(candidate_slot)
  if !gsi_candidate_type_matches_read_type(
      env, candidate_type_idx, read_type_idx,
    ) {
    return None
  }
  let value = match global_descriptor_values_by_abs_idx[abs_idx] {
    Some(value) => value
    None => return None
  }
  if gsi_expr_matches_result_type(env, value, result_type) {
    Some(value)
  } else {
    None
  }
}

///|
fn gsi_folded_candidate_global_field_expr(
  env : @validate.Env,
  fields_by_type : Array[Array[@lib.FieldType]?],
  global_values_by_abs_idx : Array[Array[@lib.Expr?]?],
  global_type_slots : Array[Int32],
  global_idx : @lib.GlobalIdx,
  read_type_idx : @lib.TypeIdx,
  field_idx : @lib.U32,
  signed : Bool,
  unsigned : Bool,
) -> @lib.Expr? {
  let @lib.GlobalIdx(abs_idx_u) = global_idx
  let abs_idx = abs_idx_u.reinterpret_as_int()
  if abs_idx < 0 ||
    abs_idx >= global_values_by_abs_idx.length() ||
    abs_idx >= global_type_slots.length() {
    return None
  }
  let candidate_slot = global_type_slots[abs_idx]
  if candidate_slot < 0 || candidate_slot >= fields_by_type.length() {
    return None
  }
  let candidate_type_idx = gsi_type_idx_from_slot(candidate_slot)
  if !gsi_candidate_type_matches_read_type(
      env, candidate_type_idx, read_type_idx,
    ) {
    return None
  }
  let fields = match fields_by_type[candidate_slot] {
    Some(fields) => fields
    None => return None
  }
  let values = match global_values_by_abs_idx[abs_idx] {
    Some(values) => values
    None => return None
  }
  let @lib.U32(raw_idx) = field_idx
  let idx = raw_idx.reinterpret_as_int()
  if idx < 0 || idx >= fields.length() || idx >= values.length() {
    return None
  }
  let value = match values[idx] {
    Some(value) => value
    None => return None
  }
  match fields[idx].get_storage_type() {
    @lib.ValTypeStorageType(_) => Some(value)
    @lib.PackTypeStorageType(@lib.I8PackType) =>
      if signed {
        gsi_packed_expr(value, 8, true)
      } else if unsigned {
        gsi_packed_expr(value, 8, false)
      } else {
        None
      }
    @lib.PackTypeStorageType(@lib.I16PackType) =>
      if signed {
        gsi_packed_expr(value, 16, true)
      } else if unsigned {
        gsi_packed_expr(value, 16, false)
      } else {
        None
      }
  }
}

///|
fn gsi_find_value_group(
  groups : Array[GsiValueGroup],
  value : @lib.Expr,
) -> Int32 {
  for idx = 0; idx < groups.length(); idx = idx + 1 {
    if pass_const_instruction_vectors_bits_equal(groups[idx].value.0, value.0) {
      return idx
    }
  }
  -1
}

///|
fn gsi_one_value_descriptor_local_fold_block(
  env : @validate.Env,
  local_env : @validate.Env,
  rewrite_candidates_by_type_slot : Array[Array[@lib.GlobalIdx]],
  global_descriptor_values_by_abs_idx : Array[@lib.Expr?],
  global_type_slots : Array[Int32],
  local_idx : @lib.LocalIdx,
  type_idx : @lib.TypeIdx,
) -> @lib.Instruction? {
  let local_ref_type = match
    gsi_local_get_ref_type_for_exact_struct(local_env, local_idx, type_idx) {
    Some(ref_type) => ref_type
    None => return None
  }
  let slot = gsi_type_slot(type_idx, rewrite_candidates_by_type_slot.length())
  if slot < 0 {
    return None
  }
  let candidates = rewrite_candidates_by_type_slot[slot]
  if candidates.length() < 1 {
    return None
  }
  let result_type = match
    gsi_descriptor_read_result_type(env, type_idx, Some(local_ref_type)) {
    Some(result_type) => result_type
    None => return None
  }
  let mut folded_value : @lib.Expr? = None
  for candidate in candidates {
    let value = match
      gsi_folded_candidate_global_descriptor_expr(
        env, global_descriptor_values_by_abs_idx, global_type_slots, candidate, type_idx,
        result_type,
      ) {
      Some(value) => value
      None => return None
    }
    match folded_value {
      None => folded_value = Some(value)
      Some(first_value) =>
        if !pass_const_instruction_vectors_bits_equal(value.0, first_value.0) {
          return None
        }
    }
  }
  let value = match folded_value {
    Some(value) => value
    None => return None
  }
  let block_body : Array[@lib.Instruction] = [
    @lib.Instruction::local_get(local_idx),
  ]
  if local_ref_type.is_nullable() {
    block_body.push(@lib.Instruction::ref_as_non_null())
  }
  block_body.push(@lib.Instruction::drop())
  gsi_append_expr_instrs(block_body, value)
  Some(
    @lib.Instruction::block(
      @lib.BlockType::val_type(result_type),
      @lib.Expr::new(block_body),
    ),
  )
}

///|
fn gsi_two_value_descriptor_singleton_select_block(
  env : @validate.Env,
  local_env : @validate.Env,
  rewrite_candidates_by_type_slot : Array[Array[@lib.GlobalIdx]],
  global_descriptor_values_by_abs_idx : Array[@lib.Expr?],
  global_type_slots : Array[Int32],
  local_idx : @lib.LocalIdx,
  type_idx : @lib.TypeIdx,
) -> @lib.Instruction? {
  let local_ref_type = match
    gsi_local_get_ref_type_for_exact_struct(local_env, local_idx, type_idx) {
    Some(ref_type) => ref_type
    None => return None
  }
  let slot = gsi_type_slot(type_idx, rewrite_candidates_by_type_slot.length())
  if slot < 0 {
    return None
  }
  let candidates = rewrite_candidates_by_type_slot[slot]
  if candidates.length() <= 1 {
    return None
  }
  let result_type = match
    gsi_descriptor_read_result_type(env, type_idx, Some(local_ref_type)) {
    Some(result_type) => result_type
    None => return None
  }
  let groups : Array[GsiValueGroup] = []
  for candidate in candidates {
    let value = match
      gsi_folded_candidate_global_descriptor_expr(
        env, global_descriptor_values_by_abs_idx, global_type_slots, candidate, type_idx,
        result_type,
      ) {
      Some(value) => value
      None => return None
    }
    let group_idx = gsi_find_value_group(groups, value)
    if group_idx >= 0 {
      groups[group_idx].globals.push(candidate)
    } else {
      if groups.length() >= 2 {
        return None
      }
      groups.push({ value, globals: [candidate], })
    }
  }
  if groups.length() != 2 {
    return None
  }
  let singleton_idx = if groups[0].globals.length() == 1 {
    0
  } else if groups[1].globals.length() == 1 {
    1
  } else {
    return None
  }
  let other_idx = if singleton_idx == 0 { 1 } else { 0 }
  let singleton = groups[singleton_idx]
  let other = groups[other_idx]
  let test_global = singleton.globals[0]
  let block_body : Array[@lib.Instruction] = []
  gsi_append_expr_instrs(block_body, singleton.value)
  gsi_append_expr_instrs(block_body, other.value)
  block_body.push(@lib.Instruction::local_get(local_idx))
  if local_ref_type.is_nullable() {
    block_body.push(@lib.Instruction::ref_as_non_null())
  }
  block_body.push(@lib.Instruction::global_get(test_global))
  block_body.push(@lib.Instruction::ref_eq())
  block_body.push(@lib.Instruction::select(types=Some([result_type])))
  Some(
    @lib.Instruction::block(
      @lib.BlockType::val_type(result_type),
      @lib.Expr::new(block_body),
    ),
  )
}

///|
fn gsi_two_value_singleton_select_block(
  env : @validate.Env,
  local_env : @validate.Env,
  rewrite_candidates_by_type_slot : Array[Array[@lib.GlobalIdx]],
  fields_by_type : Array[Array[@lib.FieldType]?],
  global_values_by_abs_idx : Array[Array[@lib.Expr?]?],
  global_type_slots : Array[Int32],
  local_idx : @lib.LocalIdx,
  type_idx : @lib.TypeIdx,
  field_idx : @lib.U32,
  signed : Bool,
  unsigned : Bool,
) -> @lib.Instruction? {
  if !gsi_struct_field_is_immutable(env, type_idx, field_idx) {
    return None
  }
  let local_ref_type = match
    gsi_local_get_ref_type_for_exact_struct(local_env, local_idx, type_idx) {
    Some(ref_type) => ref_type
    None => return None
  }
  let slot = gsi_type_slot(type_idx, rewrite_candidates_by_type_slot.length())
  if slot < 0 {
    return None
  }
  let candidates = rewrite_candidates_by_type_slot[slot]
  if candidates.length() <= 1 {
    return None
  }
  let result_type = match
    gsi_field_read_result_type(
      fields_by_type, type_idx, field_idx, signed, unsigned,
    ) {
    Some(result_type) => result_type
    None => return None
  }
  let groups : Array[GsiValueGroup] = []
  for candidate in candidates {
    let value = match
      gsi_folded_candidate_global_field_expr(
        env, fields_by_type, global_values_by_abs_idx, global_type_slots, candidate,
        type_idx, field_idx, signed, unsigned,
      ) {
      Some(value) => value
      None => return None
    }
    if !gsi_expr_matches_result_type(env, value, result_type) {
      return None
    }
    let group_idx = gsi_find_value_group(groups, value)
    if group_idx >= 0 {
      groups[group_idx].globals.push(candidate)
    } else {
      if groups.length() >= 2 {
        return None
      }
      groups.push({ value, globals: [candidate], })
    }
  }
  if groups.length() != 2 {
    return None
  }
  let singleton_idx = if groups[0].globals.length() == 1 {
    0
  } else if groups[1].globals.length() == 1 {
    1
  } else {
    return None
  }
  let other_idx = if singleton_idx == 0 { 1 } else { 0 }
  let singleton = groups[singleton_idx]
  let other = groups[other_idx]
  let test_global = singleton.globals[0]
  let block_body : Array[@lib.Instruction] = []
  gsi_append_expr_instrs(block_body, singleton.value)
  gsi_append_expr_instrs(block_body, other.value)
  block_body.push(@lib.Instruction::local_get(local_idx))
  if local_ref_type.is_nullable() {
    block_body.push(@lib.Instruction::ref_as_non_null())
  }
  block_body.push(@lib.Instruction::global_get(test_global))
  block_body.push(@lib.Instruction::ref_eq())
  block_body.push(@lib.Instruction::select(types=Some([result_type])))
  Some(
    @lib.Instruction::block(
      @lib.BlockType::val_type(result_type),
      @lib.Expr::new(block_body),
    ),
  )
}

///|
fn gsi_function_local_env(
  env : @validate.Env,
  imported_func_count : Int32,
  defined_idx : Int32,
  locals : @lib.Locals,
) -> @validate.Env? {
  let func_idx = @lib.FuncIdx::new(
    (imported_func_count + defined_idx).reinterpret_as_uint(),
  )
  match env.get_functype_by_funcidx(func_idx) {
    Some(@lib.FuncType(params, _)) => {
      let all_locals = @lib.Locals::from_types(params)
      all_locals.append(locals)
      Some(env.with_locals(all_locals))
    }
    None => None
  }
}

///|
fn gsi_folded_global_field_expr(
  env : @validate.Env,
  fields_by_type : Array[Array[@lib.FieldType]?],
  global_values_by_abs_idx : Array[Array[@lib.Expr?]?],
  global_type_slots : Array[Int32],
  global_idx : @lib.GlobalIdx,
  type_idx : @lib.TypeIdx,
  field_idx : @lib.U32,
  signed : Bool,
  unsigned : Bool,
) -> @lib.Expr? {
  let result_type = match
    gsi_field_read_result_type(
      fields_by_type, type_idx, field_idx, signed, unsigned,
    ) {
    Some(result_type) => result_type
    None => return None
  }
  let value = match
    gsi_folded_candidate_global_field_expr(
      env, fields_by_type, global_values_by_abs_idx, global_type_slots, global_idx,
      type_idx, field_idx, signed, unsigned,
    ) {
    Some(value) => value
    None => return None
  }
  if gsi_expr_matches_result_type(env, value, result_type) {
    Some(value)
  } else {
    None
  }
}

///|
fn gsi_folded_direct_global_read_expr(
  env : @validate.Env,
  fields_by_type : Array[Array[@lib.FieldType]?],
  global_values_by_abs_idx : Array[Array[@lib.Expr?]?],
  global_descriptor_values_by_abs_idx : Array[@lib.Expr?],
  global_type_slots : Array[Int32],
  global_idx : @lib.GlobalIdx,
  read_instr : @lib.Instruction,
) -> @lib.Expr? {
  match read_instr {
    @lib.StructGet(type_idx, field_idx) =>
      gsi_folded_global_field_expr(
        env, fields_by_type, global_values_by_abs_idx, global_type_slots, global_idx,
        type_idx, field_idx, false, false,
      )
    @lib.StructGetS(type_idx, field_idx) =>
      gsi_folded_global_field_expr(
        env, fields_by_type, global_values_by_abs_idx, global_type_slots, global_idx,
        type_idx, field_idx, true, false,
      )
    @lib.StructGetU(type_idx, field_idx) =>
      gsi_folded_global_field_expr(
        env, fields_by_type, global_values_by_abs_idx, global_type_slots, global_idx,
        type_idx, field_idx, false, true,
      )
    @lib.RefGetDesc(type_idx) =>
      gsi_folded_global_descriptor_expr(
        env,
        global_descriptor_values_by_abs_idx,
        global_type_slots,
        global_idx,
        type_idx,
        gsi_global_ref_type(env, global_idx),
      )
    _ => None
  }
}

///|
fn gsi_block_carrier_global_idx(instr : @lib.Instruction) -> @lib.GlobalIdx? {
  match instr {
    @lib.Block(
      @lib.ValTypeBlockType(@lib.RefTypeValType(_)),
      @lib.Expr([@lib.GlobalGet(global_idx)])
    ) => Some(global_idx)
    _ => None
  }
}

///|
fn gsi_append_direct_global_replacement(
  out : Array[@lib.Instruction],
  carrier : @lib.Instruction,
  env : @validate.Env,
  global_idx : @lib.GlobalIdx,
  value : @lib.Expr,
) -> Unit {
  out.push(carrier)
  match gsi_global_ref_type(env, global_idx) {
    Some(ref_type) =>
      if ref_type.is_nullable() {
        out.push(@lib.Instruction::ref_as_non_null())
      }
    None => ()
  }
  out.push(@lib.Instruction::drop())
  gsi_append_expr_instrs(out, value)
}

///|
fn gsi_heap_type_idx(heap_type : @lib.HeapType) -> @lib.TypeIdx? {
  match heap_type {
    @lib.HeapType(type_idx) => Some(type_idx)
    _ => None
  }
}

///|
fn gsi_type_is_strict_subtype_of(
  env : @validate.Env,
  child : @lib.TypeIdx,
  target : @lib.TypeIdx,
  visited : Array[Bool],
) -> Bool {
  let child_slot = gsi_type_slot(child, visited.length())
  if child_slot < 0 || visited[child_slot] {
    return false
  }
  visited[child_slot] = true
  for super_type in env.global_types[child_slot].super_types() {
    if super_type == target ||
      gsi_type_is_strict_subtype_of(env, super_type, target, visited) {
      return true
    }
  }
  false
}

///|
fn gsi_type_has_strict_subtypes(
  env : @validate.Env,
  target : @lib.TypeIdx,
) -> Bool {
  let type_count = env.global_types.length()
  for slot = 0; slot < type_count; slot = slot + 1 {
    let child = gsi_type_idx_from_slot(slot)
    if child == target {
      continue
    }
    if gsi_type_is_strict_subtype_of(
        env,
        child,
        target,
        Array::make(type_count, false),
      ) {
      return true
    }
  }
  false
}

///|
fn gsi_desc_cast_singleton_descriptor_global(
  env : @validate.Env,
  rewrite_candidates_by_type_slot : Array[Array[@lib.GlobalIdx]],
  heap_type : @lib.HeapType,
  exact_target : Bool,
) -> @lib.GlobalIdx? {
  let target_type = match gsi_heap_type_idx(heap_type) {
    Some(type_idx) => type_idx
    None => return None
  }
  let target_slot = gsi_type_slot(target_type, env.global_types.length())
  if target_slot < 0 {
    return None
  }
  if !exact_target && gsi_type_has_strict_subtypes(env, target_type) {
    return None
  }
  let desc_type = match env.global_types[target_slot].descriptor_type() {
    Some(type_idx) => type_idx
    None => return None
  }
  let desc_slot = gsi_type_slot(
    desc_type,
    rewrite_candidates_by_type_slot.length(),
  )
  if desc_slot < 0 {
    return None
  }
  let candidates = rewrite_candidates_by_type_slot[desc_slot]
  if candidates.length() == 1 {
    Some(candidates[0])
  } else {
    None
  }
}

///|
fn gsi_append_desc_cast_rewrite(
  out : Array[@lib.Instruction],
  operand : @lib.Instruction,
  desc_global : @lib.GlobalIdx,
  nullable : Bool,
  exact_target : Bool,
  heap_type : @lib.HeapType,
) -> Unit {
  out.push(operand)
  out.push(@lib.Instruction::global_get(desc_global))
  out.push(
    @lib.Instruction::ref_cast_desc_eq_type(
      @lib.RefType::new(nullable, heap_type, exact=exact_target),
    ),
  )
}

///|
fn gsi_rewrite_instrs(
  instrs : Array[@lib.Instruction],
  env : @validate.Env,
  local_env : @validate.Env,
  fields_by_type : Array[Array[@lib.FieldType]?],
  global_values_by_abs_idx : Array[Array[@lib.Expr?]?],
  global_descriptor_values_by_abs_idx : Array[@lib.Expr?],
  global_type_slots : Array[Int32],
  exact_single_candidate_by_type_slot : Array[@lib.GlobalIdx?],
  rewrite_candidates_by_type_slot : Array[Array[@lib.GlobalIdx]],
  optimize_desc_casts : Bool,
) -> Array[@lib.Instruction] {
  let out : Array[@lib.Instruction] = []
  let mut idx = 0
  let mut stack_unreachable = false
  while idx < instrs.length() {
    let instr = instrs[idx]
    match instr {
      @lib.Block(block_type, @lib.Expr(body)) =>
        if idx + 1 < instrs.length() {
          match gsi_block_carrier_global_idx(instr) {
            Some(global_idx) => {
              let next_instr = instrs[idx + 1]
              match
                gsi_folded_direct_global_read_expr(
                  env, fields_by_type, global_values_by_abs_idx, global_descriptor_values_by_abs_idx,
                  global_type_slots, global_idx, next_instr,
                ) {
                Some(value) => {
                  gsi_append_direct_global_replacement(
                    out, instr, env, global_idx, value,
                  )
                  idx += 2
                  continue
                }
                None => ()
              }
            }
            None => ()
          }
          out.push(
            @lib.Instruction::block(
              block_type,
              @lib.Expr::new(
                gsi_rewrite_instrs(
                  body, env, local_env, fields_by_type, global_values_by_abs_idx,
                  global_descriptor_values_by_abs_idx, global_type_slots, exact_single_candidate_by_type_slot,
                  rewrite_candidates_by_type_slot, optimize_desc_casts,
                ),
              ),
            ),
          )
        } else {
          out.push(
            @lib.Instruction::block(
              block_type,
              @lib.Expr::new(
                gsi_rewrite_instrs(
                  body, env, local_env, fields_by_type, global_values_by_abs_idx,
                  global_descriptor_values_by_abs_idx, global_type_slots, exact_single_candidate_by_type_slot,
                  rewrite_candidates_by_type_slot, optimize_desc_casts,
                ),
              ),
            ),
          )
        }
      @lib.Loop(block_type, @lib.Expr(body)) =>
        out.push(
          @lib.Instruction::loop_(
            block_type,
            @lib.Expr::new(
              gsi_rewrite_instrs(
                body, env, local_env, fields_by_type, global_values_by_abs_idx, global_descriptor_values_by_abs_idx,
                global_type_slots, exact_single_candidate_by_type_slot, rewrite_candidates_by_type_slot,
                optimize_desc_casts,
              ),
            ),
          ),
        )
      @lib.If(block_type, then_body, else_body) =>
        out.push(
          @lib.Instruction::if_(
            block_type,
            gsi_rewrite_instrs(
              then_body, env, local_env, fields_by_type, global_values_by_abs_idx,
              global_descriptor_values_by_abs_idx, global_type_slots, exact_single_candidate_by_type_slot,
              rewrite_candidates_by_type_slot, optimize_desc_casts,
            ),
            else_body.map(fn(body) {
              gsi_rewrite_instrs(
                body, env, local_env, fields_by_type, global_values_by_abs_idx, global_descriptor_values_by_abs_idx,
                global_type_slots, exact_single_candidate_by_type_slot, rewrite_candidates_by_type_slot,
                optimize_desc_casts,
              )
            }),
          ),
        )
      @lib.TryTable(block_type, catches, @lib.Expr(body)) =>
        out.push(
          @lib.Instruction::try_table(
            block_type,
            catches,
            @lib.Expr::new(
              gsi_rewrite_instrs(
                body, env, local_env, fields_by_type, global_values_by_abs_idx, global_descriptor_values_by_abs_idx,
                global_type_slots, exact_single_candidate_by_type_slot, rewrite_candidates_by_type_slot,
                optimize_desc_casts,
              ),
            ),
          ),
        )
      @lib.GlobalGet(global_idx) =>
        if idx + 1 < instrs.length() {
          let next_instr = instrs[idx + 1]
          match
            gsi_folded_direct_global_read_expr(
              env, fields_by_type, global_values_by_abs_idx, global_descriptor_values_by_abs_idx,
              global_type_slots, global_idx, next_instr,
            ) {
            Some(value) => {
              gsi_append_direct_global_replacement(
                out, instr, env, global_idx, value,
              )
              idx += 2
              continue
            }
            None =>
              match next_instr {
                @lib.RefCast(nullable, exact_target, heap_type) if optimize_desc_casts &&
                  !stack_unreachable =>
                  match
                    gsi_desc_cast_singleton_descriptor_global(
                      env, rewrite_candidates_by_type_slot, heap_type, exact_target,
                    ) {
                    Some(desc_global) => {
                      gsi_append_desc_cast_rewrite(
                        out, instr, desc_global, nullable, exact_target, heap_type,
                      )
                      idx += 2
                      continue
                    }
                    None => out.push(instr)
                  }
                _ => out.push(instr)
              }
          }
        } else {
          out.push(instr)
        }
      @lib.LocalGet(local_idx) =>
        if idx + 1 < instrs.length() {
          let next_instr = instrs[idx + 1]
          match next_instr {
            @lib.RefCast(nullable, exact_target, heap_type) if optimize_desc_casts &&
              !stack_unreachable =>
              match
                gsi_desc_cast_singleton_descriptor_global(
                  env, rewrite_candidates_by_type_slot, heap_type, exact_target,
                ) {
                Some(desc_global) => {
                  ignore(local_idx)
                  gsi_append_desc_cast_rewrite(
                    out, instr, desc_global, nullable, exact_target, heap_type,
                  )
                  idx += 2
                  continue
                }
                None => ()
              }
            _ => ()
          }
          let origin_rewrite = match next_instr {
            @lib.StructGet(type_idx, field_idx)
            | @lib.StructGetS(type_idx, field_idx)
            | @lib.StructGetU(type_idx, field_idx) => {
              let exact_origin = gsi_single_candidate_local_origin_block(
                env, local_env, exact_single_candidate_by_type_slot, local_idx, type_idx,
                field_idx,
              )
              match exact_origin {
                Some(_) => exact_origin
                None =>
                  gsi_propagated_single_candidate_local_origin_block(
                    env, local_env, rewrite_candidates_by_type_slot, local_idx, type_idx,
                    field_idx,
                  )
              }
            }
            _ => None
          }
          match origin_rewrite {
            Some(block) => out.push(block)
            None => {
              let value_rewrite = match next_instr {
                @lib.StructGet(type_idx, field_idx) =>
                  gsi_one_value_local_fold_block(
                    env, local_env, rewrite_candidates_by_type_slot, fields_by_type,
                    global_values_by_abs_idx, global_type_slots, local_idx, type_idx,
                    field_idx, false, false,
                  )
                @lib.StructGetS(type_idx, field_idx) =>
                  gsi_one_value_local_fold_block(
                    env, local_env, rewrite_candidates_by_type_slot, fields_by_type,
                    global_values_by_abs_idx, global_type_slots, local_idx, type_idx,
                    field_idx, true, false,
                  )
                @lib.StructGetU(type_idx, field_idx) =>
                  gsi_one_value_local_fold_block(
                    env, local_env, rewrite_candidates_by_type_slot, fields_by_type,
                    global_values_by_abs_idx, global_type_slots, local_idx, type_idx,
                    field_idx, false, true,
                  )
                @lib.RefGetDesc(type_idx) =>
                  gsi_one_value_descriptor_local_fold_block(
                    env, local_env, rewrite_candidates_by_type_slot, global_descriptor_values_by_abs_idx,
                    global_type_slots, local_idx, type_idx,
                  )
                _ => None
              }
              match value_rewrite {
                Some(block) => {
                  out.push(block)
                  idx += 2
                  continue
                }
                None => {
                  let select_rewrite = match next_instr {
                    @lib.StructGet(type_idx, field_idx) =>
                      gsi_two_value_singleton_select_block(
                        env, local_env, rewrite_candidates_by_type_slot, fields_by_type,
                        global_values_by_abs_idx, global_type_slots, local_idx, type_idx,
                        field_idx, false, false,
                      )
                    @lib.StructGetS(type_idx, field_idx) =>
                      gsi_two_value_singleton_select_block(
                        env, local_env, rewrite_candidates_by_type_slot, fields_by_type,
                        global_values_by_abs_idx, global_type_slots, local_idx, type_idx,
                        field_idx, true, false,
                      )
                    @lib.StructGetU(type_idx, field_idx) =>
                      gsi_two_value_singleton_select_block(
                        env, local_env, rewrite_candidates_by_type_slot, fields_by_type,
                        global_values_by_abs_idx, global_type_slots, local_idx, type_idx,
                        field_idx, false, true,
                      )
                    @lib.RefGetDesc(type_idx) =>
                      gsi_two_value_descriptor_singleton_select_block(
                        env, local_env, rewrite_candidates_by_type_slot, global_descriptor_values_by_abs_idx,
                        global_type_slots, local_idx, type_idx,
                      )
                    _ => None
                  }
                  match select_rewrite {
                    Some(block) => {
                      out.push(block)
                      idx += 2
                      continue
                    }
                    None => out.push(instr)
                  }
                }
              }
            }
          }
        } else {
          out.push(instr)
        }
      @lib.Unreachable => {
        stack_unreachable = true
        out.push(instr)
      }
      @lib.RefCast(nullable, exact_target, heap_type) if optimize_desc_casts &&
        !stack_unreachable =>
        match
          gsi_desc_cast_singleton_descriptor_global(
            env, rewrite_candidates_by_type_slot, heap_type, exact_target,
          ) {
          Some(desc_global) => {
            out.push(@lib.Instruction::global_get(desc_global))
            out.push(
              @lib.Instruction::ref_cast_desc_eq_type(
                @lib.RefType::new(nullable, heap_type, exact=exact_target),
              ),
            )
          }
          None => out.push(instr)
        }
      _ => out.push(instr)
    }
    idx += 1
  }
  out
}

///|
fn gsi_instrs_may_rewrite(
  instrs : Array[@lib.Instruction],
  env : @validate.Env,
  local_env : @validate.Env,
  fields_by_type : Array[Array[@lib.FieldType]?],
  global_values_by_abs_idx : Array[Array[@lib.Expr?]?],
  global_descriptor_values_by_abs_idx : Array[@lib.Expr?],
  global_type_slots : Array[Int32],
  exact_single_candidate_by_type_slot : Array[@lib.GlobalIdx?],
  rewrite_candidates_by_type_slot : Array[Array[@lib.GlobalIdx]],
  optimize_desc_casts : Bool,
) -> Bool {
  let mut idx = 0
  let mut stack_unreachable = false
  while idx < instrs.length() {
    let instr = instrs[idx]
    match instr {
      @lib.Unreachable => stack_unreachable = true
      @lib.RefCast(_, exact_target, heap_type) if optimize_desc_casts &&
        !stack_unreachable =>
        if gsi_desc_cast_singleton_descriptor_global(
            env, rewrite_candidates_by_type_slot, heap_type, exact_target,
          )
          is Some(_) {
          return true
        }
      @lib.GlobalGet(global_idx) =>
        if idx + 1 < instrs.length() {
          match instrs[idx + 1] {
            @lib.RefCast(_, exact_target, heap_type) if optimize_desc_casts &&
              !stack_unreachable =>
              if gsi_desc_cast_singleton_descriptor_global(
                  env, rewrite_candidates_by_type_slot, heap_type, exact_target,
                )
                is Some(_) {
                return true
              }
            @lib.StructGet(type_idx, field_idx) =>
              if gsi_folded_global_field_expr(
                  env, fields_by_type, global_values_by_abs_idx, global_type_slots,
                  global_idx, type_idx, field_idx, false, false,
                )
                is Some(_) {
                return true
              }
            @lib.StructGetS(type_idx, field_idx) =>
              if gsi_folded_global_field_expr(
                  env, fields_by_type, global_values_by_abs_idx, global_type_slots,
                  global_idx, type_idx, field_idx, true, false,
                )
                is Some(_) {
                return true
              }
            @lib.StructGetU(type_idx, field_idx) =>
              if gsi_folded_global_field_expr(
                  env, fields_by_type, global_values_by_abs_idx, global_type_slots,
                  global_idx, type_idx, field_idx, false, true,
                )
                is Some(_) {
                return true
              }
            @lib.RefGetDesc(type_idx) =>
              if gsi_folded_global_descriptor_expr(
                  env,
                  global_descriptor_values_by_abs_idx,
                  global_type_slots,
                  global_idx,
                  type_idx,
                  gsi_global_ref_type(env, global_idx),
                )
                is Some(_) {
                return true
              }
            _ => ()
          }
        }
      @lib.LocalGet(local_idx) =>
        if idx + 1 < instrs.length() {
          match instrs[idx + 1] {
            @lib.RefCast(_, exact_target, heap_type) if optimize_desc_casts &&
              !stack_unreachable =>
              if gsi_desc_cast_singleton_descriptor_global(
                  env, rewrite_candidates_by_type_slot, heap_type, exact_target,
                )
                is Some(_) {
                return true
              }
            @lib.StructGet(type_idx, field_idx) => {
              if gsi_single_candidate_local_origin_block(
                  env, local_env, exact_single_candidate_by_type_slot, local_idx,
                  type_idx, field_idx,
                )
                is Some(_) {
                return true
              }
              if gsi_propagated_single_candidate_local_origin_block(
                  env, local_env, rewrite_candidates_by_type_slot, local_idx, type_idx,
                  field_idx,
                )
                is Some(_) {
                return true
              }
              if gsi_one_value_local_fold_block(
                  env, local_env, rewrite_candidates_by_type_slot, fields_by_type,
                  global_values_by_abs_idx, global_type_slots, local_idx, type_idx,
                  field_idx, false, false,
                )
                is Some(_) {
                return true
              }
              if gsi_two_value_singleton_select_block(
                  env, local_env, rewrite_candidates_by_type_slot, fields_by_type,
                  global_values_by_abs_idx, global_type_slots, local_idx, type_idx,
                  field_idx, false, false,
                )
                is Some(_) {
                return true
              }
            }
            @lib.StructGetS(type_idx, field_idx) => {
              if gsi_single_candidate_local_origin_block(
                  env, local_env, exact_single_candidate_by_type_slot, local_idx,
                  type_idx, field_idx,
                )
                is Some(_) {
                return true
              }
              if gsi_propagated_single_candidate_local_origin_block(
                  env, local_env, rewrite_candidates_by_type_slot, local_idx, type_idx,
                  field_idx,
                )
                is Some(_) {
                return true
              }
              if gsi_one_value_local_fold_block(
                  env, local_env, rewrite_candidates_by_type_slot, fields_by_type,
                  global_values_by_abs_idx, global_type_slots, local_idx, type_idx,
                  field_idx, true, false,
                )
                is Some(_) {
                return true
              }
              if gsi_two_value_singleton_select_block(
                  env, local_env, rewrite_candidates_by_type_slot, fields_by_type,
                  global_values_by_abs_idx, global_type_slots, local_idx, type_idx,
                  field_idx, true, false,
                )
                is Some(_) {
                return true
              }
            }
            @lib.StructGetU(type_idx, field_idx) => {
              if gsi_single_candidate_local_origin_block(
                  env, local_env, exact_single_candidate_by_type_slot, local_idx,
                  type_idx, field_idx,
                )
                is Some(_) {
                return true
              }
              if gsi_propagated_single_candidate_local_origin_block(
                  env, local_env, rewrite_candidates_by_type_slot, local_idx, type_idx,
                  field_idx,
                )
                is Some(_) {
                return true
              }
              if gsi_one_value_local_fold_block(
                  env, local_env, rewrite_candidates_by_type_slot, fields_by_type,
                  global_values_by_abs_idx, global_type_slots, local_idx, type_idx,
                  field_idx, false, true,
                )
                is Some(_) {
                return true
              }
              if gsi_two_value_singleton_select_block(
                  env, local_env, rewrite_candidates_by_type_slot, fields_by_type,
                  global_values_by_abs_idx, global_type_slots, local_idx, type_idx,
                  field_idx, false, true,
                )
                is Some(_) {
                return true
              }
            }
            @lib.RefGetDesc(type_idx) => {
              if gsi_one_value_descriptor_local_fold_block(
                  env, local_env, rewrite_candidates_by_type_slot, global_descriptor_values_by_abs_idx,
                  global_type_slots, local_idx, type_idx,
                )
                is Some(_) {
                return true
              }
              if gsi_two_value_descriptor_singleton_select_block(
                  env, local_env, rewrite_candidates_by_type_slot, global_descriptor_values_by_abs_idx,
                  global_type_slots, local_idx, type_idx,
                )
                is Some(_) {
                return true
              }
            }
            _ => ()
          }
        }
      @lib.Block(_, @lib.Expr(body)) => {
        if idx + 1 < instrs.length() {
          match gsi_block_carrier_global_idx(instr) {
            Some(global_idx) =>
              if gsi_folded_direct_global_read_expr(
                  env,
                  fields_by_type,
                  global_values_by_abs_idx,
                  global_descriptor_values_by_abs_idx,
                  global_type_slots,
                  global_idx,
                  instrs[idx + 1],
                )
                is Some(_) {
                return true
              }
            None => ()
          }
        }
        if gsi_instrs_may_rewrite(
            body, env, local_env, fields_by_type, global_values_by_abs_idx, global_descriptor_values_by_abs_idx,
            global_type_slots, exact_single_candidate_by_type_slot, rewrite_candidates_by_type_slot,
            optimize_desc_casts,
          ) {
          return true
        }
      }
      @lib.Loop(_, @lib.Expr(body)) =>
        if gsi_instrs_may_rewrite(
            body, env, local_env, fields_by_type, global_values_by_abs_idx, global_descriptor_values_by_abs_idx,
            global_type_slots, exact_single_candidate_by_type_slot, rewrite_candidates_by_type_slot,
            optimize_desc_casts,
          ) {
          return true
        }
      @lib.If(_, then_body, else_body) => {
        if gsi_instrs_may_rewrite(
            then_body, env, local_env, fields_by_type, global_values_by_abs_idx,
            global_descriptor_values_by_abs_idx, global_type_slots, exact_single_candidate_by_type_slot,
            rewrite_candidates_by_type_slot, optimize_desc_casts,
          ) {
          return true
        }
        match else_body {
          Some(else_body) =>
            if gsi_instrs_may_rewrite(
                else_body, env, local_env, fields_by_type, global_values_by_abs_idx,
                global_descriptor_values_by_abs_idx, global_type_slots, exact_single_candidate_by_type_slot,
                rewrite_candidates_by_type_slot, optimize_desc_casts,
              ) {
              return true
            }
          None => ()
        }
      }
      @lib.TryTable(_, _, @lib.Expr(body)) =>
        if gsi_instrs_may_rewrite(
            body, env, local_env, fields_by_type, global_values_by_abs_idx, global_descriptor_values_by_abs_idx,
            global_type_slots, exact_single_candidate_by_type_slot, rewrite_candidates_by_type_slot,
            optimize_desc_casts,
          ) {
          return true
        }
      _ => ()
    }
    idx += 1
  }
  false
}

///|
fn gsi_cleanup_dropped_unreachable_debris(
  mod_ : @lib.Module,
) -> (@lib.Module, Bool) {
  let funcs = match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) => funcs
    None => return (mod_, false)
  }
  let next_funcs = funcs.copy()
  let mut changed = false
  for idx = 0; idx < funcs.length(); idx = idx + 1 {
    let @lib.Func(locals, @lib.Expr(instrs)) = funcs[idx]
    let (next_instrs, func_changed) = pass_raw_remove_dropped_unreachable_debris(
      instrs,
    )
    if func_changed {
      next_funcs[idx] = @lib.Func::new(locals, @lib.Expr::new(next_instrs))
      changed = true
    }
  }
  if changed {
    (mod_.with_code_sec(@lib.CodeSec::new(next_funcs)), true)
  } else {
    (mod_, false)
  }
}

///|
fn gsi_run_module_pass(
  mod_ : @lib.Module,
  closed_world : Bool,
  optimize_desc_casts : Bool,
) -> @lib.Module {
  if pass_module_has_offset_sensitive_metadata(mod_) {
    return mod_
  }
  if pass_module_has_legacy_try(mod_) {
    return mod_
  }
  let (unnested_mod, did_unnest) = gsi_unnest_global_operands(
    mod_, closed_world,
  )
  if did_unnest {
    return gsi_run_module_pass(unnested_mod, closed_world, optimize_desc_casts)
  }
  let (mod_, cleanup_changed) = gsi_cleanup_dropped_unreachable_debris(mod_)
  let env = @validate.Env::new().with_module(mod_)
  let type_count = env.global_types.length()
  let exact_single_candidate_by_type_slot : Array[@lib.GlobalIdx?] = Array::make(
    type_count,
    None,
  )
  let rewrite_candidates_by_type_slot : Array[Array[@lib.GlobalIdx]] = []
  for slot = 0; slot < type_count; slot = slot + 1 {
    ignore(slot)
    rewrite_candidates_by_type_slot.push([])
  }
  if closed_world {
    let facts = gsi_build_closed_world_facts(env, mod_)
    ignore(gsi_closed_world_facts_has_candidates(facts))
    let exact_single = gsi_exact_single_candidate_globals(env, mod_, facts)
    for slot = 0; slot < type_count; slot = slot + 1 {
      exact_single_candidate_by_type_slot[slot] = exact_single[slot]
      rewrite_candidates_by_type_slot[slot] = facts.candidate_globals_by_type_slot[slot]
    }
  }
  let globals = match mod_.global_sec {
    Some(@lib.GlobalSec(globals)) => globals
    None => return mod_
  }
  let total_globals = env.globals.length()
  let imported_global_count = total_globals - globals.length()
  let fields_by_type : Array[Array[@lib.FieldType]?] = Array::make(
    type_count,
    None,
  )
  let global_values_by_abs_idx : Array[Array[@lib.Expr?]?] = Array::make(
    total_globals,
    None,
  )
  let global_descriptor_values_by_abs_idx : Array[@lib.Expr?] = Array::make(
    total_globals,
    None,
  )
  let needs_descriptor_values = gsi_module_has_ref_get_desc(mod_)
  let global_type_slots = Array::make(total_globals, -1)

  for slot = 0; slot < type_count; slot = slot + 1 {
    match env.resolve_struct_fields(gsi_type_idx_from_slot(slot)) {
      Ok(fields) => fields_by_type[slot] = Some(fields)
      Err(_) => ()
    }
  }

  for defined_idx = 0
      defined_idx < globals.length()
      defined_idx = defined_idx + 1 {
    let global_ = globals[defined_idx]
    let @lib.Global(@lib.GlobalType(_, mutable_), init) = global_
    if mutable_ {
      continue
    }
    let abs_idx = imported_global_count + defined_idx
    match gsi_candidate_global_values(env, init) {
      Some((type_idx, next_values)) => {
        let slot = gsi_type_slot(type_idx, type_count)
        if slot < 0 {
          continue
        }
        match fields_by_type[slot] {
          Some(_) => ()
          None =>
            match env.resolve_struct_fields(type_idx) {
              Ok(fields) => fields_by_type[slot] = Some(fields)
              Err(_) => ()
            }
        }
        if abs_idx >= 0 && abs_idx < total_globals {
          global_values_by_abs_idx[abs_idx] = Some(next_values)
          global_type_slots[abs_idx] = slot
        }
      }
      None => ()
    }
    if needs_descriptor_values {
      match gsi_candidate_global_descriptor_expr(env, init) {
        Some((type_idx, desc_value)) => {
          let slot = gsi_type_slot(type_idx, type_count)
          if slot >= 0 && abs_idx >= 0 && abs_idx < total_globals {
            global_descriptor_values_by_abs_idx[abs_idx] = Some(desc_value)
            global_type_slots[abs_idx] = slot
          }
        }
        None => ()
      }
    }
  }

  let mut any_candidate = false
  for values in global_values_by_abs_idx {
    if values is Some(_) {
      any_candidate = true
      break
    }
  }
  if !any_candidate {
    for value in global_descriptor_values_by_abs_idx {
      if value is Some(_) {
        any_candidate = true
        break
      }
    }
  }
  if !any_candidate {
    return mod_
  }

  let funcs = match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) => funcs
    None => return mod_
  }
  let imported_func_count = env.funcs.length() - funcs.length()
  let next_funcs = funcs.copy()
  let mut changed = false
  for defined_idx = 0
      defined_idx < funcs.length()
      defined_idx = defined_idx + 1 {
    let @lib.Func(locals, @lib.Expr(instrs)) = funcs[defined_idx]
    let local_env = match
      gsi_function_local_env(env, imported_func_count, defined_idx, locals) {
      Some(local_env) => local_env
      None => continue
    }
    if !gsi_instrs_may_rewrite(
        instrs, env, local_env, fields_by_type, global_values_by_abs_idx, global_descriptor_values_by_abs_idx,
        global_type_slots, exact_single_candidate_by_type_slot, rewrite_candidates_by_type_slot,
        optimize_desc_casts,
      ) {
      continue
    }
    let next_instrs = gsi_rewrite_instrs(
      instrs, env, local_env, fields_by_type, global_values_by_abs_idx, global_descriptor_values_by_abs_idx,
      global_type_slots, exact_single_candidate_by_type_slot, rewrite_candidates_by_type_slot,
      optimize_desc_casts,
    )
    if next_instrs == instrs {
      continue
    }
    next_funcs[defined_idx] = @lib.Func::new(
      locals,
      @lib.Expr::new(next_instrs),
    )
    changed = true
  }
  if !changed {
    ignore(cleanup_changed)
    return mod_
  }
  mod_.with_code_sec(@lib.CodeSec::new(next_funcs))
}

///|
pub fn global_struct_inference_run_module_pass(
  mod_ : @lib.Module,
  closed_world : Bool,
) -> @lib.Module {
  gsi_run_module_pass(mod_, closed_world, false)
}

///|
pub fn global_struct_inference_desc_cast_run_module_pass(
  mod_ : @lib.Module,
  closed_world : Bool,
) -> @lib.Module {
  gsi_run_module_pass(mod_, closed_world, true)
}