///|
priv struct WTLowerCtx {
  type_recs : Array[@lib.RecType]
  flat_types : Array[@lib.SubType]
  mut indexed_type_recs : Int32
  mut type_match_env : @validate.Env?
  mut scoped_type_recs : Int32
  type_ids : Map[String, UInt32]
  func_ids : Map[String, UInt32]
  table_ids : Map[String, UInt32]
  mem_ids : Map[String, UInt32]
  global_ids : Map[String, UInt32]
  tag_ids : Map[String, UInt32]
  tag_type_idxs : Array[UInt32]
  elem_ids : Map[String, UInt32]
  data_ids : Map[String, UInt32]
  mut next_func_idx : UInt32
  mut next_table_idx : UInt32
  mut next_mem_idx : UInt32
  mut next_global_idx : UInt32
  mut next_tag_idx : UInt32
  mut next_elem_idx : UInt32
  mut next_data_idx : UInt32
}

///|
fn wt_new_ctx() -> WTLowerCtx {
  WTLowerCtx::{
    type_recs: [],
    flat_types: [],
    indexed_type_recs: 0,
    type_match_env: None,
    scoped_type_recs: 0,
    type_ids: Map([]),
    func_ids: Map([]),
    table_ids: Map([]),
    mem_ids: Map([]),
    global_ids: Map([]),
    tag_ids: Map([]),
    tag_type_idxs: [],
    elem_ids: Map([]),
    data_ids: Map([]),
    next_func_idx: 0U,
    next_table_idx: 0U,
    next_mem_idx: 0U,
    next_global_idx: 0U,
    next_tag_idx: 0U,
    next_elem_idx: 0U,
    next_data_idx: 0U,
  }
}

///|
fn wt_resolve_heap_type_ref(
  ctx : WTLowerCtx,
  ht : HeapTypeRef,
) -> Result[(@lib.HeapType, Bool), String] {
  match ht {
    TypeNum(n) => Ok((@lib.HeapType::new(@lib.TypeIdx::new(n)), false))
    TypeId(id) =>
      match ctx.type_ids.get(id) {
        Some(v) => Ok((@lib.HeapType::new(@lib.TypeIdx::new(v)), false))
        None => Err("unknown type id: \{id}")
      }
    ExactTypeNum(n) => Ok((@lib.HeapType::new(@lib.TypeIdx::new(n)), true))
    ExactTypeId(id) =>
      match ctx.type_ids.get(id) {
        Some(v) => Ok((@lib.HeapType::new(@lib.TypeIdx::new(v)), true))
        None => Err("unknown type id: \{id}")
      }
    Any => Ok((@lib.HeapType::abs(@lib.AbsHeapType::any()), false))
    String => Ok((@lib.HeapType::abs(@lib.AbsHeapType::string_()), false))
    Array => Ok((@lib.HeapType::abs(@lib.AbsHeapType::array()), false))
    Eq => Ok((@lib.HeapType::abs(@lib.AbsHeapType::eq()), false))
    I31 => Ok((@lib.HeapType::abs(@lib.AbsHeapType::i31()), false))
    None_ => Ok((@lib.HeapType::abs(@lib.AbsHeapType::none()), false))
    NoFunc => Ok((@lib.HeapType::abs(@lib.AbsHeapType::no_func()), false))
    Func => Ok((@lib.HeapType::abs(@lib.AbsHeapType::func()), false))
    NoExtern => Ok((@lib.HeapType::abs(@lib.AbsHeapType::no_extern()), false))
    NoExn => Ok((@lib.HeapType::abs(@lib.AbsHeapType::no_exn()), false))
    Waitqueue => Ok((@lib.HeapType::abs(@lib.AbsHeapType::waitqueue()), false))
    NoWaitqueue =>
      Ok((@lib.HeapType::abs(@lib.AbsHeapType::no_waitqueue()), false))
    Shared(heap) =>
      match wt_resolve_heap_type_ref(ctx, heap) {
        Ok((@lib.AbsHeapTypeHeapType(abs), false)) =>
          Ok((@lib.HeapType::shared_abs(abs), false))
        Ok(_) => Err("shared heap wrapper requires an abstract heap type")
        Err(err) => Err(err)
      }
    Struct => Ok((@lib.HeapType::abs(@lib.AbsHeapType::struct_()), false))
  }
}

///|
fn wt_value_type(
  ctx : WTLowerCtx,
  vt : ValueType,
) -> Result[@lib.ValType, String] {
  match vt {
    I32 => Ok(@lib.ValType::i32())
    I64 => Ok(@lib.ValType::i64())
    F32 => Ok(@lib.ValType::f32())
    F64 => Ok(@lib.ValType::f64())
    V128 => Ok(@lib.ValType::v128())
    FuncRef => Ok(@lib.ValType::funcref())
    ExternRef => Ok(@lib.ValType::externref())
    ExnRef =>
      Ok(
        @lib.ValType::ref_type(
          @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::exn())),
        ),
      )
    NonNullFuncRef =>
      Ok(
        @lib.ValType::ref_type(
          @lib.RefType::new(false, @lib.HeapType::abs(@lib.AbsHeapType::func())),
        ),
      )
    NonNullExternRef =>
      Ok(
        @lib.ValType::ref_type(
          @lib.RefType::new(
            false,
            @lib.HeapType::abs(@lib.AbsHeapType::extern_()),
          ),
        ),
      )
    NonNullExnRef =>
      Ok(
        @lib.ValType::ref_type(
          @lib.RefType::new(false, @lib.HeapType::abs(@lib.AbsHeapType::exn())),
        ),
      )
    TypedFuncRef(ht) => {
      let (resolved, exact) = match wt_resolve_heap_type_ref(ctx, ht) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.ValType::ref_type(@lib.RefType::new(true, resolved, exact~)))
    }
    NonNullTypedFuncRef(ht) => {
      let (resolved, exact) = match wt_resolve_heap_type_ref(ctx, ht) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.ValType::ref_type(@lib.RefType::new(false, resolved, exact~)))
    }
  }
}

///|
fn wt_ref_heap_type(
  ctx : WTLowerCtx,
  vt : ValueType,
) -> Result[@lib.HeapType?, String] {
  match vt {
    FuncRef | NonNullFuncRef =>
      Ok(Some(@lib.HeapType::abs(@lib.AbsHeapType::func())))
    ExternRef | NonNullExternRef =>
      Ok(Some(@lib.HeapType::abs(@lib.AbsHeapType::extern_())))
    ExnRef | NonNullExnRef =>
      Ok(Some(@lib.HeapType::abs(@lib.AbsHeapType::exn())))
    TypedFuncRef(ht) | NonNullTypedFuncRef(ht) => {
      let (resolved, _) = match wt_resolve_heap_type_ref(ctx, ht) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(Some(resolved))
    }
    _ => Ok(None)
  }
}

///|
fn wt_ref_type_for_elem_value_type(
  ctx : WTLowerCtx,
  vt : ValueType,
) -> Result[@lib.RefType, String] {
  match wt_value_type(ctx, vt) {
    Ok(@lib.RefTypeValType(rt)) => Ok(rt.make_nullable())
    Ok(_) =>
      Err(
        "elem type must be a reference type, got non-reference value type: \{vt}",
      )
    Err(e) => Err(e)
  }
}

///|
fn wt_elem_item_ref_func_index(item : ElemInitExpr) -> Index? {
  match item.instrs {
    [RefFunc(idx)] => Some(idx)
    _ => None
  }
}

///|
fn wt_elem_item_to_expr(
  ctx : WTLowerCtx,
  item : ElemInitExpr,
) -> Result[@lib.Expr, String] {
  match
    wt_instrs_to_lib(ctx, item.instrs, Map([]), [], wt_default_legacy_ctx()) {
    Ok(instrs) => Ok(@lib.Expr::new(instrs))
    Err(e) => Err(e)
  }
}

///|
fn wt_limits(lim : Limits) -> @lib.Limits {
  @lib.Limits::i32(lim.min, lim.max)
}

///|
fn wt_func_annotations(
  annotations : Array[Annotation],
) -> Array[@lib.FuncAnnotation] {
  let lowered : Array[@lib.FuncAnnotation] = []
  for ann in annotations {
    lowered.push(@lib.FuncAnnotation::new(ann.name, args=ann.args))
  }
  lowered
}

///|
fn wt_name_from_id(id : String) -> @lib.Name {
  let text = if id.length() > 0 && id[0] == '$' {
    id.sub(start=1).to_owned()
  } else {
    id
  }
  @lib.Name::from_string(text)
}

///|
fn wt_push_func_name(
  entries : Array[@lib.NameAssoc],
  idx : UInt32,
  id : String?,
) -> Unit {
  match id {
    Some(id) => entries.push(@lib.NameAssoc::new(idx, wt_name_from_id(id)))
    None => ()
  }
}

///|
fn wt_align_pow_from_text_align(align : UInt64) -> Result[UInt32, String] {
  if align == 0UL {
    return Err("alignment must be a positive power of two")
  }
  let mut cur = align
  let mut pow = 0U
  while cur > 1UL {
    if (cur & 1UL) != 0UL {
      return Err("alignment must be a positive power of two")
    }
    cur = cur >> 1
    pow += 1
  }
  Ok(pow)
}

///|
fn wt_mem_arg(ma : MemArg) -> Result[@lib.MemArg, String] {
  let align_pow = match wt_align_pow_from_text_align(ma.align) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  Ok(@lib.MemArg::new(@lib.U32(align_pow), None, @lib.U64(ma.offset)))
}

///|
fn wt_func_sig(
  ctx : WTLowerCtx,
  ft : FuncType,
) -> Result[(Array[@lib.ValType], Array[@lib.ValType]), String] {
  let params : Array[@lib.ValType] = []
  for p in ft.params {
    let v = match wt_value_type(ctx, p) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    params.push(v)
  }
  let results : Array[@lib.ValType] = []
  for r in ft.results {
    let v = match wt_value_type(ctx, r) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    results.push(v)
  }
  Ok((params, results))
}

///|
fn wt_append_type(ctx : WTLowerCtx, ft : FuncType) -> Result[UInt32, String] {
  let (params, results) = match wt_func_sig(ctx, ft) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let rec = @lib.RecType::new(
    @lib.SubType::comp_type(@lib.CompType::func(params, results)),
  )
  let idx = wt_flat_type_count(ctx)
  ctx.type_recs.push(rec)
  Ok(idx)
}

///|
fn wt_field_storage(
  ctx : WTLowerCtx,
  storage : FieldStorage,
) -> Result[@lib.StorageType, String] {
  match storage {
    FieldStorage::Value(vt) =>
      match wt_value_type(ctx, vt) {
        Ok(v) => Ok(@lib.StorageType::val_type(v))
        Err(e) => Err(e)
      }
    FieldStorage::I8 => Ok(@lib.StorageType::pack_type(@lib.PackType::i8()))
    FieldStorage::I16 => Ok(@lib.StorageType::pack_type(@lib.PackType::i16()))
  }
}

///|
fn wt_struct_field(
  ctx : WTLowerCtx,
  field : StructFieldDef,
) -> Result[@lib.FieldType, String] {
  let storage = match wt_field_storage(ctx, field.storage) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  Ok(
    @lib.FieldType::new(
      storage,
      if field.mutable_ {
        @lib.Mut::var_()
      } else {
        @lib.Mut::const_()
      },
    ),
  )
}

///|
fn wt_type_metadata(
  ctx : WTLowerCtx,
  metadata : TypeDefMetadata,
) -> Result[@lib.TypeMetadata, String] {
  let describes : @lib.TypeIdx? = match metadata.describes {
    Some(idx) =>
      match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(v) => Some(@lib.TypeIdx::new(v))
        Err(e) => return Err(e)
      }
    None => None
  }
  let descriptor : @lib.TypeIdx? = match metadata.descriptor {
    Some(idx) =>
      match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(v) => Some(@lib.TypeIdx::new(v))
        Err(e) => return Err(e)
      }
    None => None
  }
  Ok(@lib.TypeMetadata::new(describes~, descriptor~))
}

///|
fn wt_comp_type(
  ctx : WTLowerCtx,
  body : TypeDefBody,
) -> Result[@lib.CompType, String] {
  match body {
    TypeDefBody::Shared(body) => wt_comp_type(ctx, body)
    TypeDefBody::Func(ft) => {
      let (params, results) = match wt_func_sig(ctx, ft) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.CompType::func(params, results))
    }
    TypeDefBody::Struct(fields) => {
      let lowered : Array[@lib.FieldType] = []
      for field in fields {
        let ft = match wt_struct_field(ctx, field) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
        lowered.push(ft)
      }
      Ok(@lib.CompType::struct_(lowered))
    }
    TypeDefBody::Array(field) =>
      match wt_struct_field(ctx, field) {
        Ok(v) => Ok(@lib.CompType::array(v))
        Err(e) => Err(e)
      }
  }
}

///|
fn wt_type_def(ctx : WTLowerCtx, td : TypeDef) -> Result[@lib.RecType, String] {
  let subtype = match wt_sub_type(ctx, td) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  Ok(@lib.RecType::new(subtype))
}

