use std::collections::{HashMap, HashSet};
use wasm_encoder::{Function, Instruction};
use super::super::WasmGcError;
use super::super::lists::{emit_record_eq_inline, emit_sum_eq_inline};
use super::super::types::TypeRegistry;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum EqKind {
Record,
Sum,
}
#[derive(Default)]
pub(crate) struct EqHelperRegistry {
order: Vec<String>,
kinds: HashMap<String, EqKind>,
slots: HashMap<String, (u32, u32)>,
}
impl EqHelperRegistry {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn register(&mut self, type_name: &str, kind: EqKind) {
if !self.kinds.contains_key(type_name) {
self.order.push(type_name.to_string());
self.kinds.insert(type_name.to_string(), kind);
}
}
pub(crate) fn iter(&self) -> impl Iterator<Item = (&str, EqKind)> + '_ {
self.order.iter().map(|n| (n.as_str(), self.kinds[n]))
}
pub(crate) fn assign_slots(&mut self, next_fn_idx: &mut u32, next_type_idx: &mut u32) {
for name in &self.order {
self.slots
.insert(name.clone(), (*next_fn_idx, *next_type_idx));
*next_fn_idx += 1;
*next_type_idx += 1;
}
}
pub(crate) fn lookup_fn_idx(&self, type_name: &str) -> Option<u32> {
self.slots.get(type_name).map(|(f, _)| *f)
}
pub(crate) fn lookup_type_idx(&self, type_name: &str) -> Option<u32> {
self.slots.get(type_name).map(|(_, t)| *t)
}
pub(crate) fn emit_helper_types(&self, types: &mut wasm_encoder::TypeSection) {
let eq_ref = wasm_encoder::ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Abstract {
shared: false,
ty: wasm_encoder::AbstractHeapType::Eq,
},
});
for _ in &self.order {
types
.ty()
.function([eq_ref, eq_ref], [wasm_encoder::ValType::I32]);
}
}
pub(crate) fn emit_helper_bodies(
&self,
codes: &mut wasm_encoder::CodeSection,
registry: &TypeRegistry,
string_eq_fn_idx: Option<u32>,
) -> Result<(), WasmGcError> {
for name in &self.order {
let kind = self.kinds[name];
let mut f = Function::new(Vec::new());
match kind {
EqKind::Sum => emit_sum_eq_inline(&mut f, name, registry, 0, 1, string_eq_fn_idx)?,
EqKind::Record => {
emit_record_eq_inline(&mut f, name, registry, 0, 1, string_eq_fn_idx)?
}
}
f.instruction(&Instruction::End);
codes.function(&f);
}
Ok(())
}
pub(crate) fn needs_string_eq(&self, registry: &TypeRegistry) -> bool {
let mut visiting: HashSet<String> = HashSet::new();
for name in &self.order {
if type_has_string_field(name, self.kinds[name], registry, &mut visiting) {
return true;
}
}
false
}
}
fn type_has_string_field(
name: &str,
kind: EqKind,
registry: &TypeRegistry,
visiting: &mut HashSet<String>,
) -> bool {
if !visiting.insert(name.to_string()) {
return false;
}
match kind {
EqKind::Record => registry
.record_fields
.get(name)
.map(|fs| fs.iter().any(|(_, t)| t.trim() == "String"))
.unwrap_or(false),
EqKind::Sum => registry
.variants
.values()
.flat_map(|vs| vs.iter())
.filter(|v| v.parent == name)
.any(|v| v.fields.iter().any(|t| t.trim() == "String")),
}
}