use std::collections::{HashMap, HashSet};
use wasm_encoder::{Function, Instruction, ValType};
use super::WasmGcError;
use super::types::TypeRegistry;
use crate::ast::{FnBody, FnDef, Stmt};
use crate::ir::CallLowerCtx;
mod builtins;
mod builtins_wasip2;
mod emit;
pub(super) mod eq_helpers;
pub(super) mod hash_helpers;
mod infer;
mod slots;
use emit::emit_expr;
use infer::aver_type_str_of;
use slots::{SlotTable, count_value_params};
pub(super) struct FnMap {
pub(super) by_name: std::collections::HashMap<String, FnEntry>,
pub(super) builtins: std::collections::HashMap<String, u32>,
pub(super) effects: std::collections::HashMap<String, u32>,
pub(super) map_helpers: std::collections::HashMap<String, super::maps::MapKVHelpers>,
pub(super) list_ops: std::collections::HashMap<String, super::lists::ListOps>,
pub(super) vfl_ops: std::collections::HashMap<String, super::lists::VectorFromListOps>,
pub(super) zip_ops: std::collections::HashMap<String, u32>,
pub(super) string_split_ops: Option<super::lists::StringSplitOps>,
pub(super) eq_helpers: std::collections::HashMap<String, u32>,
}
impl FnMap {
pub(super) fn list_ops_lookup(&self, canonical: &str) -> Option<&super::lists::ListOps> {
if let Some(o) = self.list_ops.get(canonical) {
return Some(o);
}
let bare = super::types::strip_inner_dotted_prefixes(canonical);
if bare != canonical {
self.list_ops.get(&bare)
} else {
None
}
}
pub(super) fn map_helpers_lookup(&self, canonical: &str) -> Option<&super::maps::MapKVHelpers> {
if let Some(o) = self.map_helpers.get(canonical) {
return Some(o);
}
let bare = super::types::strip_inner_dotted_prefixes(canonical);
if bare != canonical {
self.map_helpers.get(&bare)
} else {
None
}
}
pub(super) fn vfl_ops_lookup(
&self,
canonical: &str,
) -> Option<&super::lists::VectorFromListOps> {
if let Some(o) = self.vfl_ops.get(canonical) {
return Some(o);
}
let bare = super::types::strip_inner_dotted_prefixes(canonical);
if bare != canonical {
self.vfl_ops.get(&bare)
} else {
None
}
}
pub(super) fn zip_ops_lookup(&self, canonical: &str) -> Option<u32> {
if let Some(&o) = self.zip_ops.get(canonical) {
return Some(o);
}
let bare = super::types::strip_inner_dotted_prefixes(canonical);
if bare != canonical {
self.zip_ops.get(&bare).copied()
} else {
None
}
}
}
pub(super) struct FnEntry {
pub(super) wasm_idx: u32,
#[allow(dead_code)]
pub(super) return_type: String,
}
#[allow(clippy::too_many_arguments)]
pub(super) fn emit_fn_body(
func: &mut Function,
fd: &FnDef,
fn_map: &FnMap,
self_wasm_idx: u32,
registry: &TypeRegistry,
effect_idx_lookup: &HashMap<String, u32>,
caller_fn_collector: &std::cell::RefCell<CallerFnCollector>,
wasip2_lowering: Option<&Wasip2Lowering>,
) -> Result<Vec<ValType>, WasmGcError> {
let slots = SlotTable::build_for_fn(fd, registry, fn_map)?;
let FnBody::Block(stmts) = fd.body.as_ref();
let last_idx = stmts.len().saturating_sub(1);
let mut binding_names: HashSet<String> = HashSet::new();
fn collect_names(stmts: &[Stmt], out: &mut HashSet<String>) {
for s in stmts {
if let Stmt::Binding(name, _, _) = s {
out.insert(name.clone());
}
}
}
collect_names(stmts, &mut binding_names);
let ctx = EmitCtx {
fn_map,
self_wasm_idx,
self_fn_name: fd.name.as_str(),
return_type: fd.return_type.as_str(),
registry,
resolution: fd.resolution.as_ref(),
params: &fd.params,
binding_names: &binding_names,
effect_idx_lookup,
caller_fn_collector,
wasip2_lowering,
};
for (i, stmt) in stmts.iter().enumerate() {
let is_last = i == last_idx;
match stmt {
Stmt::Binding(name, _annot, expr) => {
emit_expr(func, expr, &slots, &ctx)?;
let produces_value = aver_type_str_of(expr).trim() != "Unit";
if name == "_" {
if produces_value {
func.instruction(&Instruction::Drop);
}
continue;
}
let slot = ctx
.self_local_slot(name)
.ok_or(WasmGcError::Validation(format!(
"binding `{name}` has no resolver slot"
)))?;
if produces_value && (slot as usize) < slots.by_slot.len() {
func.instruction(&Instruction::LocalSet(slot));
}
}
Stmt::Expr(spanned) => {
emit_expr(func, spanned, &slots, &ctx)?;
let produces_value = aver_type_str_of(spanned).trim() != "Unit";
if !is_last && produces_value {
func.instruction(&Instruction::Drop);
}
if is_last {
if fd.return_type.trim() == "Unit" && produces_value {
func.instruction(&Instruction::Drop);
} else if fd.return_type.trim() != "Unit" && !produces_value {
return Err(WasmGcError::Validation(format!(
"fn `{}` returns {} but trailing expression yields no value",
fd.name, fd.return_type
)));
}
}
}
}
}
func.instruction(&Instruction::End);
Ok(slots.extra_locals(count_value_params(&fd.params)))
}
#[derive(Default)]
pub(super) struct CallerFnCollector {
pub(super) idx_by_name: HashMap<String, u32>,
pub(super) names: Vec<String>,
}
impl CallerFnCollector {
pub(super) fn register(&mut self, name: &str) -> u32 {
if let Some(&i) = self.idx_by_name.get(name) {
return i;
}
let i = self.names.len() as u32;
self.names.push(name.to_string());
self.idx_by_name.insert(name.to_string(), i);
i
}
}
pub(super) struct EmitCtx<'a> {
pub(super) fn_map: &'a FnMap,
pub(super) self_wasm_idx: u32,
pub(super) self_fn_name: &'a str,
pub(super) return_type: &'a str,
pub(super) registry: &'a TypeRegistry,
pub(super) resolution: Option<&'a crate::ast::FnResolution>,
pub(super) params: &'a [(String, String)],
pub(super) binding_names: &'a HashSet<String>,
pub(super) effect_idx_lookup: &'a HashMap<String, u32>,
pub(super) caller_fn_collector: &'a std::cell::RefCell<CallerFnCollector>,
pub(super) wasip2_lowering: Option<&'a Wasip2Lowering>,
}
pub(super) struct Wasip2Lowering {
pub(super) get_stdout_fn_idx: Option<u32>,
pub(super) get_stderr_fn_idx: Option<u32>,
pub(super) blocking_write_fn_idx: Option<u32>,
pub(super) stdout_handle_global: Option<u32>,
pub(super) stderr_handle_global: Option<u32>,
pub(super) str_to_lm_fn_idx: Option<u32>,
pub(super) clocks_now_fn_idx: Option<u32>,
pub(super) random_u64_fn_idx: Option<u32>,
pub(super) get_arguments_fn_idx: Option<u32>,
pub(super) cabi_realloc_fn_idx: Option<u32>,
pub(super) decode_list_string_fn_idx: Option<u32>,
pub(super) get_environment_fn_idx: Option<u32>,
pub(super) env_get_lookup_fn_idx: Option<u32>,
pub(super) fmt_iso8601_fn_idx: Option<u32>,
pub(super) console_read_line_fn_idx: Option<u32>,
pub(super) time_sleep_fn_idx: Option<u32>,
pub(super) disk_exists_fn_idx: Option<u32>,
pub(super) disk_read_text_fn_idx: Option<u32>,
pub(super) disk_write_text_fn_idx: Option<u32>,
pub(super) disk_append_text_fn_idx: Option<u32>,
pub(super) disk_delete_fn_idx: Option<u32>,
pub(super) disk_delete_dir_fn_idx: Option<u32>,
pub(super) disk_make_dir_fn_idx: Option<u32>,
pub(super) disk_list_dir_fn_idx: Option<u32>,
}
impl<'a> EmitCtx<'a> {
pub(super) fn self_local_slot(&self, name: &str) -> Option<u32> {
self.resolution
.as_ref()
.and_then(|r| r.local_slots.get(name).copied())
.map(|s| s as u32)
}
pub(super) fn is_aliased_slot(&self, slot: u16) -> bool {
self.resolution
.and_then(|r| r.aliased_slots.get(slot as usize).copied())
.unwrap_or(false)
}
pub(super) fn arg_uniquely_owned(&self, arg: &crate::ast::Spanned<crate::ast::Expr>) -> bool {
match &arg.node {
crate::ast::Expr::Resolved { slot, last_use, .. } => {
last_use.0 && !self.is_aliased_slot(*slot)
}
_ => true,
}
}
}
impl<'a> CallLowerCtx for EmitCtx<'a> {
fn is_local_value(&self, name: &str) -> bool {
self.params.iter().any(|(n, _)| n == name) || self.binding_names.contains(name)
}
fn is_user_type(&self, name: &str) -> bool {
self.registry.records.contains_key(name)
|| self.registry.variants.contains_key(name)
|| self
.registry
.variants
.values()
.flat_map(|vs| vs.iter())
.any(|info| info.parent == name)
}
fn resolve_module_call<'b>(&self, _dotted: &'b str) -> Option<(&'b str, &'b str)> {
None
}
}