///|
fn wt_sub_type(ctx : WTLowerCtx, td : TypeDef) -> Result[@lib.SubType, String] {
  let metadata = match wt_type_metadata(ctx, td.metadata) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let metadata = metadata.with_shared(td.body is TypeDefBody::Shared(_))
  let comp_type = match wt_comp_type(ctx, td.body) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  if td.is_sub {
    let supers : Array[@lib.TypeIdx] = []
    for super_idx in td.supers {
      let idx = match
        wt_resolve_named_or_num_index(
          super_idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      supers.push(@lib.TypeIdx::new(idx))
    }
    Ok(@lib.SubType::new(td.final_, supers, comp_type, metadata~))
  } else {
    Ok(@lib.SubType::comp_type(comp_type, metadata~))
  }
}

///|
fn wt_type_group(
  ctx : WTLowerCtx,
  types : Array[TypeDef],
) -> Result[@lib.RecType, String] {
  let subtypes : Array[@lib.SubType] = []
  for td in types {
    let subtype = match wt_sub_type(ctx, td) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    subtypes.push(subtype)
  }
  Ok(@lib.RecType::group(subtypes))
}

///|
fn wt_index_types(ctx : WTLowerCtx) -> Unit {
  // Type records only append during lowering. Index each record once.
  while ctx.indexed_type_recs < ctx.type_recs.length() {
    match ctx.type_recs[ctx.indexed_type_recs] {
      SingleRecType(st) => ctx.flat_types.push(st)
      GroupRecType(sts) =>
        for st in sts {
          ctx.flat_types.push(st)
        }
    }
    ctx.indexed_type_recs += 1
  }
}

///|
fn wt_flat_type_count(ctx : WTLowerCtx) -> UInt32 {
  wt_index_types(ctx)
  ctx.flat_types.length().reinterpret_as_uint()
}

///|
fn wt_type_match_env(ctx : WTLowerCtx) -> @validate.Env {
  wt_index_types(ctx)
  let env = match ctx.type_match_env {
    Some(env) => env
    None => {
      let env = @validate.Env::new().with_types(ctx.flat_types)
      ctx.type_match_env = Some(env)
      env
    }
  }
  // Canonical reference comparisons need group scopes. Allocate these only
  // when distinct reference annotations need comparison.
  while ctx.scoped_type_recs < ctx.type_recs.length() {
    match ctx.type_recs[ctx.scoped_type_recs] {
      SingleRecType(st) => env.rec_stack.push([st])
      GroupRecType(sts) => env.rec_stack.push(sts)
    }
    ctx.scoped_type_recs += 1
  }
  env
}

///|
fn wt_same_value_types(
  ctx : WTLowerCtx,
  declared : Array[@lib.ValType],
  inline : Array[@lib.ValType],
) -> Bool {
  if declared.length() != inline.length() {
    return false
  }
  for i = 0; i < declared.length(); i = i + 1 {
    let a = declared[i]
    let b = inline[i]
    if a == b {
      continue
    }
    match (a, b) {
      (@lib.RefTypeValType(_), @lib.RefTypeValType(_)) => {
        let env = wt_type_match_env(ctx)
        if !@validate.Match::matches(a, b, env) ||
          !@validate.Match::matches(b, a, env) {
          return false
        }
      }
      _ => return false
    }
  }
  true
}

///|
fn wt_find_matching_func_type_index(
  ctx : WTLowerCtx,
  params : Array[@lib.ValType],
  results : Array[@lib.ValType],
) -> UInt32? {
  let mut flat = 0U
  for rec in ctx.type_recs {
    match rec {
      SingleRecType(st) =>
        match st.get_comptype() {
          FuncCompType(ps, rs) => {
            if ps == params && rs == results {
              return Some(flat)
            }
            flat += 1
          }
          _ => flat += 1
        }
      GroupRecType(sts) => flat += sts.length().reinterpret_as_uint()
    }
  }
  None
}

///|
fn wt_resolve_named_or_num_index(
  idx : Index,
  ids : Map[String, UInt32],
  upper_bound : UInt32,
  kind : String,
) -> Result[UInt32, String] {
  ignore(upper_bound)
  match idx {
    Num(n) => Ok(n)
    Id(name) =>
      match ids.get(name) {
        Some(v) => Ok(v)
        None => Err("unknown \{kind} id: \{name}")
      }
  }
}

///|
fn wt_resolve_type_use(
  ctx : WTLowerCtx,
  tu : TypeUse,
) -> Result[UInt32, String] {
  match tu.type_index {
    Some(idx) => {
      let resolved = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          wt_flat_type_count(ctx),
          "type",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      // An omitted inline signature inherits the explicit function type.
      if tu.func_type.params.length() == 0 && tu.func_type.results.length() == 0 {
        return Ok(resolved)
      }
      let declared = match ctx.flat_types.get(resolved.reinterpret_as_int()) {
        Some(st) => st.get_comptype()
        None => return Err("invalid type index: \{resolved}")
      }
      let (params, results) = match wt_func_sig(ctx, tu.func_type) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      match declared {
        FuncCompType(declared_params, declared_results) =>
          if wt_same_value_types(ctx, declared_params, params) &&
            wt_same_value_types(ctx, declared_results, results) {
            Ok(resolved)
          } else {
            Err(
              "inline function signature does not match explicit type: \{resolved}",
            )
          }
        _ => Err("type index does not reference a function type: \{resolved}")
      }
    }
    None => {
      let (params, results) = match wt_func_sig(ctx, tu.func_type) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      match wt_find_matching_func_type_index(ctx, params, results) {
        Some(idx) => Ok(idx)
        None =>
          match wt_append_type(ctx, tu.func_type) {
            Ok(v) => Ok(v)
            Err(e) => Err(e)
          }
      }
    }
  }
}

///|
fn wt_type_param_count(
  ctx : WTLowerCtx,
  type_idx : UInt32,
) -> Result[UInt32, String] {
  wt_index_types(ctx)
  match ctx.flat_types.get(type_idx.reinterpret_as_int()) {
    Some(st) =>
      match st.get_comptype() {
        FuncCompType(params, _) => Ok(params.length().reinterpret_as_uint())
        _ => Err("type index does not reference a function type: \{type_idx}")
      }
    None => Err("invalid type index: \{type_idx}")
  }
}

///|
fn wt_resolve_label(
  idx : Index,
  labels : Array[String?],
) -> Result[UInt32, String] {
  match idx {
    Num(n) =>
      if n < labels.length().reinterpret_as_uint() {
        Ok(n)
      } else {
        Err("invalid label index: \{n}")
      }
    Id(name) => {
      let mut i = labels.length() - 1
      while i >= 0 {
        match labels[i] {
          Some(label) if label == name =>
            return Ok((labels.length() - 1 - i).reinterpret_as_uint())
          _ => ()
        }
        i = i - 1
      }
      Err("unknown label id: \{name}")
    }
  }
}

///|
fn wt_parse_i32(text : String) -> Result[@lib.I32, String] {
  let v = @string.parse_int64(text) catch {
    _ => return Err("invalid i32 literal: \{text}")
  }
  Ok(@lib.I32(v.to_int()))
}

///|
fn wt_parse_i64(text : String) -> Result[@lib.I64, String] {
  let v = @string.parse_int64(text) catch {
    _ => return Err("invalid i64 literal: \{text}")
  }
  Ok(@lib.I64(v))
}

///|
fn wt_hex_digit_value(c : Int32) -> Int32 {
  if c >= 0x30 && c <= 0x39 {
    c - 0x30
  } else if c >= 0x61 && c <= 0x66 {
    c - 0x61 + 10
  } else if c >= 0x41 && c <= 0x46 {
    c - 0x41 + 10
  } else {
    -1
  }
}

///|
fn wt_parse_hex_u64(text : String, kind : String) -> Result[UInt64, String] {
  if !(text.has_prefix("0x") || text.has_prefix("0X")) {
    return Err("invalid \{kind} literal: \{text}")
  }
  let mut value : UInt64 = 0UL
  let mut digits = 0
  for i = 2; i < text.length(); i = i + 1 {
    let c = text[i]
    if c == '_' {
      continue
    }
    let digit = wt_hex_digit_value(c.to_int())
    if digit < 0 {
      return Err("invalid \{kind} literal: \{text}")
    }
    digits = digits + 1
    if digits > 16 {
      return Err("invalid \{kind} literal: \{text}")
    }
    value = (value << 4) | digit.to_uint64()
  }
  if digits == 0 {
    return Err("invalid \{kind} literal: \{text}")
  }
  Ok(value)
}

///|
fn wt_parse_u64_decimal(text : String, kind : String) -> Result[UInt64, String] {
  let mut value : UInt64 = 0UL
  let mut digits = 0
  for i = 0; i < text.length(); i = i + 1 {
    let c = text[i]
    if c == '_' {
      continue
    }
    let code = c.to_int()
    if code < 0x30 || code > 0x39 {
      return Err("invalid \{kind} literal: \{text}")
    }
    let digit = (code - 0x30).to_uint64()
    if value > (18446744073709551615UL - digit) / 10UL {
      return Err("invalid \{kind} literal: \{text}")
    }
    value = value * 10UL + digit
    digits = digits + 1
  }
  if digits == 0 {
    return Err("invalid \{kind} literal: \{text}")
  }
  Ok(value)
}

///|
fn wt_parse_u64_literal(text : String, kind : String) -> Result[UInt64, String] {
  let trimmed = text.trim().to_owned()
  let parsed_signed = @string.parse_int64(trimmed) catch {
    _ =>
      if trimmed.has_prefix("0x") || trimmed.has_prefix("0X") {
        return wt_parse_hex_u64(trimmed, kind)
      } else {
        return wt_parse_u64_decimal(trimmed, kind)
      }
  }
  Ok(parsed_signed.reinterpret_as_uint64())
}

///|
fn wt_parse_f64(text : String) -> Result[@lib.F64, String] {
  match
    wt_parse_nan_payload_bits(
      text, 0x7ff0000000000000UL, 0x0008000000000000UL, 0x000fffffffffffffUL, 0x8000000000000000UL,
      "f64",
    ) {
    Ok(Some(bits)) =>
      return Ok(@lib.F64(bits.reinterpret_as_int64().reinterpret_as_double()))
    Ok(None) => ()
    Err(error) => return Err(error)
  }
  let value = @string.parse_double(text) catch {
    _ => return Err("invalid f64 literal: \{text}")
  }
  Ok(@lib.F64(value))
}

///|
fn wt_parse_f32(text : String) -> Result[@lib.F32, String] {
  match
    wt_parse_nan_payload_bits(
      text, 0x7f800000UL, 0x00400000UL, 0x007fffffUL, 0x80000000UL, "f32",
    ) {
    Ok(Some(bits)) => return Ok(@lib.F32(bits.to_int().reinterpret_as_float()))
    Ok(None) => ()
    Err(error) => return Err(error)
  }
  // Preserve the numeric conversion and its existing error spelling; a NaN
  // never takes this path, so f32 payload bits cannot widen and canonicalize.
  let value = @string.parse_double(text) catch {
    _ => return Err("invalid f64 literal: \{text}")
  }
  Ok(@lib.F32(Float::from_double(value)))
}

///|
fn wt_parse_i64_literal(text : String, kind : String) -> Result[Int64, String] {
  let parsed = @string.parse_int64(text) catch {
    _ => return Err("invalid \{kind} literal: \{text}")
  }
  Ok(parsed)
}

///|
fn wt_parse_nan_payload_bits(
  text : String,
  exp_bits : UInt64,
  quiet_payload : UInt64,
  payload_mask : UInt64,
  sign_mask : UInt64,
  kind : String,
) -> Result[UInt64?, String] {
  let trimmed = text.trim().to_owned()
  if trimmed.length() == 0 {
    return Ok(None)
  }
  if !trimmed.has_prefix("nan") &&
    !trimmed.has_prefix("+nan") &&
    !trimmed.has_prefix("-nan") {
    return Ok(None)
  }
  let mut negative = false
  let mut body = trimmed
  if body[0] == '+' {
    body = body.sub(start=1, end=body.length()).to_owned()
  } else if body[0] == '-' {
    negative = true
    body = body.sub(start=1, end=body.length()).to_owned()
  }
  if body == "nan" {
    let mut bits = exp_bits | quiet_payload
    if negative {
      bits = bits | sign_mask
    }
    return Ok(Some(bits))
  }
  if body.has_prefix("nan:") {
    let payload_text = body.sub(start=4, end=body.length()).to_owned()
    let payload = match wt_parse_hex_u64(payload_text, "\{kind} NaN payload") {
      Ok(v) => v
      Err(_) => return Err("invalid \{kind} NaN payload literal: \{text}")
    }
    if payload == 0UL || payload > payload_mask {
      return Err("\{kind} NaN payload out of range: \{text}")
    }
    let mut bits = exp_bits | payload
    if negative {
      bits = bits | sign_mask
    }
    return Ok(Some(bits))
  }
  Ok(None)
}

///|
fn wt_push_le_bytes(out : Array[Byte], bits : UInt64, count : Int32) -> Unit {
  for i = 0; i < count; i = i + 1 {
    out.push(((bits >> (i * 8)) & 0xffUL).to_byte())
  }
}

///|
fn wt_v128_const_instr(v : V128Const) -> Result[@lib.Instruction, String] {
  let bytes : Array[Byte] = []
  match v.shape {
    I8x16 =>
      for text in v.values {
        let lane = match wt_parse_i64_literal(text, "i8x16") {
          Ok(val) => val
          Err(e) => return Err(e)
        }
        if lane < -128L || lane > 255L {
          return Err("i8x16 lane literal out of range: \{text}")
        }
        bytes.push(lane.to_int().to_byte())
      }
    I16x8 =>
      for text in v.values {
        let lane = match wt_parse_i64_literal(text, "i16x8") {
          Ok(val) => val
          Err(e) => return Err(e)
        }
        if lane < -32768L || lane > 65535L {
          return Err("i16x8 lane literal out of range: \{text}")
        }
        wt_push_le_bytes(bytes, lane.reinterpret_as_uint64(), 2)
      }
    I32x4 =>
      for text in v.values {
        let lane = match wt_parse_i64_literal(text, "i32x4") {
          Ok(val) => val
          Err(e) => return Err(e)
        }
        if lane < -2147483648L || lane > 4294967295L {
          return Err("i32x4 lane literal out of range: \{text}")
        }
        wt_push_le_bytes(bytes, lane.reinterpret_as_uint64(), 4)
      }
    I64x2 =>
      for text in v.values {
        let lane = match wt_parse_u64_literal(text, "i64x2") {
          Ok(val) => val
          Err(e) => return Err(e)
        }
        wt_push_le_bytes(bytes, lane, 8)
      }
    F32x4 =>
      for text in v.values {
        let maybe_nan_bits = match
          wt_parse_nan_payload_bits(
            text, 0x7f800000UL, 0x00400000UL, 0x007fffffUL, 0x80000000UL, "f32x4",
          ) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
        match maybe_nan_bits {
          Some(bits) => {
            wt_push_le_bytes(bytes, bits, 4)
            continue
          }
          None => ()
        }
        let lane = match wt_parse_f32(text) {
          Ok(val) => val
          Err(e) => return Err(e)
        }
        let @lib.F32(raw) = lane
        let buf = @buffer.new(size_hint=4)
        buf.write_float_le(raw)
        for b in buf.to_bytes().to_array() {
          bytes.push(b)
        }
      }
    F64x2 =>
      for text in v.values {
        let maybe_nan_bits = match
          wt_parse_nan_payload_bits(
            text, 0x7ff0000000000000UL, 0x0008000000000000UL, 0x000fffffffffffffUL,
            0x8000000000000000UL, "f64x2",
          ) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
        match maybe_nan_bits {
          Some(bits) => {
            wt_push_le_bytes(bytes, bits, 8)
            continue
          }
          None => ()
        }
        let lane = match wt_parse_f64(text) {
          Ok(val) => val
          Err(e) => return Err(e)
        }
        let @lib.F64(raw) = lane
        let buf = @buffer.new(size_hint=8)
        buf.write_double_le(raw)
        for b in buf.to_bytes().to_array() {
          bytes.push(b)
        }
      }
  }
  if bytes.length() != 16 {
    return Err("v128.const lowering expected 16 bytes, got \{bytes.length()}")
  }
  Ok(
    @lib.Instruction::v128_const(
      bytes[0],
      bytes[1],
      bytes[2],
      bytes[3],
      bytes[4],
      bytes[5],
      bytes[6],
      bytes[7],
      bytes[8],
      bytes[9],
      bytes[10],
      bytes[11],
      bytes[12],
      bytes[13],
      bytes[14],
      bytes[15],
    ),
  )
}

///|
fn wt_shuffle_lane(lane : UInt32) -> Result[@lib.LaneIdx, String] {
  if lane <= 31U {
    Ok(@lib.LaneIdx::new(lane.to_byte()))
  } else {
    Err("i8x16.shuffle lane index out of range: \{lane}")
  }
}

///|
fn wt_lane_idx(
  op : String,
  lane : UInt32,
  max_lane : UInt32,
) -> Result[@lib.LaneIdx, String] {
  if lane <= max_lane {
    Ok(@lib.LaneIdx::new(lane.to_byte()))
  } else {
    Err("\{op} lane index out of range: \{lane}")
  }
}

///|
fn wt_select_types(
  ctx : WTLowerCtx,
  types : Array[ValueType]?,
) -> Result[Array[@lib.ValType]?, String] {
  match types {
    Some(vs) => {
      let out : Array[@lib.ValType] = []
      for v in vs {
        let vv = match wt_value_type(ctx, v) {
          Ok(vv) => vv
          Err(e) => return Err(e)
        }
        out.push(vv)
      }
      Ok(Some(out))
    }
    None => Ok(None)
  }
}

///|
fn wt_block_type(
  ctx : WTLowerCtx,
  bt : BlockType,
) -> Result[@lib.BlockType, String] {
  match bt.type_use {
    None => Ok(@lib.BlockType::void_())
    Some(tu) =>
      match tu.type_index {
        Some(_) => {
          let ti = match wt_resolve_type_use(ctx, tu) {
            Ok(v) => v
            Err(e) => return Err(e)
          }
          Ok(@lib.BlockType::type_idx(@lib.TypeIdx::new(ti)))
        }
        None =>
          if tu.func_type.params.length() == 0 &&
            tu.func_type.results.length() == 0 {
            Ok(@lib.BlockType::void_())
          } else if tu.func_type.params.length() == 0 &&
            tu.func_type.results.length() == 1 {
            let vt = match wt_value_type(ctx, tu.func_type.results[0]) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            Ok(@lib.BlockType::val_type(vt))
          } else {
            let ti = match wt_append_type(ctx, tu.func_type) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            Ok(@lib.BlockType::type_idx(@lib.TypeIdx::new(ti)))
          }
      }
  }
}

///|
fn wt_unsupported(op : String) -> Result[@lib.Instruction, String] {
  Err("unsupported instruction in text lowering bridge: \{op}")
}

///|
fn wt_numeric_noarg(op : Opcode) -> Result[@lib.Instruction, String] {
  match op {
    I32Eqz => Ok(@lib.Instruction::i32_eqz())
    I32Eq => Ok(@lib.Instruction::i32_eq())
    I32Ne => Ok(@lib.Instruction::i32_ne())
    I32LtS => Ok(@lib.Instruction::i32_lt_s())
    I32LtU => Ok(@lib.Instruction::i32_lt_u())
    I32GtS => Ok(@lib.Instruction::i32_gt_s())
    I32GtU => Ok(@lib.Instruction::i32_gt_u())
    I32LeS => Ok(@lib.Instruction::i32_le_s())
    I32LeU => Ok(@lib.Instruction::i32_le_u())
    I32GeS => Ok(@lib.Instruction::i32_ge_s())
    I32GeU => Ok(@lib.Instruction::i32_ge_u())
    I64Eqz => Ok(@lib.Instruction::i64_eqz())
    I64Eq => Ok(@lib.Instruction::i64_eq())
    I64Ne => Ok(@lib.Instruction::i64_ne())
    I64LtS => Ok(@lib.Instruction::i64_lt_s())
    I64LtU => Ok(@lib.Instruction::i64_lt_u())
    I64GtS => Ok(@lib.Instruction::i64_gt_s())
    I64GtU => Ok(@lib.Instruction::i64_gt_u())
    I64LeS => Ok(@lib.Instruction::i64_le_s())
    I64LeU => Ok(@lib.Instruction::i64_le_u())
    I64GeS => Ok(@lib.Instruction::i64_ge_s())
    I64GeU => Ok(@lib.Instruction::i64_ge_u())
    F32Eq => Ok(@lib.Instruction::f32_eq())
    F32Ne => Ok(@lib.Instruction::f32_ne())
    F32Lt => Ok(@lib.Instruction::f32_lt())
    F32Gt => Ok(@lib.Instruction::f32_gt())
    F32Le => Ok(@lib.Instruction::f32_le())
    F32Ge => Ok(@lib.Instruction::f32_ge())
    F64Eq => Ok(@lib.Instruction::f64_eq())
    F64Ne => Ok(@lib.Instruction::f64_ne())
    F64Lt => Ok(@lib.Instruction::f64_lt())
    F64Gt => Ok(@lib.Instruction::f64_gt())
    F64Le => Ok(@lib.Instruction::f64_le())
    F64Ge => Ok(@lib.Instruction::f64_ge())
    I32Clz => Ok(@lib.Instruction::i32_clz())
    I32Ctz => Ok(@lib.Instruction::i32_ctz())
    I32Popcnt => Ok(@lib.Instruction::i32_popcnt())
    I32Add => Ok(@lib.Instruction::i32_add())
    I32Sub => Ok(@lib.Instruction::i32_sub())
    I32Mul => Ok(@lib.Instruction::i32_mul())
    I32DivS => Ok(@lib.Instruction::i32_div_s())
    I32DivU => Ok(@lib.Instruction::i32_div_u())
    I32RemS => Ok(@lib.Instruction::i32_rem_s())
    I32RemU => Ok(@lib.Instruction::i32_rem_u())
    I32And => Ok(@lib.Instruction::i32_and())
    I32Or => Ok(@lib.Instruction::i32_or())
    I32Xor => Ok(@lib.Instruction::i32_xor())
    I32Shl => Ok(@lib.Instruction::i32_shl())
    I32ShrS => Ok(@lib.Instruction::i32_shr_s())
    I32ShrU => Ok(@lib.Instruction::i32_shr_u())
    I32Rotl => Ok(@lib.Instruction::i32_rotl())
    I32Rotr => Ok(@lib.Instruction::i32_rotr())
    I64Clz => Ok(@lib.Instruction::i64_clz())
    I64Ctz => Ok(@lib.Instruction::i64_ctz())
    I64Popcnt => Ok(@lib.Instruction::i64_popcnt())
    I64Add => Ok(@lib.Instruction::i64_add())
    I64Sub => Ok(@lib.Instruction::i64_sub())
    I64Mul => Ok(@lib.Instruction::i64_mul())
    I64DivS => Ok(@lib.Instruction::i64_div_s())
    I64DivU => Ok(@lib.Instruction::i64_div_u())
    I64RemS => Ok(@lib.Instruction::i64_rem_s())
    I64RemU => Ok(@lib.Instruction::i64_rem_u())
    I64And => Ok(@lib.Instruction::i64_and())
    I64Or => Ok(@lib.Instruction::i64_or())
    I64Xor => Ok(@lib.Instruction::i64_xor())
    I64Shl => Ok(@lib.Instruction::i64_shl())
    I64ShrS => Ok(@lib.Instruction::i64_shr_s())
    I64ShrU => Ok(@lib.Instruction::i64_shr_u())
    I64Rotl => Ok(@lib.Instruction::i64_rotl())
    I64Rotr => Ok(@lib.Instruction::i64_rotr())
    F32Abs => Ok(@lib.Instruction::f32_abs())
    F32Neg => Ok(@lib.Instruction::f32_neg())
    F32Ceil => Ok(@lib.Instruction::f32_ceil())
    F32Floor => Ok(@lib.Instruction::f32_floor())
    F32Trunc => Ok(@lib.Instruction::f32_trunc())
    F32Nearest => Ok(@lib.Instruction::f32_nearest())
    F32Sqrt => Ok(@lib.Instruction::f32_sqrt())
    F32Add => Ok(@lib.Instruction::f32_add())
    F32Sub => Ok(@lib.Instruction::f32_sub())
    F32Mul => Ok(@lib.Instruction::f32_mul())
    F32Div => Ok(@lib.Instruction::f32_div())
    F32Min => Ok(@lib.Instruction::f32_min())
    F32Max => Ok(@lib.Instruction::f32_max())
    F32Copysign => Ok(@lib.Instruction::f32_copysign())
    F64Abs => Ok(@lib.Instruction::f64_abs())
    F64Neg => Ok(@lib.Instruction::f64_neg())
    F64Ceil => Ok(@lib.Instruction::f64_ceil())
    F64Floor => Ok(@lib.Instruction::f64_floor())
    F64Trunc => Ok(@lib.Instruction::f64_trunc())
    F64Nearest => Ok(@lib.Instruction::f64_nearest())
    F64Sqrt => Ok(@lib.Instruction::f64_sqrt())
    F64Add => Ok(@lib.Instruction::f64_add())
    F64Sub => Ok(@lib.Instruction::f64_sub())
    F64Mul => Ok(@lib.Instruction::f64_mul())
    F64Div => Ok(@lib.Instruction::f64_div())
    F64Min => Ok(@lib.Instruction::f64_min())
    F64Max => Ok(@lib.Instruction::f64_max())
    F64Copysign => Ok(@lib.Instruction::f64_copysign())
    I32WrapI64 => Ok(@lib.Instruction::i32_wrap_i64())
    I32TruncF32S => Ok(@lib.Instruction::i32_trunc_f32s())
    I32TruncF32U => Ok(@lib.Instruction::i32_trunc_f32u())
    I32TruncF64S => Ok(@lib.Instruction::i32_trunc_f64s())
    I32TruncF64U => Ok(@lib.Instruction::i32_trunc_f64u())
    I64ExtendI32S => Ok(@lib.Instruction::i64_extend_i32s())
    I64ExtendI32U => Ok(@lib.Instruction::i64_extend_i32u())
    I64TruncF32S => Ok(@lib.Instruction::i64_trunc_f32s())
    I64TruncF32U => Ok(@lib.Instruction::i64_trunc_f32u())
    I64TruncF64S => Ok(@lib.Instruction::i64_trunc_f64s())
    I64TruncF64U => Ok(@lib.Instruction::i64_trunc_f64u())
    F32ConvertI32S => Ok(@lib.Instruction::f32_convert_i32s())
    F32ConvertI32U => Ok(@lib.Instruction::f32_convert_i32u())
    F32ConvertI64S => Ok(@lib.Instruction::f32_convert_i64s())
    F32ConvertI64U => Ok(@lib.Instruction::f32_convert_i64u())
    F32DemoteF64 => Ok(@lib.Instruction::f32_demote_f64())
    F64ConvertI32S => Ok(@lib.Instruction::f64_convert_i32s())
    F64ConvertI32U => Ok(@lib.Instruction::f64_convert_i32u())
    F64ConvertI64S => Ok(@lib.Instruction::f64_convert_i64s())
    F64ConvertI64U => Ok(@lib.Instruction::f64_convert_i64u())
    F64PromoteF32 => Ok(@lib.Instruction::f64_promote_f32())
    I32ReinterpretF32 => Ok(@lib.Instruction::i32_reinterpret_f32())
    I64ReinterpretF64 => Ok(@lib.Instruction::i64_reinterpret_f64())
    F32ReinterpretI32 => Ok(@lib.Instruction::f32_reinterpret_i32())
    F64ReinterpretI64 => Ok(@lib.Instruction::f64_reinterpret_i64())
    I32Extend8S => Ok(@lib.Instruction::i32_extend8s())
    I32Extend16S => Ok(@lib.Instruction::i32_extend16s())
    I64Extend8S => Ok(@lib.Instruction::i64_extend8s())
    I64Extend16S => Ok(@lib.Instruction::i64_extend16s())
    I64Extend32S => Ok(@lib.Instruction::i64_extend32s())
    I32TruncSatF32S => Ok(@lib.Instruction::i32_trunc_sat_f32s())
    I32TruncSatF32U => Ok(@lib.Instruction::i32_trunc_sat_f32u())
    I32TruncSatF64S => Ok(@lib.Instruction::i32_trunc_sat_f64s())
    I32TruncSatF64U => Ok(@lib.Instruction::i32_trunc_sat_f64u())
    I64TruncSatF32S => Ok(@lib.Instruction::i64_trunc_sat_f32s())
    I64TruncSatF32U => Ok(@lib.Instruction::i64_trunc_sat_f32u())
    I64TruncSatF64S => Ok(@lib.Instruction::i64_trunc_sat_f64s())
    I64TruncSatF64U => Ok(@lib.Instruction::i64_trunc_sat_f64u())
    RefEq => Ok(@lib.Instruction::ref_eq())
    RefAsNonNull => Ok(@lib.Instruction::ref_as_non_null())
    StringConst => Err("string.const requires a literal")
    StringMeasureWtf16 => Ok(@lib.Instruction::string_measure_wtf16())
    StringConcat => Ok(@lib.Instruction::string_concat())
    StringEq => Ok(@lib.Instruction::string_eq())
    StringAsWtf16 => Ok(@lib.Instruction::string_as_wtf16())
    StringViewWtf16GetCodeunit =>
      Ok(@lib.Instruction::stringview_wtf16_get_codeunit())
    StringViewWtf16Slice => Ok(@lib.Instruction::stringview_wtf16_slice())
    StringNewUtf8Array => Ok(@lib.Instruction::string_new_utf8_array())
    StringNewWtf16Array => Ok(@lib.Instruction::string_new_wtf16_array())
    StringEncodeUtf8Array => Ok(@lib.Instruction::string_encode_utf8_array())
    StringEncodeWtf16Array => Ok(@lib.Instruction::string_encode_wtf16_array())
    StringNewLossyUtf8Array =>
      Ok(@lib.Instruction::string_new_lossy_utf8_array())
    StringNewWtf8Array => Ok(@lib.Instruction::string_new_wtf8_array())
    StringEncodeLossyUtf8Array =>
      Ok(@lib.Instruction::string_encode_lossy_utf8_array())
    StringEncodeWtf8Array => Ok(@lib.Instruction::string_encode_wtf8_array())
    AnyConvertExtern => Ok(@lib.Instruction::any_convert_extern())
    ExternConvertAny => Ok(@lib.Instruction::extern_convert_any())
    RefI31 => Ok(@lib.Instruction::ref_i31())
    RefI31Shared => Ok(@lib.Instruction::ref_i31_shared())
    I31GetS => Ok(@lib.Instruction::i31_get_s())
    I31GetU => Ok(@lib.Instruction::i31_get_u())
    V128Not => Ok(@lib.Instruction::v128_not())
    V128And => Ok(@lib.Instruction::v128_and())
    V128Or => Ok(@lib.Instruction::v128_or())
    V128Xor => Ok(@lib.Instruction::v128_xor())
    V128Bitselect => Ok(@lib.Instruction::v128_bitselect())
    V128AnyTrue => Ok(@lib.Instruction::v128_any_true())
    I8x16Eq => Ok(@lib.Instruction::i8x16_eq())
    I8x16Ne => Ok(@lib.Instruction::i8x16_ne())
    I8x16LtS => Ok(@lib.Instruction::i8x16_lt_s())
    I8x16LtU => Ok(@lib.Instruction::i8x16_lt_u())
    I8x16GtS => Ok(@lib.Instruction::i8x16_gt_s())
    I8x16GtU => Ok(@lib.Instruction::i8x16_gt_u())
    I8x16LeS => Ok(@lib.Instruction::i8x16_le_s())
    I8x16LeU => Ok(@lib.Instruction::i8x16_le_u())
    I8x16GeS => Ok(@lib.Instruction::i8x16_ge_s())
    I8x16GeU => Ok(@lib.Instruction::i8x16_ge_u())
    I8x16Abs => Ok(@lib.Instruction::i8x16_abs())
    I8x16Neg => Ok(@lib.Instruction::i8x16_neg())
    I8x16Popcnt => Ok(@lib.Instruction::i8x16_popcnt())
    I8x16AllTrue => Ok(@lib.Instruction::i8x16_all_true())
    I8x16Bitmask => Ok(@lib.Instruction::i8x16_bitmask())
    I8x16NarrowI16x8S => Ok(@lib.Instruction::i8x16_narrow_i16x8s())
    I8x16NarrowI16x8U => Ok(@lib.Instruction::i8x16_narrow_i16x8u())
    I8x16Shl => Ok(@lib.Instruction::i8x16_shl())
    I8x16ShrS => Ok(@lib.Instruction::i8x16_shr_s())
    I8x16ShrU => Ok(@lib.Instruction::i8x16_shr_u())
    I8x16Add => Ok(@lib.Instruction::i8x16_add())
    I8x16AddSatS => Ok(@lib.Instruction::i8x16_add_sat_s())
    I8x16AddSatU => Ok(@lib.Instruction::i8x16_add_sat_u())
    I8x16Sub => Ok(@lib.Instruction::i8x16_sub())
    I8x16SubSatS => Ok(@lib.Instruction::i8x16_sub_sat_s())
    I8x16SubSatU => Ok(@lib.Instruction::i8x16_sub_sat_u())
    I8x16MinS => Ok(@lib.Instruction::i8x16_min_s())
    I8x16MinU => Ok(@lib.Instruction::i8x16_min_u())
    I8x16MaxS => Ok(@lib.Instruction::i8x16_max_s())
    I8x16MaxU => Ok(@lib.Instruction::i8x16_max_u())
    I8x16AvgrU => Ok(@lib.Instruction::i8x16_avgr_u())
    I16x8Eq => Ok(@lib.Instruction::i16x8_eq())
    I16x8Ne => Ok(@lib.Instruction::i16x8_ne())
    I16x8LtS => Ok(@lib.Instruction::i16x8_lt_s())
    I16x8LtU => Ok(@lib.Instruction::i16x8_lt_u())
    I16x8GtS => Ok(@lib.Instruction::i16x8_gt_s())
    I16x8GtU => Ok(@lib.Instruction::i16x8_gt_u())
    I16x8LeS => Ok(@lib.Instruction::i16x8_le_s())
    I16x8LeU => Ok(@lib.Instruction::i16x8_le_u())
    I16x8GeS => Ok(@lib.Instruction::i16x8_ge_s())
    I16x8GeU => Ok(@lib.Instruction::i16x8_ge_u())
    I16x8Abs => Ok(@lib.Instruction::i16x8_abs())
    I16x8Neg => Ok(@lib.Instruction::i16x8_neg())
    I16x8Q15mulrSatS => Ok(@lib.Instruction::i16x8q15mulr_sat_s())
    I16x8AllTrue => Ok(@lib.Instruction::i16x8_all_true())
    I16x8Bitmask => Ok(@lib.Instruction::i16x8_bitmask())
    I16x8NarrowI32x4S => Ok(@lib.Instruction::i16x8_narrow_i32x4s())
    I16x8NarrowI32x4U => Ok(@lib.Instruction::i16x8_narrow_i32x4u())
    I16x8ExtendLowI8x16S => Ok(@lib.Instruction::i16x8_extend_low_i8x16s())
    I16x8ExtendHighI8x16S => Ok(@lib.Instruction::i16x8_extend_high_i8x16s())
    I16x8ExtendLowI8x16U => Ok(@lib.Instruction::i16x8_extend_low_i8x16u())
    I16x8ExtendHighI8x16U => Ok(@lib.Instruction::i16x8_extend_high_i8x16u())
    I16x8Shl => Ok(@lib.Instruction::i16x8_shl())
    I16x8ShrS => Ok(@lib.Instruction::i16x8_shr_s())
    I16x8ShrU => Ok(@lib.Instruction::i16x8_shr_u())
    I16x8Add => Ok(@lib.Instruction::i16x8_add())
    I16x8AddSatS => Ok(@lib.Instruction::i16x8_add_sat_s())
    I16x8AddSatU => Ok(@lib.Instruction::i16x8_add_sat_u())
    I16x8Sub => Ok(@lib.Instruction::i16x8_sub())
    I16x8SubSatS => Ok(@lib.Instruction::i16x8_sub_sat_s())
    I16x8SubSatU => Ok(@lib.Instruction::i16x8_sub_sat_u())
    I16x8Mul => Ok(@lib.Instruction::i16x8_mul())
    I16x8MinS => Ok(@lib.Instruction::i16x8_min_s())
    I16x8MinU => Ok(@lib.Instruction::i16x8_min_u())
    I16x8MaxS => Ok(@lib.Instruction::i16x8_max_s())
    I16x8MaxU => Ok(@lib.Instruction::i16x8_max_u())
    I16x8AvgrU => Ok(@lib.Instruction::i16x8_avgr_u())
    I16x8ExtmulLowI8x16S => Ok(@lib.Instruction::i16x8_extmul_low_i8x16s())
    I16x8ExtmulHighI8x16S => Ok(@lib.Instruction::i16x8_extmul_high_i8x16s())
    I16x8ExtmulLowI8x16U => Ok(@lib.Instruction::i16x8_extmul_low_i8x16u())
    I16x8ExtmulHighI8x16U => Ok(@lib.Instruction::i16x8_extmul_high_i8x16u())
    I32x4Eq => Ok(@lib.Instruction::i32x4_eq())
    I32x4Ne => Ok(@lib.Instruction::i32x4_ne())
    I32x4LtS => Ok(@lib.Instruction::i32x4_lt_s())
    I32x4LtU => Ok(@lib.Instruction::i32x4_lt_u())
    I32x4GtS => Ok(@lib.Instruction::i32x4_gt_s())
    I32x4GtU => Ok(@lib.Instruction::i32x4_gt_u())
    I32x4LeS => Ok(@lib.Instruction::i32x4_le_s())
    I32x4LeU => Ok(@lib.Instruction::i32x4_le_u())
    I32x4GeS => Ok(@lib.Instruction::i32x4_ge_s())
    I32x4GeU => Ok(@lib.Instruction::i32x4_ge_u())
    I32x4Abs => Ok(@lib.Instruction::i32x4_abs())
    I32x4Neg => Ok(@lib.Instruction::i32x4_neg())
    I32x4AllTrue => Ok(@lib.Instruction::i32x4_all_true())
    I32x4Bitmask => Ok(@lib.Instruction::i32x4_bitmask())
    I32x4ExtendLowI16x8S => Ok(@lib.Instruction::i32x4_extend_low_i16x8s())
    I32x4ExtendHighI16x8S => Ok(@lib.Instruction::i32x4_extend_high_i16x8s())
    I32x4ExtendLowI16x8U => Ok(@lib.Instruction::i32x4_extend_low_i16x8u())
    I32x4ExtendHighI16x8U => Ok(@lib.Instruction::i32x4_extend_high_i16x8u())
    I32x4Shl => Ok(@lib.Instruction::i32x4_shl())
    I32x4ShrS => Ok(@lib.Instruction::i32x4_shr_s())
    I32x4ShrU => Ok(@lib.Instruction::i32x4_shr_u())
    I32x4Add => Ok(@lib.Instruction::i32x4_add())
    I32x4Sub => Ok(@lib.Instruction::i32x4_sub())
    I32x4Mul => Ok(@lib.Instruction::i32x4_mul())
    I32x4MinS => Ok(@lib.Instruction::i32x4_min_s())
    I32x4MinU => Ok(@lib.Instruction::i32x4_min_u())
    I32x4MaxS => Ok(@lib.Instruction::i32x4_max_s())
    I32x4MaxU => Ok(@lib.Instruction::i32x4_max_u())
    I32x4DotI16x8S => Ok(@lib.Instruction::i32x4_dot_i16x8s())
    I32x4ExtmulLowI16x8S => Ok(@lib.Instruction::i32x4_extmul_low_i16x8s())
    I32x4ExtmulHighI16x8S => Ok(@lib.Instruction::i32x4_extmul_high_i16x8s())
    I32x4ExtmulLowI16x8U => Ok(@lib.Instruction::i32x4_extmul_low_i16x8u())
    I32x4ExtmulHighI16x8U => Ok(@lib.Instruction::i32x4_extmul_high_i16x8u())
    I32x4TruncSatF32x4S => Ok(@lib.Instruction::i32x4_trunc_sat_f32x4s())
    I32x4TruncSatF32x4U => Ok(@lib.Instruction::i32x4_trunc_sat_f32x4u())
    I32x4TruncSatF64x2SZero =>
      Ok(@lib.Instruction::i32x4_trunc_sat_f64x2s_zero())
    I32x4TruncSatF64x2UZero =>
      Ok(@lib.Instruction::i32x4_trunc_sat_f64x2u_zero())
    I64x2Eq => Ok(@lib.Instruction::i64x2_eq())
    I64x2Ne => Ok(@lib.Instruction::i64x2_ne())
    I64x2LtS => Ok(@lib.Instruction::i64x2_lt_s())
    I64x2GtS => Ok(@lib.Instruction::i64x2_gt_s())
    I64x2LeS => Ok(@lib.Instruction::i64x2_le_s())
    I64x2GeS => Ok(@lib.Instruction::i64x2_ge_s())
    I64x2Abs => Ok(@lib.Instruction::i64x2_abs())
    I64x2Neg => Ok(@lib.Instruction::i64x2_neg())
    I64x2AllTrue => Ok(@lib.Instruction::i64x2_all_true())
    I64x2Bitmask => Ok(@lib.Instruction::i64x2_bitmask())
    I64x2ExtendLowI32x4S => Ok(@lib.Instruction::i64x2_extend_low_i32x4s())
    I64x2ExtendHighI32x4S => Ok(@lib.Instruction::i64x2_extend_high_i32x4s())
    I64x2ExtendLowI32x4U => Ok(@lib.Instruction::i64x2_extend_low_i32x4u())
    I64x2ExtendHighI32x4U => Ok(@lib.Instruction::i64x2_extend_high_i32x4u())
    I64x2Shl => Ok(@lib.Instruction::i64x2_shl())
    I64x2ShrS => Ok(@lib.Instruction::i64x2_shr_s())
    I64x2ShrU => Ok(@lib.Instruction::i64x2_shr_u())
    I64x2Add => Ok(@lib.Instruction::i64x2_add())
    I64x2Sub => Ok(@lib.Instruction::i64x2_sub())
    I64x2Mul => Ok(@lib.Instruction::i64x2_mul())
    I64x2ExtmulLowI32x4S => Ok(@lib.Instruction::i64x2_extmul_low_i32x4s())
    I64x2ExtmulHighI32x4S => Ok(@lib.Instruction::i64x2_extmul_high_i32x4s())
    I64x2ExtmulLowI32x4U => Ok(@lib.Instruction::i64x2_extmul_low_i32x4u())
    I64x2ExtmulHighI32x4U => Ok(@lib.Instruction::i64x2_extmul_high_i32x4u())
    F32x4Eq => Ok(@lib.Instruction::f32x4_eq())
    F32x4Ne => Ok(@lib.Instruction::f32x4_ne())
    F32x4Lt => Ok(@lib.Instruction::f32x4_lt())
    F32x4Gt => Ok(@lib.Instruction::f32x4_gt())
    F32x4Le => Ok(@lib.Instruction::f32x4_le())
    F32x4Ge => Ok(@lib.Instruction::f32x4_ge())
    F32x4Abs => Ok(@lib.Instruction::f32x4_abs())
    F32x4Neg => Ok(@lib.Instruction::f32x4_neg())
    F32x4Sqrt => Ok(@lib.Instruction::f32x4_sqrt())
    F32x4Add => Ok(@lib.Instruction::f32x4_add())
    F32x4Sub => Ok(@lib.Instruction::f32x4_sub())
    F32x4Mul => Ok(@lib.Instruction::f32x4_mul())
    F32x4Div => Ok(@lib.Instruction::f32x4_div())
    F32x4Min => Ok(@lib.Instruction::f32x4_min())
    F32x4Max => Ok(@lib.Instruction::f32x4_max())
    F32x4Pmin => Ok(@lib.Instruction::f32x4_pmin())
    F32x4Pmax => Ok(@lib.Instruction::f32x4_pmax())
    F32x4Ceil => Ok(@lib.Instruction::f32x4_ceil())
    F32x4Floor => Ok(@lib.Instruction::f32x4_floor())
    F32x4Trunc => Ok(@lib.Instruction::f32x4_trunc())
    F32x4Nearest => Ok(@lib.Instruction::f32x4_nearest())
    F32x4ConvertI32x4S => Ok(@lib.Instruction::f32x4_convert_i32x4s())
    F32x4ConvertI32x4U => Ok(@lib.Instruction::f32x4_convert_i32x4u())
    F32x4DemoteF64x2Zero => Ok(@lib.Instruction::f32x4_demote_f64x2_zero())
    F64x2Eq => Ok(@lib.Instruction::f64x2_eq())
    F64x2Ne => Ok(@lib.Instruction::f64x2_ne())
    F64x2Lt => Ok(@lib.Instruction::f64x2_lt())
    F64x2Gt => Ok(@lib.Instruction::f64x2_gt())
    F64x2Le => Ok(@lib.Instruction::f64x2_le())
    F64x2Ge => Ok(@lib.Instruction::f64x2_ge())
    F64x2Abs => Ok(@lib.Instruction::f64x2_abs())
    F64x2Neg => Ok(@lib.Instruction::f64x2_neg())
    F64x2Sqrt => Ok(@lib.Instruction::f64x2_sqrt())
    F64x2Add => Ok(@lib.Instruction::f64x2_add())
    F64x2Sub => Ok(@lib.Instruction::f64x2_sub())
    F64x2Mul => Ok(@lib.Instruction::f64x2_mul())
    F64x2Div => Ok(@lib.Instruction::f64x2_div())
    F64x2Min => Ok(@lib.Instruction::f64x2_min())
    F64x2Max => Ok(@lib.Instruction::f64x2_max())
    F64x2Pmin => Ok(@lib.Instruction::f64x2_pmin())
    F64x2Pmax => Ok(@lib.Instruction::f64x2_pmax())
    F64x2Ceil => Ok(@lib.Instruction::f64x2_ceil())
    F64x2Floor => Ok(@lib.Instruction::f64x2_floor())
    F64x2Trunc => Ok(@lib.Instruction::f64x2_trunc())
    F64x2Nearest => Ok(@lib.Instruction::f64x2_nearest())
    F64x2ConvertLowI32x4S => Ok(@lib.Instruction::f64x2_convert_low_i32x4s())
    F64x2ConvertLowI32x4U => Ok(@lib.Instruction::f64x2_convert_low_i32x4u())
    F64x2PromoteLowF32x4 => Ok(@lib.Instruction::f64x2_promote_low_f32x4())
    I8x16RelaxedSwizzle => Ok(@lib.Instruction::i8x16_relaxed_swizzle())
    I8x16RelaxedLaneselect => Ok(@lib.Instruction::i8x16_relaxed_laneselect())
    I16x8RelaxedLaneselect => Ok(@lib.Instruction::i16x8_relaxed_laneselect())
    I32x4RelaxedLaneselect => Ok(@lib.Instruction::i32x4_relaxed_laneselect())
    I64x2RelaxedLaneselect => Ok(@lib.Instruction::i64x2_relaxed_laneselect())
    I32x4RelaxedTruncF32x4S =>
      Ok(@lib.Instruction::i32x4_relaxed_trunc_f32x4s())
    I32x4RelaxedTruncF32x4U =>
      Ok(@lib.Instruction::i32x4_relaxed_trunc_f32x4u())
    I32x4RelaxedTruncF64x2SZero =>
      Ok(@lib.Instruction::i32x4_relaxed_trunc_zero_f64x2s())
    I32x4RelaxedTruncF64x2UZero =>
      Ok(@lib.Instruction::i32x4_relaxed_trunc_zero_f64x2u())
    F32x4RelaxedMadd => Ok(@lib.Instruction::f32x4_relaxed_madd())
    F32x4RelaxedNmadd => Ok(@lib.Instruction::f32x4_relaxed_nmadd())
    F64x2RelaxedMadd => Ok(@lib.Instruction::f64x2_relaxed_madd())
    F64x2RelaxedNmadd => Ok(@lib.Instruction::f64x2_relaxed_nmadd())
    F32x4RelaxedMin => Ok(@lib.Instruction::f32x4_relaxed_min())
    F32x4RelaxedMax => Ok(@lib.Instruction::f32x4_relaxed_max())
    F64x2RelaxedMin => Ok(@lib.Instruction::f64x2_relaxed_min())
    F64x2RelaxedMax => Ok(@lib.Instruction::f64x2_relaxed_max())
    I16x8RelaxedQ15mulrS => Ok(@lib.Instruction::i16x8_relaxed_q15mulr_s())
    I16x8RelaxedDotI8x16I7x16S =>
      Ok(@lib.Instruction::i16x8_relaxed_dot_i8x16i7x16s())
    I32x4RelaxedDotI8x16I7x16AddS =>
      Ok(@lib.Instruction::i32x4_relaxed_dot_i8x16i7x16_add_s())
    _ => wt_unsupported("\{op}")
  }
}

///|
fn wt_load_store(op : Opcode, ma : MemArg) -> Result[@lib.Instruction, String] {
  let memarg = match wt_mem_arg(ma) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  match op {
    I32Load => Ok(@lib.Instruction::i32_load(memarg))
    I64Load => Ok(@lib.Instruction::i64_load(memarg))
    F32Load => Ok(@lib.Instruction::f32_load(memarg))
    F64Load => Ok(@lib.Instruction::f64_load(memarg))
    I32Load8S => Ok(@lib.Instruction::i32_load8s(memarg))
    I32Load8U => Ok(@lib.Instruction::i32_load8u(memarg))
    I32Load16S => Ok(@lib.Instruction::i32_load16s(memarg))
    I32Load16U => Ok(@lib.Instruction::i32_load16u(memarg))
    I64Load8S => Ok(@lib.Instruction::i64_load8s(memarg))
    I64Load8U => Ok(@lib.Instruction::i64_load8u(memarg))
    I64Load16S => Ok(@lib.Instruction::i64_load16s(memarg))
    I64Load16U => Ok(@lib.Instruction::i64_load16u(memarg))
    I64Load32S => Ok(@lib.Instruction::i64_load32s(memarg))
    I64Load32U => Ok(@lib.Instruction::i64_load32u(memarg))
    I32Store => Ok(@lib.Instruction::i32_store(memarg))
    I64Store => Ok(@lib.Instruction::i64_store(memarg))
    F32Store => Ok(@lib.Instruction::f32_store(memarg))
    F64Store => Ok(@lib.Instruction::f64_store(memarg))
    I32Store8 => Ok(@lib.Instruction::i32_store8(memarg))
    I32Store16 => Ok(@lib.Instruction::i32_store16(memarg))
    I64Store8 => Ok(@lib.Instruction::i64_store8(memarg))
    I64Store16 => Ok(@lib.Instruction::i64_store16(memarg))
    I64Store32 => Ok(@lib.Instruction::i64_store32(memarg))
    _ => wt_unsupported("\{op}")
  }
}

///|
priv enum WTLegacyCatchScope {
  CatchLabel(Int32, WTLegacyCatchScope?)
}

///|
priv struct WTLegacyCtx {
  catch_scope : WTLegacyCatchScope?
  enclosing_try_count : UInt32
  try_labels : Array[String]
}

///|
fn wt_index_text(idx : Index) -> String {
  match idx {
    Num(n) => "\{n}"
    Id(name) => name
  }
}

///|
fn wt_default_legacy_ctx() -> WTLegacyCtx {
  { catch_scope: None, enclosing_try_count: 0U, try_labels: [], }
}

///|
fn wt_legacy_rethrow_valid(
  idx : Index,
  legacy : WTLegacyCtx,
  label_count : Int32,
) -> Bool {
  let raw = match idx {
    Num(raw) => raw
    Id(_) => return false
  }
  if raw >= label_count.reinterpret_as_uint() {
    return false
  }
  let target = label_count - raw.reinterpret_as_int()
  let mut scope = legacy.catch_scope
  while scope is Some(WTLegacyCatchScope::CatchLabel(prefix, parent)) {
    if prefix == target {
      return true
    }
    scope = parent
  }
  false
}

///|
fn wt_legacy_delegate_valid(idx : Index, legacy : WTLegacyCtx) -> Bool {
  match idx {
    Num(n) => n <= legacy.enclosing_try_count
    Id(name) => {
      for label in legacy.try_labels {
        if label == name {
          return true
        }
      }
      false
    }
  }
}

///|
fn wt_instrs_to_lib(
  ctx : WTLowerCtx,
  instrs : Array[Instruction],
  local_ids : Map[String, UInt32],
  labels : Array[String?],
  legacy : WTLegacyCtx,
) -> Result[Array[@lib.Instruction], String] {
  let out : Array[@lib.Instruction] = []
  for instr in instrs {
    let lib_instr = match
      wt_instr_to_lib(ctx, instr, local_ids, labels, legacy) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    out.push(lib_instr)
  }
  Ok(out)
}

///|
fn wt_legacy_try_to_lib(
  ctx : WTLowerCtx,
  lib_bt : @lib.BlockType,
  bt : BlockType,
  body : Array[Instruction],
  catches : Array[LegacyCatchClause],
  delegate_target : Index?,
  local_ids : Map[String, UInt32],
  labels : Array[String?],
  legacy : WTLegacyCtx,
) -> Result[@lib.Instruction, String] {
  if delegate_target is Some(idx) && !wt_legacy_delegate_valid(idx, legacy) {
    return Err("invalid legacy delegate label: \{wt_index_text(idx)}")
  }
  let try_labels = legacy.try_labels.copy()
  match bt.label {
    Some(label) => try_labels.push(label)
    None => ()
  }
  let nested_base : WTLegacyCtx = {
    catch_scope: legacy.catch_scope,
    enclosing_try_count: legacy.enclosing_try_count + 1U,
    try_labels,
  }
  let next_labels = labels.copy()
  next_labels.push(bt.label)
  let lib_body = match
    wt_instrs_to_lib(ctx, body, local_ids, next_labels, nested_base) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let lib_catches : Array[@lib.LegacyCatch] = []
  for clause in catches {
    let catch_ctx : WTLegacyCtx = {
      catch_scope: Some(
        WTLegacyCatchScope::CatchLabel(
          next_labels.length(),
          nested_base.catch_scope,
        ),
      ),
      enclosing_try_count: nested_base.enclosing_try_count,
      try_labels: nested_base.try_labels,
    }
    match clause {
      LegacyCatch(tag_idx, catch_body) => {
        let tag = match
          wt_resolve_named_or_num_index(
            tag_idx,
            ctx.tag_ids,
            ctx.next_tag_idx,
            "tag",
          ) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
        let lib_catch_body = match
          wt_instrs_to_lib(ctx, catch_body, local_ids, next_labels, catch_ctx) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
        lib_catches.push(
          @lib.LegacyCatch::tagged(
            @lib.TagIdx::new(tag),
            @lib.Expr::new(lib_catch_body),
          ),
        )
      }
      LegacyCatchAll(catch_body) => {
        let lib_catch_body = match
          wt_instrs_to_lib(ctx, catch_body, local_ids, next_labels, catch_ctx) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
        lib_catches.push(@lib.LegacyCatch::all(@lib.Expr::new(lib_catch_body)))
      }
    }
  }
  let delegate : @lib.LabelIdx? = match delegate_target {
    // Catchless legacy text propagates exceptions to the surrounding context.
    // Keep its branch label and try nesting; make that propagation explicit
    // in the core encoding, which requires a handler or a delegate.
    None if lib_catches.is_empty() => Some(@lib.LabelIdx::new(0))
    None => None
    Some(Num(depth)) => Some(@lib.LabelIdx::new(depth))
    Some(Id(name)) =>
      match wt_resolve_label(Id(name), labels) {
        Ok(depth) => Some(@lib.LabelIdx::new(depth))
        Err(e) => return Err(e)
      }
  }
  Ok(
    @lib.Instruction::try_(
      lib_bt,
      @lib.Expr::new(lib_body),
      lib_catches,
      delegate~,
    ),
  )
}

///|
fn wt_local_idx(
  idx : Index,
  local_ids : Map[String, UInt32],
) -> Result[UInt32, String] {
  match idx {
    Num(n) => Ok(n)
    Id(name) =>
      match local_ids.get(name) {
        Some(v) => Ok(v)
        None => Err("unknown local id: \{name}")
      }
  }
}

///|
fn wt_instr_to_lib(
  ctx : WTLowerCtx,
  instr : Instruction,
  local_ids : Map[String, UInt32],
  labels : Array[String?],
  legacy : WTLegacyCtx,
) -> Result[@lib.Instruction, String] {
  match instr {
    Unreachable => Ok(@lib.Instruction::unreachable_())
    Nop => Ok(@lib.Instruction::nop())
    MemoryAtomic(op, order, memory, ma) =>
      wt_memory_atomic(ctx, op, order, memory, ma)
    AtomicFence(order) =>
      Ok(@lib.Instruction::atomic_fence(order=wt_atomic_order(order)))
    Block(bt, body) => {
      let next_labels = labels.copy()
      next_labels.push(bt.label)
      let lib_body = match
        wt_instrs_to_lib(ctx, body, local_ids, next_labels, legacy) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let lib_bt = match wt_block_type(ctx, bt) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::block(lib_bt, @lib.Expr::new(lib_body)))
    }
    Loop(bt, body) => {
      let next_labels = labels.copy()
      next_labels.push(bt.label)
      let lib_body = match
        wt_instrs_to_lib(ctx, body, local_ids, next_labels, legacy) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let lib_bt = match wt_block_type(ctx, bt) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::loop_(lib_bt, @lib.Expr::new(lib_body)))
    }
    If(bt, then_body, else_body) => {
      let next_labels = labels.copy()
      next_labels.push(bt.label)
      let lib_then = match
        wt_instrs_to_lib(ctx, then_body, local_ids, next_labels, legacy) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let lib_else = match
        wt_instrs_to_lib(ctx, else_body, local_ids, next_labels, legacy) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let lib_bt = match wt_block_type(ctx, bt) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let else_opt : Array[@lib.Instruction]? = if lib_else.length() == 0 {
        None
      } else {
        Some(lib_else)
      }
      Ok(@lib.Instruction::if_(lib_bt, lib_then, else_opt))
    }
    Br(idx) => {
      let depth = match wt_resolve_label(idx, labels) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::br(@lib.LabelIdx::new(depth)))
    }
    BrIf(idx) => {
      let depth = match wt_resolve_label(idx, labels) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::br_if(@lib.LabelIdx::new(depth)))
    }
    BrOnCastDescEq(idx, source, target)
    | BrOnCastDescEqFail(idx, source, target) => {
      let depth = match wt_resolve_label(idx, labels) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      let source = match wt_value_type(ctx, source) {
        Ok(@lib.RefTypeValType(value)) => value
        Ok(_) => return Err("descriptor branch source must be a reference type")
        Err(err) => return Err(err)
      }
      let target = match wt_value_type(ctx, target) {
        Ok(@lib.RefTypeValType(value)) => value
        Ok(_) => return Err("descriptor branch target must be a reference type")
        Err(err) => return Err(err)
      }
      if instr is BrOnCastDescEq(_, _, _) {
        Ok(
          @lib.Instruction::br_on_cast_desc_eq(
            @lib.LabelIdx::new(depth),
            source,
            target,
          ),
        )
      } else {
        Ok(
          @lib.Instruction::br_on_cast_desc_eq_fail(
            @lib.LabelIdx::new(depth),
            source,
            target,
          ),
        )
      }
    }
    BrOnNull(idx) | BrOnNonNull(idx) => {
      let depth = match wt_resolve_label(idx, labels) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      if instr is BrOnNull(_) {
        Ok(@lib.Instruction::br_on_null(@lib.LabelIdx::new(depth)))
      } else {
        Ok(@lib.Instruction::br_on_non_null(@lib.LabelIdx::new(depth)))
      }
    }
    BrTable(targets, fallback) => {
      let ls : Array[@lib.LabelIdx] = []
      for target in targets {
        let depth = match wt_resolve_label(target, labels) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
        ls.push(@lib.LabelIdx::new(depth))
      }
      let fb = match wt_resolve_label(fallback, labels) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::br_table(ls, @lib.LabelIdx::new(fb)))
    }
    Return => Ok(@lib.Instruction::return_())
    Call(idx) => {
      let fi = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.func_ids,
          ctx.next_func_idx,
          "func",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::call(@lib.FuncIdx::new(fi)))
    }
    CallIndirect(table_idx, type_use) => {
      let ti = match wt_resolve_type_use(ctx, type_use) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let tbl = match
        wt_resolve_named_or_num_index(
          table_idx,
          ctx.table_ids,
          ctx.next_table_idx,
          "table",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(
        @lib.Instruction::call_indirect(
          @lib.TypeIdx::new(ti),
          @lib.TableIdx::new(tbl),
        ),
      )
    }
    ReturnCall(idx) => {
      let fi = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.func_ids,
          ctx.next_func_idx,
          "func",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::return_call(@lib.FuncIdx::new(fi)))
    }
    ReturnCallIndirect(table_idx, type_use) => {
      let ti = match wt_resolve_type_use(ctx, type_use) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let tbl = match
        wt_resolve_named_or_num_index(
          table_idx,
          ctx.table_ids,
          ctx.next_table_idx,
          "table",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(
        @lib.Instruction::return_call_indirect(
          @lib.TypeIdx::new(ti),
          @lib.TableIdx::new(tbl),
        ),
      )
    }
    CallRef(type_use) => {
      let ti = match wt_resolve_type_use(ctx, type_use) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::call_ref(@lib.TypeIdx::new(ti)))
    }
    ReturnCallRef(type_use) => {
      let ti = match wt_resolve_type_use(ctx, type_use) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::return_call_ref(@lib.TypeIdx::new(ti)))
    }
    Throw(idx) => {
      let ti = match
        wt_resolve_named_or_num_index(idx, ctx.tag_ids, ctx.next_tag_idx, "tag") {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::throw_(@lib.TagIdx::new(ti)))
    }
    ThrowRef => Ok(@lib.Instruction::throw_ref())
    Rethrow(idx) =>
      if wt_legacy_rethrow_valid(idx, legacy, labels.length()) {
        match idx {
          Num(depth) => Ok(@lib.Instruction::rethrow(@lib.LabelIdx::new(depth)))
          Id(_) => Err("named legacy rethrow labels are unsupported")
        }
      } else {
        Err("invalid legacy rethrow label: \{wt_index_text(idx)}")
      }
    TryLegacy(bt, body, catches, delegate_target) => {
      let lib_bt = match wt_block_type(ctx, bt) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      wt_legacy_try_to_lib(
        ctx, lib_bt, bt, body, catches, delegate_target, local_ids, labels, legacy,
      )
    }
    Drop => Ok(@lib.Instruction::drop())
    Select(types) => {
      let lib_types = match wt_select_types(ctx, types) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::select(types=lib_types))
    }
    TryTable(bt, catches, body) => {
      let next_labels = labels.copy()
      next_labels.push(None)
      // Catch targets belong to the enclosing scope; labels already includes
      // the implicit function label. The try_table's own label is body-only.
      let catch_labels = labels
      let lib_bt = match wt_block_type(ctx, bt) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let lib_body = match
        wt_instrs_to_lib(ctx, body, local_ids, next_labels, legacy) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let lib_catches : Array[@lib.Catch] = []
      for catch_clause in catches {
        match catch_clause {
          CatchClause(tag_idx, label_idx) => {
            let ti = match
              wt_resolve_named_or_num_index(
                tag_idx,
                ctx.tag_ids,
                ctx.next_tag_idx,
                "tag",
              ) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            let li = match wt_resolve_label(label_idx, catch_labels) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            lib_catches.push(
              @lib.Catch::new(@lib.TagIdx::new(ti), @lib.LabelIdx::new(li)),
            )
          }
          CatchRefClause(tag_idx, label_idx) => {
            let ti = match
              wt_resolve_named_or_num_index(
                tag_idx,
                ctx.tag_ids,
                ctx.next_tag_idx,
                "tag",
              ) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            let li = match wt_resolve_label(label_idx, catch_labels) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            lib_catches.push(
              @lib.Catch::ref_(@lib.TagIdx::new(ti), @lib.LabelIdx::new(li)),
            )
          }
          CatchAllClause(label_idx) => {
            let li = match wt_resolve_label(label_idx, catch_labels) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            lib_catches.push(@lib.Catch::all(@lib.LabelIdx::new(li)))
          }
          CatchAllRefClause(label_idx) => {
            let li = match wt_resolve_label(label_idx, catch_labels) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            lib_catches.push(@lib.Catch::all_ref(@lib.LabelIdx::new(li)))
          }
        }
      }
      Ok(
        @lib.Instruction::try_table(
          lib_bt,
          lib_catches,
          @lib.Expr::new(lib_body),
        ),
      )
    }
    LocalGet(idx) => {
      let li = match wt_local_idx(idx, local_ids) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::local_get(@lib.LocalIdx::new(li)))
    }
    LocalSet(idx) => {
      let li = match wt_local_idx(idx, local_ids) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::local_set(@lib.LocalIdx::new(li)))
    }
    LocalTee(idx) => {
      let li = match wt_local_idx(idx, local_ids) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::local_tee(@lib.LocalIdx::new(li)))
    }
    GlobalGet(idx) => {
      let gi = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.global_ids,
          ctx.next_global_idx,
          "global",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::global_get(@lib.GlobalIdx::new(gi)))
    }
    GlobalSet(idx) => {
      let gi = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.global_ids,
          ctx.next_global_idx,
          "global",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::global_set(@lib.GlobalIdx::new(gi)))
    }
    TableGet(idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.table_ids,
          ctx.next_table_idx,
          "table",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::table_get(@lib.TableIdx::new(ti)))
    }
    TableSet(idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.table_ids,
          ctx.next_table_idx,
          "table",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::table_set(@lib.TableIdx::new(ti)))
    }
    TableSize(idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.table_ids,
          ctx.next_table_idx,
          "table",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::table_size(@lib.TableIdx::new(ti)))
    }
    TableGrow(idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.table_ids,
          ctx.next_table_idx,
          "table",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::table_grow(@lib.TableIdx::new(ti)))
    }
    TableFill(idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.table_ids,
          ctx.next_table_idx,
          "table",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::table_fill(@lib.TableIdx::new(ti)))
    }
    TableCopy(dst, src) => {
      let dsti = match
        wt_resolve_named_or_num_index(
          dst,
          ctx.table_ids,
          ctx.next_table_idx,
          "table",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let srci = match
        wt_resolve_named_or_num_index(
          src,
          ctx.table_ids,
          ctx.next_table_idx,
          "table",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(
        @lib.Instruction::table_copy(
          @lib.TableIdx::new(dsti),
          @lib.TableIdx::new(srci),
        ),
      )
    }
    TableInit(table_idx, elem_idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          table_idx,
          ctx.table_ids,
          ctx.next_table_idx,
          "table",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let ei = match
        wt_resolve_named_or_num_index(
          elem_idx,
          ctx.elem_ids,
          ctx.next_elem_idx,
          "elem",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(
        @lib.Instruction::table_init(
          @lib.ElemIdx::new(ei),
          @lib.TableIdx::new(ti),
        ),
      )
    }
    ElemDrop(idx) => {
      let ei = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.elem_ids,
          ctx.next_elem_idx,
          "elem",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::elem_drop(@lib.ElemIdx::new(ei)))
    }
    Load(op, ma) | Store(op, ma) => wt_load_store(op, ma)
    ArrayLoad(op, ti, ma) | ArrayStore(op, ti, ma) =>
      wt_array_memory(ctx, op, ti, ma)
    MemorySize => Ok(@lib.Instruction::memory_size(@lib.MemIdx::new(0)))
    MemoryGrow => Ok(@lib.Instruction::memory_grow(@lib.MemIdx::new(0)))
    MemoryFill => Ok(@lib.Instruction::memory_fill(@lib.MemIdx::new(0)))
    MemoryCopy =>
      Ok(
        @lib.Instruction::memory_copy(@lib.MemIdx::new(0), @lib.MemIdx::new(0)),
      )
    MemoryInit(idx) => {
      let di = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.data_ids,
          ctx.next_data_idx,
          "data",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(
        @lib.Instruction::memory_init(
          @lib.DataIdx::new(di),
          @lib.MemIdx::new(0),
        ),
      )
    }
    DataDrop(idx) => {
      let di = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.data_ids,
          ctx.next_data_idx,
          "data",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::data_drop(@lib.DataIdx::new(di)))
    }
    Const(op, text) =>
      match op {
        I32Const =>
          match wt_parse_i32(text) {
            Ok(v) => Ok(@lib.Instruction::i32_const(v))
            Err(e) => Err(e)
          }
        I64Const =>
          match wt_parse_i64(text) {
            Ok(v) => Ok(@lib.Instruction::i64_const(v))
            Err(e) => Err(e)
          }
        F32Const =>
          match wt_parse_f32(text) {
            Ok(v) => Ok(@lib.Instruction::f32_const(v))
            Err(e) => Err(e)
          }
        F64Const =>
          match wt_parse_f64(text) {
            Ok(v) => Ok(@lib.Instruction::f64_const(v))
            Err(e) => Err(e)
          }
        _ => wt_unsupported("\{op}")
      }
    Unary(op)
    | Binary(op)
    | Test(op)
    | Compare(op)
    | Convert(op)
    | SimdUnary(op)
    | SimdBinary(op)
    | SimdTernary(op)
    | SimdShift(op)
    | SimdTest(op)
    | SimdCompare(op)
    | SimdConvert(op)
    | SimdRelaxed(op) => wt_numeric_noarg(op)
    RefNull(vt) =>
      match wt_value_type(ctx, vt) {
        Ok(@lib.RefTypeValType(rt)) =>
          Ok(@lib.Instruction::ref_null_type(rt.make_nullable()))
        Ok(_) => Err("unsupported ref.null type: \{vt}")
        Err(e) => Err(e)
      }
    RefIsNull => Ok(@lib.Instruction::ref_is_null())
    RefFunc(idx) => {
      let fi = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.func_ids,
          ctx.next_func_idx,
          "func",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::ref_func(@lib.FuncIdx::new(fi)))
    }
    StringConst(bytes) => Ok(@lib.Instruction::string_const(bytes))
    StringMeasureWtf16 => Ok(@lib.Instruction::string_measure_wtf16())
    StringConcat => Ok(@lib.Instruction::string_concat())
    StringEq => Ok(@lib.Instruction::string_eq())
    StringAsWtf16 => Ok(@lib.Instruction::string_as_wtf16())
    StringViewWtf16GetCodeunit =>
      Ok(@lib.Instruction::stringview_wtf16_get_codeunit())
    StringViewWtf16Slice => Ok(@lib.Instruction::stringview_wtf16_slice())
    StringNewUtf8Array => Ok(@lib.Instruction::string_new_utf8_array())
    StringNewWtf16Array => Ok(@lib.Instruction::string_new_wtf16_array())
    StringEncodeUtf8Array => Ok(@lib.Instruction::string_encode_utf8_array())
    StringEncodeWtf16Array => Ok(@lib.Instruction::string_encode_wtf16_array())
    StringNewLossyUtf8Array =>
      Ok(@lib.Instruction::string_new_lossy_utf8_array())
    StringNewWtf8Array => Ok(@lib.Instruction::string_new_wtf8_array())
    StringEncodeLossyUtf8Array =>
      Ok(@lib.Instruction::string_encode_lossy_utf8_array())
    StringEncodeWtf8Array => Ok(@lib.Instruction::string_encode_wtf8_array())
    StructNew(idx)
    | StructNewDefault(idx)
    | StructNewDesc(idx)
    | StructNewDefaultDesc(idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let type_idx = @lib.TypeIdx::new(ti)
      match instr {
        StructNew(_) => Ok(@lib.Instruction::struct_new(type_idx))
        StructNewDefault(_) =>
          Ok(@lib.Instruction::struct_new_default(type_idx))
        StructNewDesc(_) => Ok(@lib.Instruction::struct_new_desc(type_idx))
        StructNewDefaultDesc(_) =>
          Ok(@lib.Instruction::struct_new_default_desc(type_idx))
        _ => Err("internal error: unexpected struct allocation instruction")
      }
    }
    StructGet(idx, field_idx)
    | StructGetS(idx, field_idx)
    | StructGetU(idx, field_idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let type_idx = @lib.TypeIdx::new(ti)
      let lib_field_idx = @lib.U32(field_idx)
      match instr {
        StructGet(_, _) =>
          Ok(@lib.Instruction::struct_get(type_idx, lib_field_idx))
        StructGetS(_, _) =>
          Ok(@lib.Instruction::struct_get_s(type_idx, lib_field_idx))
        StructGetU(_, _) =>
          Ok(@lib.Instruction::struct_get_u(type_idx, lib_field_idx))
        _ => Err("internal error: unexpected struct access instruction")
      }
    }
    WaitqueueNew => Ok(@lib.Instruction::waitqueue_new())
    ArrayNew(idx)
    | ArrayNewDefault(idx)
    | ArrayNewFixed(idx, _)
    | ArrayNewData(idx, _)
    | ArrayNewElem(idx, _) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      let type_idx = @lib.TypeIdx::new(ti)
      match instr {
        ArrayNew(_) => Ok(@lib.Instruction::array_new(type_idx))
        ArrayNewDefault(_) => Ok(@lib.Instruction::array_new_default(type_idx))
        ArrayNewFixed(_, length) =>
          Ok(@lib.Instruction::array_new_fixed(type_idx, @lib.U32(length)))
        ArrayNewData(_, data_idx) =>
          match
            wt_resolve_named_or_num_index(
              data_idx,
              ctx.data_ids,
              ctx.next_data_idx,
              "data",
            ) {
            Ok(value) =>
              Ok(
                @lib.Instruction::array_new_data(
                  type_idx,
                  @lib.DataIdx::new(value),
                ),
              )
            Err(err) => Err(err)
          }
        ArrayNewElem(_, elem_idx) =>
          match
            wt_resolve_named_or_num_index(
              elem_idx,
              ctx.elem_ids,
              ctx.next_elem_idx,
              "elem",
            ) {
            Ok(value) =>
              Ok(
                @lib.Instruction::array_new_elem(
                  type_idx,
                  @lib.ElemIdx::new(value),
                ),
              )
            Err(err) => Err(err)
          }
        _ => Err("internal error: unexpected array allocation instruction")
      }
    }
    WaitqueueNotify => Ok(@lib.Instruction::waitqueue_notify())
    Publish => Ok(@lib.Instruction::publish())
    StructWait(idx, field_idx) =>
      match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(ti) =>
          Ok(
            @lib.Instruction::struct_wait(
              @lib.TypeIdx::new(ti),
              @lib.U32(field_idx),
            ),
          )
        Err(err) => Err(err)
      }
    StructAtomicGet(order, idx, field_idx)
    | StructAtomicGetS(order, idx, field_idx)
    | StructAtomicGetU(order, idx, field_idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let type_idx = @lib.TypeIdx::new(ti)
      let lib_field_idx = @lib.U32(field_idx)
      let lib_order = match order {
        SeqCst => @lib.AtomicOrder::seq_cst()
        AcqRel => @lib.AtomicOrder::acq_rel()
        Relaxed => @lib.AtomicOrder::relaxed()
      }
      match instr {
        StructAtomicGet(_, _, _) =>
          Ok(
            @lib.Instruction::struct_atomic_get(
              lib_order, type_idx, lib_field_idx,
            ),
          )
        StructAtomicGetS(_, _, _) =>
          Ok(
            @lib.Instruction::struct_atomic_get_s(
              lib_order, type_idx, lib_field_idx,
            ),
          )
        StructAtomicGetU(_, _, _) =>
          Ok(
            @lib.Instruction::struct_atomic_get_u(
              lib_order, type_idx, lib_field_idx,
            ),
          )
        _ => Err("internal error: unexpected struct atomic access instruction")
      }
    }
    StructAtomicSet(order, idx, field_idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      Ok(
        @lib.Instruction::struct_atomic_set(
          wt_atomic_order(order),
          @lib.TypeIdx::new(ti),
          @lib.U32(field_idx),
        ),
      )
    }
    ArrayAtomicSet(order, idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      Ok(
        @lib.Instruction::array_atomic_set(
          wt_atomic_order(order),
          @lib.TypeIdx::new(ti),
        ),
      )
    }
    StructAtomicRmw(order, op, idx, field_idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(
        @lib.Instruction::struct_atomic_rmw(
          op,
          @lib.TypeIdx::new(ti),
          @lib.U32(field_idx),
          order=wt_atomic_order(order),
        ),
      )
    }
    StructAtomicCmpxchg(order, idx, field_idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(
        @lib.Instruction::struct_atomic_cmpxchg(
          @lib.TypeIdx::new(ti),
          @lib.U32(field_idx),
          order=wt_atomic_order(order),
        ),
      )
    }
    ArrayAtomicGet(order, idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      Ok(
        @lib.Instruction::array_atomic_get(
          wt_atomic_order(order),
          @lib.TypeIdx::new(ti),
        ),
      )
    }
    ArrayAtomicGetS(order, idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      Ok(
        @lib.Instruction::array_atomic_get_s(
          wt_atomic_order(order),
          @lib.TypeIdx::new(ti),
        ),
      )
    }
    ArrayAtomicGetU(order, idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(value) => value
        Err(err) => return Err(err)
      }
      Ok(
        @lib.Instruction::array_atomic_get_u(
          wt_atomic_order(order),
          @lib.TypeIdx::new(ti),
        ),
      )
    }
    ArrayAtomicRmw(order, op, idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(
        @lib.Instruction::array_atomic_rmw(
          op,
          @lib.TypeIdx::new(ti),
          order=wt_atomic_order(order),
        ),
      )
    }
    ArrayAtomicCmpxchg(order, idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(
        @lib.Instruction::array_atomic_cmpxchg(
          @lib.TypeIdx::new(ti),
          order=wt_atomic_order(order),
        ),
      )
    }
    RefGetDesc(idx) =>
      match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(v) => Ok(@lib.Instruction::ref_get_desc(@lib.TypeIdx::new(v)))
        Err(e) => Err(e)
      }
    RefTestDesc(nullable, idx) | RefCastDescEq(nullable, idx) => {
      let ti = match
        wt_resolve_named_or_num_index(
          idx,
          ctx.type_ids,
          ctx.type_recs.length().reinterpret_as_uint(),
          "type",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let ht = @lib.HeapType::new(@lib.TypeIdx::new(ti))
      if instr is RefTestDesc(_, _) {
        Ok(@lib.Instruction::ref_test_desc(nullable, ht))
      } else {
        Ok(@lib.Instruction::ref_cast_desc_eq(nullable, ht))
      }
    }
    V128Const(v) => wt_v128_const_instr(v)
    V128Load(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load(memarg))
    }
    V128Load8x8S(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load8x8s(memarg))
    }
    V128Load8x8U(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load8x8u(memarg))
    }
    V128Load16x4S(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load16x4s(memarg))
    }
    V128Load16x4U(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load16x4u(memarg))
    }
    V128Load32x2S(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load32x2s(memarg))
    }
    V128Load32x2U(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load32x2u(memarg))
    }
    V128Load8Splat(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load8_splat(memarg))
    }
    V128Load16Splat(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load16_splat(memarg))
    }
    V128Load32Splat(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load32_splat(memarg))
    }
    V128Load64Splat(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load64_splat(memarg))
    }
    V128Load32Zero(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load32_zero(memarg))
    }
    V128Load64Zero(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load64_zero(memarg))
    }
    V128Store(ma) => {
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_store(memarg))
    }
    I8x16Shuffle(lanes) => {
      let li0 = match wt_shuffle_lane(lanes.lanes[0]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li1 = match wt_shuffle_lane(lanes.lanes[1]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li2 = match wt_shuffle_lane(lanes.lanes[2]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li3 = match wt_shuffle_lane(lanes.lanes[3]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li4 = match wt_shuffle_lane(lanes.lanes[4]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li5 = match wt_shuffle_lane(lanes.lanes[5]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li6 = match wt_shuffle_lane(lanes.lanes[6]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li7 = match wt_shuffle_lane(lanes.lanes[7]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li8 = match wt_shuffle_lane(lanes.lanes[8]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li9 = match wt_shuffle_lane(lanes.lanes[9]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li10 = match wt_shuffle_lane(lanes.lanes[10]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li11 = match wt_shuffle_lane(lanes.lanes[11]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li12 = match wt_shuffle_lane(lanes.lanes[12]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li13 = match wt_shuffle_lane(lanes.lanes[13]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li14 = match wt_shuffle_lane(lanes.lanes[14]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let li15 = match wt_shuffle_lane(lanes.lanes[15]) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(
        @lib.Instruction::i8x16_shuffle(
          li0, li1, li2, li3, li4, li5, li6, li7, li8, li9, li10, li11, li12, li13,
          li14, li15,
        ),
      )
    }
    I8x16Swizzle => Ok(@lib.Instruction::i8x16_swizzle())
    I8x16Splat => Ok(@lib.Instruction::i8x16_splat())
    I16x8Splat => Ok(@lib.Instruction::i16x8_splat())
    I32x4Splat => Ok(@lib.Instruction::i32x4_splat())
    I64x2Splat => Ok(@lib.Instruction::i64x2_splat())
    F32x4Splat => Ok(@lib.Instruction::f32x4_splat())
    F64x2Splat => Ok(@lib.Instruction::f64x2_splat())
    V128Load8Lane(ma, lane) => {
      let li = match wt_lane_idx("v128.load8_lane", lane, 15U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load8_lane(memarg, li))
    }
    V128Load16Lane(ma, lane) => {
      let li = match wt_lane_idx("v128.load16_lane", lane, 7U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load16_lane(memarg, li))
    }
    V128Load32Lane(ma, lane) => {
      let li = match wt_lane_idx("v128.load32_lane", lane, 3U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load32_lane(memarg, li))
    }
    V128Load64Lane(ma, lane) => {
      let li = match wt_lane_idx("v128.load64_lane", lane, 1U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_load64_lane(memarg, li))
    }
    V128Store8Lane(ma, lane) => {
      let li = match wt_lane_idx("v128.store8_lane", lane, 15U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_store8_lane(memarg, li))
    }
    V128Store16Lane(ma, lane) => {
      let li = match wt_lane_idx("v128.store16_lane", lane, 7U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_store16_lane(memarg, li))
    }
    V128Store32Lane(ma, lane) => {
      let li = match wt_lane_idx("v128.store32_lane", lane, 3U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_store32_lane(memarg, li))
    }
    V128Store64Lane(ma, lane) => {
      let li = match wt_lane_idx("v128.store64_lane", lane, 1U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let memarg = match wt_mem_arg(ma) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::v128_store64_lane(memarg, li))
    }
    I8x16ExtractLaneS(lane) => {
      let li = match wt_lane_idx("i8x16.extract_lane_s", lane, 15U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::i8x16_extract_lane_s(li))
    }
    I8x16ExtractLaneU(lane) => {
      let li = match wt_lane_idx("i8x16.extract_lane_u", lane, 15U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::i8x16_extract_lane_u(li))
    }
    I8x16ReplaceLane(lane) => {
      let li = match wt_lane_idx("i8x16.replace_lane", lane, 15U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::i8x16_replace_lane(li))
    }
    I16x8ExtractLaneS(lane) => {
      let li = match wt_lane_idx("i16x8.extract_lane_s", lane, 7U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::i16x8_extract_lane_s(li))
    }
    I16x8ExtractLaneU(lane) => {
      let li = match wt_lane_idx("i16x8.extract_lane_u", lane, 7U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::i16x8_extract_lane_u(li))
    }
    I16x8ReplaceLane(lane) => {
      let li = match wt_lane_idx("i16x8.replace_lane", lane, 7U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::i16x8_replace_lane(li))
    }
    I32x4ExtractLane(lane) => {
      let li = match wt_lane_idx("i32x4.extract_lane", lane, 3U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::i32x4_extract_lane(li))
    }
    I32x4ReplaceLane(lane) => {
      let li = match wt_lane_idx("i32x4.replace_lane", lane, 3U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::i32x4_replace_lane(li))
    }
    I64x2ExtractLane(lane) => {
      let li = match wt_lane_idx("i64x2.extract_lane", lane, 1U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::i64x2_extract_lane(li))
    }
    I64x2ReplaceLane(lane) => {
      let li = match wt_lane_idx("i64x2.replace_lane", lane, 1U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::i64x2_replace_lane(li))
    }
    F32x4ExtractLane(lane) => {
      let li = match wt_lane_idx("f32x4.extract_lane", lane, 3U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::f32x4_extract_lane(li))
    }
    F32x4ReplaceLane(lane) => {
      let li = match wt_lane_idx("f32x4.replace_lane", lane, 3U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::f32x4_replace_lane(li))
    }
    F64x2ExtractLane(lane) => {
      let li = match wt_lane_idx("f64x2.extract_lane", lane, 1U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::f64x2_extract_lane(li))
    }
    F64x2ReplaceLane(lane) => {
      let li = match wt_lane_idx("f64x2.replace_lane", lane, 1U) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      Ok(@lib.Instruction::f64x2_replace_lane(li))
    }
  }
}

///|
fn wt_lower_module(mod : Module) -> Result[@lib.Module, String] {
  let ctx = wt_new_ctx()
  let mut next_type_idx = 0U
  for field in mod.fields {
    match field {
      TypeField(td) =>
        match td.id {
          Some(id) => {
            ctx.type_ids[id] = next_type_idx
            next_type_idx += 1
          }
          None => next_type_idx += 1
        }
      RecField(types) =>
        for td in types {
          match td.id {
            Some(id) => {
              ctx.type_ids[id] = next_type_idx
              next_type_idx += 1
            }
            None => next_type_idx += 1
          }
        }
      _ => ()
    }
  }
  for field in mod.fields {
    match field {
      TypeField(td) => {
        let rec = match wt_type_def(ctx, td) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
        ctx.type_recs.push(rec)
      }
      RecField(types) => {
        let rec = match wt_type_group(ctx, types) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
        ctx.type_recs.push(rec)
      }
      _ => ()
    }
  }

  let imports : Array[@lib.Import] = []
  let func_annotation_entries : Array[@lib.FuncAnnotationAssoc] = []
  let func_name_entries : Array[@lib.NameAssoc] = []
  let func_defs : Array[(Func, UInt32)] = []
  let table_defs_raw : Array[Table] = []
  let mem_defs_raw : Array[Memory] = []
  let global_defs_raw : Array[Global] = []
  let tag_defs_raw : Array[(Tag, UInt32)] = []
  let export_defs : Array[Export] = []
  let start_defs : Array[Start] = []
  let elem_defs_raw : Array[ElemSegment] = []
  let data_defs_raw : Array[DataSegment] = []

  for field in mod.fields {
    match field {
      TypeField(_) | RecField(_) => ()
      ImportField(im) =>
        match im.desc {
          Func(type_use) => {
            let ti = match wt_resolve_type_use(ctx, type_use) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            if im.annotations.length() > 0 {
              func_annotation_entries.push(
                @lib.FuncAnnotationAssoc::new(
                  ctx.next_func_idx,
                  wt_func_annotations(im.annotations),
                ),
              )
            }
            imports.push(
              @lib.Import::new(
                @lib.Name::new(im.module_name),
                @lib.Name::new(im.field_name),
                @lib.ExternType::func(@lib.TypeIdx::new(ti)),
              ),
            )
            wt_push_func_name(func_name_entries, ctx.next_func_idx, im.id)
            match im.id {
              Some(id) => ctx.func_ids[id] = ctx.next_func_idx
              None => ()
            }
            ctx.next_func_idx += 1
          }
          Table(tt) => {
            let table_heap_type = match wt_ref_heap_type(ctx, tt.elem_type) {
              Ok(Some(ht)) => ht
              Ok(None) => @lib.HeapType::abs(@lib.AbsHeapType::func())
              Err(e) => return Err(e)
            }
            let table_type = @lib.TableType::new(
              @lib.RefType::new(true, table_heap_type),
              wt_limits(tt.limits),
            )
            imports.push(
              @lib.Import::new(
                @lib.Name::new(im.module_name),
                @lib.Name::new(im.field_name),
                @lib.ExternType::table(table_type),
              ),
            )
            match im.id {
              Some(id) => ctx.table_ids[id] = ctx.next_table_idx
              None => ()
            }
            ctx.next_table_idx += 1
          }
          Memory(mt) => {
            imports.push(
              @lib.Import::new(
                @lib.Name::new(im.module_name),
                @lib.Name::new(im.field_name),
                @lib.ExternType::mem(
                  match wt_memory_type(mt) {
                    Ok(value) => value
                    Err(err) => return Err(err)
                  },
                ),
              ),
            )
            match im.id {
              Some(id) => ctx.mem_ids[id] = ctx.next_mem_idx
              None => ()
            }
            ctx.next_mem_idx += 1
          }
          Global(gt) => {
            let global_vt = match wt_value_type(ctx, gt.val_type) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            imports.push(
              @lib.Import::new(
                @lib.Name::new(im.module_name),
                @lib.Name::new(im.field_name),
                @lib.ExternType::global(
                  @lib.GlobalType::new(global_vt, gt.mutable_),
                ),
              ),
            )
            match im.id {
              Some(id) => ctx.global_ids[id] = ctx.next_global_idx
              None => ()
            }
            ctx.next_global_idx += 1
          }
          Tag(type_use) => {
            let ti = match wt_resolve_type_use(ctx, type_use) {
              Ok(v) => v
              Err(e) => return Err(e)
            }
            imports.push(
              @lib.Import::new(
                @lib.Name::new(im.module_name),
                @lib.Name::new(im.field_name),
                @lib.ExternType::tag(@lib.TagType::new(@lib.TypeIdx::new(ti))),
              ),
            )
            match im.id {
              Some(id) => ctx.tag_ids[id] = ctx.next_tag_idx
              None => ()
            }
            ctx.tag_type_idxs.push(ti)
            ctx.next_tag_idx += 1
          }
        }
      FuncField(f) => {
        let func_idx = ctx.next_func_idx
        if f.annotations.length() > 0 {
          func_annotation_entries.push(
            @lib.FuncAnnotationAssoc::new(
              func_idx,
              wt_func_annotations(f.annotations),
            ),
          )
        }
        for inline_export in f.exports {
          match inline_export {
            Export(name) =>
              export_defs.push({ name, desc: ExportDesc::Func(Num(func_idx)), })
          }
        }
        let ti = match wt_resolve_type_use(ctx, f.type_use) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
        func_defs.push((f, ti))
        wt_push_func_name(func_name_entries, ctx.next_func_idx, f.id)
        match f.id {
          Some(id) => ctx.func_ids[id] = ctx.next_func_idx
          None => ()
        }
        ctx.next_func_idx += 1
      }
      TableField(t) => {
        let table_idx = ctx.next_table_idx
        for inline_export in t.exports {
          match inline_export {
            Export(name) =>
              export_defs.push({
                name,
                desc: ExportDesc::Table(Num(table_idx)),
              })
          }
        }
        match t.elem_init {
          Some(func_indices) => {
            let elem_exprs = t.elem_exprs.unwrap_or([])
            let mut has_non_ref_func_item = false
            let mut i = 0
            while i < elem_exprs.length() {
              if wt_elem_item_ref_func_index(elem_exprs[i]) is None {
                has_non_ref_func_item = true
              }
              i += 1
            }
            let elem_type : ValueType? = match t.elem_init_type {
              Some(v) => Some(v)
              None =>
                if has_non_ref_func_item {
                  Some(t.table_type.elem_type)
                } else {
                  None
                }
            }
            elem_defs_raw.push({
              declarative: false,
              id: None,
              table_index: Num(table_idx),
              offset: t.elem_offset.unwrap_or([Const(I32Const, "0")]),
              func_indices,
              elem_exprs,
              elem_type,
            })
            ctx.next_elem_idx += 1
          }
          None => ()
        }
        table_defs_raw.push(t)
        match t.id {
          Some(id) => ctx.table_ids[id] = ctx.next_table_idx
          None => ()
        }
        ctx.next_table_idx += 1
      }
      MemoryField(m) => {
        let mem_idx = ctx.next_mem_idx
        for inline_export in m.exports {
          match inline_export {
            Export(name) =>
              export_defs.push({ name, desc: ExportDesc::Memory(Num(mem_idx)), })
          }
        }
        mem_defs_raw.push(m)
        match m.id {
          Some(id) => ctx.mem_ids[id] = ctx.next_mem_idx
          None => ()
        }
        ctx.next_mem_idx += 1
      }
      GlobalField(g) => {
        let global_idx = ctx.next_global_idx
        for inline_export in g.exports {
          match inline_export {
            Export(name) =>
              export_defs.push({
                name,
                desc: ExportDesc::Global(Num(global_idx)),
              })
          }
        }
        global_defs_raw.push(g)
        match g.id {
          Some(id) => ctx.global_ids[id] = ctx.next_global_idx
          None => ()
        }
        ctx.next_global_idx += 1
      }
      TagField(t) => {
        let ti = match wt_resolve_type_use(ctx, t.type_use) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
        let tag_idx = ctx.next_tag_idx
        for inline_export in t.exports {
          match inline_export {
            Export(name) =>
              export_defs.push({ name, desc: ExportDesc::Tag(Num(tag_idx)), })
          }
        }
        tag_defs_raw.push((t, ti))
        match t.id {
          Some(id) => ctx.tag_ids[id] = ctx.next_tag_idx
          None => ()
        }
        ctx.tag_type_idxs.push(ti)
        ctx.next_tag_idx += 1
      }
      ExportField(e) => export_defs.push(e)
      StartField(s) => start_defs.push(s)
      ElemField(e) => {
        elem_defs_raw.push(e)
        match e.id {
          Some(id) => ctx.elem_ids[id] = ctx.next_elem_idx
          None => ()
        }
        ctx.next_elem_idx += 1
      }
      DataField(d) => {
        data_defs_raw.push(d)
        match d.id {
          Some(id) => ctx.data_ids[id] = ctx.next_data_idx
          None => ()
        }
        ctx.next_data_idx += 1
      }
    }
  }

  let func_type_idxs : Array[@lib.TypeIdx] = []
  let code_funcs : Array[@lib.Func] = []
  for entry in func_defs {
    let (f, type_idx) = entry
    func_type_idxs.push(@lib.TypeIdx::new(type_idx))
    let local_ids : Map[String, UInt32] = Map([])
    let mut param_idx = 0U
    for param_id in f.type_use.param_ids {
      match param_id {
        Some(id) => local_ids[id] = param_idx
        None => ()
      }
      param_idx += 1
    }
    let base = match f.type_use.type_index {
      Some(_) =>
        match wt_type_param_count(ctx, type_idx) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
      None => f.type_use.func_type.params.length().reinterpret_as_uint()
    }
    let mut offset = 0U
    for loc in f.locals {
      match loc.id {
        Some(id) => local_ids[id] = base + offset
        None => ()
      }
      offset += 1
    }
    let body = match
      wt_instrs_to_lib(ctx, f.body, local_ids, [None], wt_default_legacy_ctx()) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    let local_types : Array[@lib.ValType] = []
    for loc in f.locals {
      let local_vt = match wt_value_type(ctx, loc.val_type) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      local_types.push(local_vt)
    }
    code_funcs.push(
      @lib.Func::new(@lib.Locals::from_types(local_types), @lib.Expr::new(body)),
    )
  }

  let table_defs : Array[@lib.Table] = []
  for t in table_defs_raw {
    let ht = match wt_ref_heap_type(ctx, t.table_type.elem_type) {
      Ok(Some(v)) => v
      Ok(None) => @lib.HeapType::abs(@lib.AbsHeapType::func())
      Err(e) => return Err(e)
    }
    table_defs.push(
      @lib.Table::new(
        @lib.TableType::new(
          @lib.RefType::new(true, ht),
          wt_limits(t.table_type.limits),
        ),
        None,
      ),
    )
  }

  let mem_defs : Array[@lib.MemType] = []
  for m in mem_defs_raw {
    mem_defs.push(
      match wt_memory_type(m.memory_type) {
        Ok(value) => value
        Err(err) => return Err(err)
      },
    )
  }

  let global_defs : Array[@lib.Global] = []
  for g in global_defs_raw {
    let init = match
      wt_instrs_to_lib(ctx, g.init, Map([]), [], wt_default_legacy_ctx()) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    let global_vt = match wt_value_type(ctx, g.global_type.val_type) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    global_defs.push(
      @lib.Global::new(
        @lib.GlobalType::new(global_vt, g.global_type.mutable_),
        @lib.Expr::new(init),
      ),
    )
  }

  let tag_defs : Array[@lib.TagType] = []
  for entry in tag_defs_raw {
    let (_, ti) = entry
    tag_defs.push(@lib.TagType::new(@lib.TypeIdx::new(ti)))
  }

  let exports : Array[@lib.Export] = []
  for ex in export_defs {
    let ext_idx = match ex.desc {
      Func(idx) =>
        match
          wt_resolve_named_or_num_index(
            idx,
            ctx.func_ids,
            ctx.next_func_idx,
            "func",
          ) {
          Ok(v) => @lib.ExternIdx::func(@lib.FuncIdx::new(v))
          Err(e) => return Err(e)
        }
      Table(idx) =>
        match
          wt_resolve_named_or_num_index(
            idx,
            ctx.table_ids,
            ctx.next_table_idx,
            "table",
          ) {
          Ok(v) => @lib.ExternIdx::table(@lib.TableIdx::new(v))
          Err(e) => return Err(e)
        }
      Memory(idx) =>
        match
          wt_resolve_named_or_num_index(
            idx,
            ctx.mem_ids,
            ctx.next_mem_idx,
            "mem",
          ) {
          Ok(v) => @lib.ExternIdx::mem(@lib.MemIdx::new(v))
          Err(e) => return Err(e)
        }
      Global(idx) =>
        match
          wt_resolve_named_or_num_index(
            idx,
            ctx.global_ids,
            ctx.next_global_idx,
            "global",
          ) {
          Ok(v) => @lib.ExternIdx::global(@lib.GlobalIdx::new(v))
          Err(e) => return Err(e)
        }
      Tag(idx) =>
        match
          wt_resolve_named_or_num_index(
            idx,
            ctx.tag_ids,
            ctx.next_tag_idx,
            "tag",
          ) {
          Ok(v) => @lib.ExternIdx::tag(@lib.TagIdx::new(v))
          Err(e) => return Err(e)
        }
    }
    exports.push(@lib.Export::new(@lib.Name::new(ex.name), ext_idx))
  }

  let mut start : @lib.StartSec? = None
  if start_defs.length() > 0 {
    let s = start_defs[start_defs.length() - 1]
    let fi = match
      wt_resolve_named_or_num_index(
        s.func_index,
        ctx.func_ids,
        ctx.next_func_idx,
        "func",
      ) {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    start = Some(@lib.StartSec::new(@lib.FuncIdx::new(fi)))
  }

  let elems : Array[@lib.Elem] = []
  for e in elem_defs_raw {
    let funcs_from_indices : Array[@lib.FuncIdx] = []
    for fidx in e.func_indices {
      let fi = match
        wt_resolve_named_or_num_index(
          fidx,
          ctx.func_ids,
          ctx.next_func_idx,
          "func",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      funcs_from_indices.push(@lib.FuncIdx::new(fi))
    }
    let mode = if e.declarative {
      @lib.ElemMode::declarative()
    } else if e.offset.length() == 0 {
      @lib.ElemMode::passive()
    } else {
      let table_idx = match
        wt_resolve_named_or_num_index(
          e.table_index,
          ctx.table_ids,
          ctx.next_table_idx,
          "table",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let offset = match
        wt_instrs_to_lib(ctx, e.offset, Map([]), [], wt_default_legacy_ctx()) {
        Ok(v) => @lib.Expr::new(v)
        Err(e) => return Err(e)
      }
      @lib.ElemMode::active(@lib.TableIdx::new(table_idx), offset)
    }
    let elem_type_hint = e.elem_type
    let elem_exprs = e.elem_exprs
    let funcs_from_items : Array[@lib.FuncIdx] = []
    let mut has_non_ref_func_item = false
    let mut has_explicit_typed_item = false
    let mut item_i = 0
    while item_i < elem_exprs.length() {
      let item = elem_exprs[item_i]
      if item.explicit_item {
        has_explicit_typed_item = true
      }
      match wt_elem_item_ref_func_index(item) {
        Some(idx) => {
          let fi = match
            wt_resolve_named_or_num_index(
              idx,
              ctx.func_ids,
              ctx.next_func_idx,
              "func",
            ) {
            Ok(v) => v
            Err(e) => return Err(e)
          }
          funcs_from_items.push(@lib.FuncIdx::new(fi))
        }
        None => has_non_ref_func_item = true
      }
      item_i += 1
    }

    let has_explicit_typed_intent = match elem_type_hint {
      Some(_) => true
      None => has_explicit_typed_item
    }

    if !has_non_ref_func_item && !has_explicit_typed_intent {
      let funcs : Array[@lib.FuncIdx] = []
      for fi in funcs_from_indices {
        funcs.push(fi)
      }
      for fi in funcs_from_items {
        funcs.push(fi)
      }
      elems.push(@lib.Elem::new(mode, @lib.ElemKind::funcs(funcs)))
      continue
    }

    let exprs : Array[@lib.Expr] = []
    for fi in funcs_from_indices {
      exprs.push(@lib.Expr::new([@lib.Instruction::ref_func(fi)]))
    }
    let mut expr_i = 0
    while expr_i < elem_exprs.length() {
      let item = elem_exprs[expr_i]
      match wt_elem_item_ref_func_index(item) {
        Some(idx) => {
          let fi = match
            wt_resolve_named_or_num_index(
              idx,
              ctx.func_ids,
              ctx.next_func_idx,
              "func",
            ) {
            Ok(v) => v
            Err(e) => return Err(e)
          }
          exprs.push(
            @lib.Expr::new([@lib.Instruction::ref_func(@lib.FuncIdx::new(fi))]),
          )
        }
        None =>
          match wt_elem_item_to_expr(ctx, item) {
            Ok(expr) => exprs.push(expr)
            Err(e) => return Err(e)
          }
      }
      expr_i += 1
    }

    let elem_ref_type = match elem_type_hint {
      Some(vt) =>
        match wt_ref_type_for_elem_value_type(ctx, vt) {
          Ok(v) => v
          Err(e) => return Err(e)
        }
      None =>
        @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::func()))
    }
    elems.push(
      @lib.Elem::new(mode, @lib.ElemKind::typed_exprs(elem_ref_type, exprs)),
    )
  }

  let datas : Array[@lib.Data] = []
  for d in data_defs_raw {
    if d.offset.length() == 0 {
      datas.push(@lib.Data::new(@lib.DataMode::passive(), d.data))
    } else {
      let mem_idx = match
        wt_resolve_named_or_num_index(
          d.memory_index,
          ctx.mem_ids,
          ctx.next_mem_idx,
          "mem",
        ) {
        Ok(v) => v
        Err(e) => return Err(e)
      }
      let offset = match
        wt_instrs_to_lib(ctx, d.offset, Map([]), [], wt_default_legacy_ctx()) {
        Ok(v) => @lib.Expr::new(v)
        Err(e) => return Err(e)
      }
      datas.push(
        @lib.Data::new(
          @lib.DataMode::active(@lib.MemIdx::new(mem_idx), offset),
          d.data,
        ),
      )
    }
  }

  let mut out = @lib.Module::new()
  if ctx.type_recs.length() > 0 {
    out = out.with_type_sec(@lib.TypeSec::new(ctx.type_recs))
  }
  if imports.length() > 0 {
    out = out.with_import_sec(@lib.ImportSec::new(imports))
  }
  if func_annotation_entries.length() > 0 {
    out = out.with_func_annotation_sec(
      @lib.FuncAnnotationSec::new(func_annotation_entries),
    )
  }
  if func_name_entries.length() > 0 {
    out = out.with_name_sec(
      @lib.NameSec::new(func_names=Some(@lib.NameMap::new(func_name_entries))),
    )
  }
  if func_type_idxs.length() > 0 {
    out = out.with_func_sec(@lib.FuncSec::new(func_type_idxs))
  }
  if table_defs.length() > 0 {
    out = out.with_table_sec(@lib.TableSec::new(table_defs))
  }
  if mem_defs.length() > 0 {
    out = out.with_mem_sec(@lib.MemSec::new(mem_defs))
  }
  if tag_defs.length() > 0 {
    out = out.with_tag_sec(@lib.TagSec::new(tag_defs))
  }
  if global_defs.length() > 0 {
    out = out.with_global_sec(@lib.GlobalSec::new(global_defs))
  }
  if exports.length() > 0 {
    out = out.with_export_sec(@lib.ExportSec::new(exports))
  }
  match start {
    Some(sec) => out = out.with_start_sec(sec)
    None => ()
  }
  if elems.length() > 0 {
    out = out.with_elem_sec(@lib.ElemSec::new(elems))
  }
  if code_funcs.length() > 0 {
    out = out.with_code_sec(@lib.CodeSec::new(code_funcs))
  }
  if datas.length() > 0 {
    out = out.with_data_sec(@lib.DataSec::new(datas))
  }
  if datas.length() > 0 {
    out = out.with_data_cnt_sec(
      @lib.DataCntSec::new(@lib.U32(datas.length().reinterpret_as_uint())),
    )
  }
  Ok(out)
}

///|
pub fn wast_ast_to_binary_module(mod : Module) -> Result[@lib.Module, String] {
  wt_lower_module(mod)
}

///|
pub fn wast_to_binary_module(
  source : String,
  filename~ : String = "",
) -> Result[@lib.Module, String] {
  match wast_to_module(source, filename~) {
    Ok(mod) => wast_ast_to_binary_module(mod)
    Err(e) => Err(e)
  }
}

///|
pub fn wast_text_binary_roundtrip(
  source : String,
  filename~ : String = "",
) -> Result[(String, @lib.Module), String] {
  let parsed = match wast_to_module(source, filename~) {
    Ok(m) => m
    Err(e) => return Err(e)
  }
  let normalized = match module_to_wast(parsed) {
    Ok(t) => t
    Err(e) => return Err("text normalization failed during roundtrip: \{e}")
  }
  let lowered = match wast_ast_to_binary_module(parsed) {
    Ok(m) => m
    Err(e) => return Err("text lowering failed during roundtrip: \{e}")
  }
  let bytes = match @binary.encode_module(lowered) {
    Ok(raw) => raw
    Err(e) => return Err("binary encode failed during roundtrip: \{e}")
  }
  let decoded = match @binary.decode_module(bytes) {
    Ok(m) => m
    Err(e) => return Err("binary decode failed during roundtrip: \{e}")
  }
  Ok((normalized, decoded))
}

///|
test "wast_to_binary_module lowers simple function module" {
  let lowered = match
    wast_to_binary_module("(module (func (result i32) i32.const 5))") {
    Ok(m) => m
    Err(e) => fail("expected lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(@lib.CodeSec([@lib.Func(_, @lib.Expr([@lib.I32Const(@lib.I32(v))]))])) =>
      @debug.assert_eq(v, 5)
    _ => fail("expected lowered module code section")
  }
}

///|
test "wast_to_binary_module lowers memory and data segments" {
  let lowered = match
    wast_to_binary_module("(module (memory 1) (data (i32.const 0) \"A\"))") {
    Ok(m) => m
    Err(e) => fail("expected lowering success: \{e}")
  }
  match (lowered.mem_sec, lowered.data_sec) {
    (Some(_), Some(@lib.DataSec([@lib.Data(_, bytes)]))) =>
      @debug.assert_eq(bytes.length(), 1)
    _ => fail("expected lowered memory/data sections")
  }
}

///|
test "wast_to_binary_module resolves named locals after type-index params" {
  let lowered = match
    wast_to_binary_module(
      "(module (type (func (param i32))) (func (type 0) (local $x i32) local.get $x drop))",
    ) {
    Ok(m) => m
    Err(e) => fail("expected lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [@lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(i)), _]))]
      )
    ) => @debug.assert_eq(i, 1U)
    _ => fail("expected local.get on lowered named local")
  }
}

///|
test "wast_to_binary_module resolves named params in function bodies" {
  let lowered = match
    wast_to_binary_module("(module (func (param $x i32) local.get $x drop))") {
    Ok(m) => m
    Err(e) => fail("expected lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [@lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(i)), _]))]
      )
    ) => @debug.assert_eq(i, 0U)
    _ => fail("expected local.get on lowered named param")
  }
}

///|
test "wast_to_binary_module resolves named params after explicit type use" {
  let lowered = match
    wast_to_binary_module(
      "(module (type (func (param i32) (result i32))) (func (type 0) (param $x i32) (result i32) local.get $x))",
    ) {
    Ok(m) => m
    Err(e) => fail("expected lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec([@lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(i))]))])
    ) => @debug.assert_eq(i, 0U)
    _ => fail("expected named type-use param to lower to local index 0")
  }
}

///|
test "wast_to_binary_module rejects malformed text align literals" {
  assert_true(
    wt_lower_error_contains(
      "(module (memory 0) (func (drop (i32.load8_s align=0 (i32.const 0)))))", "alignment",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (memory 0) (func (drop (i32.load8_s align=7 (i32.const 0)))))", "alignment",
    ),
  )
}

///|
test "wast_to_binary_module lowers text align bytes to exponent memargs" {
  let lowered = match
    wast_to_binary_module(
      "(module (memory 1) (func (param i32) (result i32) local.get 0 i32.load align=4))",
    ) {
    Ok(m) => m
    Err(e) => fail("expected lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [@lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr]))]
      )
    ) =>
      @debug.assert_eq(
        instr,
        @lib.Instruction::i32_load(
          @lib.MemArg::new(@lib.U32(2), None, @lib.U64(0)),
        ),
      )
    _ => fail("expected i32.load with exponent-form memarg alignment")
  }
}

///|
test "wt_type_param_count supports grouped recursive function entries" {
  let ctx = wt_new_ctx()
  let st0 = @lib.SubType::comp_type(
    @lib.CompType::func([@lib.ValType::i32()], []),
  )
  let st1 = @lib.SubType::comp_type(
    @lib.CompType::func([@lib.ValType::i64(), @lib.ValType::f32()], []),
  )
  ctx.type_recs.push(@lib.RecType::group([st0, st1]))
  match wt_type_param_count(ctx, 1U) {
    Ok(count) => @debug.assert_eq(count, 2U)
    Err(e) => fail("expected grouped rec param count resolution, got \{e}")
  }
}

///|
test "wast_to_binary_module lowers SIMD extract/replace lane ops" {
  let lowered = match
    wast_to_binary_module(
      "(module (func (param v128 i32) (result v128) local.get 0 i8x16.extract_lane_u 7 drop local.get 0 local.get 1 i8x16.replace_lane 3))",
    ) {
    Ok(m) => m
    Err(e) => fail("expected lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [@lib.Func(_, @lib.Expr([_, extract_lane, _, _, _, replace_lane]))]
      )
    ) => {
      @debug.assert_eq(
        extract_lane,
        @lib.Instruction::i8x16_extract_lane_u(@lib.LaneIdx::new(7)),
      )
      @debug.assert_eq(
        replace_lane,
        @lib.Instruction::i8x16_replace_lane(@lib.LaneIdx::new(3)),
      )
    }
    _ => fail("expected lowered SIMD extract/replace lane instructions")
  }
}

///|
test "wast_to_binary_module lowers SIMD load/store lane ops" {
  let lowered = match
    wast_to_binary_module(
      "(module (memory 1) (func (param i32 v128) (result v128) local.get 0 local.get 1 v128.load8_lane 5 local.get 0 v128.store8_lane 2 local.get 1))",
    ) {
    Ok(m) => m
    Err(e) => fail("expected lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [
          @lib.Func(
            _,
            @lib.Expr([_, _, load_lane_instr, _, store_lane_instr, _])
          ),
        ]
      )
    ) => {
      @debug.assert_eq(
        load_lane_instr,
        @lib.Instruction::v128_load8_lane(
          @lib.MemArg::new(@lib.U32(0), None, @lib.U64(0)),
          @lib.LaneIdx::new(5),
        ),
      )
      @debug.assert_eq(
        store_lane_instr,
        @lib.Instruction::v128_store8_lane(
          @lib.MemArg::new(@lib.U32(0), None, @lib.U64(0)),
          @lib.LaneIdx::new(2),
        ),
      )
    }
    _ => fail("expected lowered SIMD load/store lane instructions")
  }
}

///|
test "wast_to_binary_module lowers SIMD v128.const shapes" {
  let src =
    #|(module
    #|  (func (result v128) v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)
    #|  (func (result v128) v128.const i16x8 1 2 3 4 5 6 7 8)
    #|  (func (result v128) v128.const i32x4 1 2 3 4)
    #|  (func (result v128) v128.const i64x2 1 2)
    #|  (func (result v128) v128.const f32x4 1 2 3 4)
    #|  (func (result v128) v128.const f64x2 1 2))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected SIMD v128.const lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(@lib.CodeSec(funcs)) => {
      @debug.assert_eq(funcs.length(), 6)

      match funcs[0] {
        @lib.Func(_, @lib.Expr([instr])) =>
          @debug.assert_eq(
            instr,
            @lib.Instruction::v128_const(
              (0).to_byte(),
              (1).to_byte(),
              (2).to_byte(),
              (3).to_byte(),
              (4).to_byte(),
              (5).to_byte(),
              (6).to_byte(),
              (7).to_byte(),
              (8).to_byte(),
              (9).to_byte(),
              (10).to_byte(),
              (11).to_byte(),
              (12).to_byte(),
              (13).to_byte(),
              (14).to_byte(),
              (15).to_byte(),
            ),
          )
        _ => fail("expected i8x16 v128.const function body")
      }

      match funcs[1] {
        @lib.Func(_, @lib.Expr([instr])) =>
          @debug.assert_eq(
            instr,
            @lib.Instruction::v128_const(
              (1).to_byte(),
              (0).to_byte(),
              (2).to_byte(),
              (0).to_byte(),
              (3).to_byte(),
              (0).to_byte(),
              (4).to_byte(),
              (0).to_byte(),
              (5).to_byte(),
              (0).to_byte(),
              (6).to_byte(),
              (0).to_byte(),
              (7).to_byte(),
              (0).to_byte(),
              (8).to_byte(),
              (0).to_byte(),
            ),
          )
        _ => fail("expected i16x8 v128.const function body")
      }

      match funcs[2] {
        @lib.Func(_, @lib.Expr([instr])) =>
          @debug.assert_eq(
            instr,
            @lib.Instruction::v128_const(
              (1).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (2).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (3).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (4).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
            ),
          )
        _ => fail("expected i32x4 v128.const function body")
      }

      match funcs[3] {
        @lib.Func(_, @lib.Expr([instr])) =>
          @debug.assert_eq(
            instr,
            @lib.Instruction::v128_const(
              (1).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (2).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
            ),
          )
        _ => fail("expected i64x2 v128.const function body")
      }

      match funcs[4] {
        @lib.Func(_, @lib.Expr([instr])) =>
          @debug.assert_eq(
            instr,
            @lib.Instruction::v128_const(
              (0).to_byte(),
              (0).to_byte(),
              (128).to_byte(),
              (63).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (64).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (64).to_byte(),
              (64).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (128).to_byte(),
              (64).to_byte(),
            ),
          )
        _ => fail("expected f32x4 v128.const function body")
      }

      match funcs[5] {
        @lib.Func(_, @lib.Expr([instr])) =>
          @debug.assert_eq(
            instr,
            @lib.Instruction::v128_const(
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (240).to_byte(),
              (63).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (0).to_byte(),
              (64).to_byte(),
            ),
          )
        _ => fail("expected f64x2 v128.const function body")
      }
    }
    _ => fail("expected lowered module code section for v128.const shapes")
  }
}

///|
test "wast_to_binary_module lowers SIMD non-lane memory prefixed ops" {
  let src =
    #|(module
    #|  (memory 1)
    #|  (func (param i32) (result v128) local.get 0 v128.load)
    #|  (func (param i32) (result v128) local.get 0 v128.load8x8_s)
    #|  (func (param i32) (result v128) local.get 0 v128.load8x8_u)
    #|  (func (param i32) (result v128) local.get 0 v128.load16x4_s)
    #|  (func (param i32) (result v128) local.get 0 v128.load16x4_u)
    #|  (func (param i32) (result v128) local.get 0 v128.load32x2_s)
    #|  (func (param i32) (result v128) local.get 0 v128.load32x2_u)
    #|  (func (param i32) (result v128) local.get 0 v128.load8_splat)
    #|  (func (param i32) (result v128) local.get 0 v128.load16_splat)
    #|  (func (param i32) (result v128) local.get 0 v128.load32_splat)
    #|  (func (param i32) (result v128) local.get 0 v128.load64_splat)
    #|  (func (param i32) (result v128) local.get 0 v128.load32_zero)
    #|  (func (param i32) (result v128) local.get 0 v128.load64_zero)
    #|  (func (param i32 v128) (result v128) local.get 0 local.get 1 v128.store local.get 1))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected SIMD memory-op lowering success: \{e}")
  }
  let ma16 = @lib.MemArg::new(@lib.U32(4), None, @lib.U64(0))
  let ma8 = @lib.MemArg::new(@lib.U32(3), None, @lib.U64(0))
  let ma4 = @lib.MemArg::new(@lib.U32(2), None, @lib.U64(0))
  let ma2 = @lib.MemArg::new(@lib.U32(1), None, @lib.U64(0))
  let ma1 = @lib.MemArg::new(@lib.U32(0), None, @lib.U64(0))
  match lowered.code_sec {
    Some(@lib.CodeSec(funcs)) => {
      @debug.assert_eq(funcs.length(), 14)

      match funcs[0] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::v128_load(ma16))
        _ => fail("expected v128.load lowered body")
      }

      match funcs[1] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::v128_load8x8s(ma8))
        _ => fail("expected v128.load8x8_s lowered body")
      }

      match funcs[2] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::v128_load8x8u(ma8))
        _ => fail("expected v128.load8x8_u lowered body")
      }

      match funcs[3] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::v128_load16x4s(ma8))
        _ => fail("expected v128.load16x4_s lowered body")
      }

      match funcs[4] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::v128_load16x4u(ma8))
        _ => fail("expected v128.load16x4_u lowered body")
      }

      match funcs[5] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::v128_load32x2s(ma8))
        _ => fail("expected v128.load32x2_s lowered body")
      }

      match funcs[6] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::v128_load32x2u(ma8))
        _ => fail("expected v128.load32x2_u lowered body")
      }

      match funcs[7] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::v128_load8_splat(ma1))
        _ => fail("expected v128.load8_splat lowered body")
      }

      match funcs[8] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::v128_load16_splat(ma2))
        _ => fail("expected v128.load16_splat lowered body")
      }

      match funcs[9] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::v128_load32_splat(ma4))
        _ => fail("expected v128.load32_splat lowered body")
      }

      match funcs[10] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::v128_load64_splat(ma8))
        _ => fail("expected v128.load64_splat lowered body")
      }

      match funcs[11] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::v128_load32_zero(ma4))
        _ => fail("expected v128.load32_zero lowered body")
      }

      match funcs[12] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::v128_load64_zero(ma8))
        _ => fail("expected v128.load64_zero lowered body")
      }

      match funcs[13] {
        @lib.Func(
          _,
          @lib.Expr(
            [
              @lib.LocalGet(@lib.LocalIdx(i0)),
              @lib.LocalGet(@lib.LocalIdx(i1)),
              store_instr,
              @lib.LocalGet(@lib.LocalIdx(i2)),
            ]
          )
        ) => {
          @debug.assert_eq(i0, 0U)
          @debug.assert_eq(i1, 1U)
          @debug.assert_eq(i2, 1U)
          @debug.assert_eq(store_instr, @lib.Instruction::v128_store(ma16))
        }
        _ => fail("expected v128.store lowered body")
      }
    }
    _ => fail("expected lowered module code section for SIMD memory ops")
  }
}

