use std::collections::HashMap;
use wasm_encoder::{BlockType, CodeSection, Function, HeapType, Instruction, RefType, ValType};
use super::WasmGcError;
use super::types::TypeRegistry;
#[derive(Debug, Clone, Copy)]
pub(super) struct ListOps {
pub(super) len: u32,
pub(super) reverse: u32,
pub(super) concat: u32,
pub(super) take: u32,
pub(super) drop: u32,
pub(super) contains: Option<u32>,
pub(super) cons: u32,
pub(super) eq: Option<u32>,
pub(super) hash: Option<u32>,
}
#[derive(Debug, Clone, Copy, Default)]
pub(super) struct ListTypeIdx {
pub(super) len: u32,
pub(super) reverse: u32,
pub(super) concat: u32,
pub(super) take: u32,
pub(super) drop: u32,
pub(super) contains: Option<u32>,
pub(super) cons: u32,
pub(super) eq: Option<u32>,
pub(super) hash: Option<u32>,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct VectorFromListOps {
pub(super) from_list: u32,
pub(super) to_list: u32,
pub(super) eq: Option<u32>,
pub(super) hash: Option<u32>,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct StringSplitOps {
pub(super) split: u32,
pub(super) join: u32,
}
#[derive(Default)]
pub(super) struct ListHelperRegistry {
list_ops: HashMap<String, ListOps>,
list_order: Vec<String>,
list_type_indices: HashMap<String, ListTypeIdx>,
vfl_ops: HashMap<String, VectorFromListOps>,
vfl_order: Vec<String>,
vfl_type_indices: HashMap<String, (u32, u32, Option<u32>, Option<u32>)>,
zip_ops: HashMap<String, u32>,
zip_order: Vec<String>,
zip_type_indices: HashMap<String, u32>,
string_split: Option<StringSplitOps>,
string_split_type_indices: Option<(u32, u32)>,
}
impl ListHelperRegistry {
#[allow(clippy::too_many_arguments)]
pub(super) fn assign_slots(
&mut self,
list_canonicals: &[String],
vector_canonicals: &[String],
tuple_canonicals: &[String],
register_string_split_join: bool,
registry: &TypeRegistry,
next_wasm_fn_idx: &mut u32,
next_type_idx: &mut u32,
) -> Result<(), WasmGcError> {
for canonical in list_canonicals {
if self.list_ops.contains_key(canonical) {
continue;
}
let len_type = *next_type_idx;
*next_type_idx += 1;
let rev_type = *next_type_idx;
*next_type_idx += 1;
let concat_type = *next_type_idx;
*next_type_idx += 1;
let take_type = *next_type_idx;
*next_type_idx += 1;
let drop_type = *next_type_idx;
*next_type_idx += 1;
let cons_type = *next_type_idx;
*next_type_idx += 1;
let elem =
TypeRegistry::list_element_type(canonical).ok_or(WasmGcError::Validation(
format!("list canonical `{canonical}` has no parsable element type"),
))?;
let contains_eq = list_eq_kind(elem.trim(), registry);
let contains_type = if contains_eq.is_some() {
let t = *next_type_idx;
*next_type_idx += 1;
Some(t)
} else {
None
};
let needs_list_helpers = contains_eq.is_some();
let list_eq_type = if needs_list_helpers {
let t = *next_type_idx;
*next_type_idx += 1;
Some(t)
} else {
None
};
let list_hash_type = if needs_list_helpers {
let t = *next_type_idx;
*next_type_idx += 1;
Some(t)
} else {
None
};
let len_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let rev_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let concat_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let take_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let drop_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let cons_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let contains_fn = if contains_type.is_some() {
let f = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
Some(f)
} else {
None
};
let list_eq_fn = if list_eq_type.is_some() {
let f = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
Some(f)
} else {
None
};
let list_hash_fn = if list_hash_type.is_some() {
let f = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
Some(f)
} else {
None
};
self.list_ops.insert(
canonical.clone(),
ListOps {
len: len_fn,
reverse: rev_fn,
concat: concat_fn,
take: take_fn,
drop: drop_fn,
contains: contains_fn,
cons: cons_fn,
eq: list_eq_fn,
hash: list_hash_fn,
},
);
self.list_type_indices.insert(
canonical.clone(),
ListTypeIdx {
len: len_type,
reverse: rev_type,
concat: concat_type,
take: take_type,
drop: drop_type,
contains: contains_type,
cons: cons_type,
eq: list_eq_type,
hash: list_hash_type,
},
);
self.list_order.push(canonical.clone());
}
for canonical in list_canonicals {
let elem =
TypeRegistry::list_element_type(canonical).ok_or(WasmGcError::Validation(
format!("list canonical `{canonical}` has no parsable element type"),
))?;
let vec_canonical = format!("Vector<{}>", elem.trim());
if !vector_canonicals.iter().any(|v| v == &vec_canonical) {
continue;
}
if self.vfl_ops.contains_key(canonical) {
continue;
}
let from_ty = *next_type_idx;
*next_type_idx += 1;
let to_ty = *next_type_idx;
*next_type_idx += 1;
let from_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let to_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let eq_kind_ok = list_eq_kind(elem.trim(), registry).is_some();
let (vec_eq_ty, vec_hash_ty, vec_eq_fn, vec_hash_fn) = if eq_kind_ok {
let eq_ty = *next_type_idx;
*next_type_idx += 1;
let hash_ty = *next_type_idx;
*next_type_idx += 1;
let eq_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let hash_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
(Some(eq_ty), Some(hash_ty), Some(eq_fn), Some(hash_fn))
} else {
(None, None, None, None)
};
self.vfl_ops.insert(
canonical.clone(),
VectorFromListOps {
from_list: from_fn,
to_list: to_fn,
eq: vec_eq_fn,
hash: vec_hash_fn,
},
);
self.vfl_type_indices
.insert(canonical.clone(), (from_ty, to_ty, vec_eq_ty, vec_hash_ty));
self.vfl_order.push(canonical.clone());
}
for tup_canonical in tuple_canonicals {
let (a, b) = match super::types::TypeRegistry::tuple_ab(tup_canonical) {
Some(x) => x,
None => continue,
};
let la = format!("List<{}>", a.trim());
let lb = format!("List<{}>", b.trim());
let lt = format!("List<{tup_canonical}>");
if !list_canonicals.iter().any(|c| c == &la)
|| !list_canonicals.iter().any(|c| c == &lb)
|| !list_canonicals.iter().any(|c| c == <)
{
continue;
}
if self.zip_ops.contains_key(tup_canonical) {
continue;
}
let ty = *next_type_idx;
*next_type_idx += 1;
let fnx = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
self.zip_ops.insert(tup_canonical.clone(), fnx);
self.zip_type_indices.insert(tup_canonical.clone(), ty);
self.zip_order.push(tup_canonical.clone());
}
if register_string_split_join && self.string_split.is_none() {
let split_type = *next_type_idx;
*next_type_idx += 1;
let join_type = *next_type_idx;
*next_type_idx += 1;
let split_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let join_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
self.string_split = Some(StringSplitOps {
split: split_fn,
join: join_fn,
});
self.string_split_type_indices = Some((split_type, join_type));
}
Ok(())
}
pub(super) fn list_ops_for(&self, canonical: &str) -> Option<ListOps> {
self.list_ops.get(canonical).copied()
}
pub(super) fn vfl_ops_for(&self, list_canonical: &str) -> Option<VectorFromListOps> {
self.vfl_ops.get(list_canonical).copied()
}
pub(super) fn zip_op_for(&self, tuple_canonical: &str) -> Option<u32> {
self.zip_ops.get(tuple_canonical).copied()
}
pub(super) fn string_split_ops(&self) -> Option<StringSplitOps> {
self.string_split
}
pub(super) fn emit_helper_types(
&self,
types: &mut wasm_encoder::TypeSection,
registry: &TypeRegistry,
) -> Result<(), WasmGcError> {
for canonical in &self.list_order {
let list_idx = registry
.list_type_idx(canonical)
.ok_or(WasmGcError::Validation(format!(
"list `{canonical}` not registered"
)))?;
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
types.ty().function([list_ref], [ValType::I64]); types.ty().function([list_ref], [list_ref]); types.ty().function([list_ref, list_ref], [list_ref]);
types.ty().function([list_ref, ValType::I64], [list_ref]);
types.ty().function([list_ref, ValType::I64], [list_ref]);
let elem = TypeRegistry::list_element_type(canonical).unwrap();
let elem_val = super::types::aver_to_wasm(elem.trim(), Some(registry))?.ok_or(
WasmGcError::Validation(format!(
"list element type `{elem}` has no wasm representation"
)),
)?;
types.ty().function([elem_val, list_ref], [list_ref]);
let kind = list_eq_kind(elem.trim(), registry);
if kind.is_some() {
types.ty().function([list_ref, elem_val], [ValType::I32]);
}
if kind.is_some() {
types.ty().function([list_ref, list_ref], [ValType::I32]);
types.ty().function([list_ref], [ValType::I32]);
}
}
for canonical in &self.vfl_order {
let list_idx = registry
.list_type_idx(canonical)
.ok_or(WasmGcError::Validation(format!(
"list `{canonical}` not registered"
)))?;
let elem = TypeRegistry::list_element_type(canonical).unwrap();
let vec_canonical = format!("Vector<{}>", elem.trim());
let vec_idx =
registry
.vector_type_idx(&vec_canonical)
.ok_or(WasmGcError::Validation(format!(
"vector `{vec_canonical}` not registered for from_list"
)))?;
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let vec_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(vec_idx),
});
types.ty().function([list_ref], [vec_ref]);
types.ty().function([vec_ref], [list_ref]);
let elem = TypeRegistry::list_element_type(canonical).unwrap();
if list_eq_kind(elem.trim(), registry).is_some() {
types.ty().function([vec_ref, vec_ref], [ValType::I32]);
types.ty().function([vec_ref], [ValType::I32]);
}
}
for tup_canonical in &self.zip_order {
let (a, b) = super::types::TypeRegistry::tuple_ab(tup_canonical).unwrap();
let la_idx = registry
.list_type_idx(&format!("List<{}>", a.trim()))
.ok_or(WasmGcError::Validation(format!(
"List.zip: List<{a}> not registered"
)))?;
let lb_idx = registry
.list_type_idx(&format!("List<{}>", b.trim()))
.ok_or(WasmGcError::Validation(format!(
"List.zip: List<{b}> not registered"
)))?;
let lt_idx = registry
.list_type_idx(&format!("List<{tup_canonical}>"))
.ok_or(WasmGcError::Validation(format!(
"List.zip: List<{tup_canonical}> not registered"
)))?;
let la_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(la_idx),
});
let lb_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(lb_idx),
});
let lt_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(lt_idx),
});
types.ty().function([la_ref, lb_ref], [lt_ref]);
}
if self.string_split.is_some() {
let s_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"string slot not registered for String.split/join helpers".into(),
))?;
let list_str_idx =
registry
.list_type_idx("List<String>")
.ok_or(WasmGcError::Validation(
"List<String> not registered for String.split/join helpers".into(),
))?;
let s_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(s_idx),
});
let l_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_str_idx),
});
types.ty().function([s_ref, s_ref], [l_ref]);
types.ty().function([l_ref, s_ref], [s_ref]);
}
Ok(())
}
pub(super) fn emit_function_section(&self, funcs: &mut wasm_encoder::FunctionSection) {
for canonical in &self.list_order {
let idx = self.list_type_indices[canonical];
funcs.function(idx.len);
funcs.function(idx.reverse);
funcs.function(idx.concat);
funcs.function(idx.take);
funcs.function(idx.drop);
funcs.function(idx.cons);
if let Some(t) = idx.contains {
funcs.function(t);
}
if let Some(t) = idx.eq {
funcs.function(t);
}
if let Some(t) = idx.hash {
funcs.function(t);
}
}
for canonical in &self.vfl_order {
let (from_t, to_t, eq_t, hash_t) = self.vfl_type_indices[canonical];
funcs.function(from_t);
funcs.function(to_t);
if let Some(t) = eq_t {
funcs.function(t);
}
if let Some(t) = hash_t {
funcs.function(t);
}
}
for tup_canonical in &self.zip_order {
funcs.function(self.zip_type_indices[tup_canonical]);
}
if let Some((split_t, join_t)) = self.string_split_type_indices {
funcs.function(split_t);
funcs.function(join_t);
}
}
pub(super) fn emit_helper_bodies(
&self,
codes: &mut CodeSection,
registry: &TypeRegistry,
string_eq_fn_idx: Option<u32>,
eq_helper_fn_idx: &std::collections::HashMap<String, u32>,
hash_helper_fn_idx: &std::collections::HashMap<String, u32>,
) -> Result<(), WasmGcError> {
for canonical in &self.list_order {
codes.function(&emit_list_len(canonical, registry)?);
codes.function(&emit_list_reverse(canonical, registry)?);
let ops = self.list_ops[canonical];
codes.function(&emit_list_concat(canonical, registry, ops.reverse)?);
codes.function(&emit_list_take(canonical, registry, ops.reverse)?);
codes.function(&emit_list_drop(canonical, registry)?);
codes.function(&emit_list_cons(canonical, registry)?);
if ops.contains.is_some() {
let elem = TypeRegistry::list_element_type(canonical).unwrap();
let kind = list_eq_kind(elem.trim(), registry).unwrap();
codes.function(&emit_list_contains(
canonical,
registry,
kind.clone(),
string_eq_fn_idx,
eq_helper_fn_idx,
)?);
if let (Some(eq_fn), Some(_hash_fn)) = (ops.eq, ops.hash) {
codes.function(&emit_list_eq(
canonical,
registry,
kind.clone(),
string_eq_fn_idx,
eq_fn,
eq_helper_fn_idx,
)?);
codes.function(&emit_list_hash(
canonical,
registry,
kind,
string_eq_fn_idx,
ops.hash.unwrap(),
hash_helper_fn_idx,
)?);
}
}
}
for canonical in &self.vfl_order {
codes.function(&emit_vec_from_list(canonical, registry)?);
codes.function(&emit_vec_to_list(canonical, registry)?);
let ops = self.vfl_ops[canonical];
if ops.eq.is_some() {
let elem = TypeRegistry::list_element_type(canonical).unwrap();
let kind = list_eq_kind(elem.trim(), registry).unwrap();
codes.function(&emit_vec_eq(
canonical,
registry,
kind.clone(),
string_eq_fn_idx,
eq_helper_fn_idx,
)?);
codes.function(&emit_vec_hash(
canonical,
registry,
kind,
string_eq_fn_idx,
hash_helper_fn_idx,
)?);
}
}
for tup_canonical in &self.zip_order {
let lt_canonical = format!("List<{tup_canonical}>");
let reverse_fn = self.list_ops_for(<_canonical).map(|o| o.reverse).ok_or(
WasmGcError::Validation(format!(
"List.zip: reverse fn for `{lt_canonical}` not registered"
)),
)?;
codes.function(&emit_list_zip(tup_canonical, registry, reverse_fn)?);
}
if self.string_split.is_some() {
let reverse_fn_idx = self.list_ops_for("List<String>").map(|o| o.reverse).ok_or(
WasmGcError::Validation(
"string_split helper needs List<String>.reverse — \
register List<String> via list_canonicals first"
.into(),
),
)?;
codes.function(&emit_string_split(registry, reverse_fn_idx)?);
codes.function(&emit_string_join(registry)?);
}
Ok(())
}
}
#[derive(Debug, Clone)]
enum ListEqKind {
I64,
F64,
I32,
StringEq,
RecordEq(String),
SumEq(String),
CarrierEq(String),
}
fn list_eq_kind(elem: &str, registry: &TypeRegistry) -> Option<ListEqKind> {
let trimmed = elem.trim();
if let Some(under) = registry.newtype_underlying(trimmed) {
return list_eq_kind(under, registry);
}
if (trimmed.starts_with("Option<")
|| trimmed.starts_with("Result<")
|| trimmed.starts_with("Tuple<")
|| trimmed.starts_with("List<")
|| trimmed.starts_with("Vector<")
|| trimmed.starts_with("Map<"))
&& trimmed.ends_with('>')
{
let canonical: String = trimmed.chars().filter(|c| !c.is_whitespace()).collect();
let mut seen = std::collections::HashSet::new();
if field_type_resolvable(trimmed, registry, &mut seen) {
return Some(ListEqKind::CarrierEq(canonical));
}
return None;
}
match trimmed {
"Int" => Some(ListEqKind::I64),
"Float" => Some(ListEqKind::F64),
"Bool" => Some(ListEqKind::I32),
"String" => Some(ListEqKind::StringEq),
other => {
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
if registry.record_type_idx(other).is_some() {
if record_fields_resolvable(other, registry, &mut seen) {
Some(ListEqKind::RecordEq(other.to_string()))
} else {
None
}
} else if registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| v.parent == other)
{
if sum_fields_resolvable(other, registry, &mut seen) {
Some(ListEqKind::SumEq(other.to_string()))
} else {
None
}
} else {
None
}
}
}
}
pub(super) fn record_fields_resolvable(
record: &str,
registry: &TypeRegistry,
seen: &mut std::collections::HashSet<String>,
) -> bool {
if !seen.insert(record.to_string()) {
return true; }
let Some(fields) = registry.record_fields.get(record) else {
return false;
};
fields
.iter()
.all(|(_, t)| field_type_resolvable(t.trim(), registry, seen))
}
pub(super) fn sum_fields_resolvable(
parent: &str,
registry: &TypeRegistry,
seen: &mut std::collections::HashSet<String>,
) -> bool {
if !seen.insert(parent.to_string()) {
return true;
}
registry
.variants
.values()
.flat_map(|vs| vs.iter())
.filter(|v| v.parent == parent)
.all(|v| {
v.fields
.iter()
.all(|t| field_type_resolvable(t.trim(), registry, seen))
})
}
pub(super) fn field_type_resolvable(
field: &str,
registry: &TypeRegistry,
seen: &mut std::collections::HashSet<String>,
) -> bool {
if matches!(field, "Int" | "Float" | "Bool" | "String") {
return true;
}
if registry.record_type_idx(field).is_some() {
return record_fields_resolvable(field, registry, seen);
}
if registry
.variants
.values()
.flat_map(|vs| vs.iter())
.any(|v| v.parent == field)
{
return sum_fields_resolvable(field, registry, seen);
}
if let Some(inner) = field
.strip_prefix("Option<")
.and_then(|s| s.strip_suffix('>'))
{
return field_type_resolvable(inner.trim(), registry, seen);
}
if let Some(inner) = field
.strip_prefix("Result<")
.and_then(|s| s.strip_suffix('>'))
{
let bytes = inner.as_bytes();
let mut depth: i32 = 0;
for (idx, b) in bytes.iter().enumerate() {
match b {
b'<' | b'(' => depth += 1,
b'>' | b')' => depth -= 1,
b',' if depth == 0 => {
let ok = inner[..idx].trim();
let err = inner[idx + 1..].trim();
return field_type_resolvable(ok, registry, seen)
&& field_type_resolvable(err, registry, seen);
}
_ => {}
}
}
return false;
}
if let Some(inner) = field
.strip_prefix("Tuple<")
.and_then(|s| s.strip_suffix('>'))
{
let bytes = inner.as_bytes();
let mut depth: i32 = 0;
let mut start = 0;
for (idx, b) in bytes.iter().enumerate() {
match b {
b'<' | b'(' => depth += 1,
b'>' | b')' => depth -= 1,
b',' if depth == 0 => {
let elem = inner[start..idx].trim();
if !field_type_resolvable(elem, registry, seen) {
return false;
}
start = idx + 1;
}
_ => {}
}
}
return field_type_resolvable(inner[start..].trim(), registry, seen);
}
if let Some(inner) = field
.strip_prefix("List<")
.and_then(|s| s.strip_suffix('>'))
{
return field_type_resolvable(inner.trim(), registry, seen);
}
if let Some(inner) = field
.strip_prefix("Vector<")
.and_then(|s| s.strip_suffix('>'))
{
return field_type_resolvable(inner.trim(), registry, seen);
}
if let Some(inner) = field.strip_prefix("Map<").and_then(|s| s.strip_suffix('>')) {
let bytes = inner.as_bytes();
let mut depth: i32 = 0;
for (idx, b) in bytes.iter().enumerate() {
match b {
b'<' | b'(' => depth += 1,
b'>' | b')' => depth -= 1,
b',' if depth == 0 => {
let k = inner[..idx].trim();
let v = inner[idx + 1..].trim();
return field_type_resolvable(k, registry, seen)
&& field_type_resolvable(v, registry, seen);
}
_ => {}
}
}
return false;
}
false
}
fn list_idx_of(canonical: &str, registry: &TypeRegistry) -> Result<u32, WasmGcError> {
registry
.list_type_idx(canonical)
.ok_or(WasmGcError::Validation(format!(
"list `{canonical}` not registered"
)))
}
fn vec_idx_of_pair(
list_canonical: &str,
registry: &TypeRegistry,
) -> Result<(u32, ValType), WasmGcError> {
let elem = TypeRegistry::list_element_type(list_canonical).unwrap();
let vec_canonical = format!("Vector<{}>", elem.trim());
let vec_idx = registry
.vector_type_idx(&vec_canonical)
.ok_or(WasmGcError::Validation(format!(
"vector `{vec_canonical}` not registered"
)))?;
let elem_val =
super::types::aver_to_wasm(elem.trim(), Some(registry))?.ok_or(WasmGcError::Validation(
format!("list element type `{elem}` has no wasm representation"),
))?;
Ok((vec_idx, elem_val))
}
fn emit_list_len(canonical: &str, registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let list_idx = list_idx_of(canonical, registry)?;
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let mut f = Function::new([(1, list_ref), (1, ValType::I64)]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::I64Const(0));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::I64Const(1));
f.instruction(&Instruction::I64Add);
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_list_reverse(canonical: &str, registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let list_idx = list_idx_of(canonical, registry)?;
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let elem = TypeRegistry::list_element_type(canonical).unwrap();
let elem_val =
super::types::aver_to_wasm(elem.trim(), Some(registry))?.ok_or(WasmGcError::Validation(
format!("list element type `{elem}` has no wasm representation"),
))?;
let mut f = Function::new([(1, list_ref), (1, list_ref), (1, elem_val)]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::RefNull(HeapType::Concrete(list_idx)));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructNew(list_idx));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_vec_from_list(canonical: &str, registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let list_idx = list_idx_of(canonical, registry)?;
let (vec_idx, _elem_val) = vec_idx_of_pair(canonical, registry)?;
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let vec_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(vec_idx),
});
let mut f = Function::new([
(1, list_ref), (1, ValType::I32), (1, vec_ref), (1, ValType::I32), ]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::ArrayNewDefault(vec_idx));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
f.instruction(&Instruction::ArraySet(vec_idx));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_string_split(
registry: &TypeRegistry,
reverse_fn_idx: u32,
) -> Result<Function, WasmGcError> {
let s_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation("string slot not registered".into()))?;
let list_idx = registry
.list_type_idx("List<String>")
.ok_or(WasmGcError::Validation(
"List<String> not registered".into(),
))?;
let s_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(s_idx),
});
let l_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let mut f = Function::new([
(1, ValType::I32), (1, ValType::I32), (1, ValType::I32), (1, ValType::I32), (1, ValType::I32), (1, s_ref), (1, l_ref), (1, ValType::I32), (1, ValType::I32), ]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::ArrayLen);
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::ArrayLen);
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::RefNull(HeapType::Concrete(list_idx)));
f.instruction(&Instruction::LocalSet(8));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::I32GeU);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::ArrayNewDefault(s_idx));
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::ArrayGetU(s_idx));
f.instruction(&Instruction::ArraySet(s_idx));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::StructNew(list_idx));
f.instruction(&Instruction::LocalSet(8));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End); f.instruction(&Instruction::End); f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::Call(reverse_fn_idx));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32LtU);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::I32GtU);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::LocalSet(10));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(9));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32GeU);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::ArrayGetU(s_idx));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::ArrayGetU(s_idx));
f.instruction(&Instruction::I32Ne);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(10));
f.instruction(&Instruction::Br(2));
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(9));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End); f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(10));
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalSet(6));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::ArrayNewDefault(s_idx));
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::ArrayCopy {
array_type_index_dst: s_idx,
array_type_index_src: s_idx,
});
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::StructNew(list_idx));
f.instruction(&Instruction::LocalSet(8));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::Br(1));
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End); f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalSet(6));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::ArrayNewDefault(s_idx));
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::ArrayCopy {
array_type_index_dst: s_idx,
array_type_index_src: s_idx,
});
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::StructNew(list_idx));
f.instruction(&Instruction::LocalSet(8));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::Call(reverse_fn_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_string_join(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let s_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation("string slot not registered".into()))?;
let list_idx = registry
.list_type_idx("List<String>")
.ok_or(WasmGcError::Validation(
"List<String> not registered".into(),
))?;
let s_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(s_idx),
});
let l_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let mut f = Function::new([
(1, l_ref),
(1, ValType::I32),
(1, ValType::I32),
(1, ValType::I32),
(1, s_ref),
(1, ValType::I32),
(1, s_ref),
(1, ValType::I32),
]);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::ArrayLen);
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::End);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
f.instruction(&Instruction::ArrayLen);
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayNewDefault(s_idx));
f.instruction(&Instruction::LocalSet(8));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(9));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::ArrayCopy {
array_type_index_dst: s_idx,
array_type_index_src: s_idx,
});
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(9));
f.instruction(&Instruction::End);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
f.instruction(&Instruction::LocalSet(6));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::ArrayLen);
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::ArrayCopy {
array_type_index_dst: s_idx,
array_type_index_src: s_idx,
});
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(9));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_list_concat(
canonical: &str,
registry: &TypeRegistry,
reverse_fn: u32,
) -> Result<Function, WasmGcError> {
let list_idx = list_idx_of(canonical, registry)?;
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let mut f = Function::new([(1, list_ref), (1, list_ref)]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::Call(reverse_fn));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::StructNew(list_idx));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_list_take(
canonical: &str,
registry: &TypeRegistry,
reverse_fn: u32,
) -> Result<Function, WasmGcError> {
let list_idx = list_idx_of(canonical, registry)?;
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let mut f = Function::new([(1, list_ref), (1, list_ref), (1, ValType::I64)]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::RefNull(HeapType::Concrete(list_idx)));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::I64Const(0));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I64GeS);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::StructNew(list_idx));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I64Const(1));
f.instruction(&Instruction::I64Add);
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::Call(reverse_fn));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_list_drop(canonical: &str, registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let list_idx = list_idx_of(canonical, registry)?;
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let mut f = Function::new([(1, list_ref), (1, ValType::I64)]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::I64Const(0));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I64GeS);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I64Const(1));
f.instruction(&Instruction::I64Add);
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_list_contains(
canonical: &str,
registry: &TypeRegistry,
kind: ListEqKind,
string_eq_fn_idx: Option<u32>,
eq_helper_fn_idx: &std::collections::HashMap<String, u32>,
) -> Result<Function, WasmGcError> {
let list_idx = list_idx_of(canonical, registry)?;
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let elem = TypeRegistry::list_element_type(canonical).unwrap();
let elem_val =
super::types::aver_to_wasm(elem.trim(), Some(registry))?.ok_or(WasmGcError::Validation(
format!("list element type `{elem}` has no wasm representation"),
))?;
let mut locals: Vec<(u32, ValType)> = vec![(1, list_ref)];
if matches!(&kind, ListEqKind::RecordEq(_) | ListEqKind::SumEq(_)) {
locals.push((1, elem_val));
locals.push((1, elem_val));
}
let mut f = Function::new(locals);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
match &kind {
ListEqKind::RecordEq(record_name) => {
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::LocalSet(4));
emit_record_eq_inline(
&mut f,
record_name,
registry,
3,
4,
string_eq_fn_idx,
eq_helper_fn_idx,
None,
)?;
}
ListEqKind::SumEq(parent_name) => {
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::LocalSet(4));
emit_sum_eq_inline(
&mut f,
parent_name,
registry,
3,
4,
string_eq_fn_idx,
eq_helper_fn_idx,
None,
)?;
}
ListEqKind::CarrierEq(canonical) => {
let eq_fn = eq_helper_fn_idx.get(canonical).copied().ok_or_else(|| {
WasmGcError::Validation(format!(
"List.contains over carrier `{canonical}`: \
__eq_{canonical} not registered in eq_helpers"
))
})?;
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(eq_fn));
}
_ => {
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
f.instruction(&Instruction::LocalGet(1));
match &kind {
ListEqKind::I64 => f.instruction(&Instruction::I64Eq),
ListEqKind::F64 => f.instruction(&Instruction::F64Eq),
ListEqKind::I32 => f.instruction(&Instruction::I32Eq),
ListEqKind::StringEq => {
let eq_fn = string_eq_fn_idx.ok_or(WasmGcError::Validation(
"List.contains over String/Char needs __wasmgc_string_eq registered".into(),
))?;
f.instruction(&Instruction::Call(eq_fn))
}
ListEqKind::RecordEq(_) | ListEqKind::SumEq(_) | ListEqKind::CarrierEq(_) => {
unreachable!(
"filtered by outer match arms — RecordEq/SumEq/CarrierEq \
handled above before this fallthrough"
)
}
};
}
}
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::End);
Ok(f)
}
#[allow(clippy::too_many_arguments)]
pub(super) fn emit_record_eq_inline(
f: &mut Function,
record_name: &str,
registry: &TypeRegistry,
head_local: u32,
needle_local: u32,
string_eq_fn_idx: Option<u32>,
eq_helper_fn_idx: &std::collections::HashMap<String, u32>,
self_fn_idx: Option<u32>,
) -> Result<(), WasmGcError> {
let record_idx = registry
.record_type_idx(record_name)
.ok_or(WasmGcError::Validation(format!(
"List.contains: record `{record_name}` not registered"
)))?;
let fields = registry
.record_fields
.get(record_name)
.ok_or(WasmGcError::Validation(format!(
"List.contains: record `{record_name}` has no field info"
)))?;
if fields.is_empty() {
f.instruction(&Instruction::I32Const(1));
return Ok(());
}
for (i, (_, field_ty)) in fields.iter().enumerate() {
f.instruction(&Instruction::LocalGet(head_local));
f.instruction(&Instruction::StructGet {
struct_type_index: record_idx,
field_index: i as u32,
});
f.instruction(&Instruction::LocalGet(needle_local));
f.instruction(&Instruction::StructGet {
struct_type_index: record_idx,
field_index: i as u32,
});
match field_ty.trim() {
"Int" => {
f.instruction(&Instruction::I64Eq);
}
"Bool" => {
f.instruction(&Instruction::I32Eq);
}
"Float" => {
f.instruction(&Instruction::F64Eq);
}
"String" => {
let eq_fn = string_eq_fn_idx.ok_or(WasmGcError::Validation(
"List.contains record field of String type needs \
__wasmgc_string_eq registered"
.into(),
))?;
f.instruction(&Instruction::Call(eq_fn));
}
other if other == record_name && self_fn_idx.is_some() => {
f.instruction(&Instruction::Call(self_fn_idx.unwrap()));
}
other => {
let normalized = super::types::normalize_compound(other);
let key = if eq_helper_fn_idx.contains_key(other) {
Some(other.to_string())
} else if eq_helper_fn_idx.contains_key(normalized.as_str()) {
Some(normalized.clone())
} else {
None
};
if let Some(k) = key {
let idx = eq_helper_fn_idx[k.as_str()];
f.instruction(&Instruction::Call(idx));
} else {
return Err(WasmGcError::Validation(format!(
"record `{record_name}` field type `{other}` has no eq dispatch \
(not in {{Int, Float, Bool, String}}, no `__eq_{other}` helper, \
not self-recursive)"
)));
}
}
}
if i > 0 {
f.instruction(&Instruction::I32And);
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(super) fn emit_sum_eq_inline(
f: &mut Function,
parent_name: &str,
registry: &TypeRegistry,
head_local: u32,
needle_local: u32,
string_eq_fn_idx: Option<u32>,
eq_helper_fn_idx: &std::collections::HashMap<String, u32>,
self_fn_idx: Option<u32>,
) -> Result<(), WasmGcError> {
let mut variants: Vec<(String, super::types::VariantInfo)> = registry
.variants
.iter()
.flat_map(|(n, vs)| vs.iter().map(move |v| (n.clone(), v.clone())))
.filter(|(_, v)| v.parent == parent_name)
.collect();
variants.sort_by(|a, b| a.0.cmp(&b.0));
if variants.is_empty() {
return Err(WasmGcError::Validation(format!(
"List.contains: sum type `{parent_name}` has no variants"
)));
}
f.instruction(&Instruction::Block(BlockType::Result(ValType::I32)));
for (_v_name, info) in &variants {
let v_idx = info.type_idx;
let v_heap = wasm_encoder::HeapType::Concrete(v_idx);
f.instruction(&Instruction::LocalGet(head_local));
f.instruction(&Instruction::RefTestNonNull(v_heap));
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(needle_local));
f.instruction(&Instruction::RefTestNonNull(v_heap));
f.instruction(&Instruction::If(BlockType::Empty));
if info.fields.is_empty() {
f.instruction(&Instruction::I32Const(1));
} else {
for (i, field_ty) in info.fields.iter().enumerate() {
f.instruction(&Instruction::LocalGet(head_local));
f.instruction(&Instruction::RefCastNonNull(v_heap));
f.instruction(&Instruction::StructGet {
struct_type_index: v_idx,
field_index: i as u32,
});
f.instruction(&Instruction::LocalGet(needle_local));
f.instruction(&Instruction::RefCastNonNull(v_heap));
f.instruction(&Instruction::StructGet {
struct_type_index: v_idx,
field_index: i as u32,
});
match field_ty.trim() {
"Int" => {
f.instruction(&Instruction::I64Eq);
}
"Bool" => {
f.instruction(&Instruction::I32Eq);
}
"Float" => {
f.instruction(&Instruction::F64Eq);
}
"String" => {
let eq_fn = string_eq_fn_idx.ok_or(WasmGcError::Validation(
"List.contains sum field of String type needs \
__wasmgc_string_eq registered"
.into(),
))?;
f.instruction(&Instruction::Call(eq_fn));
}
other if other == parent_name && self_fn_idx.is_some() => {
f.instruction(&Instruction::Call(self_fn_idx.unwrap()));
}
other => {
let normalized = super::types::normalize_compound(other);
let key = if eq_helper_fn_idx.contains_key(other) {
other
} else if eq_helper_fn_idx.contains_key(normalized.as_str()) {
normalized.as_str()
} else {
return Err(WasmGcError::Validation(format!(
"sum `{parent_name}` variant field type `{other}` has no eq \
dispatch (not primitive, no `__eq_{other}` helper, not \
self-recursive)"
)));
};
let idx = eq_helper_fn_idx[key];
f.instruction(&Instruction::Call(idx));
}
}
if i > 0 {
f.instruction(&Instruction::I32And);
}
}
}
f.instruction(&Instruction::Br(2)); f.instruction(&Instruction::Else);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::Br(2));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
}
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::End); Ok(())
}
fn emit_list_cons(canonical: &str, registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let list_idx = list_idx_of(canonical, registry)?;
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructNew(list_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_vec_to_list(canonical: &str, registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let list_idx = list_idx_of(canonical, registry)?;
let (vec_idx, _) = vec_idx_of_pair(canonical, registry)?;
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let mut f = Function::new([(1, list_ref), (1, ValType::I32)]);
f.instruction(&Instruction::RefNull(HeapType::Concrete(list_idx)));
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::ArrayLen);
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::I32LtS);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::ArrayGet(vec_idx));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructNew(list_idx));
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_list_zip(
tup_canonical: &str,
registry: &TypeRegistry,
reverse_fn: u32,
) -> Result<Function, WasmGcError> {
let (a, b) = super::types::TypeRegistry::tuple_ab(tup_canonical).unwrap();
let la_idx = registry
.list_type_idx(&format!("List<{}>", a.trim()))
.unwrap();
let lb_idx = registry
.list_type_idx(&format!("List<{}>", b.trim()))
.unwrap();
let lt_idx = registry
.list_type_idx(&format!("List<{tup_canonical}>"))
.unwrap();
let tuple_idx = registry.tuple_type_idx(tup_canonical).unwrap();
let la_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(la_idx),
});
let lb_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(lb_idx),
});
let lt_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(lt_idx),
});
let mut f = Function::new([(1, la_ref), (1, lb_ref), (1, lt_ref)]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::RefNull(HeapType::Concrete(lt_idx)));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: la_idx,
field_index: 0,
});
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::StructGet {
struct_type_index: lb_idx,
field_index: 0,
});
f.instruction(&Instruction::StructNew(tuple_idx));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::StructNew(lt_idx));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: la_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::StructGet {
struct_type_index: lb_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::Call(reverse_fn));
f.instruction(&Instruction::End);
Ok(f)
}
#[allow(clippy::too_many_arguments)]
fn emit_list_eq(
canonical: &str,
registry: &TypeRegistry,
kind: ListEqKind,
string_eq_fn_idx: Option<u32>,
self_fn_idx: u32,
eq_helper_fn_idx: &std::collections::HashMap<String, u32>,
) -> Result<Function, WasmGcError> {
let list_idx = list_idx_of(canonical, registry)?;
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
match &kind {
ListEqKind::I64 => {
f.instruction(&Instruction::I64Eq);
}
ListEqKind::F64 => {
f.instruction(&Instruction::F64Eq);
}
ListEqKind::I32 => {
f.instruction(&Instruction::I32Eq);
}
ListEqKind::StringEq => {
let eq_fn = string_eq_fn_idx.ok_or(WasmGcError::Validation(
"List eq over String needs __wasmgc_string_eq".into(),
))?;
f.instruction(&Instruction::Call(eq_fn));
}
ListEqKind::RecordEq(name) | ListEqKind::SumEq(name) | ListEqKind::CarrierEq(name) => {
let idx = eq_helper_fn_idx
.get(name)
.copied()
.ok_or(WasmGcError::Validation(format!(
"List eq over `{name}`: __eq_{name} helper not registered \
(discovery walker should have transitively flagged it)"
)))?;
f.instruction(&Instruction::Call(idx));
}
}
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::ReturnCall(self_fn_idx));
f.instruction(&Instruction::End);
Ok(f)
}
#[allow(clippy::too_many_arguments)]
fn emit_list_hash(
canonical: &str,
registry: &TypeRegistry,
kind: ListEqKind,
string_eq_fn_idx: Option<u32>,
_self_fn_idx: u32,
hash_helper_fn_idx: &std::collections::HashMap<String, u32>,
) -> Result<Function, WasmGcError> {
let list_idx = list_idx_of(canonical, registry)?;
let _ = string_eq_fn_idx;
let elem = TypeRegistry::list_element_type(canonical).unwrap();
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let mut locals: Vec<(u32, ValType)> = vec![(1, list_ref), (1, ValType::I32)];
match &kind {
ListEqKind::RecordEq(record_name) => {
let r_idx = registry
.record_type_idx(record_name)
.ok_or(WasmGcError::Validation(format!(
"list hash for `List<{record_name}>`: record not registered"
)))?;
let r_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(r_idx),
});
locals.push((1, r_ref)); locals.push((1, ValType::I32)); }
ListEqKind::SumEq(_) => {
let eq_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Abstract {
shared: false,
ty: wasm_encoder::AbstractHeapType::Eq,
},
});
locals.push((1, eq_ref)); locals.push((1, ValType::I32)); }
_ => {}
}
let mut f = Function::new(locals);
f.instruction(&Instruction::I32Const(5381));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::I32Const(5));
f.instruction(&Instruction::I32Shl);
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
let _ = elem;
match &kind {
ListEqKind::I64 => {
f.instruction(&Instruction::I32WrapI64);
}
ListEqKind::I32 => {} ListEqKind::F64 => {
f.instruction(&Instruction::I64ReinterpretF64);
f.instruction(&Instruction::I32WrapI64);
}
ListEqKind::StringEq => {
f.instruction(&Instruction::ArrayLen);
}
ListEqKind::RecordEq(record_name) => {
let r_idx = registry
.record_type_idx(record_name)
.ok_or(WasmGcError::Validation(format!(
"list hash dispatch: record `{record_name}` not registered"
)))?;
let fields = registry
.record_fields
.get(record_name)
.ok_or(WasmGcError::Validation(format!(
"list hash dispatch: record `{record_name}` has no field info"
)))?;
emit_record_inline_hash(
&mut f,
r_idx,
fields,
3,
4,
registry,
hash_helper_fn_idx,
)?;
}
ListEqKind::SumEq(parent_name) => {
emit_sum_inline_hash(
&mut f,
parent_name,
registry,
3,
4,
hash_helper_fn_idx,
)?;
}
ListEqKind::CarrierEq(canonical) => {
let idx = hash_helper_fn_idx
.get(canonical)
.copied()
.ok_or(WasmGcError::Validation(format!(
"list hash over carrier `{canonical}`: __hash_{canonical} \
not registered in hash_helpers"
)))?;
f.instruction(&Instruction::Call(idx));
}
}
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::End);
Ok(f)
}
#[allow(clippy::too_many_arguments)]
fn emit_sum_inline_hash(
f: &mut Function,
parent_name: &str,
registry: &TypeRegistry,
elem_local: u32,
elem_hash_local: u32,
hash_helper_fn_idx: &std::collections::HashMap<String, u32>,
) -> Result<(), WasmGcError> {
let mut variants: Vec<(String, super::types::VariantInfo)> = registry
.variants
.iter()
.flat_map(|(n, vs)| vs.iter().map(move |v| (n.clone(), v.clone())))
.filter(|(_, v)| v.parent == parent_name)
.collect();
variants.sort_by(|a, b| a.0.cmp(&b.0));
if variants.is_empty() {
return Err(WasmGcError::Validation(format!(
"list hash dispatch: sum type `{parent_name}` has no variants"
)));
}
f.instruction(&Instruction::LocalSet(elem_local));
f.instruction(&Instruction::I32Const(5381));
f.instruction(&Instruction::LocalSet(elem_hash_local));
for (_v_name, info) in &variants {
let v_idx = info.type_idx;
let v_heap = wasm_encoder::HeapType::Concrete(v_idx);
f.instruction(&Instruction::LocalGet(elem_local));
f.instruction(&Instruction::RefTestNonNull(v_heap));
f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
f.instruction(&Instruction::LocalGet(elem_hash_local));
f.instruction(&Instruction::I32Const(5));
f.instruction(&Instruction::I32Shl);
f.instruction(&Instruction::LocalGet(elem_hash_local));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::I32Const(v_idx as i32));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(elem_hash_local));
for (i, field_ty) in info.fields.iter().enumerate() {
f.instruction(&Instruction::LocalGet(elem_hash_local));
f.instruction(&Instruction::I32Const(5));
f.instruction(&Instruction::I32Shl);
f.instruction(&Instruction::LocalGet(elem_hash_local));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(elem_local));
f.instruction(&Instruction::RefCastNonNull(v_heap));
f.instruction(&Instruction::StructGet {
struct_type_index: v_idx,
field_index: i as u32,
});
let resolved: String = if let Some(under) = registry.newtype_underlying(field_ty.trim())
{
under.to_string()
} else {
field_ty.trim().to_string()
};
match resolved.as_str() {
"Int" => {
f.instruction(&Instruction::I32WrapI64);
}
"Bool" => {} "Float" => {
f.instruction(&Instruction::I64ReinterpretF64);
f.instruction(&Instruction::I32WrapI64);
}
"String" => {
f.instruction(&Instruction::ArrayLen);
}
other if hash_helper_fn_idx.contains_key(other) => {
f.instruction(&Instruction::Call(hash_helper_fn_idx[other]));
}
_ => {
f.instruction(&Instruction::Drop);
f.instruction(&Instruction::I32Const(0));
}
}
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(elem_hash_local));
}
f.instruction(&Instruction::End); }
f.instruction(&Instruction::LocalGet(elem_hash_local));
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn emit_record_inline_hash(
f: &mut Function,
record_idx: u32,
fields: &[(String, String)],
elem_local: u32,
elem_hash_local: u32,
registry: &TypeRegistry,
hash_helper_fn_idx: &std::collections::HashMap<String, u32>,
) -> Result<(), WasmGcError> {
f.instruction(&Instruction::LocalSet(elem_local));
f.instruction(&Instruction::I32Const(5381));
f.instruction(&Instruction::LocalSet(elem_hash_local));
for (i, (_field_name, field_type)) in fields.iter().enumerate() {
f.instruction(&Instruction::LocalGet(elem_hash_local));
f.instruction(&Instruction::I32Const(5));
f.instruction(&Instruction::I32Shl);
f.instruction(&Instruction::LocalGet(elem_hash_local));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(elem_local));
f.instruction(&Instruction::StructGet {
struct_type_index: record_idx,
field_index: i as u32,
});
let resolved: String = if let Some(under) = registry.newtype_underlying(field_type.trim()) {
under.to_string()
} else {
field_type.trim().to_string()
};
match resolved.as_str() {
"Int" => {
f.instruction(&Instruction::I32WrapI64);
}
"Bool" => {} "Float" => {
f.instruction(&Instruction::I64ReinterpretF64);
f.instruction(&Instruction::I32WrapI64);
}
"String" => {
f.instruction(&Instruction::ArrayLen);
}
other if hash_helper_fn_idx.contains_key(other) => {
f.instruction(&Instruction::Call(hash_helper_fn_idx[other]));
}
_ => {
f.instruction(&Instruction::Drop);
f.instruction(&Instruction::I32Const(0));
}
}
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(elem_hash_local));
}
f.instruction(&Instruction::LocalGet(elem_hash_local));
Ok(())
}
fn emit_vec_eq(
canonical: &str,
registry: &TypeRegistry,
kind: ListEqKind,
string_eq_fn_idx: Option<u32>,
eq_helper_fn_idx: &std::collections::HashMap<String, u32>,
) -> Result<Function, WasmGcError> {
let (vec_idx, _) = vec_idx_of_pair(canonical, registry)?;
let mut f = Function::new([(1, ValType::I32), (1, ValType::I32)]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::ArrayLen);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::ArrayLen);
f.instruction(&Instruction::I32Ne);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::ArrayLen);
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::I32GeU);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayGet(vec_idx));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayGet(vec_idx));
match &kind {
ListEqKind::I64 => {
f.instruction(&Instruction::I64Eq);
}
ListEqKind::F64 => {
f.instruction(&Instruction::F64Eq);
}
ListEqKind::I32 => {
f.instruction(&Instruction::I32Eq);
}
ListEqKind::StringEq => {
let eq_fn = string_eq_fn_idx.ok_or(WasmGcError::Validation(
"Vector eq over String needs __wasmgc_string_eq".into(),
))?;
f.instruction(&Instruction::Call(eq_fn));
}
ListEqKind::RecordEq(name) | ListEqKind::SumEq(name) | ListEqKind::CarrierEq(name) => {
let idx = eq_helper_fn_idx
.get(name)
.copied()
.ok_or(WasmGcError::Validation(format!(
"Vector eq over `{name}`: __eq_{name} helper not registered"
)))?;
f.instruction(&Instruction::Call(idx));
}
}
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_vec_hash(
canonical: &str,
registry: &TypeRegistry,
kind: ListEqKind,
_string_eq_fn_idx: Option<u32>,
hash_helper_fn_idx: &std::collections::HashMap<String, u32>,
) -> Result<Function, WasmGcError> {
let (vec_idx, _) = vec_idx_of_pair(canonical, registry)?;
let elem = TypeRegistry::list_element_type(canonical).unwrap();
let mut locals: Vec<(u32, ValType)> =
vec![(1, ValType::I32), (1, ValType::I32), (1, ValType::I32)];
match &kind {
ListEqKind::RecordEq(record_name) => {
let r_idx = registry
.record_type_idx(record_name)
.ok_or(WasmGcError::Validation(format!(
"vector hash for `Vector<{record_name}>`: record not registered"
)))?;
let r_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(r_idx),
});
locals.push((1, r_ref)); locals.push((1, ValType::I32)); }
ListEqKind::SumEq(_) => {
let eq_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Abstract {
shared: false,
ty: wasm_encoder::AbstractHeapType::Eq,
},
});
locals.push((1, eq_ref)); locals.push((1, ValType::I32)); }
_ => {}
}
let mut f = Function::new(locals);
f.instruction(&Instruction::I32Const(5381));
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::ArrayLen);
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::I32GeU);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I32Const(5));
f.instruction(&Instruction::I32Shl);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayGet(vec_idx));
let _ = elem;
match &kind {
ListEqKind::I64 => {
f.instruction(&Instruction::I32WrapI64);
}
ListEqKind::I32 => {} ListEqKind::F64 => {
f.instruction(&Instruction::I64ReinterpretF64);
f.instruction(&Instruction::I32WrapI64);
}
ListEqKind::StringEq => {
f.instruction(&Instruction::ArrayLen);
}
ListEqKind::RecordEq(record_name) => {
let r_idx = registry
.record_type_idx(record_name)
.ok_or(WasmGcError::Validation(format!(
"vector hash dispatch: record `{record_name}` not registered"
)))?;
let fields = registry
.record_fields
.get(record_name)
.ok_or(WasmGcError::Validation(format!(
"vector hash dispatch: record `{record_name}` has no field info"
)))?;
emit_record_inline_hash(
&mut f,
r_idx,
fields,
4,
5,
registry,
hash_helper_fn_idx,
)?;
}
ListEqKind::SumEq(parent_name) => {
emit_sum_inline_hash(
&mut f,
parent_name,
registry,
4,
5,
hash_helper_fn_idx,
)?;
}
ListEqKind::CarrierEq(canonical) => {
let idx = hash_helper_fn_idx
.get(canonical)
.copied()
.ok_or(WasmGcError::Validation(format!(
"vector hash over carrier `{canonical}`: \
__hash_{canonical} not registered in hash_helpers"
)))?;
f.instruction(&Instruction::Call(idx));
}
}
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::End);
Ok(f)
}