use std::collections::{HashMap, HashSet};
use wasm_encoder::{
CodeSection, ConstExpr, DataSection, EntityType, ExportKind, ExportSection, FunctionSection,
GlobalSection, GlobalType, ImportSection, IndirectNameMap, Instruction, MemoryType, Module,
NameMap, NameSection, StartSection, TypeSection, ValType,
};
use crate::ast::{Expr, FnBody, FnDef, Literal, Pattern, Stmt, StrPart, TopLevel};
use crate::codegen::CodegenContext;
use crate::ir::{ThinKind, classify_thin_fn_def, thin_body_plan_is_parent_thin_candidate};
use super::expr::{ExprEmitter, StringLiteral, build_variant_registry};
use super::runtime::{self, AverRuntimeImports, RuntimeFuncIndices};
use super::types::{WasmType, aver_type_to_wasm};
use super::value;
struct UserFnEntry<'a> {
fd: &'a FnDef,
canonical_name: String,
module_prefix: Option<String>,
}
struct MutualTcoGroup {
trampoline_name: String,
member_indices: Vec<usize>,
}
#[derive(Clone)]
struct MutualTcoSlot {
owner_index: usize,
owner_param_index: usize,
wasm_type: WasmType,
aver_type: crate::types::Type,
}
struct MutualTcoLayout {
trampoline_name: String,
trampoline_type_idx: u32,
trampoline_fn_idx: u32,
return_type: WasmType,
slots: Vec<MutualTcoSlot>,
member_ids: HashMap<usize, u32>,
member_param_locals: HashMap<usize, Vec<u32>>,
uniform_signature: bool,
}
pub fn build_wasm_module(
ctx: &CodegenContext,
adapter: super::WasmAdapter,
handler: Option<&str>,
) -> Result<Vec<u8>, String> {
let mut module = Module::new();
let mut user_fns: Vec<UserFnEntry<'_>> = ctx
.items
.iter()
.filter_map(|item| {
if let TopLevel::FnDef(fd) = item {
Some(UserFnEntry {
fd,
canonical_name: fd.name.clone(),
module_prefix: None,
})
} else {
None
}
})
.collect();
for module_info in &ctx.modules {
for fd in &module_info.fn_defs {
if fd.name == "main" {
continue; }
user_fns.push(UserFnEntry {
fd,
canonical_name: format!("{}.{}", module_info.prefix, fd.name),
module_prefix: Some(module_info.prefix.clone()),
});
}
}
if !matches!(adapter, super::WasmAdapter::Fetch) {
let uses_http = |ty: &str| ty == "HttpRequest" || ty == "HttpResponse";
if let Some(fd) = user_fns
.iter()
.map(|e| e.fd)
.find(|fd| uses_http(&fd.return_type) || fd.params.iter().any(|(_, t)| uses_http(t)))
{
let bridge_hint = match adapter {
super::WasmAdapter::Wasi => " (currently `--bridge wasip1`)",
super::WasmAdapter::Aver => " (no `--bridge`)",
super::WasmAdapter::Fetch => unreachable!(),
};
return Err(format!(
"fn `{}` uses HttpRequest/HttpResponse, which only the fetch bridge can lower{}.\n\
Use one of:\n\
\x20 --preset cloudflare # Cloudflare Workers (fetch + bundling)\n\
\x20 --bridge fetch # bare fetch bridge (browsers, Deno, Bun)",
fd.name, bridge_hint
));
}
}
let mutual_tco_groups = build_mutual_tco_groups(ctx, &user_fns);
let alloc_policy = super::WasmAllocPolicy;
let alloc_fn_defs: Vec<&crate::ast::FnDef> = user_fns.iter().map(|e| e.fd).collect();
let alloc_info_by_local_name = crate::ir::compute_alloc_info(&alloc_fn_defs, &alloc_policy);
let no_alloc_by_canonical: HashMap<String, bool> = user_fns
.iter()
.map(|e| {
let allocates = *alloc_info_by_local_name.get(&e.fd.name).unwrap_or(&true);
(e.canonical_name.clone(), !allocates)
})
.collect();
let mut string_set: HashSet<String> = HashSet::new();
for entry in &user_fns {
collect_strings_from_body(&entry.fd.body, &mut string_set);
}
string_set.insert("true".to_string());
string_set.insert("false".to_string());
for method in ["GET", "HEAD", "DELETE", "POST", "PUT", "PATCH"] {
string_set.insert(method.to_string());
}
let mut sorted_strings: Vec<String> = string_set.into_iter().collect();
sorted_strings.sort();
let mut string_literals: HashMap<String, StringLiteral> = HashMap::new();
let mut data_offset = runtime::IO_SCRATCH_SIZE;
let mut data_bytes: Vec<u8> = Vec::new();
for s in &sorted_strings {
let len = s.len() as u32;
let header = value::make_header(value::OBJ_STRING, 0, 0, len as u64);
data_bytes.extend_from_slice(&header.to_le_bytes());
data_bytes.extend_from_slice(s.as_bytes());
let padded_len = (len as usize + 7) & !7;
data_bytes.resize(data_bytes.len() + padded_len - s.len(), 0);
string_literals.insert(s.clone(), (data_offset, len));
data_offset += 8 + padded_len as u32;
}
let heap_base = if data_offset > runtime::IO_SCRATCH_SIZE {
((data_offset + 7) & !7) as i32
} else {
1024
};
let mut needed_host_imports: Vec<&str> = Vec::new();
let mut host_import_set: HashSet<String> = HashSet::new();
for entry in &user_fns {
collect_host_calls_from_body(&entry.fd.body, &mut host_import_set);
}
for name in &host_import_set {
needed_host_imports.push(name.as_str());
}
needed_host_imports.sort();
let mut type_section = TypeSection::new();
let rti = runtime::emit_base_type_section(&mut type_section);
let rt_base_type_count = rti.count;
let mut fn_type_indices: HashMap<String, u32> = HashMap::new();
for (i, entry) in user_fns.iter().enumerate() {
let (param_vals, result_vals) = if let Some((param_types, ret_type, _)) = ctx
.fn_sigs
.get(entry.canonical_name.as_str())
.or_else(|| ctx.fn_sigs.get(&entry.fd.name))
{
let params: Vec<ValType> = param_types
.iter()
.map(|t| aver_type_to_wasm(t).to_val_type())
.collect();
let ret = vec![aver_type_to_wasm(ret_type).to_val_type()];
(params, ret)
} else {
(
vec![ValType::I64; entry.fd.params.len()],
vec![ValType::I64],
)
};
type_section.ty().function(param_vals, result_vals);
fn_type_indices.insert(entry.canonical_name.clone(), rt_base_type_count + i as u32);
}
let mut mutual_tco_layouts = Vec::new();
for (group_idx, group) in mutual_tco_groups.iter().enumerate() {
let layout = build_mutual_tco_layout(
group,
&user_fns,
ctx,
rt_base_type_count + user_fns.len() as u32 + group_idx as u32,
)?;
type_section.ty().function(
std::iter::once(ValType::I32)
.chain(layout.slots.iter().map(|slot| slot.wasm_type.to_val_type()))
.collect::<Vec<_>>(),
vec![layout.return_type.to_val_type()],
);
mutual_tco_layouts.push(layout);
}
module.section(&type_section);
let has_effects = !needed_host_imports.is_empty();
let mut host_imports: HashMap<String, u32> = HashMap::new();
let mut import_section = ImportSection::new();
let aver_rt_imports = AverRuntimeImports {
rt_alloc: 0,
rt_truncate: 1,
rt_obj_kind: 2,
rt_obj_tag: 3,
rt_obj_meta: 4,
rt_obj_field: 5,
rt_obj_field_f64: 6,
rt_obj_field_i32: 7,
rt_unwrap: 8,
rt_unwrap_f64: 9,
rt_unwrap_i32: 10,
rt_wrap: 11,
rt_wrap_f64: 12,
rt_wrap_i32: 13,
rt_str_eq: 14,
rt_str_concat: 15,
rt_list_cons: 16,
rt_list_cons_f64: 17,
rt_str_byte_len: 18,
rt_str_find: 19,
rt_str_starts_with: 20,
rt_str_ends_with: 21,
rt_str_contains: 22,
rt_list_take: 23,
rt_list_drop: 24,
rt_list_concat: 25,
rt_list_reverse: 26,
rt_list_contains: 27,
rt_list_zip: 28,
rt_map_get: 29,
rt_map_set: 30,
rt_map_has: 31,
rt_map_keys: 32,
rt_map_entries: 33,
rt_vec_from_list: 34,
rt_vec_get: 35,
rt_vec_len: 36,
rt_vec_set: 37,
rt_vec_new: 38,
rt_vec_to_list: 39,
rt_int_to_str: 40,
rt_float_to_str: 41,
rt_i64_to_str_obj: 42,
rt_f64_to_str_obj: 43,
rt_str_len: 44,
rt_char_to_code: 45,
rt_byte_to_hex: 46,
rt_byte_from_hex: 47,
rt_char_from_code: 48,
rt_str_char_at: 49,
rt_str_to_lower: 50,
rt_str_to_upper: 51,
rt_str_trim: 52,
rt_str_slice: 53,
rt_str_chars: 54,
rt_str_split: 55,
rt_str_join: 56,
rt_str_replace: 57,
rt_int_from_str: 58,
rt_float_from_str: 59,
rt_collect_begin: 60,
rt_rebase_i32: 61,
rt_collect_end: 62,
rt_retain_i32: 63,
rt_map_len: 64,
rt_map_from_list: 65,
rt_map_set_owned: 66,
rt_buffer_new: 67,
rt_buffer_append_str: 68,
rt_buffer_finalize: 69,
};
import_section.import("aver_runtime", "rt_alloc", EntityType::Function(rti.alloc));
import_section.import(
"aver_runtime",
"rt_truncate",
EntityType::Function(rti.i32_to_empty),
);
import_section.import(
"aver_runtime",
"rt_obj_kind",
EntityType::Function(rti.obj_kind),
);
import_section.import(
"aver_runtime",
"rt_obj_tag",
EntityType::Function(rti.obj_tag),
);
import_section.import(
"aver_runtime",
"rt_obj_meta",
EntityType::Function(rti.obj_meta),
);
import_section.import(
"aver_runtime",
"rt_obj_field",
EntityType::Function(rti.obj_field_i64),
);
import_section.import(
"aver_runtime",
"rt_obj_field_f64",
EntityType::Function(rti.obj_field_f64),
);
import_section.import(
"aver_runtime",
"rt_obj_field_i32",
EntityType::Function(rti.obj_field_i32),
);
import_section.import(
"aver_runtime",
"rt_unwrap",
EntityType::Function(rti.unwrap_i64),
);
import_section.import(
"aver_runtime",
"rt_unwrap_f64",
EntityType::Function(rti.unwrap_f64),
);
import_section.import(
"aver_runtime",
"rt_unwrap_i32",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_wrap",
EntityType::Function(rti.wrap_i64),
);
import_section.import(
"aver_runtime",
"rt_wrap_f64",
EntityType::Function(rti.wrap_f64),
);
import_section.import(
"aver_runtime",
"rt_wrap_i32",
EntityType::Function(rti.wrap_i32),
);
import_section.import(
"aver_runtime",
"rt_str_eq",
EntityType::Function(rti.i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_str_concat",
EntityType::Function(rti.i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_list_cons",
EntityType::Function(rti.list_cons_i64),
);
import_section.import(
"aver_runtime",
"rt_list_cons_f64",
EntityType::Function(rti.list_cons_f64),
);
import_section.import(
"aver_runtime",
"rt_str_byte_len",
EntityType::Function(rti.unwrap_i64),
);
import_section.import(
"aver_runtime",
"rt_str_find",
EntityType::Function(rti.i32_i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_str_starts_with",
EntityType::Function(rti.i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_str_ends_with",
EntityType::Function(rti.i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_str_contains",
EntityType::Function(rti.i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_list_take",
EntityType::Function(rti.i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_list_drop",
EntityType::Function(rti.i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_list_concat",
EntityType::Function(rti.i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_list_reverse",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_list_contains",
EntityType::Function(rti.i32_i64_to_i32),
);
import_section.import(
"aver_runtime",
"rt_list_zip",
EntityType::Function(rti.i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_map_get",
EntityType::Function(rti.i32_i64_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_map_set",
EntityType::Function(rti.i32_i64_i32_i64_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_map_has",
EntityType::Function(rti.i32_i64_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_map_keys",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_map_entries",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_vec_from_list",
EntityType::Function(rti.i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_vec_get",
EntityType::Function(rti.i32_i64_to_i32),
);
import_section.import(
"aver_runtime",
"rt_vec_len",
EntityType::Function(rti.unwrap_i64),
);
import_section.import(
"aver_runtime",
"rt_vec_set",
EntityType::Function(rti.i32_i64_i64_to_i32),
);
import_section.import(
"aver_runtime",
"rt_vec_new",
EntityType::Function(rti.i64_i64_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_vec_to_list",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_int_to_str",
EntityType::Function(rti.int_to_str),
);
import_section.import(
"aver_runtime",
"rt_float_to_str",
EntityType::Function(rti.float_to_str),
);
import_section.import(
"aver_runtime",
"rt_i64_to_str_obj",
EntityType::Function(rti.i64_to_i32),
);
import_section.import(
"aver_runtime",
"rt_f64_to_str_obj",
EntityType::Function(rti.f64_to_i32),
);
import_section.import(
"aver_runtime",
"rt_str_len",
EntityType::Function(rti.unwrap_i64),
);
import_section.import(
"aver_runtime",
"rt_char_to_code",
EntityType::Function(rti.unwrap_i64),
);
import_section.import(
"aver_runtime",
"rt_byte_to_hex",
EntityType::Function(rti.i64_to_i32),
);
import_section.import(
"aver_runtime",
"rt_byte_from_hex",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_char_from_code",
EntityType::Function(rti.i64_to_i32),
);
import_section.import(
"aver_runtime",
"rt_str_char_at",
EntityType::Function(rti.i32_i64_to_i32),
);
import_section.import(
"aver_runtime",
"rt_str_to_lower",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_str_to_upper",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_str_trim",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_str_slice",
EntityType::Function(rti.i32_i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_str_chars",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_str_split",
EntityType::Function(rti.i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_str_join",
EntityType::Function(rti.i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_str_replace",
EntityType::Function(rti.i32_i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_int_from_str",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_float_from_str",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_collect_begin",
EntityType::Function(rti.i32_to_empty),
);
import_section.import(
"aver_runtime",
"rt_rebase_i32",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_collect_end",
EntityType::Function(rti.empty_to_empty),
);
import_section.import(
"aver_runtime",
"rt_retain_i32",
EntityType::Function(rti.unwrap_i32),
);
import_section.import(
"aver_runtime",
"rt_map_len",
EntityType::Function(rti.unwrap_i64),
);
import_section.import(
"aver_runtime",
"rt_map_from_list",
EntityType::Function(rti.i32_i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_map_set_owned",
EntityType::Function(rti.i32_i64_i32_i64_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_buffer_new",
EntityType::Function(rti.alloc), );
import_section.import(
"aver_runtime",
"rt_buffer_append_str",
EntityType::Function(rti.i32_i32_to_i32),
);
import_section.import(
"aver_runtime",
"rt_buffer_finalize",
EntityType::Function(rti.alloc), );
import_section.import(
"aver_runtime",
"memory",
EntityType::Memory(MemoryType {
minimum: 1,
maximum: None,
memory64: false,
shared: false,
page_size_log2: None,
}),
);
import_section.import(
"aver_runtime",
"heap_ptr",
EntityType::Global(GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
}),
);
for name in ["collect_mark", "collect_from", "collect_dst"] {
import_section.import(
"aver_runtime",
name,
EntityType::Global(GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
}),
);
}
let mut import_func_count = 70u32;
let user_fn_names: Vec<&str> = user_fns
.iter()
.map(|entry| entry.canonical_name.as_str())
.collect();
let needed = super::abi::collect_needed_imports(&ctx.fn_sigs, &user_fn_names, &host_import_set);
let emit_aver_import = |import_section: &mut ImportSection,
host_imports: &mut HashMap<String, u32>,
import_func_count: &mut u32,
abi_entry: &super::abi::AbiImport|
-> Result<u32, String> {
let type_idx = runtime::lookup_type_index(&rti, abi_entry.params, abi_entry.results)
.ok_or_else(|| {
format!(
"Missing WASM type mapping for ABI import {} ({:?} -> {:?})",
abi_entry.import_name, abi_entry.params, abi_entry.results
)
})?;
let idx = *import_func_count;
import_section.import(
super::abi::ABI_MODULE,
abi_entry.import_name,
wasm_encoder::EntityType::Function(type_idx),
);
host_imports.insert(abi_entry.import_name.to_string(), idx);
*import_func_count += 1;
Ok(idx)
};
let mut have_console_print = false;
for abi_entry in &needed {
emit_aver_import(
&mut import_section,
&mut host_imports,
&mut import_func_count,
abi_entry,
)?;
if abi_entry.import_name == "console_print" {
have_console_print = true;
}
}
if !have_console_print {
let abi_entry = super::abi::lookup("Console.print").unwrap();
emit_aver_import(
&mut import_section,
&mut host_imports,
&mut import_func_count,
abi_entry,
)?;
}
if matches!(adapter, super::WasmAdapter::Fetch) {
for effect in [
"Request.method",
"Request.url",
"Request.query",
"Request.body",
"Request.headersLoad",
"Response.text",
"Response.setHeader",
"Http.send",
"Http.addRequestHeader",
"Http.clearRequestHeaders",
] {
if let Some(abi_entry) = super::abi::lookup(effect)
&& !host_imports.contains_key(abi_entry.import_name)
{
emit_aver_import(
&mut import_section,
&mut host_imports,
&mut import_func_count,
abi_entry,
)?;
}
}
}
if let Some(abi_entry) = super::abi::lookup("Print.value") {
emit_aver_import(
&mut import_section,
&mut host_imports,
&mut import_func_count,
abi_entry,
)?;
}
if let Some(abi_entry) = super::abi::lookup("Format.value") {
emit_aver_import(
&mut import_section,
&mut host_imports,
&mut import_func_count,
abi_entry,
)?;
}
module.section(&import_section);
let mut rt = RuntimeFuncIndices::new(import_func_count, aver_rt_imports);
rt.adapter = adapter;
let trampoline_fn_base = import_func_count + rt.count;
let user_fn_base = trampoline_fn_base + mutual_tco_layouts.len() as u32;
if has_effects {
for &name in &needed_host_imports {
host_imports.insert(name.to_string(), 0); }
}
let mut function_section = FunctionSection::new();
for idx in import_func_count..(import_func_count + rt.count) {
let type_idx = runtime::rt_type_index(&rt, &rti, idx, import_func_count);
function_section.function(type_idx);
}
for (group_idx, layout) in mutual_tco_layouts.iter_mut().enumerate() {
function_section.function(layout.trampoline_type_idx);
layout.trampoline_fn_idx = trampoline_fn_base + group_idx as u32;
}
let mut fn_indices: HashMap<String, u32> = HashMap::new();
for (i, entry) in user_fns.iter().enumerate() {
let type_idx = fn_type_indices[&entry.canonical_name];
function_section.function(type_idx);
let idx = user_fn_base + i as u32;
fn_indices.insert(entry.canonical_name.clone(), idx);
if entry.module_prefix.is_none() {
fn_indices.insert(entry.fd.name.clone(), idx);
}
}
let start_fn_idx = user_fn_base + user_fns.len() as u32;
function_section.function(rti.empty_to_empty);
let wasi_start_wrapper_idx: Option<u32> =
if matches!(adapter, super::WasmAdapter::Wasi) && fn_indices.contains_key("main") {
function_section.function(rti.empty_to_empty);
Some(start_fn_idx + 1)
} else {
None
};
module.section(&function_section);
let variant_registry = build_variant_registry(ctx);
let mut global_section = GlobalSection::new();
let variant_name_json = {
let mut entries: Vec<(u32, String)> = Vec::new();
for ((type_name, variant_name), info) in &variant_registry {
let full = format!("{}.{}", type_name, variant_name);
if !entries.iter().any(|(t, _)| *t == info.tag) {
entries.push((info.tag, full));
}
}
entries.sort_by_key(|(t, _)| *t);
entries
.iter()
.map(|(t, n)| format!("{}:{}", t, n))
.collect::<Vec<_>>()
.join("|")
};
let variant_name_offset = runtime::IO_SCRATCH_SIZE as usize + data_bytes.len();
let variant_name_bytes = variant_name_json.as_bytes();
data_bytes.extend_from_slice(variant_name_bytes);
global_section.global(
GlobalType {
val_type: ValType::I32,
mutable: false,
shared: false,
},
&ConstExpr::i32_const(variant_name_offset as i32),
);
global_section.global(
GlobalType {
val_type: ValType::I32,
mutable: false,
shared: false,
},
&ConstExpr::i32_const(variant_name_bytes.len() as i32),
);
module.section(&global_section);
let mut export_section = ExportSection::new();
if let Some(&main_idx) = fn_indices.get("main") {
export_section.export("main", ExportKind::Func, main_idx);
let start_idx = wasi_start_wrapper_idx.unwrap_or(main_idx);
export_section.export("_start", ExportKind::Func, start_idx);
}
if let Some(handler_name) = handler {
match fn_indices.get(handler_name) {
Some(&fn_idx) => {
export_section.export("aver_http_handle", ExportKind::Func, fn_idx);
}
None => {
return Err(format!(
"--handler refers to fn `{}` which doesn't exist in this program. \
Define `fn {}(req: HttpRequest) -> HttpResponse` and try again.",
handler_name, handler_name
));
}
}
}
export_section.export("memory", ExportKind::Memory, 0);
export_section.export("alloc", ExportKind::Func, rt.alloc);
export_section.export("$heap_ptr", ExportKind::Global, 0);
export_section.export("$variant_names_ptr", ExportKind::Global, 4);
export_section.export("$variant_names_len", ExportKind::Global, 5);
module.section(&export_section);
module.section(&StartSection {
function_index: start_fn_idx,
});
let mut code_section = CodeSection::new();
let mut user_fn_local_names: Vec<(u32, Vec<(String, u32)>)> = Vec::new();
let rt_funcs = runtime::emit_runtime_functions(&rt);
for func in &rt_funcs {
code_section.function(func);
}
let type_fields: HashMap<(String, String), u32> = build_type_fields(ctx);
let tco_fns: HashSet<String> = user_fns
.iter()
.filter(|entry| {
body_has_self_tailcall(&entry.fd.body, &entry.fd.name, &entry.canonical_name)
})
.map(|entry| entry.canonical_name.clone())
.collect();
let mut mutual_group_by_member = HashMap::new();
for (group_idx, group) in mutual_tco_groups.iter().enumerate() {
for member_idx in &group.member_indices {
mutual_group_by_member.insert(*member_idx, group_idx);
}
}
for (group, layout) in mutual_tco_groups.iter().zip(&mutual_tco_layouts) {
let group_is_no_alloc = group.member_indices.iter().all(|&idx| {
no_alloc_by_canonical
.get(&user_fns[idx].canonical_name)
.copied()
.unwrap_or(false)
});
let func = emit_mutual_tco_trampoline(
group,
layout,
&user_fns,
&fn_indices,
&rt,
&string_literals,
&type_fields,
ctx,
&variant_registry,
&host_imports,
group_is_no_alloc,
)?;
code_section.function(&func);
}
for (entry_idx, entry) in user_fns.iter().enumerate() {
if let Some(&group_idx) = mutual_group_by_member.get(&entry_idx) {
let func =
emit_mutual_tco_wrapper(entry_idx, &mutual_tco_layouts[group_idx], &user_fns);
code_section.function(&func);
continue;
}
let entry_is_no_alloc = no_alloc_by_canonical
.get(&entry.canonical_name)
.copied()
.unwrap_or(false);
let (func, locals) = emit_plain_user_function(
entry,
&fn_indices,
&rt,
&string_literals,
&type_fields,
ctx,
&variant_registry,
&host_imports,
tco_fns.contains(&entry.canonical_name),
entry_is_no_alloc,
)?;
code_section.function(&func);
if let Some(&fn_idx) = fn_indices.get(&entry.canonical_name) {
user_fn_local_names.push((fn_idx, locals));
}
}
{
let mut start_fn = wasm_encoder::Function::new(Vec::new());
start_fn.instruction(&Instruction::I32Const(heap_base));
start_fn.instruction(&Instruction::GlobalSet(0));
start_fn.instruction(&Instruction::End);
code_section.function(&start_fn);
}
if wasi_start_wrapper_idx.is_some()
&& let Some(&main_idx) = fn_indices.get("main")
{
let main_ret_type = ctx
.fn_sigs
.get("main")
.map(|(_, ret_type, _)| aver_type_to_wasm(ret_type))
.unwrap_or(WasmType::I32);
let mut wrapper = wasm_encoder::Function::new(Vec::new());
wrapper.instruction(&Instruction::Call(main_idx));
let _ = main_ret_type;
wrapper.instruction(&Instruction::Drop);
wrapper.instruction(&Instruction::End);
code_section.function(&wrapper);
}
module.section(&code_section);
if !data_bytes.is_empty() {
let mut data_section = DataSection::new();
data_section.active(
0,
&ConstExpr::i32_const(runtime::IO_SCRATCH_SIZE as i32),
data_bytes,
);
module.section(&data_section);
}
let mut func_names = NameMap::new();
let mut named: Vec<(u32, String)> = Vec::new();
for (name, idx) in &host_imports {
if name.contains('.') {
continue;
}
if *idx > 0 || !needed_host_imports.is_empty() {
named.push((*idx, format!("host_{}", name.replace('/', "_"))));
}
}
for (idx, name) in rt.name_pairs() {
named.push((idx, format!("rt_{}", name)));
}
for (name, idx) in &fn_indices {
named.push((*idx, name.clone()));
}
named.push((start_fn_idx, "__aver_init_heap".to_string()));
named.sort_by_key(|(idx, _)| *idx);
named.dedup_by_key(|(idx, _)| *idx);
for (idx, name) in &named {
func_names.append(*idx, name);
}
let mut name_section = NameSection::new();
name_section.functions(&func_names);
user_fn_local_names.sort_by_key(|(idx, _)| *idx);
let mut local_names = IndirectNameMap::new();
for (fn_idx, locals) in &user_fn_local_names {
let mut sorted = locals.clone();
sorted.sort_by_key(|(_, idx)| *idx);
sorted.dedup_by_key(|(_, idx)| *idx);
let mut map = NameMap::new();
for (name, idx) in &sorted {
map.append(*idx, name);
}
local_names.append(*fn_idx, &map);
}
name_section.locals(&local_names);
module.section(&name_section);
Ok(module.finish())
}
fn lookup_sig_for_entry<'a>(
ctx: &'a CodegenContext,
entry: &UserFnEntry<'_>,
) -> Option<&'a (Vec<crate::types::Type>, crate::types::Type, Vec<String>)> {
ctx.fn_sigs
.get(entry.canonical_name.as_str())
.or_else(|| ctx.fn_sigs.get(&entry.fd.name))
}
fn param_types_for_entry(ctx: &CodegenContext, entry: &UserFnEntry<'_>) -> Vec<crate::types::Type> {
if let Some((param_types, _, _)) = lookup_sig_for_entry(ctx, entry) {
return param_types.clone();
}
entry
.fd
.params
.iter()
.map(|(_, ty)| crate::types::parse_type_str(ty))
.collect()
}
fn ret_wasm_type_for_entry(ctx: &CodegenContext, entry: &UserFnEntry<'_>) -> WasmType {
if let Some((_, ret_type, _)) = lookup_sig_for_entry(ctx, entry) {
aver_type_to_wasm(ret_type)
} else {
aver_type_to_wasm(&crate::types::parse_type_str(&entry.fd.return_type))
}
}
fn emit_default_value_to_func(func: &mut wasm_encoder::Function, wt: WasmType) {
match wt {
WasmType::I32 => func.instruction(&Instruction::I32Const(0)),
WasmType::I64 => func.instruction(&Instruction::I64Const(0)),
WasmType::F64 => func.instruction(&Instruction::F64Const(0.0)),
};
}
fn build_emitter_locals(emitter: &ExprEmitter<'_>, num_params: u32) -> Vec<(u32, ValType)> {
let mut locals = Vec::new();
for idx in num_params..emitter.next_local {
let vt = emitter
.local_types
.get(&idx)
.map(|wt| wt.to_val_type())
.unwrap_or(ValType::I64);
locals.push((1, vt));
}
locals
}
#[allow(clippy::too_many_arguments)]
fn emit_plain_user_function(
entry: &UserFnEntry<'_>,
fn_indices: &HashMap<String, u32>,
rt: &RuntimeFuncIndices,
string_literals: &HashMap<String, StringLiteral>,
type_fields: &HashMap<(String, String), u32>,
ctx: &CodegenContext,
variant_registry: &HashMap<(String, String), super::expr::VariantInfo>,
host_imports: &HashMap<String, u32>,
needs_tco: bool,
is_no_alloc: bool,
) -> Result<(wasm_encoder::Function, Vec<(String, u32)>), String> {
let mut emitter = ExprEmitter::new(
fn_indices,
rt,
string_literals,
type_fields,
&ctx.fn_sigs,
ctx,
variant_registry,
);
let param_types = param_types_for_entry(ctx, entry);
emitter.add_params(&entry.fd.params, ¶m_types);
emitter.fn_return_type = ret_wasm_type_for_entry(ctx, entry);
emitter.fn_return_is_heap = lookup_sig_for_entry(ctx, entry)
.map(|(_, ret_type, _)| emitter.is_heap_type(ret_type))
.unwrap_or(false);
emitter.current_fn_name = entry.canonical_name.clone();
emitter.current_module_prefix = entry.module_prefix.clone();
emitter.host_import_indices = host_imports.clone();
let thin_plan = if entry.fd.effects.is_empty() && !needs_tco {
classify_thin_fn_def(entry.fd, &emitter.ir_ctx())
} else {
None
};
emitter.is_thin = !emitter.fn_return_is_heap
&& entry.fd.effects.is_empty()
&& !needs_tco
&& thin_plan.as_ref().is_some_and(|plan| {
matches!(plan.kind, ThinKind::Leaf | ThinKind::Dispatch)
|| matches!(plan.kind, ThinKind::Direct | ThinKind::Forward)
});
emitter.is_parent_thin = !emitter.fn_return_is_heap
&& entry.canonical_name != "main"
&& entry.fd.effects.is_empty()
&& !needs_tco
&& !emitter.is_thin
&& thin_plan
.as_ref()
.is_some_and(thin_body_plan_is_parent_thin_candidate);
emitter.is_no_alloc = is_no_alloc;
if !(emitter.is_thin || emitter.is_parent_thin || emitter.is_no_alloc) {
let boundary_mark_local = emitter.alloc_local(WasmType::I32);
emitter.boundary_mark_local = Some(boundary_mark_local);
emitter.instructions.push(Instruction::GlobalGet(0));
emitter
.instructions
.push(Instruction::LocalSet(boundary_mark_local));
}
if needs_tco {
if emitter.boundary_mark_local.is_some() {
let iter_mark = emitter.alloc_local(WasmType::I32);
emitter.iter_mark_local = Some(iter_mark);
}
emitter
.instructions
.push(Instruction::Loop(wasm_encoder::BlockType::Result(
emitter.fn_return_type.to_val_type(),
)));
emitter.block_depth += 1;
emitter.enable_tco_loop();
if let Some(iter_mark) = emitter.iter_mark_local {
emitter.instructions.push(Instruction::GlobalGet(0));
emitter.instructions.push(Instruction::LocalSet(iter_mark));
}
}
emitter.emit_body(&entry.fd.body);
if !emitter.errors.is_empty() {
return Err(emitter.errors.join("\n"));
}
if needs_tco {
emitter.emit_end();
}
if !emitter.is_no_alloc {
emitter.emit_boundary_truncate_or_compact_for_stack_value(
emitter.fn_return_type,
emitter.fn_return_is_heap,
);
}
let locals = build_emitter_locals(&emitter, entry.fd.params.len() as u32);
let mut func = wasm_encoder::Function::new(locals);
for instr in &emitter.instructions {
func.instruction(instr);
}
func.instruction(&Instruction::End);
let local_names: Vec<(String, u32)> = emitter.locals.into_iter().collect();
Ok((func, local_names))
}
fn build_mutual_tco_groups(
ctx: &CodegenContext,
user_fns: &[UserFnEntry<'_>],
) -> Vec<MutualTcoGroup> {
let mut groups = Vec::new();
let mut next_group_id = 1usize;
let entry_index_by_name: HashMap<String, usize> = user_fns
.iter()
.enumerate()
.filter(|(_, entry)| entry.module_prefix.is_none() && entry.fd.name != "main")
.map(|(idx, entry)| (entry.fd.name.clone(), idx))
.collect();
let entry_fns: Vec<&FnDef> = ctx.fn_defs.iter().filter(|fd| fd.name != "main").collect();
for group in crate::call_graph::tailcall_scc_components(&entry_fns) {
let member_indices: Vec<usize> = group
.iter()
.filter_map(|fd| entry_index_by_name.get(&fd.name).copied())
.collect();
if member_indices.len() > 1 {
groups.push(MutualTcoGroup {
trampoline_name: format!("__mutual_tco_trampoline_{}", next_group_id),
member_indices,
});
next_group_id += 1;
}
}
for module in &ctx.modules {
let module_index_by_name: HashMap<String, usize> = user_fns
.iter()
.enumerate()
.filter(|(_, entry)| entry.module_prefix.as_deref() == Some(module.prefix.as_str()))
.map(|(idx, entry)| (entry.fd.name.clone(), idx))
.collect();
let module_fns: Vec<&FnDef> = module.fn_defs.iter().collect();
for group in crate::call_graph::tailcall_scc_components(&module_fns) {
let member_indices: Vec<usize> = group
.iter()
.filter_map(|fd| module_index_by_name.get(&fd.name).copied())
.collect();
if member_indices.len() > 1 {
groups.push(MutualTcoGroup {
trampoline_name: format!("__mutual_tco_trampoline_{}", next_group_id),
member_indices,
});
next_group_id += 1;
}
}
}
groups
}
fn build_mutual_tco_layout(
group: &MutualTcoGroup,
user_fns: &[UserFnEntry<'_>],
ctx: &CodegenContext,
trampoline_type_idx: u32,
) -> Result<MutualTcoLayout, String> {
let first_entry = &user_fns[group.member_indices[0]];
let return_type = ret_wasm_type_for_entry(ctx, first_entry);
let first_param_types = param_types_for_entry(ctx, first_entry);
let first_wasm_types: Vec<WasmType> = first_param_types.iter().map(aver_type_to_wasm).collect();
let uniform_signature = group.member_indices.iter().all(|&idx| {
let entry = &user_fns[idx];
let pt = param_types_for_entry(ctx, entry);
pt.len() == first_param_types.len()
&& pt
.iter()
.map(aver_type_to_wasm)
.eq(first_wasm_types.iter().copied())
});
let mut slots = Vec::new();
let mut member_ids = HashMap::new();
let mut member_param_locals = HashMap::new();
let mut next_local = 1u32;
if uniform_signature {
let shared_locals: Vec<u32> = (0..first_param_types.len())
.map(|i| {
let local_index = next_local + i as u32;
slots.push(MutualTcoSlot {
owner_index: group.member_indices[0],
owner_param_index: i,
wasm_type: first_wasm_types[i],
aver_type: first_param_types[i].clone(),
});
local_index
})
.collect();
let _ = next_local;
for (member_id, member_index) in group.member_indices.iter().copied().enumerate() {
let entry = &user_fns[member_index];
let member_return_type = ret_wasm_type_for_entry(ctx, entry);
if member_return_type != return_type {
return Err(format!(
"mutual TCO group `{}` has mismatched return types (`{}` vs `{}`)",
group.trampoline_name, first_entry.canonical_name, entry.canonical_name
));
}
member_ids.insert(member_index, member_id as u32);
member_param_locals.insert(member_index, shared_locals.clone());
}
} else {
for (member_id, member_index) in group.member_indices.iter().copied().enumerate() {
let entry = &user_fns[member_index];
let member_return_type = ret_wasm_type_for_entry(ctx, entry);
if member_return_type != return_type {
return Err(format!(
"mutual TCO group `{}` has mismatched return types (`{}` vs `{}`)",
group.trampoline_name, first_entry.canonical_name, entry.canonical_name
));
}
let param_types = param_types_for_entry(ctx, entry);
let mut param_locals = Vec::new();
for (param_index, aver_type) in param_types.into_iter().enumerate() {
slots.push(MutualTcoSlot {
owner_index: member_index,
owner_param_index: param_index,
wasm_type: aver_type_to_wasm(&aver_type),
aver_type,
});
param_locals.push(next_local);
next_local += 1;
}
member_ids.insert(member_index, member_id as u32);
member_param_locals.insert(member_index, param_locals);
}
}
Ok(MutualTcoLayout {
trampoline_name: group.trampoline_name.clone(),
trampoline_type_idx,
trampoline_fn_idx: 0,
return_type,
slots,
member_ids,
member_param_locals,
uniform_signature,
})
}
fn emit_mutual_tco_wrapper(
entry_index: usize,
layout: &MutualTcoLayout,
_user_fns: &[UserFnEntry<'_>],
) -> wasm_encoder::Function {
let mut func = wasm_encoder::Function::new(Vec::new());
func.instruction(&Instruction::I32Const(
*layout.member_ids.get(&entry_index).unwrap_or(&0) as i32,
));
if layout.uniform_signature {
for i in 0..layout.slots.len() {
func.instruction(&Instruction::LocalGet(i as u32));
}
} else {
for slot in &layout.slots {
if slot.owner_index == entry_index {
func.instruction(&Instruction::LocalGet(slot.owner_param_index as u32));
} else {
emit_default_value_to_func(&mut func, slot.wasm_type);
}
}
}
func.instruction(&Instruction::Call(layout.trampoline_fn_idx));
func.instruction(&Instruction::End);
func
}
fn emit_mutual_dispatch_chain(
emitter: &mut ExprEmitter<'_>,
group: &MutualTcoGroup,
layout: &MutualTcoLayout,
user_fns: &[UserFnEntry<'_>],
member_pos: usize,
) {
let member_index = group.member_indices[member_pos];
let entry = &user_fns[member_index];
let member_id = layout.member_ids[&member_index] as i32;
emitter.instructions.push(Instruction::LocalGet(0));
emitter.instructions.push(Instruction::I32Const(member_id));
emitter.instructions.push(Instruction::I32Eq);
emitter.emit_if(wasm_encoder::BlockType::Result(
layout.return_type.to_val_type(),
));
let saved_locals = std::mem::take(&mut emitter.locals);
let saved_fn_name = emitter.current_fn_name.clone();
let saved_module_prefix = emitter.current_module_prefix.clone();
for ((name, _), slot) in entry
.fd
.params
.iter()
.zip(layout.member_param_locals[&member_index].iter())
{
emitter.locals.insert(name.clone(), *slot);
}
emitter.current_fn_name = entry.canonical_name.clone();
emitter.current_module_prefix = entry.module_prefix.clone();
emitter.emit_body(&entry.fd.body);
emitter.locals = saved_locals;
emitter.current_fn_name = saved_fn_name;
emitter.current_module_prefix = saved_module_prefix;
if member_pos + 1 < group.member_indices.len() {
emitter.emit_else();
emit_mutual_dispatch_chain(emitter, group, layout, user_fns, member_pos + 1);
} else {
emitter.emit_else();
emitter.instructions.push(Instruction::Unreachable);
}
emitter.emit_end();
}
#[allow(clippy::too_many_arguments)]
fn emit_mutual_tco_trampoline(
group: &MutualTcoGroup,
layout: &MutualTcoLayout,
user_fns: &[UserFnEntry<'_>],
fn_indices: &HashMap<String, u32>,
rt: &RuntimeFuncIndices,
string_literals: &HashMap<String, StringLiteral>,
type_fields: &HashMap<(String, String), u32>,
ctx: &CodegenContext,
variant_registry: &HashMap<(String, String), super::expr::VariantInfo>,
host_imports: &HashMap<String, u32>,
is_no_alloc: bool,
) -> Result<wasm_encoder::Function, String> {
let mut emitter = ExprEmitter::new(
fn_indices,
rt,
string_literals,
type_fields,
&ctx.fn_sigs,
ctx,
variant_registry,
);
emitter.fn_return_type = layout.return_type;
emitter.current_fn_name = layout.trampoline_name.clone();
emitter.host_import_indices = host_imports.clone();
emitter.mutual_tco_dispatch_local = Some(0);
emitter.fn_return_is_heap = group
.member_indices
.iter()
.filter_map(|member_index| lookup_sig_for_entry(ctx, &user_fns[*member_index]))
.next()
.map(|(_, ret_type, _)| emitter.is_heap_type(ret_type))
.unwrap_or(false);
emitter.mutual_tco_targets = group
.member_indices
.iter()
.map(|member_index| {
(
user_fns[*member_index].canonical_name.clone(),
(
layout.member_ids[member_index],
layout.member_param_locals[member_index].clone(),
),
)
})
.collect();
emitter.local_types.insert(0, WasmType::I32);
for (slot_index, slot) in layout.slots.iter().enumerate() {
let local_index = 1 + slot_index as u32;
emitter.local_types.insert(local_index, slot.wasm_type);
emitter
.local_aver_types
.insert(local_index, slot.aver_type.clone());
}
emitter.next_local = 1 + layout.slots.len() as u32;
emitter.is_no_alloc = is_no_alloc;
emitter.mutual_tco_uniform = layout.uniform_signature;
if !is_no_alloc {
let boundary_mark_local = emitter.alloc_local(WasmType::I32);
emitter.boundary_mark_local = Some(boundary_mark_local);
emitter.instructions.push(Instruction::GlobalGet(0));
emitter
.instructions
.push(Instruction::LocalSet(boundary_mark_local));
let iter_mark = emitter.alloc_local(WasmType::I32);
emitter.iter_mark_local = Some(iter_mark);
let gc_watermark = emitter.alloc_local(WasmType::I32);
emitter.gc_watermark_local = Some(gc_watermark);
emitter.instructions.push(Instruction::GlobalGet(0));
emitter
.instructions
.push(Instruction::LocalSet(gc_watermark));
}
emitter
.instructions
.push(Instruction::Loop(wasm_encoder::BlockType::Result(
layout.return_type.to_val_type(),
)));
emitter.block_depth += 1;
emitter.enable_tco_loop();
if let Some(iter_mark) = emitter.iter_mark_local {
emitter.instructions.push(Instruction::GlobalGet(0));
emitter.instructions.push(Instruction::LocalSet(iter_mark));
}
emit_mutual_dispatch_chain(&mut emitter, group, layout, user_fns, 0);
if !emitter.errors.is_empty() {
return Err(emitter.errors.join("\n"));
}
emitter.emit_end();
if !emitter.is_no_alloc {
emitter.emit_boundary_truncate_or_compact_for_stack_value(
emitter.fn_return_type,
emitter.fn_return_is_heap,
);
}
let locals = build_emitter_locals(&emitter, 1 + layout.slots.len() as u32);
let mut func = wasm_encoder::Function::new(locals);
for instr in &emitter.instructions {
func.instruction(instr);
}
func.instruction(&Instruction::End);
Ok(func)
}
fn collect_strings_from_body(body: &FnBody, strings: &mut HashSet<String>) {
match body {
FnBody::Block(stmts) => {
for stmt in stmts {
match stmt {
Stmt::Binding(_, _, expr) => collect_strings_from_expr(&expr.node, strings),
Stmt::Expr(expr) => collect_strings_from_expr(&expr.node, strings),
}
}
}
}
}
fn collect_strings_from_expr(expr: &Expr, strings: &mut HashSet<String>) {
match expr {
Expr::Literal(Literal::Str(s)) => {
strings.insert(s.clone());
}
Expr::InterpolatedStr(parts) => {
for part in parts {
if let StrPart::Literal(s) = part {
strings.insert(s.clone());
} else if let StrPart::Parsed(e) = part {
collect_strings_from_expr(&e.node, strings);
}
}
}
Expr::BinOp(_, lhs, rhs) => {
collect_strings_from_expr(&lhs.node, strings);
collect_strings_from_expr(&rhs.node, strings);
}
Expr::FnCall(callee, args) => {
collect_strings_from_expr(&callee.node, strings);
for arg in args {
collect_strings_from_expr(&arg.node, strings);
}
}
Expr::Match { subject, arms } => {
collect_strings_from_expr(&subject.node, strings);
for arm in arms {
collect_strings_from_pattern(&arm.pattern, strings);
collect_strings_from_expr(&arm.body.node, strings);
}
}
Expr::Constructor(_, Some(e)) => {
collect_strings_from_expr(&e.node, strings);
}
Expr::ErrorProp(e) => collect_strings_from_expr(&e.node, strings),
Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
for item in items {
collect_strings_from_expr(&item.node, strings);
}
}
Expr::MapLiteral(entries) => {
for (key, value) in entries {
collect_strings_from_expr(&key.node, strings);
collect_strings_from_expr(&value.node, strings);
}
}
Expr::RecordCreate { fields, .. } => {
for (_, expr) in fields {
collect_strings_from_expr(&expr.node, strings);
}
}
Expr::RecordUpdate { base, updates, .. } => {
collect_strings_from_expr(&base.node, strings);
for (_, expr) in updates {
collect_strings_from_expr(&expr.node, strings);
}
}
Expr::Attr(base, _) => collect_strings_from_expr(&base.node, strings),
Expr::TailCall(tc) => {
for arg in &tc.args {
collect_strings_from_expr(&arg.node, strings);
}
}
_ => {}
}
}
fn collect_strings_from_pattern(pattern: &Pattern, strings: &mut HashSet<String>) {
match pattern {
Pattern::Literal(Literal::Str(s)) => {
strings.insert(s.clone());
}
Pattern::Tuple(items) => {
for item in items {
collect_strings_from_pattern(item, strings);
}
}
_ => {}
}
}
fn collect_host_calls_from_body(body: &FnBody, imports: &mut HashSet<String>) {
match body {
FnBody::Block(stmts) => {
for stmt in stmts {
match stmt {
Stmt::Binding(_, _, expr) => {
collect_host_calls_from_expr(&expr.node, imports);
}
Stmt::Expr(expr) => collect_host_calls_from_expr(&expr.node, imports),
}
}
}
}
}
fn collect_host_calls_from_expr(expr: &Expr, imports: &mut HashSet<String>) {
match expr {
Expr::FnCall(callee, args) => {
if let Expr::Attr(base, method) = &callee.node
&& let Expr::Ident(ns) | Expr::Resolved { name: ns, .. } = &base.node
{
let qualified = format!("{}.{}", ns, method);
if is_host_builtin(&qualified) {
imports.insert(qualified);
}
}
collect_host_calls_from_expr(&callee.node, imports);
for arg in args {
collect_host_calls_from_expr(&arg.node, imports);
}
}
Expr::BinOp(_, lhs, rhs) => {
collect_host_calls_from_expr(&lhs.node, imports);
collect_host_calls_from_expr(&rhs.node, imports);
}
Expr::Match { subject, arms } => {
collect_host_calls_from_expr(&subject.node, imports);
for arm in arms {
collect_host_calls_from_expr(&arm.body.node, imports);
}
}
Expr::Constructor(_, Some(e)) => {
collect_host_calls_from_expr(&e.node, imports);
}
Expr::ErrorProp(e) => collect_host_calls_from_expr(&e.node, imports),
Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
for item in items {
collect_host_calls_from_expr(&item.node, imports);
}
}
Expr::RecordCreate { fields, .. } => {
for (_, expr) in fields {
collect_host_calls_from_expr(&expr.node, imports);
}
}
Expr::RecordUpdate { base, updates, .. } => {
collect_host_calls_from_expr(&base.node, imports);
for (_, expr) in updates {
collect_host_calls_from_expr(&expr.node, imports);
}
}
Expr::Attr(base, _) => collect_host_calls_from_expr(&base.node, imports),
Expr::TailCall(tc) => {
for arg in &tc.args {
collect_host_calls_from_expr(&arg.node, imports);
}
}
_ => {}
}
}
fn is_host_builtin(name: &str) -> bool {
matches!(
name,
"Args.get"
| "Console.print"
| "Console.error"
| "Console.warn"
| "Console.readLine"
| "Terminal.enableRawMode"
| "Terminal.disableRawMode"
| "Terminal.clear"
| "Terminal.moveTo"
| "Terminal.print"
| "Terminal.setColor"
| "Terminal.resetColor"
| "Terminal.readKey"
| "Terminal.size"
| "Terminal.hideCursor"
| "Terminal.showCursor"
| "Terminal.flush"
| "Float.sin"
| "Float.cos"
| "Float.atan2"
| "Float.pow"
| "Random.int"
| "Time.now"
| "Time.unixMs"
| "Time.sleep"
)
}
fn body_has_self_tailcall(body: &FnBody, local_name: &str, canonical_name: &str) -> bool {
match body {
FnBody::Block(stmts) => stmts.iter().any(|s| match s {
Stmt::Expr(e) => expr_has_self_tailcall(&e.node, local_name, canonical_name),
Stmt::Binding(_, _, e) => expr_has_self_tailcall(&e.node, local_name, canonical_name),
}),
}
}
fn expr_has_self_tailcall(expr: &Expr, local_name: &str, canonical_name: &str) -> bool {
match expr {
Expr::TailCall(boxed) => boxed.target == local_name || boxed.target == canonical_name,
Expr::Match { arms, .. } => arms
.iter()
.any(|arm| expr_has_self_tailcall(&arm.body.node, local_name, canonical_name)),
Expr::BinOp(_, l, r) => {
expr_has_self_tailcall(&l.node, local_name, canonical_name)
|| expr_has_self_tailcall(&r.node, local_name, canonical_name)
}
Expr::FnCall(c, args) => {
expr_has_self_tailcall(&c.node, local_name, canonical_name)
|| args
.iter()
.any(|a| expr_has_self_tailcall(&a.node, local_name, canonical_name))
}
_ => false,
}
}
fn build_type_fields(ctx: &CodegenContext) -> HashMap<(String, String), u32> {
let mut map = HashMap::new();
for td in &ctx.type_defs {
if let crate::ast::TypeDef::Product { name, fields, .. } = td {
for (i, (field_name, _field_type)) in fields.iter().enumerate() {
map.insert((name.clone(), field_name.clone()), i as u32);
}
}
}
for module in &ctx.modules {
for td in &module.type_defs {
if let crate::ast::TypeDef::Product { name, fields, .. } = td {
let qualified = format!("{}.{}", module.prefix, name);
for (i, (field_name, _field_type)) in fields.iter().enumerate() {
map.insert((qualified.clone(), field_name.clone()), i as u32);
map.insert((name.clone(), field_name.clone()), i as u32);
}
}
}
}
for record in crate::codegen::builtin_records::BUILTIN_RECORDS {
for (i, field) in record.fields.iter().enumerate() {
map.insert(
(record.aver_name.to_string(), field.name.to_string()),
i as u32,
);
}
}
map
}