///|
test "wast_to_binary_module lowers SIMD shuffle and swizzle ops" {
  let src =
    #|(module
    #|  (func (param v128 v128) (result v128)
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.shuffle 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.swizzle))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected SIMD shuffle/swizzle lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [
          @lib.Func(
            _,
            @lib.Expr(
              [
                @lib.LocalGet(@lib.LocalIdx(0)),
                @lib.LocalGet(@lib.LocalIdx(1)),
                shuffle_instr,
                @lib.Drop,
                @lib.LocalGet(@lib.LocalIdx(0)),
                @lib.LocalGet(@lib.LocalIdx(1)),
                swizzle_instr,
              ]
            )
          ),
        ]
      )
    ) => {
      @debug.assert_eq(
        shuffle_instr,
        @lib.Instruction::i8x16_shuffle(
          @lib.LaneIdx::new(0),
          @lib.LaneIdx::new(1),
          @lib.LaneIdx::new(2),
          @lib.LaneIdx::new(3),
          @lib.LaneIdx::new(4),
          @lib.LaneIdx::new(5),
          @lib.LaneIdx::new(6),
          @lib.LaneIdx::new(7),
          @lib.LaneIdx::new(8),
          @lib.LaneIdx::new(9),
          @lib.LaneIdx::new(10),
          @lib.LaneIdx::new(11),
          @lib.LaneIdx::new(12),
          @lib.LaneIdx::new(13),
          @lib.LaneIdx::new(14),
          @lib.LaneIdx::new(15),
        ),
      )
      @debug.assert_eq(swizzle_instr, @lib.Instruction::i8x16_swizzle())
    }
    _ => fail("expected lowered module shuffle/swizzle function body")
  }
}

