use std::collections::HashMap;
use wasm_encoder::{BlockType, CodeSection, Function, HeapType, Instruction, RefType, ValType};
use super::WasmGcError;
use super::types::{MapSlots, TypeRegistry};
use super::wat_helper;
const INITIAL_CAP: i32 = 16384;
#[derive(Debug, Clone, Copy)]
pub(super) struct KeyHelpers {
pub(super) hash: u32,
pub(super) eq: u32,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct MapKVHelpers {
pub(super) empty: u32,
pub(super) set: u32,
pub(super) set_in_place: u32,
pub(super) get: u32,
pub(super) len: u32,
pub(super) get_or_default: u32,
pub(super) get_pair: u32,
pub(super) keys: u32,
pub(super) values: u32,
pub(super) remove: u32,
pub(super) entries: u32,
pub(super) from_list: u32,
pub(super) eq: u32,
pub(super) hash: u32,
}
#[derive(Default)]
pub(super) struct MapHelperRegistry {
key: HashMap<String, KeyHelpers>,
kv: HashMap<String, MapKVHelpers>,
key_order: Vec<String>,
kv_order: Vec<String>,
key_type_indices: HashMap<String, (u32, u32)>, kv_type_indices: HashMap<String, MapKVTypeIdx>,
}
#[derive(Debug, Clone, Copy)]
struct MapKVTypeIdx {
empty: u32,
set: u32,
set_in_place: u32,
get: u32,
len: u32,
get_or_default: u32,
get_pair: u32,
keys: u32,
values: u32,
remove: u32,
entries: u32,
from_list: u32,
eq: u32,
hash: u32,
}
impl MapHelperRegistry {
pub(super) fn assign_slots(
&mut self,
map_canonicals: &[String],
registry: &TypeRegistry,
next_wasm_fn_idx: &mut u32,
next_type_idx: &mut u32,
) -> Result<(), WasmGcError> {
let mut k_names: Vec<String> = Vec::new();
let mut k_seen: std::collections::HashSet<String> = std::collections::HashSet::new();
for canonical in map_canonicals {
let (k_aver, v_aver) =
super::types::parse_map_kv(canonical).ok_or(WasmGcError::Validation(format!(
"MapHelperRegistry: cannot parse K, V from `{canonical}`"
)))?;
let mut needs_string = false;
if let Some(fs) = registry.record_fields.get(k_aver) {
needs_string |= fs.iter().any(|(_, t)| t.trim() == "String");
}
if registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| v.parent == k_aver)
{
needs_string |= registry
.variants
.values()
.flat_map(|vs| vs.iter())
.filter(|v| v.parent == k_aver)
.any(|v| v.fields.iter().any(|t| t.trim() == "String"));
}
let v_aver_trim = v_aver.trim();
if v_aver_trim == "String" {
needs_string = true;
}
if needs_string && k_seen.insert("String".into()) {
k_names.push("String".into());
}
if k_seen.insert(k_aver.to_string()) {
k_names.push(k_aver.to_string());
}
if !super::types::TypeRegistry::is_primitive_map_key(v_aver_trim)
&& v_aver_trim != "String"
&& k_seen.insert(v_aver_trim.to_string())
{
k_names.push(v_aver_trim.to_string());
}
}
let mut to_visit: Vec<String> = k_names
.iter()
.filter(|n| {
registry.record_type_idx(n).is_some()
|| registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| v.parent == *n.as_str())
})
.cloned()
.collect();
while let Some(parent) = to_visit.pop() {
let mut field_types: Vec<String> = Vec::new();
if let Some(fields) = registry.record_fields.get(&parent) {
for (_, t) in fields {
field_types.push(t.trim().to_string());
}
}
for variant in registry
.variants
.values()
.flat_map(|v| v.iter())
.filter(|v| v.parent == parent)
{
for t in &variant.fields {
field_types.push(t.trim().to_string());
}
}
for ft in field_types {
let is_record = registry.record_type_idx(&ft).is_some();
let is_sum = registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| v.parent == ft);
let is_carrier = ft.starts_with("Option<")
|| ft.starts_with("Result<")
|| ft.starts_with("Tuple<");
if (is_record || is_sum || is_carrier) && k_seen.insert(ft.clone()) {
k_names.push(ft.clone());
if !is_carrier {
to_visit.push(ft.clone());
}
let mut nested_needs_string = false;
if let Some(fs) = registry.record_fields.get(&ft) {
nested_needs_string |= fs.iter().any(|(_, t)| t.trim() == "String");
}
if is_sum {
nested_needs_string |= registry
.variants
.values()
.flat_map(|vs| vs.iter())
.filter(|v| v.parent == ft)
.any(|v| v.fields.iter().any(|t| t.trim() == "String"));
}
if is_carrier {
nested_needs_string |= ft.contains("String");
}
if nested_needs_string && k_seen.insert("String".into()) {
k_names.push("String".into());
}
}
}
}
for k_aver in &k_names {
if !self.key.contains_key(k_aver) {
let hash_type_idx = *next_type_idx;
*next_type_idx += 1;
let eq_type_idx = *next_type_idx;
*next_type_idx += 1;
let hash_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let eq_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
self.key.insert(
k_aver.clone(),
KeyHelpers {
hash: hash_fn,
eq: eq_fn,
},
);
self.key_type_indices
.insert(k_aver.clone(), (hash_type_idx, eq_type_idx));
self.key_order.push(k_aver.clone());
}
}
for canonical in map_canonicals {
if self.kv.contains_key(canonical) {
continue;
}
let empty_type_idx = *next_type_idx;
*next_type_idx += 1;
let set_type_idx = *next_type_idx;
*next_type_idx += 1;
let set_in_place_type_idx = *next_type_idx;
*next_type_idx += 1;
let get_type_idx = *next_type_idx;
*next_type_idx += 1;
let len_type_idx = *next_type_idx;
*next_type_idx += 1;
let god_type_idx = *next_type_idx;
*next_type_idx += 1;
let pair_type_idx = *next_type_idx;
*next_type_idx += 1;
let keys_type_idx = *next_type_idx;
*next_type_idx += 1;
let values_type_idx = *next_type_idx;
*next_type_idx += 1;
let remove_type_idx = *next_type_idx;
*next_type_idx += 1;
let entries_type_idx = *next_type_idx;
*next_type_idx += 1;
let from_list_type_idx = *next_type_idx;
*next_type_idx += 1;
let eq_type_idx = *next_type_idx;
*next_type_idx += 1;
let hash_type_idx = *next_type_idx;
*next_type_idx += 1;
let empty_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let set_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let set_in_place_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let get_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let len_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let god_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let pair_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let keys_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let values_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let remove_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let entries_fn = *next_wasm_fn_idx;
*next_wasm_fn_idx += 1;
let from_list_fn = *next_wasm_fn_idx;
*next_wasm_fn_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;
let (k_aver, _) = super::types::parse_map_kv(canonical).ok_or(
WasmGcError::Validation(format!("bad map canonical `{canonical}`")),
)?;
let is_primitive_k = super::types::TypeRegistry::is_primitive_map_key(k_aver);
let is_sum_k = registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| v.parent == k_aver);
let is_carrier_k = k_aver.starts_with("Option<")
|| k_aver.starts_with("Result<")
|| k_aver.starts_with("Tuple<");
let is_list_or_vec_k = k_aver.starts_with("List<") || k_aver.starts_with("Vector<");
let is_map_k = k_aver.starts_with("Map<");
if k_aver != "String"
&& registry.record_type_idx(k_aver).is_none()
&& !is_primitive_k
&& !is_sum_k
&& !is_carrier_k
&& !is_list_or_vec_k
&& !is_map_k
{
return Err(WasmGcError::Unimplemented(
"phase 3c — Map<K, V> with K not String / user-record / sum / \
primitive / generic-carrier (Option/Result/Tuple) / List<T> / \
Vector<T> / Map<K2,V2>",
));
}
self.kv.insert(
canonical.clone(),
MapKVHelpers {
empty: empty_fn,
set: set_fn,
set_in_place: set_in_place_fn,
get: get_fn,
len: len_fn,
get_or_default: god_fn,
get_pair: pair_fn,
keys: keys_fn,
values: values_fn,
remove: remove_fn,
entries: entries_fn,
from_list: from_list_fn,
eq: eq_fn,
hash: hash_fn,
},
);
self.kv_type_indices.insert(
canonical.clone(),
MapKVTypeIdx {
empty: empty_type_idx,
set: set_type_idx,
set_in_place: set_in_place_type_idx,
get: get_type_idx,
len: len_type_idx,
get_or_default: god_type_idx,
get_pair: pair_type_idx,
keys: keys_type_idx,
values: values_type_idx,
remove: remove_type_idx,
entries: entries_type_idx,
from_list: from_list_type_idx,
eq: eq_type_idx,
hash: hash_type_idx,
},
);
self.kv_order.push(canonical.clone());
}
Ok(())
}
pub(super) fn key_helpers(&self, k_aver: &str) -> Option<KeyHelpers> {
self.key.get(k_aver).copied()
}
pub(super) fn kv_helpers(&self, canonical: &str) -> Option<MapKVHelpers> {
self.kv.get(canonical).copied()
}
pub(super) fn emit_helper_types(
&self,
types: &mut wasm_encoder::TypeSection,
registry: &TypeRegistry,
) -> Result<(), WasmGcError> {
for k_aver in &self.key_order {
let k_val = super::types::aver_to_wasm(k_aver, Some(registry))?.ok_or(
WasmGcError::Validation(format!("Map K `{k_aver}` has no wasm rep")),
)?;
types.ty().function([k_val], [ValType::I32]);
types.ty().function([k_val, k_val], [ValType::I32]);
}
for canonical in &self.kv_order {
let slots = registry
.map_slots(canonical)
.ok_or(WasmGcError::Validation(format!(
"Map slots missing for `{canonical}`"
)))?;
let map_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.map),
});
let (k_aver, v_aver) = super::types::parse_map_kv(canonical).ok_or(
WasmGcError::Validation(format!("parse_map_kv `{canonical}`")),
)?;
let k_val = super::types::aver_to_wasm(k_aver, Some(registry))?.ok_or(
WasmGcError::Validation(format!("Map K `{k_aver}` has no wasm rep")),
)?;
let v_val = super::types::aver_to_wasm(v_aver, Some(registry))?.ok_or(
WasmGcError::Validation(format!("Map V `{v_aver}` has no wasm rep")),
)?;
let opt_idx = registry
.option_type_idx(&format!("Option<{v_aver}>"))
.ok_or(WasmGcError::Validation(format!(
"Option<{v_aver}> not registered (Map.get needs it)"
)))?;
let opt_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(opt_idx),
});
types.ty().function([], [map_ref]);
types.ty().function([map_ref, k_val, v_val], [map_ref]);
types.ty().function([map_ref, k_val, v_val], [map_ref]);
types.ty().function([map_ref, k_val], [opt_ref]);
types.ty().function([map_ref], [ValType::I64]);
types.ty().function([map_ref, k_val, v_val], [v_val]);
types.ty().function([map_ref, k_val], [ValType::I32, v_val]);
let list_k_idx = registry.list_type_idx(&format!("List<{k_aver}>")).ok_or(
WasmGcError::Validation(format!("Map.keys: List<{k_aver}> not registered")),
)?;
let list_k_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_k_idx),
});
types.ty().function([map_ref], [list_k_ref]);
let list_v_idx = registry.list_type_idx(&format!("List<{v_aver}>")).ok_or(
WasmGcError::Validation(format!("Map.values: List<{v_aver}> not registered")),
)?;
let list_v_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_v_idx),
});
types.ty().function([map_ref], [list_v_ref]);
types.ty().function([map_ref, k_val], [map_ref]);
let tup_canonical = format!("Tuple<{k_aver},{v_aver}>");
let tup_idx =
registry
.tuple_type_idx(&tup_canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.entries: `{tup_canonical}` not registered"
)))?;
let lt_idx = registry
.list_type_idx(&format!("List<{tup_canonical}>"))
.ok_or(WasmGcError::Validation(format!(
"Map.entries: `List<{tup_canonical}>` not registered"
)))?;
let lt_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(lt_idx),
});
types.ty().function([map_ref], [lt_ref]);
types.ty().function([lt_ref], [map_ref]);
let eq_ref = ValType::Ref(RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Abstract {
shared: false,
ty: wasm_encoder::AbstractHeapType::Eq,
},
});
types.ty().function([eq_ref, eq_ref], [ValType::I32]);
types.ty().function([eq_ref], [ValType::I32]);
let _ = opt_ref;
let _ = tup_idx;
}
Ok(())
}
pub(super) fn emit_function_section(&self, funcs: &mut wasm_encoder::FunctionSection) {
for k in &self.key_order {
let (h, e) = self.key_type_indices[k];
funcs.function(h);
funcs.function(e);
}
for canonical in &self.kv_order {
let t = self.kv_type_indices[canonical];
funcs.function(t.empty);
funcs.function(t.set);
funcs.function(t.set_in_place);
funcs.function(t.get);
funcs.function(t.len);
funcs.function(t.get_or_default);
funcs.function(t.get_pair);
funcs.function(t.keys);
funcs.function(t.values);
funcs.function(t.remove);
funcs.function(t.entries);
funcs.function(t.from_list);
funcs.function(t.eq);
funcs.function(t.hash);
}
}
pub(super) fn emit_helper_bodies(
&self,
codes: &mut CodeSection,
registry: &TypeRegistry,
list_eq_hash: &HashMap<String, (u32, u32)>,
carrier_eq_hash: &HashMap<String, (u32, u32)>,
) -> Result<(), WasmGcError> {
let string_key_helpers = self.key.get("String").copied();
let mut all_key_helpers: HashMap<String, KeyHelpers> =
self.key.iter().map(|(k, h)| (k.clone(), *h)).collect();
for (carrier, &(eq_fn, hash_fn)) in carrier_eq_hash {
all_key_helpers.insert(
carrier.clone(),
KeyHelpers {
hash: hash_fn,
eq: eq_fn,
},
);
}
for (list_canonical, &(eq_fn, hash_fn)) in list_eq_hash {
all_key_helpers.insert(
list_canonical.clone(),
KeyHelpers {
hash: hash_fn,
eq: eq_fn,
},
);
}
for (map_canonical, kv) in &self.kv {
all_key_helpers.insert(
map_canonical.clone(),
KeyHelpers {
hash: kv.hash,
eq: kv.eq,
},
);
}
for k_aver in &self.key_order {
codes.function(&emit_hash_for(
k_aver,
registry,
string_key_helpers,
&all_key_helpers,
)?);
codes.function(&emit_eq_for(
k_aver,
registry,
string_key_helpers,
&all_key_helpers,
)?);
}
for canonical in &self.kv_order {
let (k_aver, _) = super::types::parse_map_kv(canonical).ok_or(
WasmGcError::Validation(format!("parse_map_kv `{canonical}`")),
)?;
let key_h = self
.key_helpers(k_aver)
.ok_or(WasmGcError::Validation(format!(
"key helpers missing for K=`{k_aver}`"
)))?;
codes.function(&emit_map_empty(canonical, registry)?);
codes.function(&emit_map_set(canonical, registry, key_h)?);
codes.function(&emit_map_set_in_place(canonical, registry, key_h)?);
codes.function(&emit_map_get(canonical, registry, key_h)?);
codes.function(&emit_map_len(canonical, registry)?);
codes.function(&emit_map_get_or_default(canonical, registry, key_h)?);
codes.function(&emit_map_get_pair(canonical, registry, key_h)?);
codes.function(&emit_map_keys(canonical, registry)?);
codes.function(&emit_map_values(canonical, registry)?);
codes.function(&emit_map_remove(canonical, registry, key_h)?);
let helpers = self.kv[canonical];
codes.function(&emit_map_entries(canonical, registry)?);
codes.function(&emit_map_from_list(canonical, registry, helpers.set)?);
let v_helpers = v_helper_for(canonical, &all_key_helpers, registry)?;
codes.function(&emit_map_eq(
canonical,
registry,
key_h,
v_helpers,
helpers.get,
)?);
codes.function(&emit_map_hash(canonical, registry, key_h, v_helpers)?);
}
Ok(())
}
}
fn v_helper_for(
canonical: &str,
all_key_helpers: &HashMap<String, KeyHelpers>,
_registry: &TypeRegistry,
) -> Result<Option<KeyHelpers>, WasmGcError> {
let (_, v_aver) = super::types::parse_map_kv(canonical).ok_or(WasmGcError::Validation(
format!("v_helper_for: bad canonical `{canonical}`"),
))?;
let v_aver = v_aver.trim();
if super::types::TypeRegistry::is_primitive_map_key(v_aver) {
return Ok(None);
}
Ok(all_key_helpers.get(v_aver).copied())
}
fn emit_hash_for(
k_aver: &str,
registry: &TypeRegistry,
string_key_helpers: Option<KeyHelpers>,
all_key_helpers: &HashMap<String, KeyHelpers>,
) -> Result<Function, WasmGcError> {
if k_aver == "String" {
return emit_hash_string(registry);
}
if registry.record_type_idx(k_aver).is_some() {
return emit_hash_record(k_aver, registry, string_key_helpers, all_key_helpers);
}
if super::types::TypeRegistry::is_primitive_map_key(k_aver) {
return emit_hash_primitive(k_aver);
}
if registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| v.parent == k_aver)
{
return emit_hash_sum(k_aver, registry, string_key_helpers, all_key_helpers);
}
if k_aver.starts_with("Option<")
|| k_aver.starts_with("Result<")
|| k_aver.starts_with("Tuple<")
|| k_aver.starts_with("List<")
|| k_aver.starts_with("Vector<")
|| k_aver.starts_with("Map<")
{
let helpers = all_key_helpers
.get(k_aver)
.ok_or(WasmGcError::Validation(format!(
"hash_for: compound `{k_aver}` has no registered hash helper"
)))?;
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::Call(helpers.hash));
f.instruction(&Instruction::End);
return Ok(f);
}
Err(WasmGcError::Unimplemented(
"phase 3c — hash for unsupported K kind",
))
}
fn emit_eq_for(
k_aver: &str,
registry: &TypeRegistry,
string_key_helpers: Option<KeyHelpers>,
all_key_helpers: &HashMap<String, KeyHelpers>,
) -> Result<Function, WasmGcError> {
if k_aver == "String" {
return emit_eq_string(registry);
}
if registry.record_type_idx(k_aver).is_some() {
return emit_eq_record(k_aver, registry, string_key_helpers, all_key_helpers);
}
if super::types::TypeRegistry::is_primitive_map_key(k_aver) {
return emit_eq_primitive(k_aver);
}
if registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| v.parent == k_aver)
{
return emit_eq_sum(k_aver, registry, string_key_helpers, all_key_helpers);
}
if k_aver.starts_with("Option<")
|| k_aver.starts_with("Result<")
|| k_aver.starts_with("Tuple<")
|| k_aver.starts_with("List<")
|| k_aver.starts_with("Vector<")
|| k_aver.starts_with("Map<")
{
let helpers = all_key_helpers
.get(k_aver)
.ok_or(WasmGcError::Validation(format!(
"eq_for: compound `{k_aver}` has no registered eq helper"
)))?;
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(helpers.eq));
f.instruction(&Instruction::End);
return Ok(f);
}
Err(WasmGcError::Unimplemented(
"phase 3c — eq for unsupported K kind",
))
}
fn emit_hash_primitive(k_aver: &str) -> Result<Function, WasmGcError> {
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
match k_aver {
"Int" => {
f.instruction(&Instruction::I32WrapI64);
}
"Float" => {
f.instruction(&Instruction::I64ReinterpretF64);
f.instruction(&Instruction::I32WrapI64);
}
"Bool" => {
}
_ => panic!(
"internal compiler error: emit_hash_primitive called with \
non-primitive K = `{k_aver}`; caller must dispatch to \
emit_hash_record / emit_hash_sum / __wasmgc_string_hash for \
non-primitive K. Please file at https://github.com/jasisz/aver/issues"
),
}
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_eq_primitive(k_aver: &str) -> Result<Function, WasmGcError> {
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(1));
match k_aver {
"Int" => f.instruction(&Instruction::I64Eq),
"Float" => f.instruction(&Instruction::F64Eq),
"Bool" => f.instruction(&Instruction::I32Eq),
_ => panic!(
"internal compiler error: emit_eq_primitive called with \
non-primitive K = `{k_aver}`; caller must dispatch to \
emit_eq_record / emit_eq_sum / __wasmgc_string_eq for \
non-primitive K. Please file at https://github.com/jasisz/aver/issues"
),
};
f.instruction(&Instruction::End);
Ok(f)
}
fn key_storage_val_type(k_aver: &str, registry: &TypeRegistry) -> Result<ValType, WasmGcError> {
if let Some(box_idx) = registry.primitive_key_box_idx(k_aver) {
Ok(ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(box_idx),
}))
} else {
super::types::aver_to_wasm(k_aver, Some(registry))?.ok_or(WasmGcError::Validation(format!(
"Map key type `{k_aver}` has no wasm representation"
)))
}
}
fn emit_unbox_key(f: &mut Function, k_aver: &str, registry: &TypeRegistry) {
if let Some(box_idx) = registry.primitive_key_box_idx(k_aver) {
f.instruction(&Instruction::StructGet {
struct_type_index: box_idx,
field_index: 0,
});
}
}
fn emit_box_key(f: &mut Function, k_aver: &str, registry: &TypeRegistry) {
if let Some(box_idx) = registry.primitive_key_box_idx(k_aver) {
f.instruction(&Instruction::StructNew(box_idx));
}
}
fn key_storage_null_heap(k_aver: &str, registry: &TypeRegistry) -> HeapType {
if let Some(box_idx) = registry.primitive_key_box_idx(k_aver) {
return HeapType::Concrete(box_idx);
}
if k_aver == "String"
&& let Some(s) = registry.string_array_type_idx
{
return HeapType::Concrete(s);
}
if let Some(r) = registry.record_type_idx(k_aver) {
return HeapType::Concrete(r);
}
if let Some(o) = registry.option_type_idx(k_aver) {
return HeapType::Concrete(o);
}
if let Some(r) = registry.result_type_idx(k_aver) {
return HeapType::Concrete(r);
}
if let Some(t) = registry.tuple_type_idx(k_aver) {
return HeapType::Concrete(t);
}
if let Some(l) = registry.list_type_idx(k_aver) {
return HeapType::Concrete(l);
}
if let Some(v) = registry.vector_type_idx(k_aver) {
return HeapType::Concrete(v);
}
if let Some(slots) = registry.map_slots(k_aver) {
return HeapType::Concrete(slots.map);
}
HeapType::Abstract {
shared: false,
ty: wasm_encoder::AbstractHeapType::Eq,
}
}
fn string_idx(registry: &TypeRegistry) -> Result<u32, WasmGcError> {
registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"Map<String, _> helper requires String slot".into(),
))
}
fn emit_hash_string(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let s_idx = string_idx(registry)?;
let padding = wat_helper::padding_types(s_idx);
let wat = format!(
r#"
(module
{padding}
(type $string (array (mut i8)))
(func (export "helper") (param $s (ref null $string)) (result i32)
(local $h i32)
(local $i i32)
(local $n i32)
;; DJB2: h = 5381; for each byte: h = h * 33 + byte.
i32.const 5381
local.set $h
local.get $s
array.len
local.set $n
i32.const 0
local.set $i
(block $break
(loop $next
local.get $i
local.get $n
i32.ge_u
br_if $break
;; h = (h << 5) + h + s[i]
local.get $h
i32.const 5
i32.shl
local.get $h
i32.add
local.get $s
local.get $i
array.get_u $string
i32.add
local.set $h
local.get $i
i32.const 1
i32.add
local.set $i
br $next))
local.get $h)
)
"#
);
wat_helper::compile_wat_helper(&wat)
}
fn emit_eq_string(registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let s_idx = string_idx(registry)?;
let padding = wat_helper::padding_types(s_idx);
let wat = format!(
r#"
(module
{padding}
(type $string (array (mut i8)))
(func (export "helper") (param $a (ref null $string)) (param $b (ref null $string)) (result i32)
(local $i i32)
(local $n i32)
;; Length mismatch ⇒ 0.
local.get $a
array.len
local.get $b
array.len
i32.ne
(if
(then i32.const 0 return))
local.get $a
array.len
local.set $n
i32.const 0
local.set $i
(block $break
(loop $next
local.get $i
local.get $n
i32.ge_u
br_if $break
local.get $a
local.get $i
array.get_u $string
local.get $b
local.get $i
array.get_u $string
i32.ne
(if
(then i32.const 0 return))
local.get $i
i32.const 1
i32.add
local.set $i
br $next))
i32.const 1)
)
"#
);
wat_helper::compile_wat_helper(&wat)
}
fn slots_for(canonical: &str, registry: &TypeRegistry) -> Result<MapSlots, WasmGcError> {
registry
.map_slots(canonical)
.ok_or(WasmGcError::Validation(format!(
"Map slots missing for `{canonical}`"
)))
}
fn emit_map_empty(canonical: &str, registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(0)); f.instruction(&Instruction::I32Const(INITIAL_CAP)); f.instruction(&Instruction::I32Const(INITIAL_CAP));
f.instruction(&Instruction::ArrayNewDefault(slots.keys_array));
f.instruction(&Instruction::I32Const(INITIAL_CAP));
f.instruction(&Instruction::ArrayNewDefault(slots.values_array));
f.instruction(&Instruction::StructNew(slots.map));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_map_set(
canonical: &str,
registry: &TypeRegistry,
keyh: KeyHelpers,
) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let (k_aver, v_aver) = super::types::parse_map_kv(canonical).unwrap();
let k_val = super::types::aver_to_wasm(k_aver, Some(registry))?.unwrap();
let v_val = super::types::aver_to_wasm(v_aver, Some(registry))?.unwrap();
let mut f = Function::new([
(1, ValType::I32), (1, ValType::I32), (1, ValType::I32), (
1,
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.keys_array),
}),
), (
1,
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.values_array),
}),
), (1, key_storage_val_type(k_aver, registry)?), ]);
let _ = (v_val, k_val);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayNewDefault(slots.keys_array));
f.instruction(&Instruction::LocalSet(6));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 2,
});
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayCopy {
array_type_index_dst: slots.keys_array,
array_type_index_src: slots.keys_array,
});
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayNewDefault(slots.values_array));
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 3,
});
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayCopy {
array_type_index_dst: slots.values_array,
array_type_index_src: slots.values_array,
});
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(keyh.hash));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::ArrayGet(slots.keys_array));
f.instruction(&Instruction::LocalSet(8));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(1));
emit_box_key(&mut f, k_aver, registry);
f.instruction(&Instruction::ArraySet(slots.keys_array));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::ArraySet(slots.values_array));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 0,
});
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::StructNew(slots.map));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(8));
emit_unbox_key(&mut f, k_aver, registry);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(keyh.eq));
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::ArraySet(slots.values_array));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 0,
});
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::StructNew(slots.map));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End); f.instruction(&Instruction::End); f.instruction(&Instruction::Unreachable);
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_map_set_in_place(
canonical: &str,
registry: &TypeRegistry,
keyh: KeyHelpers,
) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let (k_aver, v_aver) = super::types::parse_map_kv(canonical).unwrap();
let _ = super::types::aver_to_wasm(k_aver, Some(registry))?.unwrap();
let _ = super::types::aver_to_wasm(v_aver, Some(registry))?.unwrap();
let mut f = Function::new([
(1, ValType::I32),
(1, ValType::I32),
(1, ValType::I32),
(
1,
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.keys_array),
}),
),
(
1,
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.values_array),
}),
),
(1, key_storage_val_type(k_aver, registry)?),
]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 2,
});
f.instruction(&Instruction::LocalSet(6));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 3,
});
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(keyh.hash));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::ArrayGet(slots.keys_array));
f.instruction(&Instruction::LocalSet(8));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(1));
emit_box_key(&mut f, k_aver, registry);
f.instruction(&Instruction::ArraySet(slots.keys_array));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::ArraySet(slots.values_array));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 0,
});
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::StructNew(slots.map));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(8));
emit_unbox_key(&mut f, k_aver, registry);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(keyh.eq));
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::ArraySet(slots.values_array));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 0,
});
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::StructNew(slots.map));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End); f.instruction(&Instruction::End); f.instruction(&Instruction::Unreachable);
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_map_get(
canonical: &str,
registry: &TypeRegistry,
keyh: KeyHelpers,
) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let (k_aver, v_aver) = super::types::parse_map_kv(canonical).unwrap();
let k_val = super::types::aver_to_wasm(k_aver, Some(registry))?.unwrap();
let v_val = super::types::aver_to_wasm(v_aver, Some(registry))?.unwrap();
let opt_canonical = format!("Option<{v_aver}>");
let opt_idx = registry
.option_type_idx(&opt_canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.get: Option<{v_aver}> not registered"
)))?;
let mut f = Function::new([
(1, ValType::I32), (1, ValType::I32), (1, ValType::I32), (
1,
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.keys_array),
}),
), (
1,
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.values_array),
}),
), (1, key_storage_val_type(k_aver, registry)?), ]);
let _ = k_val;
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 2,
});
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 3,
});
f.instruction(&Instruction::LocalSet(6));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(keyh.hash));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::ArrayGet(slots.keys_array));
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::I32Const(0));
emit_default_value_for(&mut f, v_val);
f.instruction(&Instruction::StructNew(opt_idx));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(7));
emit_unbox_key(&mut f, k_aver, registry);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(keyh.eq));
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::ArrayGet(slots.values_array));
f.instruction(&Instruction::StructNew(opt_idx));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End); f.instruction(&Instruction::End); f.instruction(&Instruction::Unreachable);
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_map_get_or_default(
canonical: &str,
registry: &TypeRegistry,
keyh: KeyHelpers,
) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let (k_aver, v_aver) = super::types::parse_map_kv(canonical).unwrap();
let k_val = super::types::aver_to_wasm(k_aver, Some(registry))?.unwrap();
let v_val = super::types::aver_to_wasm(v_aver, Some(registry))?.unwrap();
let _ = (k_val, v_val);
let mut f = Function::new([
(1, ValType::I32), (1, ValType::I32), (1, ValType::I32), (
1,
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.keys_array),
}),
),
(
1,
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.values_array),
}),
),
(1, key_storage_val_type(k_aver, registry)?), ]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 2,
});
f.instruction(&Instruction::LocalSet(6));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 3,
});
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(keyh.hash));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::ArrayGet(slots.keys_array));
f.instruction(&Instruction::LocalSet(8));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(8));
emit_unbox_key(&mut f, k_aver, registry);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(keyh.eq));
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::ArrayGet(slots.values_array));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::Unreachable);
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_map_get_pair(
canonical: &str,
registry: &TypeRegistry,
keyh: KeyHelpers,
) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let (k_aver, v_aver) = super::types::parse_map_kv(canonical).unwrap();
let k_val = super::types::aver_to_wasm(k_aver, Some(registry))?.unwrap();
let v_val = super::types::aver_to_wasm(v_aver, Some(registry))?.unwrap();
let _ = k_val;
let mut f = Function::new([
(1, ValType::I32), (1, ValType::I32), (1, ValType::I32), (
1,
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.keys_array),
}),
),
(
1,
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.values_array),
}),
),
(1, key_storage_val_type(k_aver, registry)?), ]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 2,
});
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 3,
});
f.instruction(&Instruction::LocalSet(6));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(keyh.hash));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::ArrayGet(slots.keys_array));
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::I32Const(0));
emit_default_value_for(&mut f, v_val);
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(7));
emit_unbox_key(&mut f, k_aver, registry);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(keyh.eq));
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::ArrayGet(slots.values_array));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::Unreachable);
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_map_len(canonical: &str, registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 0,
});
f.instruction(&Instruction::I64ExtendI32U);
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_default_value_for(f: &mut Function, ty: ValType) {
match ty {
ValType::I32 => {
f.instruction(&Instruction::I32Const(0));
}
ValType::I64 => {
f.instruction(&Instruction::I64Const(0));
}
ValType::F32 => {
f.instruction(&Instruction::F32Const(0.0_f32.into()));
}
ValType::F64 => {
f.instruction(&Instruction::F64Const(0.0_f64.into()));
}
ValType::Ref(rt) => {
f.instruction(&Instruction::RefNull(rt.heap_type));
}
ValType::V128 => {
f.instruction(&Instruction::V128Const(0));
}
}
}
fn emit_hash_record(
record_name: &str,
registry: &TypeRegistry,
string_key_helpers: Option<KeyHelpers>,
all_key_helpers: &HashMap<String, KeyHelpers>,
) -> Result<Function, WasmGcError> {
let record_idx = registry
.record_type_idx(record_name)
.ok_or(WasmGcError::Validation(format!(
"hash_record: `{record_name}` not registered"
)))?;
let fields = registry
.record_fields
.get(record_name)
.ok_or(WasmGcError::Validation(format!(
"hash_record: `{record_name}` has no field info"
)))?;
let mut f = Function::new([(1, ValType::I32) ]);
f.instruction(&Instruction::I32Const(5381));
f.instruction(&Instruction::LocalSet(1));
for (i, (_field_name, field_ty)) in fields.iter().enumerate() {
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::StructGet {
struct_type_index: record_idx,
field_index: i as u32,
});
match field_ty.trim() {
"Int" => {
f.instruction(&Instruction::I32WrapI64);
}
"Bool" => {
}
"Float" => {
f.instruction(&Instruction::I64ReinterpretF64);
f.instruction(&Instruction::I32WrapI64);
}
"String" => {
let helpers = string_key_helpers.ok_or(WasmGcError::Validation(
"hash_record: String field needs String key helpers".into(),
))?;
f.instruction(&Instruction::Call(helpers.hash));
}
other => {
let lookup_key = if other.starts_with("List<") || other.starts_with("Vector<") {
super::types::normalize_compound(other).to_string()
} else {
other.to_string()
};
let is_compound = other.starts_with("List<") || other.starts_with("Vector<");
let is_carrier = other.starts_with("Option<")
|| other.starts_with("Result<")
|| other.starts_with("Tuple<");
let is_sum = registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| v.parent == other);
if registry.record_type_idx(other).is_some() || is_compound || is_sum || is_carrier
{
let inner = all_key_helpers
.get(&lookup_key)
.ok_or(WasmGcError::Validation(format!(
"hash_record: field `{other}` has no key helpers \
(record / list / vector / sum / Option / Result / Tuple T \
may need force-registration)"
)))?;
f.instruction(&Instruction::Call(inner.hash));
} else {
return Err(WasmGcError::Unimplemented(
"phase 3c — record-key field type not in \
{Int, Float, Bool, String, nested record, List<T>, Vector<T>, sum, \
Option/Result/Tuple}",
));
}
}
}
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(1));
}
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_eq_record(
record_name: &str,
registry: &TypeRegistry,
string_key_helpers: Option<KeyHelpers>,
all_key_helpers: &HashMap<String, KeyHelpers>,
) -> Result<Function, WasmGcError> {
let record_idx = registry
.record_type_idx(record_name)
.ok_or(WasmGcError::Validation(format!(
"eq_record: `{record_name}` not registered"
)))?;
let fields = registry
.record_fields
.get(record_name)
.ok_or(WasmGcError::Validation(format!(
"eq_record: `{record_name}` has no field info"
)))?;
let mut f = Function::new([]);
for (i, (_field_name, field_ty)) in fields.iter().enumerate() {
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: record_idx,
field_index: i as u32,
});
f.instruction(&Instruction::LocalGet(1));
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 helpers = string_key_helpers.ok_or(WasmGcError::Validation(
"eq_record: String field needs String key helpers".into(),
))?;
f.instruction(&Instruction::Call(helpers.eq))
}
other => {
let lookup_key = if other.starts_with("List<") || other.starts_with("Vector<") {
super::types::normalize_compound(other).to_string()
} else {
other.to_string()
};
let is_compound = other.starts_with("List<") || other.starts_with("Vector<");
let is_carrier = other.starts_with("Option<")
|| other.starts_with("Result<")
|| other.starts_with("Tuple<");
let is_sum = registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| v.parent == other);
if registry.record_type_idx(other).is_some() || is_compound || is_sum || is_carrier
{
let inner = all_key_helpers
.get(&lookup_key)
.ok_or(WasmGcError::Validation(format!(
"eq_record: field `{other}` has no key helpers"
)))?;
f.instruction(&Instruction::Call(inner.eq))
} else {
return Err(WasmGcError::Unimplemented(
"phase 3c — record-key field type not in \
{Int, Float, Bool, String, nested record, List<T>, Vector<T>, sum, \
Option/Result/Tuple}",
));
}
}
};
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::I32Const(1));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_map_keys(canonical: &str, registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let (k_aver, _) = super::types::parse_map_kv(canonical).unwrap();
let list_canonical = format!("List<{k_aver}>");
let list_idx = registry
.list_type_idx(&list_canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.keys: `{list_canonical}` not registered"
)))?;
emit_map_walk_keys_to_list(slots.map, slots.keys_array, list_idx, k_aver, registry)
}
fn emit_map_values(canonical: &str, registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let (_, v_aver) = super::types::parse_map_kv(canonical).unwrap();
let list_canonical = format!("List<{v_aver}>");
let list_idx = registry
.list_type_idx(&list_canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.values: `{list_canonical}` not registered"
)))?;
emit_map_walk_values_to_list(slots, registry, list_idx)
}
fn emit_map_walk_keys_to_list(
map_idx: u32,
keys_array_idx: u32,
list_idx: u32,
k_aver: &str,
registry: &TypeRegistry,
) -> Result<Function, WasmGcError> {
let keys_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(keys_array_idx),
});
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let mut f = Function::new([(1, keys_ref), (1, ValType::I32), (1, list_ref)]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: map_idx,
field_index: 2,
});
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: map_idx,
field_index: 1,
});
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::RefNull(HeapType::Concrete(list_idx)));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::Block(wasm_encoder::BlockType::Empty));
f.instruction(&Instruction::Loop(wasm_encoder::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(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::ArrayGet(keys_array_idx));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::ArrayGet(keys_array_idx));
emit_unbox_key(&mut f, k_aver, registry);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::StructNew(list_idx));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::End);
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(3));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_map_walk_values_to_list(
slots: super::types::MapSlots,
_registry: &TypeRegistry,
list_idx: u32,
) -> Result<Function, WasmGcError> {
let keys_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.keys_array),
});
let values_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.values_array),
});
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let mut f = Function::new([
(1, keys_ref),
(1, values_ref),
(1, ValType::I32),
(1, list_ref),
]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 2,
});
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 3,
});
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 1,
});
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::RefNull(HeapType::Concrete(list_idx)));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::Block(wasm_encoder::BlockType::Empty));
f.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::I32LtS);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayGet(slots.keys_array));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayGet(slots.values_array));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::StructNew(list_idx));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
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::End);
Ok(f)
}
fn emit_map_eq(
canonical: &str,
registry: &TypeRegistry,
keyh: KeyHelpers,
v_helpers: Option<KeyHelpers>,
get_fn_idx: u32,
) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let (k_aver, v_aver) = super::types::parse_map_kv(canonical).unwrap();
let _ = keyh;
let map_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.map),
});
let keys_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.keys_array),
});
let values_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.values_array),
});
let v_val =
super::types::aver_to_wasm(v_aver, Some(registry))?.ok_or(WasmGcError::Validation(
format!("Map<{k_aver},{v_aver}>.eq: V `{v_aver}` has no wasm rep"),
))?;
let opt_idx = registry
.option_type_idx(&format!("Option<{v_aver}>"))
.ok_or(WasmGcError::Validation(format!(
"Map.eq: `Option<{v_aver}>` not registered"
)))?;
let opt_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(opt_idx),
});
let mut f = Function::new(vec![
(1, map_ref),
(1, map_ref),
(1, ValType::I32),
(1, ValType::I32),
(1, keys_ref),
(1, values_ref),
(1, key_storage_val_type(k_aver, registry)?),
(1, opt_ref),
(1, v_val),
(1, v_val),
]);
let map_heap = HeapType::Concrete(slots.map);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::RefCastNonNull(map_heap));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::RefCastNonNull(map_heap));
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 0,
});
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 0,
});
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(2));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 2,
});
f.instruction(&Instruction::LocalSet(6));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 3,
});
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32GeS);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::ArrayGet(slots.keys_array));
f.instruction(&Instruction::LocalSet(8));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(8));
emit_unbox_key(&mut f, k_aver, registry);
f.instruction(&Instruction::Call(get_fn_idx));
f.instruction(&Instruction::LocalSet(9));
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::StructGet {
struct_type_index: opt_idx,
field_index: 0,
});
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(7));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::ArrayGet(slots.values_array));
f.instruction(&Instruction::LocalSet(10));
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::StructGet {
struct_type_index: opt_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(11));
f.instruction(&Instruction::LocalGet(10));
f.instruction(&Instruction::LocalGet(11));
emit_v_eq(&mut f, v_aver, v_helpers)?;
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::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::I32Const(1));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_v_eq(
f: &mut Function,
v_aver: &str,
v_helpers: Option<KeyHelpers>,
) -> Result<(), WasmGcError> {
match v_aver.trim() {
"Int" => {
f.instruction(&Instruction::I64Eq);
}
"Bool" => {
f.instruction(&Instruction::I32Eq);
}
"Float" => {
f.instruction(&Instruction::F64Eq);
}
_ => {
let h = v_helpers.ok_or(WasmGcError::Validation(format!(
"emit_v_eq: V `{v_aver}` needs ref helpers (record/sum/carrier/list/vec/map)"
)))?;
f.instruction(&Instruction::Call(h.eq));
}
}
Ok(())
}
fn emit_v_hash(
f: &mut Function,
v_aver: &str,
v_helpers: Option<KeyHelpers>,
) -> Result<(), WasmGcError> {
match v_aver.trim() {
"Int" => {
f.instruction(&Instruction::I32WrapI64);
}
"Bool" => {} "Float" => {
f.instruction(&Instruction::I64ReinterpretF64);
f.instruction(&Instruction::I32WrapI64);
}
_ => {
let h = v_helpers.ok_or(WasmGcError::Validation(format!(
"emit_v_hash: V `{v_aver}` needs ref helpers"
)))?;
f.instruction(&Instruction::Call(h.hash));
}
}
Ok(())
}
fn emit_map_hash(
canonical: &str,
registry: &TypeRegistry,
keyh: KeyHelpers,
v_helpers: Option<KeyHelpers>,
) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let (k_aver, v_aver) = super::types::parse_map_kv(canonical).unwrap();
let map_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.map),
});
let keys_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.keys_array),
});
let values_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.values_array),
});
let mut f = Function::new(vec![
(1, map_ref),
(1, ValType::I32),
(1, ValType::I32),
(1, keys_ref),
(1, values_ref),
(1, key_storage_val_type(k_aver, registry)?),
(1, ValType::I32),
(1, ValType::I32),
]);
let map_heap = HeapType::Concrete(slots.map);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::RefCastNonNull(map_heap));
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 2,
});
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 3,
});
f.instruction(&Instruction::LocalSet(5));
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::I32GeS);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayGet(slots.keys_array));
f.instruction(&Instruction::LocalSet(6));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(6));
emit_unbox_key(&mut f, k_aver, registry);
f.instruction(&Instruction::Call(keyh.hash));
f.instruction(&Instruction::I32Const(5));
f.instruction(&Instruction::I32Shl);
f.instruction(&Instruction::LocalGet(6));
emit_unbox_key(&mut f, k_aver, registry);
f.instruction(&Instruction::Call(keyh.hash));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayGet(slots.values_array));
emit_v_hash(&mut f, v_aver, v_helpers)?;
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(8));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::I32Xor);
f.instruction(&Instruction::LocalSet(7));
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::LocalGet(7));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_map_remove(
canonical: &str,
registry: &TypeRegistry,
keyh: KeyHelpers,
) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let (k_aver, v_aver) = super::types::parse_map_kv(canonical).unwrap();
let _k_val = super::types::aver_to_wasm(k_aver, Some(registry))?.unwrap();
let v_val = super::types::aver_to_wasm(v_aver, Some(registry))?.unwrap();
let _ = v_val; let mut f = Function::new([
(1, ValType::I32), (1, ValType::I32), (
1,
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.keys_array),
}),
), (
1,
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.values_array),
}),
), (1, ValType::I32), (1, ValType::I32), (1, ValType::I32), (1, key_storage_val_type(k_aver, registry)?), (1, ValType::I32), (1, ValType::I32), (1, ValType::I32), ]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 2,
});
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 3,
});
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(keyh.hash));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(6));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::ArrayGet(slots.keys_array));
f.instruction(&Instruction::LocalSet(9));
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(9));
emit_unbox_key(&mut f, k_aver, registry);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Call(keyh.eq));
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::I32Eq);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(8));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::ArrayGet(slots.keys_array));
f.instruction(&Instruction::LocalSet(9));
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(9));
emit_unbox_key(&mut f, k_aver, registry);
f.instruction(&Instruction::Call(keyh.hash));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(10));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(11));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::LocalGet(10));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(12));
f.instruction(&Instruction::LocalGet(12));
f.instruction(&Instruction::LocalGet(11));
f.instruction(&Instruction::I32LtU);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::ArraySet(slots.keys_array));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::ArrayGet(slots.values_array));
f.instruction(&Instruction::ArraySet(slots.values_array));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::LocalGet(8));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(8));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
let null_heap = key_storage_null_heap(k_aver, registry);
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::RefNull(null_heap));
f.instruction(&Instruction::ArraySet(slots.keys_array));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 0,
});
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::StructSet {
struct_type_index: slots.map,
field_index: 0,
});
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_map_entries(canonical: &str, registry: &TypeRegistry) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let (k_aver, v_aver) = super::types::parse_map_kv(canonical).unwrap();
let tup_canonical = format!("Tuple<{k_aver},{v_aver}>");
let tup_idx = registry
.tuple_type_idx(&tup_canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.entries: `{tup_canonical}` not registered"
)))?;
let lt_canonical = format!("List<{tup_canonical}>");
let lt_idx = registry
.list_type_idx(<_canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.entries: `{lt_canonical}` not registered"
)))?;
let keys_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.keys_array),
});
let values_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.values_array),
});
let lt_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(lt_idx),
});
let mut f = Function::new([
(1, keys_ref),
(1, values_ref),
(1, ValType::I32),
(1, lt_ref),
]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 2,
});
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 3,
});
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructGet {
struct_type_index: slots.map,
field_index: 1,
});
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
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(3));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::I32LtS);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayGet(slots.keys_array));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayGet(slots.keys_array));
emit_unbox_key(&mut f, k_aver, registry);
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::ArrayGet(slots.values_array));
f.instruction(&Instruction::StructNew(tup_idx));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::StructNew(lt_idx));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Sub);
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::End);
Ok(f)
}
fn emit_map_from_list(
canonical: &str,
registry: &TypeRegistry,
set_fn: u32,
) -> Result<Function, WasmGcError> {
let slots = slots_for(canonical, registry)?;
let (k_aver, v_aver) = super::types::parse_map_kv(canonical).unwrap();
let tup_canonical = format!("Tuple<{k_aver},{v_aver}>");
let tup_idx = registry
.tuple_type_idx(&tup_canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.fromList: `{tup_canonical}` not registered"
)))?;
let lt_canonical = format!("List<{tup_canonical}>");
let lt_idx = registry
.list_type_idx(<_canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.fromList: `{lt_canonical}` not registered"
)))?;
let map_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(slots.map),
});
let lt_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(lt_idx),
});
let mut f = Function::new([
(1, lt_ref),
(1, map_ref),
(
1,
ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(tup_idx),
}),
),
]);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::I32Const(INITIAL_CAP));
f.instruction(&Instruction::I32Const(INITIAL_CAP));
f.instruction(&Instruction::ArrayNewDefault(slots.keys_array));
f.instruction(&Instruction::I32Const(INITIAL_CAP));
f.instruction(&Instruction::ArrayNewDefault(slots.values_array));
f.instruction(&Instruction::StructNew(slots.map));
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(1));
f.instruction(&Instruction::StructGet {
struct_type_index: lt_idx,
field_index: 0,
});
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::StructGet {
struct_type_index: tup_idx,
field_index: 0,
});
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::StructGet {
struct_type_index: tup_idx,
field_index: 1,
});
f.instruction(&Instruction::Call(set_fn));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructGet {
struct_type_index: lt_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_hash_sum(
parent_name: &str,
registry: &TypeRegistry,
string_key_helpers: Option<KeyHelpers>,
all_key_helpers: &HashMap<String, KeyHelpers>,
) -> Result<Function, 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!(
"hash_sum: `{parent_name}` has no variants"
)));
}
let mut f = Function::new([(1, ValType::I32) ]);
f.instruction(&Instruction::Block(BlockType::Empty));
for (tag, (_v_name, info)) in variants.iter().enumerate() {
let v_idx = info.type_idx;
let v_heap = wasm_encoder::HeapType::Concrete(v_idx);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::RefTestNonNull(v_heap));
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::I32Const(5381 * 33 + tag as i32));
f.instruction(&Instruction::LocalSet(1));
for (i, field_ty) in info.fields.iter().enumerate() {
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::RefCastNonNull(v_heap));
f.instruction(&Instruction::StructGet {
struct_type_index: v_idx,
field_index: i as u32,
});
let field_ty_trim = field_ty.trim();
match field_ty_trim {
"Int" => {
f.instruction(&Instruction::I32WrapI64);
}
"Bool" => {}
"Float" => {
f.instruction(&Instruction::I64ReinterpretF64);
f.instruction(&Instruction::I32WrapI64);
}
"String" => {
let helpers = string_key_helpers.ok_or(WasmGcError::Validation(
"hash_sum: String field needs String key helpers".into(),
))?;
f.instruction(&Instruction::Call(helpers.hash));
}
_ => {
let lookup_key = super::types::normalize_compound(field_ty_trim);
let helpers = all_key_helpers
.get(&lookup_key)
.or_else(|| all_key_helpers.get(field_ty_trim))
.ok_or_else(|| {
WasmGcError::Validation(format!(
"hash_sum: no helper registered for sum-variant field \
type `{field_ty_trim}` of `{parent_name}`"
))
})?;
f.instruction(&Instruction::Call(helpers.hash));
}
}
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(1));
}
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::Return);
f.instruction(&Instruction::End);
}
f.instruction(&Instruction::End);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_eq_sum(
parent_name: &str,
registry: &TypeRegistry,
string_key_helpers: Option<KeyHelpers>,
all_key_helpers: &HashMap<String, KeyHelpers>,
) -> Result<Function, 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!(
"eq_sum: `{parent_name}` has no variants"
)));
}
let mut f = Function::new([]);
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(0));
f.instruction(&Instruction::RefTestNonNull(v_heap));
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(1));
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(0));
f.instruction(&Instruction::RefCastNonNull(v_heap));
f.instruction(&Instruction::StructGet {
struct_type_index: v_idx,
field_index: i as u32,
});
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::RefCastNonNull(v_heap));
f.instruction(&Instruction::StructGet {
struct_type_index: v_idx,
field_index: i as u32,
});
let field_ty_trim = field_ty.trim();
match field_ty_trim {
"Int" => f.instruction(&Instruction::I64Eq),
"Bool" => f.instruction(&Instruction::I32Eq),
"Float" => f.instruction(&Instruction::F64Eq),
"String" => {
let helpers = string_key_helpers.ok_or(WasmGcError::Validation(
"eq_sum: String field needs String key helpers".into(),
))?;
f.instruction(&Instruction::Call(helpers.eq))
}
_ => {
let lookup_key = super::types::normalize_compound(field_ty_trim);
let helpers = all_key_helpers
.get(&lookup_key)
.or_else(|| all_key_helpers.get(field_ty_trim))
.ok_or_else(|| {
WasmGcError::Validation(format!(
"eq_sum: no helper registered for sum-variant field \
type `{field_ty_trim}` of `{parent_name}`"
))
})?;
f.instruction(&Instruction::Call(helpers.eq))
}
};
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);
f.instruction(&Instruction::End);
Ok(f)
}