///|
test "wast_to_binary_module lowers SIMD splat ops" {
  let src =
    #|(module
    #|  (func (param i32) (result v128) local.get 0 i8x16.splat)
    #|  (func (param i32) (result v128) local.get 0 i16x8.splat)
    #|  (func (param i32) (result v128) local.get 0 i32x4.splat)
    #|  (func (param i64) (result v128) local.get 0 i64x2.splat)
    #|  (func (param f32) (result v128) local.get 0 f32x4.splat)
    #|  (func (param f64) (result v128) local.get 0 f64x2.splat))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected SIMD splat lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(@lib.CodeSec(funcs)) => {
      @debug.assert_eq(funcs.length(), 6)

      match funcs[0] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::i8x16_splat())
        _ => fail("expected i8x16.splat lowered body")
      }
      match funcs[1] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::i16x8_splat())
        _ => fail("expected i16x8.splat lowered body")
      }
      match funcs[2] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::i32x4_splat())
        _ => fail("expected i32x4.splat lowered body")
      }
      match funcs[3] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::i64x2_splat())
        _ => fail("expected i64x2.splat lowered body")
      }
      match funcs[4] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::f32x4_splat())
        _ => fail("expected f32x4.splat lowered body")
      }
      match funcs[5] {
        @lib.Func(_, @lib.Expr([@lib.LocalGet(@lib.LocalIdx(0)), instr])) =>
          @debug.assert_eq(instr, @lib.Instruction::f64x2_splat())
        _ => fail("expected f64x2.splat lowered body")
      }
    }
    _ => fail("expected lowered module code section for SIMD splat ops")
  }
}

///|
test "wast_to_binary_module lowers SIMD i8x16 no-immediate ops" {
  let src =
    #|(module
    #|  (func (param v128 v128 i32)
    #|    local.get 0
    #|    i8x16.abs
    #|    drop
    #|    local.get 0
    #|    i8x16.neg
    #|    drop
    #|    local.get 0
    #|    i8x16.popcnt
    #|    drop
    #|    local.get 0
    #|    i8x16.all_true
    #|    drop
    #|    local.get 0
    #|    i8x16.bitmask
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.eq
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.ne
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.lt_s
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.lt_u
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.gt_s
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.gt_u
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.le_s
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.le_u
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.ge_s
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.ge_u
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.narrow_i16x8_s
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.narrow_i16x8_u
    #|    drop
    #|    local.get 0
    #|    local.get 2
    #|    i8x16.shl
    #|    drop
    #|    local.get 0
    #|    local.get 2
    #|    i8x16.shr_s
    #|    drop
    #|    local.get 0
    #|    local.get 2
    #|    i8x16.shr_u
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.add
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.add_sat_s
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.add_sat_u
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.sub
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.sub_sat_s
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.sub_sat_u
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.min_s
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.min_u
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.max_s
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.max_u
    #|    drop
    #|    local.get 0
    #|    local.get 1
    #|    i8x16.avgr_u
    #|    drop))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected i8x16 no-immediate lowering success: \{e}")
  }
  let expected : Array[@lib.Instruction] = [
    @lib.Instruction::i8x16_abs(),
    @lib.Instruction::i8x16_neg(),
    @lib.Instruction::i8x16_popcnt(),
    @lib.Instruction::i8x16_all_true(),
    @lib.Instruction::i8x16_bitmask(),
    @lib.Instruction::i8x16_eq(),
    @lib.Instruction::i8x16_ne(),
    @lib.Instruction::i8x16_lt_s(),
    @lib.Instruction::i8x16_lt_u(),
    @lib.Instruction::i8x16_gt_s(),
    @lib.Instruction::i8x16_gt_u(),
    @lib.Instruction::i8x16_le_s(),
    @lib.Instruction::i8x16_le_u(),
    @lib.Instruction::i8x16_ge_s(),
    @lib.Instruction::i8x16_ge_u(),
    @lib.Instruction::i8x16_narrow_i16x8s(),
    @lib.Instruction::i8x16_narrow_i16x8u(),
    @lib.Instruction::i8x16_shl(),
    @lib.Instruction::i8x16_shr_s(),
    @lib.Instruction::i8x16_shr_u(),
    @lib.Instruction::i8x16_add(),
    @lib.Instruction::i8x16_add_sat_s(),
    @lib.Instruction::i8x16_add_sat_u(),
    @lib.Instruction::i8x16_sub(),
    @lib.Instruction::i8x16_sub_sat_s(),
    @lib.Instruction::i8x16_sub_sat_u(),
    @lib.Instruction::i8x16_min_s(),
    @lib.Instruction::i8x16_min_u(),
    @lib.Instruction::i8x16_max_s(),
    @lib.Instruction::i8x16_max_u(),
    @lib.Instruction::i8x16_avgr_u(),
  ]
  match lowered.code_sec {
    Some(@lib.CodeSec([@lib.Func(_, @lib.Expr(body))])) => {
      let lowered_ops : Array[@lib.Instruction] = []
      for instr in body {
        match instr {
          @lib.LocalGet(_) | @lib.Drop => ()
          _ => lowered_ops.push(instr)
        }
      }
      @debug.assert_eq(lowered_ops, expected)
    }
    _ => fail("expected lowered module code section for i8x16 no-immediate ops")
  }
}

///|
test "wast_to_binary_module lowers SIMD no-immediate ops for i16x8 i32x4 i64x2 f32x4 f64x2" {
  let src =
    #|(module
    #|  (func (param v128 v128 i32)
    #|    local.get 0 local.get 1 i16x8.eq drop
    #|    local.get 0 local.get 1 i16x8.ne drop
    #|    local.get 0 local.get 1 i16x8.lt_s drop
    #|    local.get 0 local.get 1 i16x8.lt_u drop
    #|    local.get 0 local.get 1 i16x8.gt_s drop
    #|    local.get 0 local.get 1 i16x8.gt_u drop
    #|    local.get 0 local.get 1 i16x8.le_s drop
    #|    local.get 0 local.get 1 i16x8.le_u drop
    #|    local.get 0 local.get 1 i16x8.ge_s drop
    #|    local.get 0 local.get 1 i16x8.ge_u drop
    #|    local.get 0 i16x8.abs drop
    #|    local.get 0 i16x8.neg drop
    #|    local.get 0 local.get 1 i16x8.q15mulr_sat_s drop
    #|    local.get 0 i16x8.all_true drop
    #|    local.get 0 i16x8.bitmask drop
    #|    local.get 0 local.get 1 i16x8.narrow_i32x4_s drop
    #|    local.get 0 local.get 1 i16x8.narrow_i32x4_u drop
    #|    local.get 0 i16x8.extend_low_i8x16_s drop
    #|    local.get 0 i16x8.extend_high_i8x16_s drop
    #|    local.get 0 i16x8.extend_low_i8x16_u drop
    #|    local.get 0 i16x8.extend_high_i8x16_u drop
    #|    local.get 0 local.get 2 i16x8.shl drop
    #|    local.get 0 local.get 2 i16x8.shr_s drop
    #|    local.get 0 local.get 2 i16x8.shr_u drop
    #|    local.get 0 local.get 1 i16x8.add drop
    #|    local.get 0 local.get 1 i16x8.add_sat_s drop
    #|    local.get 0 local.get 1 i16x8.add_sat_u drop
    #|    local.get 0 local.get 1 i16x8.sub drop
    #|    local.get 0 local.get 1 i16x8.sub_sat_s drop
    #|    local.get 0 local.get 1 i16x8.sub_sat_u drop
    #|    local.get 0 local.get 1 i16x8.mul drop
    #|    local.get 0 local.get 1 i16x8.min_s drop
    #|    local.get 0 local.get 1 i16x8.min_u drop
    #|    local.get 0 local.get 1 i16x8.max_s drop
    #|    local.get 0 local.get 1 i16x8.max_u drop
    #|    local.get 0 local.get 1 i16x8.avgr_u drop
    #|    local.get 0 local.get 1 i16x8.extmul_low_i8x16_s drop
    #|    local.get 0 local.get 1 i16x8.extmul_high_i8x16_s drop
    #|    local.get 0 local.get 1 i16x8.extmul_low_i8x16_u drop
    #|    local.get 0 local.get 1 i16x8.extmul_high_i8x16_u drop)
    #|  (func (param v128 v128 i32)
    #|    local.get 0 local.get 1 i32x4.eq drop
    #|    local.get 0 local.get 1 i32x4.ne drop
    #|    local.get 0 local.get 1 i32x4.lt_s drop
    #|    local.get 0 local.get 1 i32x4.lt_u drop
    #|    local.get 0 local.get 1 i32x4.gt_s drop
    #|    local.get 0 local.get 1 i32x4.gt_u drop
    #|    local.get 0 local.get 1 i32x4.le_s drop
    #|    local.get 0 local.get 1 i32x4.le_u drop
    #|    local.get 0 local.get 1 i32x4.ge_s drop
    #|    local.get 0 local.get 1 i32x4.ge_u drop
    #|    local.get 0 i32x4.abs drop
    #|    local.get 0 i32x4.neg drop
    #|    local.get 0 i32x4.all_true drop
    #|    local.get 0 i32x4.bitmask drop
    #|    local.get 0 i32x4.extend_low_i16x8_s drop
    #|    local.get 0 i32x4.extend_high_i16x8_s drop
    #|    local.get 0 i32x4.extend_low_i16x8_u drop
    #|    local.get 0 i32x4.extend_high_i16x8_u drop
    #|    local.get 0 local.get 2 i32x4.shl drop
    #|    local.get 0 local.get 2 i32x4.shr_s drop
    #|    local.get 0 local.get 2 i32x4.shr_u drop
    #|    local.get 0 local.get 1 i32x4.add drop
    #|    local.get 0 local.get 1 i32x4.sub drop
    #|    local.get 0 local.get 1 i32x4.mul drop
    #|    local.get 0 local.get 1 i32x4.min_s drop
    #|    local.get 0 local.get 1 i32x4.min_u drop
    #|    local.get 0 local.get 1 i32x4.max_s drop
    #|    local.get 0 local.get 1 i32x4.max_u drop
    #|    local.get 0 local.get 1 i32x4.dot_i16x8_s drop
    #|    local.get 0 local.get 1 i32x4.extmul_low_i16x8_s drop
    #|    local.get 0 local.get 1 i32x4.extmul_high_i16x8_s drop
    #|    local.get 0 local.get 1 i32x4.extmul_low_i16x8_u drop
    #|    local.get 0 local.get 1 i32x4.extmul_high_i16x8_u drop
    #|    local.get 0 i32x4.trunc_sat_f32x4_s drop
    #|    local.get 0 i32x4.trunc_sat_f32x4_u drop
    #|    local.get 0 i32x4.trunc_sat_f64x2_s_zero drop
    #|    local.get 0 i32x4.trunc_sat_f64x2_u_zero drop)
    #|  (func (param v128 v128 i32)
    #|    local.get 0 local.get 1 i64x2.eq drop
    #|    local.get 0 local.get 1 i64x2.ne drop
    #|    local.get 0 local.get 1 i64x2.lt_s drop
    #|    local.get 0 local.get 1 i64x2.gt_s drop
    #|    local.get 0 local.get 1 i64x2.le_s drop
    #|    local.get 0 local.get 1 i64x2.ge_s drop
    #|    local.get 0 i64x2.abs drop
    #|    local.get 0 i64x2.neg drop
    #|    local.get 0 i64x2.all_true drop
    #|    local.get 0 i64x2.bitmask drop
    #|    local.get 0 i64x2.extend_low_i32x4_s drop
    #|    local.get 0 i64x2.extend_high_i32x4_s drop
    #|    local.get 0 i64x2.extend_low_i32x4_u drop
    #|    local.get 0 i64x2.extend_high_i32x4_u drop
    #|    local.get 0 local.get 2 i64x2.shl drop
    #|    local.get 0 local.get 2 i64x2.shr_s drop
    #|    local.get 0 local.get 2 i64x2.shr_u drop
    #|    local.get 0 local.get 1 i64x2.add drop
    #|    local.get 0 local.get 1 i64x2.sub drop
    #|    local.get 0 local.get 1 i64x2.mul drop
    #|    local.get 0 local.get 1 i64x2.extmul_low_i32x4_s drop
    #|    local.get 0 local.get 1 i64x2.extmul_high_i32x4_s drop
    #|    local.get 0 local.get 1 i64x2.extmul_low_i32x4_u drop
    #|    local.get 0 local.get 1 i64x2.extmul_high_i32x4_u drop)
    #|  (func (param v128 v128)
    #|    local.get 0 local.get 1 f32x4.eq drop
    #|    local.get 0 local.get 1 f32x4.ne drop
    #|    local.get 0 local.get 1 f32x4.lt drop
    #|    local.get 0 local.get 1 f32x4.gt drop
    #|    local.get 0 local.get 1 f32x4.le drop
    #|    local.get 0 local.get 1 f32x4.ge drop
    #|    local.get 0 f32x4.abs drop
    #|    local.get 0 f32x4.neg drop
    #|    local.get 0 f32x4.sqrt drop
    #|    local.get 0 local.get 1 f32x4.add drop
    #|    local.get 0 local.get 1 f32x4.sub drop
    #|    local.get 0 local.get 1 f32x4.mul drop
    #|    local.get 0 local.get 1 f32x4.div drop
    #|    local.get 0 local.get 1 f32x4.min drop
    #|    local.get 0 local.get 1 f32x4.max drop
    #|    local.get 0 local.get 1 f32x4.pmin drop
    #|    local.get 0 local.get 1 f32x4.pmax drop
    #|    local.get 0 f32x4.ceil drop
    #|    local.get 0 f32x4.floor drop
    #|    local.get 0 f32x4.trunc drop
    #|    local.get 0 f32x4.nearest drop
    #|    local.get 0 f32x4.convert_i32x4_s drop
    #|    local.get 0 f32x4.convert_i32x4_u drop
    #|    local.get 0 f32x4.demote_f64x2_zero drop)
    #|  (func (param v128 v128)
    #|    local.get 0 local.get 1 f64x2.eq drop
    #|    local.get 0 local.get 1 f64x2.ne drop
    #|    local.get 0 local.get 1 f64x2.lt drop
    #|    local.get 0 local.get 1 f64x2.gt drop
    #|    local.get 0 local.get 1 f64x2.le drop
    #|    local.get 0 local.get 1 f64x2.ge drop
    #|    local.get 0 f64x2.abs drop
    #|    local.get 0 f64x2.neg drop
    #|    local.get 0 f64x2.sqrt drop
    #|    local.get 0 local.get 1 f64x2.add drop
    #|    local.get 0 local.get 1 f64x2.sub drop
    #|    local.get 0 local.get 1 f64x2.mul drop
    #|    local.get 0 local.get 1 f64x2.div drop
    #|    local.get 0 local.get 1 f64x2.min drop
    #|    local.get 0 local.get 1 f64x2.max drop
    #|    local.get 0 local.get 1 f64x2.pmin drop
    #|    local.get 0 local.get 1 f64x2.pmax drop
    #|    local.get 0 f64x2.ceil drop
    #|    local.get 0 f64x2.floor drop
    #|    local.get 0 f64x2.trunc drop
    #|    local.get 0 f64x2.nearest drop
    #|    local.get 0 f64x2.convert_low_i32x4_s drop
    #|    local.get 0 f64x2.convert_low_i32x4_u drop
    #|    local.get 0 f64x2.promote_low_f32x4 drop))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail(
        "expected non-i8x16 SIMD no-immediate lowering success for full family coverage: \{e}",
      )
  }
  match lowered.code_sec {
    Some(@lib.CodeSec(funcs)) => {
      @debug.assert_eq(funcs.length(), 5)

      match funcs[0] {
        @lib.Func(_, @lib.Expr(body)) => {
          let lowered_ops : Array[@lib.Instruction] = []
          for instr in body {
            match instr {
              @lib.LocalGet(_) | @lib.Drop => ()
              _ => lowered_ops.push(instr)
            }
          }
          @debug.assert_eq(lowered_ops[0], @lib.Instruction::i16x8_eq())
          @debug.assert_eq(
            lowered_ops[12],
            @lib.Instruction::i16x8q15mulr_sat_s(),
          )
          @debug.assert_eq(lowered_ops[21], @lib.Instruction::i16x8_shl())
          @debug.assert_eq(lowered_ops[35], @lib.Instruction::i16x8_avgr_u())
          @debug.assert_eq(
            lowered_ops[39],
            @lib.Instruction::i16x8_extmul_high_i8x16u(),
          )
        }
      }

      match funcs[1] {
        @lib.Func(_, @lib.Expr(body)) => {
          let lowered_ops : Array[@lib.Instruction] = []
          for instr in body {
            match instr {
              @lib.LocalGet(_) | @lib.Drop => ()
              _ => lowered_ops.push(instr)
            }
          }
          @debug.assert_eq(lowered_ops[0], @lib.Instruction::i32x4_eq())
          @debug.assert_eq(lowered_ops[18], @lib.Instruction::i32x4_shl())
          @debug.assert_eq(
            lowered_ops[28],
            @lib.Instruction::i32x4_dot_i16x8s(),
          )
          @debug.assert_eq(
            lowered_ops[33],
            @lib.Instruction::i32x4_trunc_sat_f32x4s(),
          )
          @debug.assert_eq(
            lowered_ops[36],
            @lib.Instruction::i32x4_trunc_sat_f64x2u_zero(),
          )
        }
      }

      match funcs[2] {
        @lib.Func(_, @lib.Expr(body)) => {
          let lowered_ops : Array[@lib.Instruction] = []
          for instr in body {
            match instr {
              @lib.LocalGet(_) | @lib.Drop => ()
              _ => lowered_ops.push(instr)
            }
          }
          @debug.assert_eq(lowered_ops[0], @lib.Instruction::i64x2_eq())
          @debug.assert_eq(lowered_ops[14], @lib.Instruction::i64x2_shl())
          @debug.assert_eq(lowered_ops[19], @lib.Instruction::i64x2_mul())
          @debug.assert_eq(
            lowered_ops[23],
            @lib.Instruction::i64x2_extmul_high_i32x4u(),
          )
        }
      }

      match funcs[3] {
        @lib.Func(_, @lib.Expr(body)) => {
          let lowered_ops : Array[@lib.Instruction] = []
          for instr in body {
            match instr {
              @lib.LocalGet(_) | @lib.Drop => ()
              _ => lowered_ops.push(instr)
            }
          }
          @debug.assert_eq(lowered_ops[0], @lib.Instruction::f32x4_eq())
          @debug.assert_eq(lowered_ops[8], @lib.Instruction::f32x4_sqrt())
          @debug.assert_eq(lowered_ops[15], @lib.Instruction::f32x4_pmin())
          @debug.assert_eq(lowered_ops[20], @lib.Instruction::f32x4_nearest())
          @debug.assert_eq(
            lowered_ops[23],
            @lib.Instruction::f32x4_demote_f64x2_zero(),
          )
        }
      }

      match funcs[4] {
        @lib.Func(_, @lib.Expr(body)) => {
          let lowered_ops : Array[@lib.Instruction] = []
          for instr in body {
            match instr {
              @lib.LocalGet(_) | @lib.Drop => ()
              _ => lowered_ops.push(instr)
            }
          }
          @debug.assert_eq(lowered_ops[0], @lib.Instruction::f64x2_eq())
          @debug.assert_eq(lowered_ops[8], @lib.Instruction::f64x2_sqrt())
          @debug.assert_eq(lowered_ops[15], @lib.Instruction::f64x2_pmin())
          @debug.assert_eq(lowered_ops[20], @lib.Instruction::f64x2_nearest())
          @debug.assert_eq(
            lowered_ops[23],
            @lib.Instruction::f64x2_promote_low_f32x4(),
          )
        }
      }
    }
    _ =>
      fail(
        "expected lowered module code section for non-i8x16 SIMD no-immediate ops",
      )
  }
}

///|
test "wast_to_binary_module lowers relaxed SIMD no-immediate ops" {
  let src =
    #|(module
    #|  (func (param v128 v128 v128)
    #|    local.get 0 local.get 1 i8x16.relaxed_swizzle drop
    #|    local.get 0 local.get 1 local.get 2 i8x16.relaxed_laneselect drop
    #|    local.get 0 local.get 1 local.get 2 i16x8.relaxed_laneselect drop
    #|    local.get 0 local.get 1 local.get 2 i32x4.relaxed_laneselect drop
    #|    local.get 0 local.get 1 local.get 2 i64x2.relaxed_laneselect drop
    #|    local.get 0 i32x4.relaxed_trunc_f32x4_s drop
    #|    local.get 0 i32x4.relaxed_trunc_f32x4_u drop
    #|    local.get 0 i32x4.relaxed_trunc_f64x2_s_zero drop
    #|    local.get 0 i32x4.relaxed_trunc_f64x2_u_zero drop
    #|    local.get 0 local.get 1 local.get 2 f32x4.relaxed_madd drop
    #|    local.get 0 local.get 1 local.get 2 f32x4.relaxed_nmadd drop
    #|    local.get 0 local.get 1 local.get 2 f64x2.relaxed_madd drop
    #|    local.get 0 local.get 1 local.get 2 f64x2.relaxed_nmadd drop
    #|    local.get 0 local.get 1 f32x4.relaxed_min drop
    #|    local.get 0 local.get 1 f32x4.relaxed_max drop
    #|    local.get 0 local.get 1 f64x2.relaxed_min drop
    #|    local.get 0 local.get 1 f64x2.relaxed_max drop
    #|    local.get 0 local.get 1 i16x8.relaxed_q15mulr_s drop
    #|    local.get 0 local.get 1 i16x8.relaxed_dot_i8x16_i7x16_s drop
    #|    local.get 0 local.get 1 local.get 2 i32x4.relaxed_dot_i8x16_i7x16_add_s drop))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected relaxed SIMD no-immediate lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(@lib.CodeSec([@lib.Func(_, @lib.Expr(body))])) => {
      let lowered_ops : Array[@lib.Instruction] = []
      for instr in body {
        match instr {
          @lib.LocalGet(_) | @lib.Drop => ()
          _ => lowered_ops.push(instr)
        }
      }
      @debug.assert_eq(lowered_ops.length(), 20)
      @debug.assert_eq(
        lowered_ops[0],
        @lib.Instruction::i8x16_relaxed_swizzle(),
      )
      @debug.assert_eq(
        lowered_ops[2],
        @lib.Instruction::i16x8_relaxed_laneselect(),
      )
      @debug.assert_eq(
        lowered_ops[5],
        @lib.Instruction::i32x4_relaxed_trunc_f32x4s(),
      )
      @debug.assert_eq(
        lowered_ops[7],
        @lib.Instruction::i32x4_relaxed_trunc_zero_f64x2s(),
      )
      @debug.assert_eq(lowered_ops[9], @lib.Instruction::f32x4_relaxed_madd())
      @debug.assert_eq(lowered_ops[12], @lib.Instruction::f64x2_relaxed_nmadd())
      @debug.assert_eq(
        lowered_ops[17],
        @lib.Instruction::i16x8_relaxed_q15mulr_s(),
      )
      @debug.assert_eq(
        lowered_ops[18],
        @lib.Instruction::i16x8_relaxed_dot_i8x16i7x16s(),
      )
      @debug.assert_eq(
        lowered_ops[19],
        @lib.Instruction::i32x4_relaxed_dot_i8x16i7x16_add_s(),
      )
    }
    _ =>
      fail(
        "expected lowered module code section for relaxed SIMD no-immediate ops",
      )
  }
}

///|
test "wast_text_binary_roundtrip returns validator-clean module for core fixture" {
  let src =
    #|(module
    #|  (func (param i32) (result i32)
    #|    local.get 0
    #|    i32.const 1
    #|    i32.add)
    #|  (export "inc" (func 0)))
  let (_, roundtripped) = match
    wast_text_binary_roundtrip(src, filename="roundtrip-core.wast") {
    Ok(m) => m
    Err(e) => fail("expected full text/binary roundtrip to succeed: \{e}")
  }
  match roundtripped.export_sec {
    Some(
      @lib.ExportSec([@lib.Export(name, @lib.FuncExternIdx(@lib.FuncIdx(idx)))])
    ) => {
      @debug.assert_eq(name.0, "inc")
      @debug.assert_eq(idx, 0U)
    }
    _ => fail("expected roundtripped core fixture to preserve export shape")
  }
}

///|
test "wast_text_binary_roundtrip preserves descriptor instruction families" {
  let src =
    #|(module
    #|  (func (param funcref) (result i32)
    #|    local.get 0
    #|    ref.get_desc 0
    #|    drop
    #|    local.get 0
    #|    ref.test_desc 0
    #|    drop
    #|    local.get 0
    #|    ref.cast_desc_eq_null 0
    #|    drop
    #|    (i32.const 0))
    #|  (export "f" (func 0)))
  let (normalized, _) = match
    wast_text_binary_roundtrip(src, filename="roundtrip-descriptor.wast") {
    Ok(m) => m
    Err(e) =>
      fail(
        "expected descriptor fixture full text/binary roundtrip to succeed: \{e}",
      )
  }
  assert_true(normalized.contains("ref.get_desc"))
  assert_true(normalized.contains("ref.test_desc 0"))
  assert_true(normalized.contains("ref.cast_desc_eq_null 0"))
}

///|
test "wt_instr_to_lib lowers struct allocation instruction families" {
  let ctx = wt_new_ctx()
  ctx.type_recs.push(
    @lib.RecType::new(@lib.SubType::comp_type(@lib.CompType::struct_([]))),
  )
  ctx.type_recs.push(
    @lib.RecType::new(
      @lib.SubType::comp_type(
        @lib.CompType::struct_([]),
        metadata=@lib.TypeMetadata::new(descriptor=Some(@lib.TypeIdx::new(1U))),
      ),
    ),
  )
  let struct_new = match
    wt_instr_to_lib(
      ctx,
      StructNew(Num(0U)),
      Map([]),
      [],
      wt_default_legacy_ctx(),
    ) {
    Ok(instr) => instr
    Err(e) => fail("expected struct.new lowering success: \{e}")
  }
  @debug.assert_eq(
    struct_new,
    @lib.Instruction::struct_new(@lib.TypeIdx::new(0U)),
  )
  let struct_new_default = match
    wt_instr_to_lib(
      ctx,
      StructNewDefault(Num(0U)),
      Map([]),
      [],
      wt_default_legacy_ctx(),
    ) {
    Ok(instr) => instr
    Err(e) => fail("expected struct.new_default lowering success: \{e}")
  }
  @debug.assert_eq(
    struct_new_default,
    @lib.Instruction::struct_new_default(@lib.TypeIdx::new(0U)),
  )
  let struct_new_desc = match
    wt_instr_to_lib(
      ctx,
      StructNewDesc(Num(1U)),
      Map([]),
      [],
      wt_default_legacy_ctx(),
    ) {
    Ok(instr) => instr
    Err(e) => fail("expected struct.new_desc lowering success: \{e}")
  }
  @debug.assert_eq(
    struct_new_desc,
    @lib.Instruction::struct_new_desc(@lib.TypeIdx::new(1U)),
  )
  let struct_new_default_desc = match
    wt_instr_to_lib(
      ctx,
      StructNewDefaultDesc(Num(1U)),
      Map([]),
      [],
      wt_default_legacy_ctx(),
    ) {
    Ok(instr) => instr
    Err(e) => fail("expected struct.new_default_desc lowering success: \{e}")
  }
  @debug.assert_eq(
    struct_new_default_desc,
    @lib.Instruction::struct_new_default_desc(@lib.TypeIdx::new(1U)),
  )
}

///|
test "wast_to_binary_module lowers struct type defs with forward descriptor metadata" {
  let src =
    #|(module
    #|  (type $node
    #|    (descriptor $node_desc)
    #|    (struct
    #|      (field i32)
    #|      (field (mut (ref null $node)))))
    #|  (type $node_desc
    #|    (describes $node)
    #|    (struct))
    #|  (func (result (ref null $node))
    #|    (struct.new_desc $node (struct.new $node_desc))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail(
        "expected struct type-def lowering success with forward metadata: \{e}",
      )
  }
  match lowered.type_sec {
    Some(@lib.TypeSec(types)) => {
      assert_true(types.length() >= 2)
      let node_sub = match types[0] {
        @lib.SingleRecType(node_sub) => node_sub
        _ => fail("expected first lowered type to be a single rec subtype")
      }
      let node_desc_sub = match types[1] {
        @lib.SingleRecType(node_desc_sub) => node_desc_sub
        _ => fail("expected second lowered type to be a single rec subtype")
      }
      @debug.assert_eq(
        node_sub,
        @lib.SubType::comp_type(
          @lib.CompType::struct_([
            @lib.FieldType::new(
              @lib.StorageType::val_type(@lib.ValType::i32()),
              @lib.Mut::const_(),
            ),
            @lib.FieldType::new(
              @lib.StorageType::val_type(
                @lib.ValType::ref_type(
                  @lib.RefType::new(
                    true,
                    @lib.HeapType::new(@lib.TypeIdx::new(0U)),
                  ),
                ),
              ),
              @lib.Mut::var_(),
            ),
          ]),
          metadata=@lib.TypeMetadata::new(
            descriptor=Some(@lib.TypeIdx::new(1U)),
          ),
        ),
      )
      @debug.assert_eq(
        node_desc_sub,
        @lib.SubType::comp_type(
          @lib.CompType::struct_([]),
          metadata=@lib.TypeMetadata::new(describes=Some(@lib.TypeIdx::new(0U))),
        ),
      )
    }
    _ =>
      fail("expected lowered type section for struct type-def metadata fixture")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [
          @lib.Func(
            _,
            @lib.Expr(
              [
                @lib.StructNew(@lib.TypeIdx::TypeIdx(1U)),
                @lib.StructNewDesc(@lib.TypeIdx::TypeIdx(0U)),
              ]
            )
          ),
        ]
      )
    ) => ()
    _ =>
      fail(
        "expected lowered code section using descriptor-bearing struct constructors",
      )
  }
}

///|
test "wast_to_binary_module lowers array type defs with descriptor metadata" {
  let src =
    #|(module
    #|  (type $bytes (array i8))
    #|  (type $arr
    #|    (sub $bytes
    #|      (descriptor $arr_desc)
    #|      (array (mut (ref null $bytes)))))
    #|  (type $arr_desc
    #|    (describes $arr)
    #|    (array i16)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail("expected array type-def lowering success with metadata: \{e}")
  }
  match lowered.type_sec {
    Some(@lib.TypeSec(types)) => {
      assert_true(types.length() >= 3)
      let bytes_sub = match types[0] {
        @lib.SingleRecType(bytes_sub) => bytes_sub
        _ => fail("expected first lowered type to be a single rec subtype")
      }
      let arr_sub = match types[1] {
        @lib.SingleRecType(arr_sub) => arr_sub
        _ => fail("expected second lowered type to be a single rec subtype")
      }
      let arr_desc_sub = match types[2] {
        @lib.SingleRecType(arr_desc_sub) => arr_desc_sub
        _ => fail("expected third lowered type to be a single rec subtype")
      }
      @debug.assert_eq(
        bytes_sub,
        @lib.SubType::comp_type(
          @lib.CompType::array(
            @lib.FieldType::new(
              @lib.StorageType::pack_type(@lib.PackType::i8()),
              @lib.Mut::const_(),
            ),
          ),
        ),
      )
      @debug.assert_eq(
        arr_sub,
        @lib.SubType::new(
          false,
          [@lib.TypeIdx::new(0U)],
          @lib.CompType::array(
            @lib.FieldType::new(
              @lib.StorageType::val_type(
                @lib.ValType::ref_type(
                  @lib.RefType::new(
                    true,
                    @lib.HeapType::new(@lib.TypeIdx::new(0U)),
                  ),
                ),
              ),
              @lib.Mut::var_(),
            ),
          ),
          metadata=@lib.TypeMetadata::new(
            descriptor=Some(@lib.TypeIdx::new(2U)),
          ),
        ),
      )
      @debug.assert_eq(
        arr_desc_sub,
        @lib.SubType::comp_type(
          @lib.CompType::array(
            @lib.FieldType::new(
              @lib.StorageType::pack_type(@lib.PackType::i16()),
              @lib.Mut::const_(),
            ),
          ),
          metadata=@lib.TypeMetadata::new(describes=Some(@lib.TypeIdx::new(1U))),
        ),
      )
    }
    _ =>
      fail("expected lowered type section for array type-def metadata fixture")
  }
}

///|
test "wast_to_binary_module lowers rec groups into grouped type entries" {
  let src =
    #|(module
    #|  (rec
    #|    (type $node
    #|      (descriptor $node_desc)
    #|      (struct (field (ref null $node))))
    #|    (type $node_desc
    #|      (describes $node)
    #|      (struct)))
    #|  (rec
    #|    (type $sig (func (param i32) (result i32))))
    #|  (func (type $sig) local.get 0))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected rec-group lowering success: \{e}")
  }
  match lowered.type_sec {
    Some(
      @lib.TypeSec(
        [
          @lib.GroupRecType([node_sub, node_desc_sub]),
          @lib.GroupRecType([sig_sub]),
        ]
      )
    ) => {
      @debug.assert_eq(
        node_sub,
        @lib.SubType::comp_type(
          @lib.CompType::struct_([
            @lib.FieldType::new(
              @lib.StorageType::val_type(
                @lib.ValType::ref_type(
                  @lib.RefType::new(
                    true,
                    @lib.HeapType::new(@lib.TypeIdx::new(0U)),
                  ),
                ),
              ),
              @lib.Mut::const_(),
            ),
          ]),
          metadata=@lib.TypeMetadata::new(
            descriptor=Some(@lib.TypeIdx::new(1U)),
          ),
        ),
      )
      @debug.assert_eq(
        node_desc_sub,
        @lib.SubType::comp_type(
          @lib.CompType::struct_([]),
          metadata=@lib.TypeMetadata::new(describes=Some(@lib.TypeIdx::new(0U))),
        ),
      )
      @debug.assert_eq(
        sig_sub,
        @lib.SubType::comp_type(
          @lib.CompType::func([@lib.ValType::i32()], [@lib.ValType::i32()]),
        ),
      )
    }
    _ => fail("expected grouped rec entries in lowered type section")
  }
  match lowered.func_sec {
    Some(@lib.FuncSec([@lib.TypeIdx::TypeIdx(2U)])) => ()
    _ => fail("expected function section to reference grouped function type")
  }
}

///|
test "wast_to_binary_module validates descriptor final-subtype pair from text" {
  let src =
    #|(module
    #|  (rec
    #|    (type $base (sub (descriptor $base_desc) (struct)))
    #|    (type $base_desc (sub (describes $base) (struct)))
    #|    (type $leaf (sub final $base (descriptor $leaf_desc) (struct (field i32))))
    #|    (type $leaf_desc (sub final $base_desc (describes $leaf) (struct (field f32))))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail("expected descriptor final-subtype fixture lowering success: \{e}")
  }
  match lowered.type_sec {
    Some(@lib.TypeSec([@lib.GroupRecType([base, base_desc, leaf, leaf_desc])])) => {
      @debug.assert_eq(
        base,
        @lib.SubType::new(
          false,
          [],
          @lib.CompType::struct_([]),
          metadata=@lib.TypeMetadata::new(
            descriptor=Some(@lib.TypeIdx::new(1U)),
          ),
        ),
      )
      @debug.assert_eq(
        base_desc,
        @lib.SubType::new(
          false,
          [],
          @lib.CompType::struct_([]),
          metadata=@lib.TypeMetadata::new(describes=Some(@lib.TypeIdx::new(0U))),
        ),
      )
      @debug.assert_eq(
        leaf,
        @lib.SubType::new(
          true,
          [@lib.TypeIdx::new(0U)],
          @lib.CompType::struct_([
            @lib.FieldType::new(
              @lib.StorageType::val_type(@lib.ValType::i32()),
              @lib.Mut::const_(),
            ),
          ]),
          metadata=@lib.TypeMetadata::new(
            descriptor=Some(@lib.TypeIdx::new(3U)),
          ),
        ),
      )
      @debug.assert_eq(
        leaf_desc,
        @lib.SubType::new(
          true,
          [@lib.TypeIdx::new(1U)],
          @lib.CompType::struct_([
            @lib.FieldType::new(
              @lib.StorageType::val_type(@lib.ValType::f32()),
              @lib.Mut::const_(),
            ),
          ]),
          metadata=@lib.TypeMetadata::new(describes=Some(@lib.TypeIdx::new(2U))),
        ),
      )
    }
    _ => fail("expected grouped descriptor final-subtype lowering shape")
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail(
        "expected descriptor final-subtype fixture to validate after lowering: \{e}",
      )
  }
}

///|
test "wast_to_binary_module validates nested descriptor chains from text" {
  let src =
    #|(module
    #|  (rec
    #|    (type $node (descriptor $node_desc) (struct))
    #|    (type $node_desc (describes $node) (descriptor $node_meta) (struct))
    #|    (type $node_meta (describes $node_desc) (struct))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail("expected nested descriptor-chain fixture lowering success: \{e}")
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail(
        "expected nested descriptor-chain fixture to validate after lowering: \{e}",
      )
  }
}

///|
test "wast_to_binary_module validates cross-rec descriptor subtype chains from text" {
  let src =
    #|(module
    #|  (rec
    #|    (type $base (sub (descriptor $base_desc) (struct)))
    #|    (type $base_desc (sub (describes $base) (struct))))
    #|  (rec
    #|    (type $leaf (sub $base (descriptor $leaf_desc) (struct)))
    #|    (type $leaf_desc (sub $base_desc (describes $leaf) (struct)))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail(
        "expected cross-rec descriptor subtype-chain fixture lowering success: \{e}",
      )
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail(
        "expected cross-rec descriptor subtype-chain fixture to validate after lowering: \{e}",
      )
  }
}

///|
fn wt_lower_error_contains(source : String, needle : String) -> Bool {
  match wast_to_binary_module(source) {
    Ok(_) => false
    Err(e) => e.contains(needle)
  }
}

///|
test "wast_to_binary_module rejects func import shorthand mixed with inline exports" {
  let src =
    #|(module
    #|  (func $f (export "f") (import "env" "f")))
  assert_true(
    wt_lower_error_contains(
      src, "inline exports are not supported on inline func import shorthand",
    ),
  )
}

///|
test "wast_to_binary_module supports imported funcs exported via explicit export field" {
  let src =
    #|(module
    #|  (func $f (import "env" "f"))
    #|  (export "f" (func $f)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected explicit import+export lowering success: \{e}")
  }
  match lowered.import_sec {
    Some(@lib.ImportSec([@lib.Import(_, _, @lib.FuncExternType(_))])) => ()
    _ => fail("expected lowered import section containing one imported func")
  }
  match lowered.export_sec {
    Some(
      @lib.ExportSec(
        [@lib.Export(name, @lib.FuncExternIdx(@lib.FuncIdx(func_idx)))]
      )
    ) => {
      @debug.assert_eq(name.0, "f")
      @debug.assert_eq(func_idx, 0U)
    }
    _ =>
      fail("expected lowered export section for explicit import+export fixture")
  }
}

///|
test "wast_to_binary_module lowers function identifiers into function names" {
  let src =
    #|(module
    #|  (type $t (func))
    #|  (import "env" "imp" (func $imp (type $t)))
    #|  (func $f (type $t)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected function name lowering success: \{e}")
  }
  match lowered.name_sec {
    Some(name_sec) =>
      match name_sec.func_names {
        Some(
          @lib.NameMap(
            [
              @lib.NameAssoc(0U, @lib.Name(imp_name)),
              @lib.NameAssoc(1U, @lib.Name(func_name)),
            ]
          )
        ) => {
          @debug.assert_eq(imp_name.to_owned(), "imp")
          @debug.assert_eq(func_name.to_owned(), "f")
        }
        _ => fail("expected lowered function names")
      }
    None => fail("expected name section")
  }
}

///|
test "wast_to_binary_module lowers function annotations for imports and defs" {
  let src =
    #|(module
    #|  (type $t (func))
    #|  (@binaryen.js.called)
    #|  (import "env" "imp" (func $imp (type $t)))
    #|  (@binaryen.idempotent)
    #|  (@metadata.code.inline "\00")
    #|  (func $f (type $t)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected function annotation lowering success: \{e}")
  }
  match lowered.func_annotation_sec {
    Some(
      @lib.FuncAnnotationSec(
        [
          @lib.FuncAnnotationAssoc(0U, import_annotations),
          @lib.FuncAnnotationAssoc(1U, func_annotations),
        ]
      )
    ) => {
      @debug.assert_eq(import_annotations, [
        @lib.FuncAnnotation::new("binaryen.js.called"),
      ])
      @debug.assert_eq(func_annotations, [
        @lib.FuncAnnotation::new("binaryen.idempotent"),
        @lib.FuncAnnotation::new("metadata.code.inline", args=["\"\\00\""]),
      ])
    }
    _ => fail("expected lowered function annotation section")
  }
}

///|
test "wast_to_binary_module lowers legacy gc reference aliases" {
  let src =
    #|(module
    #|  (table 1 anyref)
    #|  (func
    #|    (param anyref eqref i31ref structref arrayref nullref)
    #|    (result anyref structref arrayref nullref)
    #|    (ref.null struct)
    #|    (ref.null array)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected legacy alias lowering success: \{e}")
  }
  match lowered.table_sec {
    Some(@lib.TableSec([@lib.Table(@lib.TableType(elem_type, _), _)])) =>
      @debug.assert_eq(
        elem_type,
        @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::any())),
      )
    _ => fail("expected lowered table section")
  }
  let env = @validate.Env::new().with_module(lowered)
  match env.resolve_functype(@lib.TypeIdx::new(0U)) {
    Some(@lib.FuncType(params, results)) => {
      @debug.assert_eq(params, [
        @lib.ValType::ref_type(
          @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::any())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::eq())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::i31())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(
            true,
            @lib.HeapType::abs(@lib.AbsHeapType::struct_()),
          ),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::array())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::none())),
        ),
      ])
      @debug.assert_eq(results, [
        @lib.ValType::ref_type(
          @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::any())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(
            true,
            @lib.HeapType::abs(@lib.AbsHeapType::struct_()),
          ),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::array())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::none())),
        ),
      ])
    }
    _ => fail("expected lowered function type")
  }
}

///|
test "wast_to_binary_module rejects tag import shorthand mixed with inline exports" {
  let src =
    #|(module
    #|  (type $t (func))
    #|  (tag $e (export "e") (import "env" "e") (type $t)))
  assert_true(
    wt_lower_error_contains(
      src, "inline exports are not supported on inline tag import shorthand",
    ),
  )
}

///|
test "wast_to_binary_module supports imported tags exported via explicit export field" {
  let src =
    #|(module
    #|  (type $t (func))
    #|  (tag $e (import "env" "e") (type $t))
    #|  (export "e" (tag $e)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected explicit tag import+export lowering success: \{e}")
  }
  match lowered.import_sec {
    Some(
      @lib.ImportSec(
        [@lib.Import(_, _, @lib.TagExternType(@lib.TagType(@lib.TypeIdx(idx))))]
      )
    ) => @debug.assert_eq(idx, 0U)
    _ => fail("expected lowered import section containing one imported tag")
  }
  match lowered.export_sec {
    Some(
      @lib.ExportSec(
        [@lib.Export(name, @lib.TagExternIdx(@lib.TagIdx(tag_idx)))]
      )
    ) => {
      @debug.assert_eq(name.0, "e")
      @debug.assert_eq(tag_idx, 0U)
    }
    _ =>
      fail(
        "expected lowered export section for explicit tag import+export fixture",
      )
  }
}

///|
test "wast_to_binary_module lowers table elem-abbreviation with func keyword and non-zero offset" {
  let src =
    #|(module
    #|  (func $f)
    #|  (table 4 funcref (elem (i32.const 2) func $f)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail("expected func-keyword table abbreviation lowering success: \{e}")
  }
  let expected = @lib.Elem::new(
    @lib.ElemMode::active(
      @lib.TableIdx::new(0),
      @lib.Expr::new([@lib.Instruction::i32_const(@lib.I32(2))]),
    ),
    @lib.ElemKind::funcs([@lib.FuncIdx::new(0)]),
  )
  match lowered.elem_sec {
    Some(@lib.ElemSec([elem])) => @debug.assert_eq(elem, expected)
    _ =>
      fail(
        "expected lowered elem section for func-keyword table abbreviation fixture",
      )
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail("expected func-keyword table abbreviation fixture to validate: \{e}")
  }
}

///|
test "wast_to_binary_module lowers table elem-abbreviation typed ref.func items" {
  let src =
    #|(module
    #|  (func $f)
    #|  (table 2 funcref (elem funcref (ref.func $f) (item (ref.func $f)))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail("expected typed table elem-abbreviation lowering success: \{e}")
  }
  let expected = @lib.Elem::new(
    @lib.ElemMode::active(
      @lib.TableIdx::new(0),
      @lib.Expr::new([@lib.Instruction::i32_const(@lib.I32(0))]),
    ),
    @lib.ElemKind::typed_exprs(
      @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::func())),
      [
        @lib.Expr::new([@lib.Instruction::ref_func(@lib.FuncIdx::new(0))]),
        @lib.Expr::new([@lib.Instruction::ref_func(@lib.FuncIdx::new(0))]),
      ],
    ),
  )
  match lowered.elem_sec {
    Some(@lib.ElemSec([elem])) => @debug.assert_eq(elem, expected)
    _ =>
      fail(
        "expected lowered elem section for typed table elem-abbreviation fixture",
      )
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail("expected typed table elem-abbreviation fixture to validate: \{e}")
  }
}

///|
test "wast_to_binary_module lowers explicit typed ref.func-only elem segments as typed exprs" {
  let src =
    #|(module
    #|  (table 1 funcref)
    #|  (func $f)
    #|  (elem (i32.const 0) funcref (item (ref.func $f)) (item (ref.func $f))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail(
        "expected explicit typed ref.func-only elem-segment lowering success: \{e}",
      )
  }
  let expected = @lib.Elem::new(
    @lib.ElemMode::active(
      @lib.TableIdx::new(0),
      @lib.Expr::new([@lib.Instruction::i32_const(@lib.I32(0))]),
    ),
    @lib.ElemKind::typed_exprs(
      @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::func())),
      [
        @lib.Expr::new([@lib.Instruction::ref_func(@lib.FuncIdx::new(0))]),
        @lib.Expr::new([@lib.Instruction::ref_func(@lib.FuncIdx::new(0))]),
      ],
    ),
  )
  match lowered.elem_sec {
    Some(@lib.ElemSec([elem])) => @debug.assert_eq(elem, expected)
    _ =>
      fail(
        "expected lowered typed expr elem section for explicit typed ref.func-only fixture",
      )
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail(
        "expected explicit typed ref.func-only elem fixture to validate: \{e}",
      )
  }
}

///|
test "wast_to_binary_module lowers table elem-abbreviation explicit item ref.func-only as typed exprs" {
  let src =
    #|(module
    #|  (func $f)
    #|  (table 2 funcref (elem (item (ref.func $f)) (item (ref.func $f)))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail(
        "expected explicit-item ref.func-only table abbreviation lowering success: \{e}",
      )
  }
  let expected = @lib.Elem::new(
    @lib.ElemMode::active(
      @lib.TableIdx::new(0),
      @lib.Expr::new([@lib.Instruction::i32_const(@lib.I32(0))]),
    ),
    @lib.ElemKind::typed_exprs(
      @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::func())),
      [
        @lib.Expr::new([@lib.Instruction::ref_func(@lib.FuncIdx::new(0))]),
        @lib.Expr::new([@lib.Instruction::ref_func(@lib.FuncIdx::new(0))]),
      ],
    ),
  )
  match lowered.elem_sec {
    Some(@lib.ElemSec([elem])) => @debug.assert_eq(elem, expected)
    _ =>
      fail(
        "expected lowered typed expr elem section for explicit-item table abbreviation fixture",
      )
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail(
        "expected explicit-item ref.func-only table abbreviation fixture to validate: \{e}",
      )
  }
}

///|
test "wast_to_binary_module lowers explicit elem segment explicit item ref.func-only as typed exprs" {
  let src =
    #|(module
    #|  (table 1 funcref)
    #|  (func $f)
    #|  (elem (i32.const 0) (item (ref.func $f)) (item (ref.func $f))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail(
        "expected explicit-item ref.func-only elem segment lowering success: \{e}",
      )
  }
  let expected = @lib.Elem::new(
    @lib.ElemMode::active(
      @lib.TableIdx::new(0),
      @lib.Expr::new([@lib.Instruction::i32_const(@lib.I32(0))]),
    ),
    @lib.ElemKind::typed_exprs(
      @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::func())),
      [
        @lib.Expr::new([@lib.Instruction::ref_func(@lib.FuncIdx::new(0))]),
        @lib.Expr::new([@lib.Instruction::ref_func(@lib.FuncIdx::new(0))]),
      ],
    ),
  )
  match lowered.elem_sec {
    Some(@lib.ElemSec([elem])) => @debug.assert_eq(elem, expected)
    _ =>
      fail(
        "expected lowered typed expr elem section for explicit-item elem segment fixture",
      )
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail(
        "expected explicit-item ref.func-only elem segment fixture to validate: \{e}",
      )
  }
}

///|
test "wast_to_binary_module lowers table elem-abbreviation typed ref.null items" {
  let src =
    #|(module
    #|  (func $f)
    #|  (table 2 funcref (elem funcref (item (ref.null func)) (item (ref.func $f)))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail("expected typed ref.null table abbreviation lowering success: \{e}")
  }
  let expected = @lib.Elem::new(
    @lib.ElemMode::active(
      @lib.TableIdx::new(0),
      @lib.Expr::new([@lib.Instruction::i32_const(@lib.I32(0))]),
    ),
    @lib.ElemKind::typed_exprs(
      @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::func())),
      [
        @lib.Expr::new([
          @lib.Instruction::ref_null(
            @lib.HeapType::abs(@lib.AbsHeapType::func()),
          ),
        ]),
        @lib.Expr::new([@lib.Instruction::ref_func(@lib.FuncIdx::new(0))]),
      ],
    ),
  )
  match lowered.elem_sec {
    Some(@lib.ElemSec([elem])) => @debug.assert_eq(elem, expected)
    _ =>
      fail(
        "expected lowered typed elem section for table abbreviation ref.null fixture",
      )
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail(
        "expected typed ref.null table abbreviation fixture to validate: \{e}",
      )
  }
}

///|
test "wast_to_binary_module lowers explicit elem segments with typed item expressions" {
  let src =
    #|(module
    #|  (table 1 funcref)
    #|  (func $f)
    #|  (elem (i32.const 0) funcref (item (ref.null func)) (item (ref.func $f))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail("expected explicit typed elem-segment lowering success: \{e}")
  }
  let expected = @lib.Elem::new(
    @lib.ElemMode::active(
      @lib.TableIdx::new(0),
      @lib.Expr::new([@lib.Instruction::i32_const(@lib.I32(0))]),
    ),
    @lib.ElemKind::typed_exprs(
      @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::func())),
      [
        @lib.Expr::new([
          @lib.Instruction::ref_null(
            @lib.HeapType::abs(@lib.AbsHeapType::func()),
          ),
        ]),
        @lib.Expr::new([@lib.Instruction::ref_func(@lib.FuncIdx::new(0))]),
      ],
    ),
  )
  match lowered.elem_sec {
    Some(@lib.ElemSec([elem])) => @debug.assert_eq(elem, expected)
    _ => fail("expected lowered typed explicit elem section fixture")
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail("expected explicit typed elem-segment fixture to validate: \{e}")
  }
}

///|
test "wast_to_binary_module lowers typed-heap value types to concrete TypeIdx refs" {
  let src =
    #|(module
    #|  (type $t (func))
    #|  (func (param (ref $t)) (result (ref null $t))
    #|    (ref.null $t)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected typed-heap value-type lowering success: \{e}")
  }
  match lowered.type_sec {
    Some(@lib.TypeSec(types)) => {
      @debug.assert_eq(types.length(), 2)
      @debug.assert_eq(
        types[1],
        @lib.RecType::new(
          @lib.SubType::comp_type(
            @lib.CompType::func(
              [
                @lib.ValType::ref_type(
                  @lib.RefType::new(
                    false,
                    @lib.HeapType::new(@lib.TypeIdx::new(0)),
                  ),
                ),
              ],
              [
                @lib.ValType::ref_type(
                  @lib.RefType::new(
                    true,
                    @lib.HeapType::new(@lib.TypeIdx::new(0)),
                  ),
                ),
              ],
            ),
          ),
        ),
      )
    }
    _ =>
      fail(
        "expected concrete TypeIdx-backed ref param/result types in lowered type section",
      )
  }
  match lowered.code_sec {
    Some(@lib.CodeSec([@lib.Func(_, @lib.Expr([instr]))])) =>
      @debug.assert_eq(
        instr,
        @lib.Instruction::ref_null(@lib.HeapType::new(@lib.TypeIdx::new(0))),
      )
    _ =>
      fail(
        "expected typed ref.null instruction to lower with concrete type index heap type",
      )
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) => fail("expected typed-heap value-type fixture to validate: \{e}")
  }
}

///|
test "wast_to_binary_module lowers exact typed-heap value types to exact refs" {
  let src =
    #|(module
    #|  (type $t (func))
    #|  (func (param (ref (exact $t))) (result (ref (exact $t)))
    #|    (local.get 0)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail("expected exact typed-heap value-type lowering success: \{e}")
  }
  match lowered.type_sec {
    Some(@lib.TypeSec(types)) => {
      @debug.assert_eq(types.length(), 2)
      @debug.assert_eq(
        types[1],
        @lib.RecType::new(
          @lib.SubType::comp_type(
            @lib.CompType::func(
              [
                @lib.ValType::ref_type(
                  @lib.RefType::new(
                    false,
                    @lib.HeapType::new(@lib.TypeIdx::new(0)),
                    exact=true,
                  ),
                ),
              ],
              [
                @lib.ValType::ref_type(
                  @lib.RefType::new(
                    false,
                    @lib.HeapType::new(@lib.TypeIdx::new(0)),
                    exact=true,
                  ),
                ),
              ],
            ),
          ),
        ),
      )
    }
    _ => fail("expected exact concrete TypeIdx-backed ref param/result types")
  }
  match lowered.code_sec {
    Some(@lib.CodeSec([@lib.Func(_, @lib.Expr([instr]))])) =>
      @debug.assert_eq(
        instr,
        @lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
      )
    _ => fail("expected exact typed ref.null instruction lowering")
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) => fail("expected exact typed-heap fixture to validate: \{e}")
  }
}

///|
test "wast_to_binary_module lowers abstract heap ref kinds without collapsing to func" {
  let src =
    #|(module
    #|  (func
    #|    (param (ref any) (ref eq) (ref i31) (ref none) (ref nofunc) (ref noextern) (ref noexn))
    #|    (result (ref null any) (ref null eq) (ref null i31) (ref null none) (ref null nofunc) (ref null noextern) (ref null noexn))
    #|    (ref.null any)
    #|    (ref.null eq)
    #|    (ref.null i31)
    #|    (ref.null none)
    #|    (ref.null nofunc)
    #|    (ref.null noextern)
    #|    (ref.null noexn)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail("expected abstract heap-kind value-type lowering success: \{e}")
  }
  match lowered.type_sec {
    Some(
      @lib.TypeSec(
        [
          @lib.RecType::SingleRecType(
            @lib.SubType::CompTypeSubType(
              _,
              @lib.CompType::FuncCompType(params, results)
            )
          ),
        ]
      )
    ) => {
      @debug.assert_eq(params, [
        @lib.ValType::ref_type(
          @lib.RefType::new(false, @lib.HeapType::abs(@lib.AbsHeapType::any())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(false, @lib.HeapType::abs(@lib.AbsHeapType::eq())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(false, @lib.HeapType::abs(@lib.AbsHeapType::i31())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(false, @lib.HeapType::abs(@lib.AbsHeapType::none())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(
            false,
            @lib.HeapType::abs(@lib.AbsHeapType::no_func()),
          ),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(
            false,
            @lib.HeapType::abs(@lib.AbsHeapType::no_extern()),
          ),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(
            false,
            @lib.HeapType::abs(@lib.AbsHeapType::no_exn()),
          ),
        ),
      ])
      @debug.assert_eq(results, [
        @lib.ValType::ref_type(
          @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::any())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::eq())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::i31())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::none())),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(
            true,
            @lib.HeapType::abs(@lib.AbsHeapType::no_func()),
          ),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(
            true,
            @lib.HeapType::abs(@lib.AbsHeapType::no_extern()),
          ),
        ),
        @lib.ValType::ref_type(
          @lib.RefType::new(
            true,
            @lib.HeapType::abs(@lib.AbsHeapType::no_exn()),
          ),
        ),
      ])
    }
    _ =>
      fail(
        "expected function signature in type section to preserve abstract heap-kind refs",
      )
  }
  match lowered.code_sec {
    Some(@lib.CodeSec([@lib.Func(_, @lib.Expr(instrs))])) =>
      @debug.assert_eq(instrs, [
        @lib.Instruction::ref_null(@lib.HeapType::abs(@lib.AbsHeapType::any())),
        @lib.Instruction::ref_null(@lib.HeapType::abs(@lib.AbsHeapType::eq())),
        @lib.Instruction::ref_null(@lib.HeapType::abs(@lib.AbsHeapType::i31())),
        @lib.Instruction::ref_null(@lib.HeapType::abs(@lib.AbsHeapType::none())),
        @lib.Instruction::ref_null(
          @lib.HeapType::abs(@lib.AbsHeapType::no_func()),
        ),
        @lib.Instruction::ref_null(
          @lib.HeapType::abs(@lib.AbsHeapType::no_extern()),
        ),
        @lib.Instruction::ref_null(
          @lib.HeapType::abs(@lib.AbsHeapType::no_exn()),
        ),
      ])
    _ =>
      fail(
        "expected ref.null instructions to preserve abstract heap kinds in lowered code",
      )
  }
}

///|
test "wast_to_binary_module lowers abstract heap refs across table global tag and import surfaces" {
  let src =
    #|(module
    #|  (type $tag_t (func (param (ref null noextern))))
    #|  (import "env" "itbl" (table 1 (ref null nofunc)))
    #|  (import "env" "ig" (global (mut (ref null any))))
    #|  (import "env" "itag" (tag (type $tag_t)))
    #|  (table 2 (ref null noextern))
    #|  (global (mut (ref null none)) (ref.null none))
    #|  (tag (type $tag_t)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail("expected abstract heap-kind module-surface lowering success: \{e}")
  }

  match lowered.type_sec {
    Some(@lib.TypeSec(types)) => {
      @debug.assert_eq(types.length(), 1)
      @debug.assert_eq(
        types[0],
        @lib.RecType::new(
          @lib.SubType::comp_type(
            @lib.CompType::func(
              [
                @lib.ValType::ref_type(
                  @lib.RefType::new(
                    true,
                    @lib.HeapType::abs(@lib.AbsHeapType::no_extern()),
                  ),
                ),
              ],
              [],
            ),
          ),
        ),
      )
    }
    _ =>
      fail("expected abstract heap-kind tag signature in lowered type section")
  }

  match lowered.import_sec {
    Some(
      @lib.ImportSec(
        [
          @lib.Import(_, _, @lib.TableExternType(import_table_ty)),
          @lib.Import(_, _, @lib.GlobalExternType(import_global_ty)),
          @lib.Import(_, _, @lib.TagExternType(import_tag_ty)),
        ]
      )
    ) => {
      @debug.assert_eq(
        import_table_ty,
        @lib.TableType::new(
          @lib.RefType::new(
            true,
            @lib.HeapType::abs(@lib.AbsHeapType::no_func()),
          ),
          @lib.Limits::i32(1, None),
        ),
      )
      @debug.assert_eq(
        import_global_ty,
        @lib.GlobalType::new(
          @lib.ValType::ref_type(
            @lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::any())),
          ),
          true,
        ),
      )
      @debug.assert_eq(import_tag_ty, @lib.TagType::new(@lib.TypeIdx::new(0)))
    }
    _ =>
      fail(
        "expected lowered imports to preserve abstract heap-kind table/global/tag types",
      )
  }

  match lowered.table_sec {
    Some(@lib.TableSec([table])) =>
      @debug.assert_eq(
        table,
        @lib.Table::new(
          @lib.TableType::new(
            @lib.RefType::new(
              true,
              @lib.HeapType::abs(@lib.AbsHeapType::no_extern()),
            ),
            @lib.Limits::i32(2, None),
          ),
          None,
        ),
      )
    _ => fail("expected lowered table section for abstract heap-kind fixture")
  }

  match lowered.global_sec {
    Some(@lib.GlobalSec([global])) =>
      @debug.assert_eq(
        global,
        @lib.Global::new(
          @lib.GlobalType::new(
            @lib.ValType::ref_type(
              @lib.RefType::new(
                true,
                @lib.HeapType::abs(@lib.AbsHeapType::none()),
              ),
            ),
            true,
          ),
          @lib.Expr::new([
            @lib.Instruction::ref_null(
              @lib.HeapType::abs(@lib.AbsHeapType::none()),
            ),
          ]),
        ),
      )
    _ => fail("expected lowered global section for abstract heap-kind fixture")
  }

  match lowered.tag_sec {
    Some(@lib.TagSec([tag])) =>
      @debug.assert_eq(tag, @lib.TagType::new(@lib.TypeIdx::new(0)))
    _ => fail("expected lowered tag section for abstract heap-kind fixture")
  }
}

///|
test "wast_to_binary_module keeps implicit table-abbrev elem order ahead of explicit elem segments" {
  let src =
    #|(module
    #|  (func $f)
    #|  (table 3 funcref (elem $f))
    #|  (elem (i32.const 1) $f)
    #|  (elem (i32.const 2) $f))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail(
        "expected table abbreviation ordering fixture lowering success: \{e}",
      )
  }
  let expected = [
    @lib.Elem::new(
      @lib.ElemMode::active(
        @lib.TableIdx::new(0),
        @lib.Expr::new([@lib.Instruction::i32_const(@lib.I32(0))]),
      ),
      @lib.ElemKind::funcs([@lib.FuncIdx::new(0)]),
    ),
    @lib.Elem::new(
      @lib.ElemMode::active(
        @lib.TableIdx::new(0),
        @lib.Expr::new([@lib.Instruction::i32_const(@lib.I32(1))]),
      ),
      @lib.ElemKind::funcs([@lib.FuncIdx::new(0)]),
    ),
    @lib.Elem::new(
      @lib.ElemMode::active(
        @lib.TableIdx::new(0),
        @lib.Expr::new([@lib.Instruction::i32_const(@lib.I32(2))]),
      ),
      @lib.ElemKind::funcs([@lib.FuncIdx::new(0)]),
    ),
  ]
  match lowered.elem_sec {
    Some(@lib.ElemSec(elems)) => @debug.assert_eq(elems, expected)
    _ => fail("expected lowered elem section in table-abbrev ordering fixture")
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail(
        "expected table-abbrev ordering fixture to validate after lowering: \{e}",
      )
  }
}

///|
test "wast_to_binary_module gives catchless legacy try an outer delegate" {
  let src =
    #|(module
    #|  (tag $e)
    #|  (func (result i32)
    #|    (try $value (result i32) (do (i32.const 42) (br $value))))
    #|  (func (result i32)
    #|    (try (result i32)
    #|      (do (try (result i32) (do (throw $e))))
    #|      (catch $e (i32.const 42)))))
  let lowered = match wast_to_binary_module(src) {
    Ok(module_) => module_
    Err(error) => fail("catchless try lowering failed: \{error}")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [
          @lib.Func(_, @lib.Expr([@lib.Try(_, _, [], Some(@lib.LabelIdx(0)))])),
          @lib.Func(
            _,
            @lib.Expr(
              [
                @lib.Try(
                  _,
                  @lib.Expr([@lib.Try(_, _, [], Some(@lib.LabelIdx(0)))]),
                  catches,
                  None
                ),
              ]
            )
          ),
        ]
      )
    ) => assert_eq(catches.length(), 1)
    _ => fail("catchless tries must delegate without losing the outer handler")
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(error) => fail("catchless try must validate: \{error}")
  }
}

///|
test "wast_to_binary_module reports targeted legacy delegate/rethrow label diagnostics" {
  let invalid_delegate_depth =
    #|(module
    #|  (func
    #|    (try
    #|      (do nop)
    #|      (delegate 1))))
  assert_true(
    wt_lower_error_contains(
      invalid_delegate_depth, "invalid legacy delegate label",
    ),
  )

  let invalid_delegate_name =
    #|(module
    #|  (func
    #|    (try
    #|      (do nop)
    #|      (delegate $missing))))
  assert_true(
    wt_lower_error_contains(
      invalid_delegate_name, "invalid legacy delegate label",
    ),
  )

  let invalid_rethrow_depth =
    #|(module
    #|  (tag $e)
    #|  (func
    #|    (try
    #|      (do (throw $e))
    #|      (catch $e
    #|        (rethrow 1)))))
  assert_true(
    wt_lower_error_contains(
      invalid_rethrow_depth, "invalid legacy rethrow label",
    ),
  )
}

///|
test "wast_to_binary_module lowers advanced reference no-immediate instructions" {
  let src =
    #|(module
    #|  (func (param externref externref i32 externref)
    #|    local.get 0
    #|    local.get 1
    #|    ref.eq
    #|    drop
    #|    local.get 0
    #|    ref.as_non_null
    #|    drop
    #|    local.get 2
    #|    ref.i31
    #|    i31.get_s
    #|    drop
    #|    local.get 2
    #|    ref.i31
    #|    i31.get_u
    #|    drop
    #|    local.get 3
    #|    any.convert_extern
    #|    extern.convert_any
    #|    drop))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected advanced reference lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [
          @lib.Func(
            _,
            @lib.Expr(
              [
                @lib.LocalGet(@lib.LocalIdx(0)),
                @lib.LocalGet(@lib.LocalIdx(1)),
                ref_eq_instr,
                @lib.Drop,
                @lib.LocalGet(@lib.LocalIdx(0)),
                ref_as_non_null_instr,
                @lib.Drop,
                @lib.LocalGet(@lib.LocalIdx(2)),
                ref_i31_s_instr,
                i31_get_s_instr,
                @lib.Drop,
                @lib.LocalGet(@lib.LocalIdx(2)),
                ref_i31_u_instr,
                i31_get_u_instr,
                @lib.Drop,
                @lib.LocalGet(@lib.LocalIdx(3)),
                any_convert_extern_instr,
                extern_convert_any_instr,
                @lib.Drop,
              ]
            )
          ),
        ]
      )
    ) => {
      @debug.assert_eq(ref_eq_instr, @lib.Instruction::ref_eq())
      @debug.assert_eq(
        ref_as_non_null_instr,
        @lib.Instruction::ref_as_non_null(),
      )
      @debug.assert_eq(ref_i31_s_instr, @lib.Instruction::ref_i31())
      @debug.assert_eq(i31_get_s_instr, @lib.Instruction::i31_get_s())
      @debug.assert_eq(ref_i31_u_instr, @lib.Instruction::ref_i31())
      @debug.assert_eq(i31_get_u_instr, @lib.Instruction::i31_get_u())
      @debug.assert_eq(
        any_convert_extern_instr,
        @lib.Instruction::any_convert_extern(),
      )
      @debug.assert_eq(
        extern_convert_any_instr,
        @lib.Instruction::extern_convert_any(),
      )
    }
    _ =>
      fail(
        "expected lowered function body containing advanced reference instructions",
      )
  }
}

///|
test "wast_to_binary_module lowers throw throw_ref and try_table catch forms" {
  let src =
    #|(module
    #|  (type $void (func))
    #|  (tag $e0 (type $void))
    #|  (func
    #|    (block $h
    #|      (try_table (catch $e0 $h)
    #|        (throw $e0))
    #|      (try_table (catch_ref $e0 $h)
    #|        throw_ref)
    #|      (try_table (catch_all $h)
    #|        unreachable)
    #|      (try_table (catch_all_ref $h)
    #|        unreachable))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected exception-form lowering success: \{e}")
  }

  match lowered.tag_sec {
    Some(@lib.TagSec([@lib.TagType(@lib.TypeIdx(tag_type_idx))])) =>
      @debug.assert_eq(tag_type_idx, 0U)
    _ => fail("expected lowered tag section for exception fixture")
  }

  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [
          @lib.Func(
            _,
            @lib.Expr(
              [
                @lib.Block(
                  _,
                  @lib.Expr(
                    [try_catch, try_catch_ref, try_catch_all, try_catch_all_ref]
                  )
                ),
              ]
            )
          ),
        ]
      )
    ) => {
      match try_catch {
        @lib.TryTable(
          _,
          [@lib.Catch(@lib.TagIdx(catch_tag), @lib.LabelIdx(catch_label))],
          @lib.Expr([@lib.Throw(@lib.TagIdx(throw_tag))])
        ) => {
          @debug.assert_eq(catch_tag, 0U)
          @debug.assert_eq(catch_label, 0U)
          @debug.assert_eq(throw_tag, 0U)
        }
        _ => fail("expected try_table catch lowering shape")
      }

      match try_catch_ref {
        @lib.TryTable(
          _,
          [@lib.CatchRef(@lib.TagIdx(catch_tag), @lib.LabelIdx(catch_label))],
          @lib.Expr([@lib.ThrowRef])
        ) => {
          @debug.assert_eq(catch_tag, 0U)
          @debug.assert_eq(catch_label, 0U)
        }
        _ => fail("expected try_table catch_ref lowering shape")
      }

      match try_catch_all {
        @lib.TryTable(
          _,
          [@lib.CatchAll(@lib.LabelIdx(catch_label))],
          @lib.Expr([@lib.Unreachable])
        ) => @debug.assert_eq(catch_label, 0U)
        _ => fail("expected try_table catch_all lowering shape")
      }

      match try_catch_all_ref {
        @lib.TryTable(
          _,
          [@lib.CatchAllRef(@lib.LabelIdx(catch_label))],
          @lib.Expr([@lib.Unreachable])
        ) => @debug.assert_eq(catch_label, 0U)
        _ => fail("expected try_table catch_all_ref lowering shape")
      }
    }
    _ =>
      fail(
        "expected lowered function body containing try_table exception forms",
      )
  }
}

///|
test "wast_to_binary_module resolves try_table catch labels across nested numeric and named depths" {
  let src =
    #|(module
    #|  (tag $e0) (tag $e1) (tag $e2) (tag $e3) (tag $e4)
    #|  (func
    #|    (block $outer
    #|      (block $inner
    #|        (try_table
    #|          (catch $e0 0)
    #|          (catch $e1 1)
    #|          (catch $e2 2)
    #|          (catch $e3 $inner)
    #|          (catch $e4 $outer)
    #|          (throw $e0))))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail("expected nested try_table catch-depth lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [
          @lib.Func(
            _,
            @lib.Expr(
              [
                @lib.Block(
                  _,
                  @lib.Expr(
                    [
                      @lib.Block(
                        _,
                        @lib.Expr([@lib.TryTable(_, catches, @lib.Expr(_))])
                      ),
                    ]
                  )
                ),
              ]
            )
          ),
        ]
      )
    ) => {
      @debug.assert_eq(catches.length(), 5)
      @debug.assert_eq(catches, [
        @lib.Catch::new(@lib.TagIdx::new(0), @lib.LabelIdx::new(0)),
        @lib.Catch::new(@lib.TagIdx::new(1), @lib.LabelIdx::new(1)),
        @lib.Catch::new(@lib.TagIdx::new(2), @lib.LabelIdx::new(2)),
        @lib.Catch::new(@lib.TagIdx::new(3), @lib.LabelIdx::new(0)),
        @lib.Catch::new(@lib.TagIdx::new(4), @lib.LabelIdx::new(1)),
      ])
    }
    _ =>
      fail(
        "expected nested try_table lowering with numeric/named catch-depth mapping",
      )
  }
}

///|
test "wast_to_binary_module rejects try_table catch depth beyond implicit function label" {
  let src =
    #|(module
    #|  (tag $e0)
    #|  (func (try_table (catch $e0 1) (throw $e0))))
  assert_true(wt_lower_error_contains(src, "invalid label index: 1"))
}

///|
test "wast_to_binary_module preserves try_table param-only blocktype as typeidx" {
  let src =
    #|(module
    #|  (func
    #|    (i32.const 0)
    #|    (try_table (param i32) (drop))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected try_table param-only lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [@lib.Func(_, @lib.Expr([@lib.I32Const(_), @lib.TryTable(bt, _, _)]))]
      )
    ) =>
      match bt {
        @lib.TypeIdxBlockType(@lib.TypeIdx(_)) => ()
        _ =>
          fail("expected param-only try_table blocktype to lower as type_idx")
      }
    _ => fail("expected lowered function containing param-only try_table")
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail(
        "expected param-only try_table fixture to validate after lowering: \{e}",
      )
  }
}

///|
test "wast_to_binary_module reuses matching function type for implicit typeuse" {
  let src =
    #|(module
    #|  (type $t (func))
    #|  (func $dummy)
    #|  (elem declare func $dummy)
    #|  (tag $e (param (ref $t)))
    #|  (func (throw $e (ref.func $dummy))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected implicit typeuse lowering success: \{e}")
  }
  match lowered.func_sec {
    Some(@lib.FuncSec([@lib.TypeIdx(t0), @lib.TypeIdx(t1)])) => {
      @debug.assert_eq(t0, 0U)
      @debug.assert_eq(t1, 0U)
    }
    _ => fail("expected two defined functions with stable type indices")
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail(
        "expected implicit typeuse fixture with ref.func tag payload to validate: \{e}",
      )
  }
}

///|
test "wast_to_binary_module lowers inline tag exports into export section" {
  let src =
    #|(module
    #|  (type $void (func))
    #|  (tag $e0 (export "e0") (export "e0_alias") (type $void)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected inline tag export lowering success: \{e}")
  }
  match lowered.tag_sec {
    Some(@lib.TagSec([@lib.TagType(@lib.TypeIdx(type_idx))])) =>
      @debug.assert_eq(type_idx, 0U)
    _ => fail("expected lowered tag section for inline-export tag fixture")
  }
  match lowered.export_sec {
    Some(
      @lib.ExportSec(
        [
          @lib.Export(name0, @lib.TagExternIdx(@lib.TagIdx(tag_idx0))),
          @lib.Export(name1, @lib.TagExternIdx(@lib.TagIdx(tag_idx1))),
        ]
      )
    ) => {
      @debug.assert_eq(name0.0, "e0")
      @debug.assert_eq(name1.0, "e0_alias")
      @debug.assert_eq(tag_idx0, 0U)
      @debug.assert_eq(tag_idx1, 0U)
    }
    _ => fail("expected lowered export section containing inline tag exports")
  }
}

///|
test "wast_to_binary_module lowers inline exports across func table memory and global fields" {
  let src =
    #|(module
    #|  (import "env" "ifunc" (func))
    #|  (import "env" "itbl" (table 1 funcref))
    #|  (import "env" "imem" (memory 1))
    #|  (import "env" "ig" (global i32))
    #|  (func (export "f"))
    #|  (table (export "t") 1 funcref)
    #|  (memory (export "m") 1)
    #|  (global (export "g") i32 (i32.const 0)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail(
        "expected shared inline export lowering success across field kinds: \{e}",
      )
  }
  match lowered.export_sec {
    Some(
      @lib.ExportSec(
        [
          @lib.Export(fname, @lib.FuncExternIdx(@lib.FuncIdx(fidx))),
          @lib.Export(tname, @lib.TableExternIdx(@lib.TableIdx(tidx))),
          @lib.Export(mname, @lib.MemExternIdx(@lib.MemIdx(midx))),
          @lib.Export(gname, @lib.GlobalExternIdx(@lib.GlobalIdx(gidx))),
        ]
      )
    ) => {
      @debug.assert_eq(fname.0, "f")
      @debug.assert_eq(tname.0, "t")
      @debug.assert_eq(mname.0, "m")
      @debug.assert_eq(gname.0, "g")
      @debug.assert_eq(fidx, 1U)
      @debug.assert_eq(tidx, 1U)
      @debug.assert_eq(midx, 1U)
      @debug.assert_eq(gidx, 1U)
    }
    _ =>
      fail(
        "expected export section containing inline exports for func/table/memory/global",
      )
  }
}

///|
test "wast_to_binary_module validates catch_ref and catch_all_ref exnref label flows" {
  let src =
    #|(module
    #|  (tag $e0)
    #|  (func
    #|    (block $h (result exnref)
    #|      (try_table (catch_ref $e0 $h) (throw $e0))
    #|      (unreachable))
    #|    (drop))
    #|  (func
    #|    (block $h (result exnref)
    #|      (try_table (result exnref) (catch_all_ref $h) (throw $e0)))
    #|    (drop)))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail(
        "expected catch_ref/catch_all_ref exnref flow fixture lowering success: \{e}",
      )
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [
          @lib.Func(
            _,
            @lib.Expr(
              [
                @lib.Block(_, @lib.Expr([try_catch_ref, @lib.Unreachable])),
                @lib.Drop,
              ]
            )
          ),
          @lib.Func(
            _,
            @lib.Expr(
              [@lib.Block(_, @lib.Expr([try_catch_all_ref])), @lib.Drop]
            )
          ),
        ]
      )
    ) => {
      match try_catch_ref {
        @lib.TryTable(
          _,
          [@lib.CatchRef(@lib.TagIdx(catch_tag), @lib.LabelIdx(catch_label))],
          @lib.Expr([@lib.Throw(@lib.TagIdx(throw_tag))])
        ) => {
          @debug.assert_eq(catch_tag, 0U)
          @debug.assert_eq(catch_label, 0U)
          @debug.assert_eq(throw_tag, 0U)
        }
        _ => fail("expected catch_ref exnref-flow lowering shape")
      }
      match try_catch_all_ref {
        @lib.TryTable(
          _,
          [@lib.CatchAllRef(@lib.LabelIdx(catch_label))],
          @lib.Expr([@lib.Throw(@lib.TagIdx(throw_tag))])
        ) => {
          @debug.assert_eq(catch_label, 0U)
          @debug.assert_eq(throw_tag, 0U)
        }
        _ => fail("expected catch_all_ref exnref-flow lowering shape")
      }
    }
    _ =>
      fail(
        "expected lowered code for catch_ref/catch_all_ref exnref flow fixture",
      )
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) =>
      fail(
        "expected catch_ref/catch_all_ref exnref flow fixture to validate: \{e}",
      )
  }
}

///|
test "wast_to_binary_module keeps imported-tag wiring stable across catch and throw forms" {
  let src =
    #|(module
    #|  (type $void (func))
    #|  (import "env" "imp0" (tag $imp0 (type $void)))
    #|  (tag $imp1 (import "env" "imp1") (type $void))
    #|  (tag $local (type $void))
    #|  (func
    #|    (block $h
    #|      (try_table (catch $imp0 $h) (throw $imp1))
    #|      (try_table (catch $local $h) (throw $local)))))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail("expected imported-tag wiring fixture lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [
          @lib.Func(
            _,
            @lib.Expr([@lib.Block(_, @lib.Expr([first_try, second_try]))])
          ),
        ]
      )
    ) => {
      match first_try {
        @lib.TryTable(
          _,
          [@lib.Catch(@lib.TagIdx(catch_tag), @lib.LabelIdx(catch_label))],
          @lib.Expr([@lib.Throw(@lib.TagIdx(throw_tag))])
        ) => {
          @debug.assert_eq(catch_tag, 0U)
          @debug.assert_eq(catch_label, 0U)
          @debug.assert_eq(throw_tag, 1U)
        }
        _ => fail("expected first imported-tag try_table lowering shape")
      }
      match second_try {
        @lib.TryTable(
          _,
          [@lib.Catch(@lib.TagIdx(catch_tag), @lib.LabelIdx(catch_label))],
          @lib.Expr([@lib.Throw(@lib.TagIdx(throw_tag))])
        ) => {
          @debug.assert_eq(catch_tag, 2U)
          @debug.assert_eq(catch_label, 0U)
          @debug.assert_eq(throw_tag, 2U)
        }
        _ => fail("expected second imported/local-tag try_table lowering shape")
      }
    }
    _ => fail("expected lowered code for imported-tag wiring fixture")
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) => fail("expected imported-tag wiring fixture to validate: \{e}")
  }
}

///|
test "wast_to_binary_module reports malformed NaN payloads in v128.const" {
  assert_true(
    wt_lower_error_contains(
      "(module (func (result v128) v128.const f32x4 nan:0x 0 0 0))", "invalid f32x4 NaN payload literal: nan:0x",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (result v128) v128.const f32x4 nan:0xg 0 0 0))", "invalid f32x4 NaN payload literal: nan:0xg",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (result v128) v128.const f64x2 nan:0x 0))", "invalid f64x2 NaN payload literal: nan:0x",
    ),
  )
}

///|
test "wast_to_binary_module enforces i64x2 unsigned literal boundaries" {
  let lowered = match
    wast_to_binary_module(
      "(module (func (result v128) v128.const i64x2 18446744073709551615 0))",
    ) {
    Ok(m) => m
    Err(e) =>
      fail(
        "expected i64x2 v128.const max unsigned literal lowering success: \{e}",
      )
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [
          @lib.Func(
            _,
            @lib.Expr(
              [
                @lib.V128Const(
                  b0,
                  b1,
                  b2,
                  b3,
                  b4,
                  b5,
                  b6,
                  b7,
                  b8,
                  b9,
                  b10,
                  b11,
                  b12,
                  b13,
                  b14,
                  b15
                ),
              ]
            )
          ),
        ]
      )
    ) => {
      @debug.assert_eq(b0, (0xff).to_byte())
      @debug.assert_eq(b1, (0xff).to_byte())
      @debug.assert_eq(b2, (0xff).to_byte())
      @debug.assert_eq(b3, (0xff).to_byte())
      @debug.assert_eq(b4, (0xff).to_byte())
      @debug.assert_eq(b5, (0xff).to_byte())
      @debug.assert_eq(b6, (0xff).to_byte())
      @debug.assert_eq(b7, (0xff).to_byte())
      @debug.assert_eq(b8, (0x00).to_byte())
      @debug.assert_eq(b9, (0x00).to_byte())
      @debug.assert_eq(b10, (0x00).to_byte())
      @debug.assert_eq(b11, (0x00).to_byte())
      @debug.assert_eq(b12, (0x00).to_byte())
      @debug.assert_eq(b13, (0x00).to_byte())
      @debug.assert_eq(b14, (0x00).to_byte())
      @debug.assert_eq(b15, (0x00).to_byte())
    }
    _ => fail("expected lowered i64x2 max-literal v128.const function body")
  }

  assert_true(
    wt_lower_error_contains(
      "(module (func (result v128) v128.const i64x2 18446744073709551616 0))", "invalid i64x2 literal: 18446744073709551616",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (result v128) v128.const i64x2 0x10000000000000000 0))", "invalid i64x2 literal: 0x10000000000000000",
    ),
  )
}

///|
test "native text pipeline accepts exception fixtures with .wat and .wast inputs" {
  let wat_source =
    #|(module
    #|  (type (func))
    #|  (tag (type 0))
    #|  (func (throw 0)))
  let wat_module = match
    wast_to_binary_module(wat_source, filename="exception-fixture.wat") {
    Ok(m) => m
    Err(e) => fail("expected native .wat pipeline success: \{e}")
  }
  match wat_module.tag_sec {
    Some(@lib.TagSec([_])) => ()
    _ => fail("expected .wat pipeline to produce a tag section")
  }

  let wast_source =
    #|(module
    #|  (type (func))
    #|  (tag (type 0))
    #|  (func (throw 0)))
  let report = run_wast_spec_file("exception-fixture.wast", wast_source)
  match report.status {
    Passed => assert_true(report.checked_commands >= 1)
    Skipped(reason) =>
      fail(
        "expected native .wast pipeline check to run without skips: \{reason}",
      )
    Failed(msg) => fail("expected native .wast pipeline success: \{msg}")
  }
}

///|
test "wast_to_binary_module rejects out-of-range SIMD lane immediates" {
  assert_true(
    wt_lower_error_contains(
      "(module (memory 1) (func (param i32 v128) local.get 0 local.get 1 v128.load8_lane 16 drop))",
      "v128.load8_lane lane index out of range: 16",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (memory 1) (func (param i32 v128) local.get 0 local.get 1 v128.load16_lane 8 drop))",
      "v128.load16_lane lane index out of range: 8",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (memory 1) (func (param i32 v128) local.get 0 local.get 1 v128.load32_lane 4 drop))",
      "v128.load32_lane lane index out of range: 4",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (memory 1) (func (param i32 v128) local.get 0 local.get 1 v128.load64_lane 2 drop))",
      "v128.load64_lane lane index out of range: 2",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (memory 1) (func (param i32 v128) local.get 0 local.get 1 v128.store8_lane 16))",
      "v128.store8_lane lane index out of range: 16",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (memory 1) (func (param i32 v128) local.get 0 local.get 1 v128.store16_lane 8))",
      "v128.store16_lane lane index out of range: 8",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (memory 1) (func (param i32 v128) local.get 0 local.get 1 v128.store32_lane 4))",
      "v128.store32_lane lane index out of range: 4",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (memory 1) (func (param i32 v128) local.get 0 local.get 1 v128.store64_lane 2))",
      "v128.store64_lane lane index out of range: 2",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (param v128) local.get 0 i8x16.extract_lane_u 16 drop))", "i8x16.extract_lane_u lane index out of range: 16",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (param v128 i32) local.get 0 local.get 1 i8x16.replace_lane 16 drop))",
      "i8x16.replace_lane lane index out of range: 16",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (param v128) local.get 0 i16x8.extract_lane_u 8 drop))", "i16x8.extract_lane_u lane index out of range: 8",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (param v128 i32) local.get 0 local.get 1 i16x8.replace_lane 8 drop))",
      "i16x8.replace_lane lane index out of range: 8",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (param v128) local.get 0 i32x4.extract_lane 4 drop))", "i32x4.extract_lane lane index out of range: 4",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (param v128 i32) local.get 0 local.get 1 i32x4.replace_lane 4 drop))",
      "i32x4.replace_lane lane index out of range: 4",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (param v128) local.get 0 i64x2.extract_lane 2 drop))", "i64x2.extract_lane lane index out of range: 2",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (param v128 i64) local.get 0 local.get 1 i64x2.replace_lane 2 drop))",
      "i64x2.replace_lane lane index out of range: 2",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (param v128) local.get 0 f32x4.extract_lane 4 drop))", "f32x4.extract_lane lane index out of range: 4",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (param v128 f32) local.get 0 local.get 1 f32x4.replace_lane 4 drop))",
      "f32x4.replace_lane lane index out of range: 4",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (param v128) local.get 0 f64x2.extract_lane 2 drop))", "f64x2.extract_lane lane index out of range: 2",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (param v128 f64) local.get 0 local.get 1 f64x2.replace_lane 2 drop))",
      "f64x2.replace_lane lane index out of range: 2",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (param v128 v128) local.get 0 local.get 1 i8x16.shuffle 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 32 drop))",
      "i8x16.shuffle lane index out of range: 32",
    ),
  )
}

///|
test "wast_to_binary_module rejects out-of-range SIMD v128.const lane values" {
  assert_true(
    wt_lower_error_contains(
      "(module (func (result v128) v128.const i8x16 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 256))",
      "i8x16 lane literal out of range: 256",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (result v128) v128.const i16x8 0 0 0 0 0 0 0 70000))", "i16x8 lane literal out of range: 70000",
    ),
  )
  assert_true(
    wt_lower_error_contains(
      "(module (func (result v128) v128.const i32x4 0 0 0 4294967296))", "i32x4 lane literal out of range: 4294967296",
    ),
  )
}

///|
test "wast_to_binary_module preserves hexadecimal integer v128.const literals" {
  let src =
    #|(module
    #|  (func (result v128)
    #|    v128.const i8x16
    #|      0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07
    #|      0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f)
    #|  (func (result v128)
    #|    v128.const i16x8 0x0102 0x0304 0x0506 0x0708 0x090a 0x0b0c 0x0d0e 0x0f10)
    #|  (func (result v128)
    #|    v128.const i32x4 0x01020304 0x05060708 0x090a0b0c 0x0d0e0f10)
    #|  (func (result v128)
    #|    v128.const i64x2 0xffffffffffffffff 0x8000000000000000))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) =>
      fail("expected hexadecimal integer v128.const lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(@lib.CodeSec(funcs)) => {
      @debug.assert_eq(funcs.length(), 4)

      match funcs[0] {
        @lib.Func(_, @lib.Expr([instr])) =>
          @debug.assert_eq(
            instr,
            @lib.Instruction::v128_const(
              (0x00).to_byte(),
              (0x01).to_byte(),
              (0x02).to_byte(),
              (0x03).to_byte(),
              (0x04).to_byte(),
              (0x05).to_byte(),
              (0x06).to_byte(),
              (0x07).to_byte(),
              (0x08).to_byte(),
              (0x09).to_byte(),
              (0x0a).to_byte(),
              (0x0b).to_byte(),
              (0x0c).to_byte(),
              (0x0d).to_byte(),
              (0x0e).to_byte(),
              (0x0f).to_byte(),
            ),
          )
        _ => fail("expected i8x16 hex v128.const function body")
      }

      match funcs[1] {
        @lib.Func(_, @lib.Expr([instr])) =>
          @debug.assert_eq(
            instr,
            @lib.Instruction::v128_const(
              (0x02).to_byte(),
              (0x01).to_byte(),
              (0x04).to_byte(),
              (0x03).to_byte(),
              (0x06).to_byte(),
              (0x05).to_byte(),
              (0x08).to_byte(),
              (0x07).to_byte(),
              (0x0a).to_byte(),
              (0x09).to_byte(),
              (0x0c).to_byte(),
              (0x0b).to_byte(),
              (0x0e).to_byte(),
              (0x0d).to_byte(),
              (0x10).to_byte(),
              (0x0f).to_byte(),
            ),
          )
        _ => fail("expected i16x8 hex v128.const function body")
      }

      match funcs[2] {
        @lib.Func(_, @lib.Expr([instr])) =>
          @debug.assert_eq(
            instr,
            @lib.Instruction::v128_const(
              (0x04).to_byte(),
              (0x03).to_byte(),
              (0x02).to_byte(),
              (0x01).to_byte(),
              (0x08).to_byte(),
              (0x07).to_byte(),
              (0x06).to_byte(),
              (0x05).to_byte(),
              (0x0c).to_byte(),
              (0x0b).to_byte(),
              (0x0a).to_byte(),
              (0x09).to_byte(),
              (0x10).to_byte(),
              (0x0f).to_byte(),
              (0x0e).to_byte(),
              (0x0d).to_byte(),
            ),
          )
        _ => fail("expected i32x4 hex v128.const function body")
      }

      match funcs[3] {
        @lib.Func(_, @lib.Expr([instr])) =>
          @debug.assert_eq(
            instr,
            @lib.Instruction::v128_const(
              (0xff).to_byte(),
              (0xff).to_byte(),
              (0xff).to_byte(),
              (0xff).to_byte(),
              (0xff).to_byte(),
              (0xff).to_byte(),
              (0xff).to_byte(),
              (0xff).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0x80).to_byte(),
            ),
          )
        _ => fail("expected i64x2 hex v128.const function body")
      }
    }
    _ =>
      fail("expected lowered module code section for hex v128.const literals")
  }
}

///|
test "wast_to_binary_module preserves NaN payload lanes in v128.const" {
  let src =
    #|(module
    #|  (func (result v128)
    #|    v128.const f32x4 nan:0x1 -nan:0x400001 +nan:0x7fffff nan:0x123456)
    #|  (func (result v128)
    #|    v128.const f64x2 nan:0x1 -nan:0x8000000000001))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected NaN payload v128.const lowering success: \{e}")
  }
  match lowered.code_sec {
    Some(@lib.CodeSec(funcs)) => {
      @debug.assert_eq(funcs.length(), 2)

      match funcs[0] {
        @lib.Func(_, @lib.Expr([instr])) =>
          @debug.assert_eq(
            instr,
            @lib.Instruction::v128_const(
              (0x01).to_byte(),
              (0x00).to_byte(),
              (0x80).to_byte(),
              (0x7f).to_byte(),
              (0x01).to_byte(),
              (0x00).to_byte(),
              (0xc0).to_byte(),
              (0xff).to_byte(),
              (0xff).to_byte(),
              (0xff).to_byte(),
              (0xff).to_byte(),
              (0x7f).to_byte(),
              (0x56).to_byte(),
              (0x34).to_byte(),
              (0x92).to_byte(),
              (0x7f).to_byte(),
            ),
          )
        _ => fail("expected f32x4 NaN payload v128.const function body")
      }

      match funcs[1] {
        @lib.Func(_, @lib.Expr([instr])) =>
          @debug.assert_eq(
            instr,
            @lib.Instruction::v128_const(
              (0x01).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0xf0).to_byte(),
              (0x7f).to_byte(),
              (0x01).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0x00).to_byte(),
              (0xf8).to_byte(),
              (0xff).to_byte(),
            ),
          )
        _ => fail("expected f64x2 NaN payload v128.const function body")
      }
    }
    _ => fail("expected lowered module code section for NaN payload v128.const")
  }
}

///|
test "wast_to_binary_module lowers string array instructions and stringref types" {
  let src =
    #|(module
    #|  (type $u16 (array i16))
    #|  (type $u8 (array (mut i8)))
    #|  (func $build (param (ref null $u16) i32 i32) (result externref)
    #|    local.get 0
    #|    local.get 1
    #|    local.get 2
    #|    string.new_wtf16_array)
    #|  (func $encode (param stringref (ref null $u8) i32) (result i32)
    #|    local.get 0
    #|    local.get 1
    #|    local.get 2
    #|    string.encode_utf8_array))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected string array lowering fixture to succeed: \{e}")
  }
  match lowered.type_sec {
    Some(@lib.TypeSec(types)) => {
      @debug.assert_eq(types.length(), 4)
      match types[2] {
        @lib.RecType::SingleRecType(
          @lib.SubType::CompTypeSubType(
            _,
            @lib.CompType::FuncCompType(_, [result])
          )
        ) => @debug.assert_eq(result, @lib.ValType::externref())
        _ => fail("expected lowered string builder functype")
      }
    }
    _ => fail("expected lowered type section for string array fixture")
  }
  match lowered.code_sec {
    Some(
      @lib.CodeSec(
        [@lib.Func(_, @lib.Expr(build)), @lib.Func(_, @lib.Expr(encode))]
      )
    ) => {
      @debug.assert_eq(build[3], @lib.Instruction::string_new_wtf16_array())
      @debug.assert_eq(encode[3], @lib.Instruction::string_encode_utf8_array())
    }
    _ => fail("expected lowered code section for string array fixture")
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) => fail("expected lowered string array fixture to validate: \{e}")
  }
}

///|
test "wast_to_binary_module lowers string.const literals" {
  let src =
    #|(module
    #|  (global $g stringref string.const "hello")
    #|  (func $f (result stringref)
    #|    string.const "hello"))
  let lowered = match wast_to_binary_module(src) {
    Ok(m) => m
    Err(e) => fail("expected string.const lowering fixture to succeed: \{e}")
  }
  match lowered.global_sec {
    Some(@lib.GlobalSec([@lib.Global(_, @lib.Expr([instr]))])) =>
      @debug.assert_eq(instr, @lib.Instruction::string_const(b"hello"))
    _ => fail("expected lowered global string.const initializer")
  }
  match lowered.code_sec {
    Some(@lib.CodeSec([@lib.Func(_, @lib.Expr([instr]))])) =>
      @debug.assert_eq(instr, @lib.Instruction::string_const(b"hello"))
    _ => fail("expected lowered function string.const body")
  }
  match @validate.validate_module(lowered) {
    Ok(_) => ()
    Err(e) => fail("expected lowered string.const fixture to validate: \{e}")
  }
}

///|
fn wt_array_memory(
  ctx : WTLowerCtx,
  op : Opcode,
  ti : Index,
  ma : MemArg,
) -> Result[@lib.Instruction, String] {
  let index = match
    wt_resolve_named_or_num_index(ti, ctx.type_ids, 0U, "type") {
    Ok(index) => @lib.TypeIdx::new(index)
    Err(err) => return Err(err)
  }
  if ma.offset > 0xffffffffUL {
    return Err("array memory offset exceeds u32")
  }
  let memarg = match wt_mem_arg(ma) {
    Ok(ma) => ma
    Err(err) => return Err(err)
  }
  match op {
    I32Load =>
      Ok(@lib.Instruction::array_load(@lib.LoadOp::i32_load(), index, memarg~))
    I64Load =>
      Ok(@lib.Instruction::array_load(@lib.LoadOp::i64_load(), index, memarg~))
    F32Load =>
      Ok(@lib.Instruction::array_load(@lib.LoadOp::f32_load(), index, memarg~))
    F64Load =>
      Ok(@lib.Instruction::array_load(@lib.LoadOp::f64_load(), index, memarg~))
    I32Load8S =>
      Ok(
        @lib.Instruction::array_load(@lib.LoadOp::i32_load8s(), index, memarg~),
      )
    I32Load8U =>
      Ok(
        @lib.Instruction::array_load(@lib.LoadOp::i32_load8u(), index, memarg~),
      )
    I32Load16S =>
      Ok(
        @lib.Instruction::array_load(@lib.LoadOp::i32_load16s(), index, memarg~),
      )
    I32Load16U =>
      Ok(
        @lib.Instruction::array_load(@lib.LoadOp::i32_load16u(), index, memarg~),
      )
    I64Load8S =>
      Ok(
        @lib.Instruction::array_load(@lib.LoadOp::i64_load8s(), index, memarg~),
      )
    I64Load8U =>
      Ok(
        @lib.Instruction::array_load(@lib.LoadOp::i64_load8u(), index, memarg~),
      )
    I64Load16S =>
      Ok(
        @lib.Instruction::array_load(@lib.LoadOp::i64_load16s(), index, memarg~),
      )
    I64Load16U =>
      Ok(
        @lib.Instruction::array_load(@lib.LoadOp::i64_load16u(), index, memarg~),
      )
    I64Load32S =>
      Ok(
        @lib.Instruction::array_load(@lib.LoadOp::i64_load32s(), index, memarg~),
      )
    I64Load32U =>
      Ok(
        @lib.Instruction::array_load(@lib.LoadOp::i64_load32u(), index, memarg~),
      )
    V128Load =>
      Ok(@lib.Instruction::array_load(@lib.LoadOp::v128_load(), index, memarg~))
    I32Store =>
      Ok(
        @lib.Instruction::array_store(@lib.StoreOp::i32_store(), index, memarg~),
      )
    I64Store =>
      Ok(
        @lib.Instruction::array_store(@lib.StoreOp::i64_store(), index, memarg~),
      )
    F32Store =>
      Ok(
        @lib.Instruction::array_store(@lib.StoreOp::f32_store(), index, memarg~),
      )
    F64Store =>
      Ok(
        @lib.Instruction::array_store(@lib.StoreOp::f64_store(), index, memarg~),
      )
    I32Store8 =>
      Ok(
        @lib.Instruction::array_store(
          @lib.StoreOp::i32_store8(),
          index,
          memarg~,
        ),
      )
    I32Store16 =>
      Ok(
        @lib.Instruction::array_store(
          @lib.StoreOp::i32_store16(),
          index,
          memarg~,
        ),
      )
    I64Store8 =>
      Ok(
        @lib.Instruction::array_store(
          @lib.StoreOp::i64_store8(),
          index,
          memarg~,
        ),
      )
    I64Store16 =>
      Ok(
        @lib.Instruction::array_store(
          @lib.StoreOp::i64_store16(),
          index,
          memarg~,
        ),
      )
    I64Store32 =>
      Ok(
        @lib.Instruction::array_store(
          @lib.StoreOp::i64_store32(),
          index,
          memarg~,
        ),
      )
    V128Store =>
      Ok(
        @lib.Instruction::array_store(
          @lib.StoreOp::v128_store(),
          index,
          memarg~,
        ),
      )
    _ => wt_unsupported("array memory opcode \{op}")
  }
}

///|
fn wt_atomic_order(order : AtomicOrder) -> @lib.AtomicOrder {
  match order {
    SeqCst => @lib.AtomicOrder::seq_cst()
    AcqRel => @lib.AtomicOrder::acq_rel()
    Relaxed => @lib.AtomicOrder::relaxed()
  }
}

///|
fn wt_memory_type(memory : MemoryType) -> Result[@lib.MemType, String] {
  let limits = if memory.memory64 {
    @lib.Limits::i64(memory.min, memory.max)
  } else {
    if memory.min > 4294967295UL ||
      (memory.max is Some(max) && max > 4294967295UL) {
      return Err("memory32 limit exceeds address width")
    }
    @lib.Limits::i32(
      memory.min.to_uint(),
      memory.max.map(fn(value) { value.to_uint() }),
    )
  }
  Ok(@lib.MemType::new(limits, shared=memory.shared))
}