use std::collections::HashMap;
use wasm_encoder::{
CodeSection, DataCountSection, DataSection, EntityType, ExportKind, ExportSection, Function,
FunctionSection, ImportSection, Instruction, Module, TypeSection, ValType,
};
use super::WasmGcError;
use super::body::eq_helpers::{EqHelperRegistry, EqKind};
#[allow(dead_code)]
struct Wasip2Globals {
bump_alloc_ptr: u32,
stdout_handle: Option<u32>,
stderr_handle: Option<u32>,
stdin_handle: Option<u32>,
disk_preopen_handle: Option<u32>,
}
use super::body::hash_helpers::{HashHelperRegistry, HashKind};
use super::body::{FnEntry, FnMap, emit_fn_body};
use super::builtins::{BuiltinName, BuiltinRegistry};
use super::effects::{EffectName, EffectRegistry};
use super::maps::MapHelperRegistry;
use super::types::{TypeRegistry, param_types, record_struct_type, return_results};
use super::wasip2_helpers::{
CabiReallocIndices, ConsoleReadLineIndices, DecodeListStringIndices, DiskExistsIndices,
DiskListDirIndices, DiskReadTextIndices, DiskSimplePathOpIndices, DiskWriteTextIndices,
EnvGetLookupIndices, FormatIso8601Indices, TimeSleepIndices, emit_cabi_realloc,
emit_console_read_line, emit_decode_list_string, emit_disk_exists, emit_disk_list_dir,
emit_disk_read_text, emit_disk_simple_path_op, emit_disk_write_text, emit_env_get_lookup,
emit_format_iso8601, emit_time_sleep,
};
use super::wat_helper;
use crate::types::Type as AverType;
use crate::ast::{Expr, FnDef, Stmt, TopLevel, TypeDef};
pub(super) fn emit_module_with(
items: &[TopLevel],
handler_name: Option<&str>,
target: super::TargetMode,
) -> Result<Vec<u8>, WasmGcError> {
let registry = TypeRegistry::build_with_handler(items, handler_name.is_some());
let caller_fn_collector = std::cell::RefCell::new(super::body::CallerFnCollector::default());
let fn_defs: Vec<&FnDef> = items
.iter()
.filter_map(|it| match it {
TopLevel::FnDef(fd) => Some(fd),
_ => None,
})
.collect();
let mut builtin_registry = BuiltinRegistry::new();
let mut effect_registry = EffectRegistry::new();
let mut eq_helpers_registry = EqHelperRegistry::new();
let mut hash_helpers_registry = HashHelperRegistry::new();
for fd in &fn_defs {
discover_builtins_in_fn(
fd,
&mut builtin_registry,
&mut effect_registry,
&mut eq_helpers_registry,
®istry,
);
}
let mut nominal_seed: Vec<String> = Vec::new();
for canonical in ®istry.list_order {
if let Some(elem) = super::types::TypeRegistry::list_element_type(canonical) {
nominal_seed.push(elem.trim().to_string());
}
}
for canonical in ®istry.vector_order {
if let Some(elem) = super::types::TypeRegistry::vector_element_type(canonical) {
nominal_seed.push(elem.trim().to_string());
}
}
for canonical in ®istry.map_order {
if let Some((k, _v)) = super::types::parse_map_kv(canonical) {
nominal_seed.push(k.trim().to_string());
}
}
for name in &nominal_seed {
if registry.record_fields.contains_key(name) {
eq_helpers_registry.register_transitive(name, EqKind::Record, ®istry);
hash_helpers_registry.register_transitive(name, HashKind::Record, ®istry);
} else if registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| &v.parent == name)
{
eq_helpers_registry.register_transitive(name, EqKind::Sum, ®istry);
hash_helpers_registry.register_transitive(name, HashKind::Sum, ®istry);
} else if name.starts_with("Option<") && name.ends_with('>') {
eq_helpers_registry.register_transitive(name, EqKind::OptionEq, ®istry);
hash_helpers_registry.register_transitive(name, HashKind::OptionHash, ®istry);
} else if name.starts_with("Result<") && name.ends_with('>') {
eq_helpers_registry.register_transitive(name, EqKind::ResultEq, ®istry);
hash_helpers_registry.register_transitive(name, HashKind::ResultHash, ®istry);
} else if name.starts_with("Tuple<") && name.ends_with('>') {
eq_helpers_registry.register_transitive(name, EqKind::TupleEq, ®istry);
hash_helpers_registry.register_transitive(name, HashKind::TupleHash, ®istry);
}
}
let eq_snapshot: Vec<(String, EqKind)> = eq_helpers_registry
.iter()
.map(|(n, k)| (n.to_string(), k))
.collect();
for (name, kind) in &eq_snapshot {
let hk = match kind {
EqKind::Record => HashKind::Record,
EqKind::Sum => HashKind::Sum,
EqKind::OptionEq => HashKind::OptionHash,
EqKind::ResultEq => HashKind::ResultHash,
EqKind::TupleEq => HashKind::TupleHash,
};
hash_helpers_registry.register_transitive(name, hk, ®istry);
}
if eq_helpers_registry.needs_string_eq(®istry) {
builtin_registry.register(BuiltinName::StringEq);
}
if handler_name.is_some() {
for eff in [
EffectName::RequestMethod,
EffectName::RequestUrl,
EffectName::RequestQuery,
EffectName::RequestBody,
EffectName::RequestHeadersLoad,
EffectName::ResponseText,
EffectName::ResponseSetHeader,
] {
effect_registry.register(eff);
}
}
if registry.list_order.iter().any(|c| c == "List<String>") {
builtin_registry.register(BuiltinName::StringEq);
}
for canonical in ®istry.list_order {
if let Some(elem) = super::types::TypeRegistry::list_element_type(canonical)
&& let Some(fields) = registry.record_fields.get(elem.trim())
&& fields.iter().any(|(_, t)| t.trim() == "String")
{
builtin_registry.register(BuiltinName::StringEq);
break;
}
}
if fn_defs.is_empty() {
return Err(WasmGcError::Validation(
"module has no fn definitions".into(),
));
}
let main_idx: Option<usize> = fn_defs
.iter()
.position(|fd| fd.name == "__entry__")
.or_else(|| fn_defs.iter().position(|fd| fd.name == "main"));
let mut module = Module::new();
let mut types = TypeSection::new();
emit_user_types(&mut types, items, ®istry)?;
let mut next_type_idx = registry.user_type_count;
let mut wasip2_imports = super::wasip2_imports::Wasip2ImportRegistry::new();
match target {
super::TargetMode::AverBridge => {
effect_registry.assign_slots(&mut next_type_idx);
for name in effect_registry.iter() {
let p = name.params(®istry)?;
let r = name.results(®istry)?;
types.ty().function(p, r);
}
}
super::TargetMode::Wasip2 => {
use super::effects::EffectName;
use super::wasip2_imports::Wasip2ImportSlot;
for name in effect_registry.iter() {
if !name.lowers_on_wasip2() {
continue;
}
match name {
EffectName::ConsolePrint => {
wasip2_imports.register(Wasip2ImportSlot::CliGetStdout);
wasip2_imports
.register(Wasip2ImportSlot::OutputStreamBlockingWriteAndFlush);
}
EffectName::ConsoleError | EffectName::ConsoleWarn => {
wasip2_imports.register(Wasip2ImportSlot::CliGetStderr);
wasip2_imports
.register(Wasip2ImportSlot::OutputStreamBlockingWriteAndFlush);
}
EffectName::TimeUnixMs | EffectName::TimeNow => {
wasip2_imports.register(Wasip2ImportSlot::ClocksWallClockNow);
}
EffectName::RandomInt | EffectName::RandomFloat => {
wasip2_imports.register(Wasip2ImportSlot::RandomGetRandomU64);
}
EffectName::ArgsGet => {
wasip2_imports.register(Wasip2ImportSlot::CliEnvironmentGetArguments);
}
EffectName::EnvGet => {
wasip2_imports.register(Wasip2ImportSlot::CliEnvironmentGetEnvironment);
}
EffectName::ConsoleReadLine => {
wasip2_imports.register(Wasip2ImportSlot::CliStdinGetStdin);
wasip2_imports.register(Wasip2ImportSlot::InputStreamBlockingRead);
}
EffectName::TimeSleep => {
wasip2_imports.register(Wasip2ImportSlot::ClocksMonotonicSubscribeDuration);
wasip2_imports.register(Wasip2ImportSlot::IoPollPoll);
wasip2_imports.register(Wasip2ImportSlot::IoPollResourceDropPollable);
}
EffectName::DiskExists => {
wasip2_imports.register(Wasip2ImportSlot::FilesystemPreopensGetDirectories);
wasip2_imports.register(Wasip2ImportSlot::FilesystemTypesStatAt);
}
EffectName::DiskReadText => {
wasip2_imports.register(Wasip2ImportSlot::FilesystemPreopensGetDirectories);
wasip2_imports.register(Wasip2ImportSlot::FilesystemTypesOpenAt);
wasip2_imports.register(Wasip2ImportSlot::FilesystemTypesReadViaStream);
wasip2_imports.register(Wasip2ImportSlot::InputStreamBlockingRead);
wasip2_imports
.register(Wasip2ImportSlot::FilesystemTypesResourceDropDescriptor);
wasip2_imports.register(Wasip2ImportSlot::IoStreamsResourceDropInputStream);
}
EffectName::DiskWriteText => {
wasip2_imports.register(Wasip2ImportSlot::FilesystemPreopensGetDirectories);
wasip2_imports.register(Wasip2ImportSlot::FilesystemTypesOpenAt);
wasip2_imports.register(Wasip2ImportSlot::FilesystemTypesWriteViaStream);
wasip2_imports
.register(Wasip2ImportSlot::OutputStreamBlockingWriteAndFlush);
wasip2_imports
.register(Wasip2ImportSlot::FilesystemTypesResourceDropDescriptor);
wasip2_imports
.register(Wasip2ImportSlot::IoStreamsResourceDropOutputStream);
}
EffectName::DiskAppendText => {
wasip2_imports.register(Wasip2ImportSlot::FilesystemPreopensGetDirectories);
wasip2_imports.register(Wasip2ImportSlot::FilesystemTypesOpenAt);
wasip2_imports.register(Wasip2ImportSlot::FilesystemTypesAppendViaStream);
wasip2_imports
.register(Wasip2ImportSlot::OutputStreamBlockingWriteAndFlush);
wasip2_imports
.register(Wasip2ImportSlot::FilesystemTypesResourceDropDescriptor);
wasip2_imports
.register(Wasip2ImportSlot::IoStreamsResourceDropOutputStream);
}
EffectName::DiskDelete => {
wasip2_imports.register(Wasip2ImportSlot::FilesystemPreopensGetDirectories);
wasip2_imports.register(Wasip2ImportSlot::FilesystemTypesUnlinkFileAt);
}
EffectName::DiskDeleteDir => {
wasip2_imports.register(Wasip2ImportSlot::FilesystemPreopensGetDirectories);
wasip2_imports.register(Wasip2ImportSlot::FilesystemTypesRemoveDirectoryAt);
}
EffectName::DiskMakeDir => {
wasip2_imports.register(Wasip2ImportSlot::FilesystemPreopensGetDirectories);
wasip2_imports.register(Wasip2ImportSlot::FilesystemTypesCreateDirectoryAt);
}
EffectName::DiskListDir => {
wasip2_imports.register(Wasip2ImportSlot::FilesystemPreopensGetDirectories);
wasip2_imports.register(Wasip2ImportSlot::FilesystemTypesOpenAt);
wasip2_imports.register(Wasip2ImportSlot::FilesystemTypesReadDirectory);
wasip2_imports.register(
Wasip2ImportSlot::FilesystemTypesDirectoryEntryStreamReadDirectoryEntry,
);
wasip2_imports
.register(Wasip2ImportSlot::FilesystemTypesResourceDropDescriptor);
wasip2_imports.register(
Wasip2ImportSlot::FilesystemTypesResourceDropDirectoryEntryStream,
);
}
_ => {} }
}
wasip2_imports.assign_slots(&mut next_type_idx);
for slot in wasip2_imports.iter() {
let p = slot.params();
let r = slot.results();
types.ty().function(p, r);
}
}
}
let start_returns_i32 = matches!(target, super::TargetMode::Wasip2);
if start_returns_i32 {
types.ty().function([], [ValType::I32]);
} else {
types.ty().function([], []);
}
let start_type_idx = next_type_idx;
next_type_idx += 1;
let mut fn_type_indices: Vec<u32> = Vec::with_capacity(fn_defs.len());
for fd in &fn_defs {
let params = param_types(&fd.params, Some(®istry))?;
let results = return_results(&fd.return_type, Some(®istry))?;
types.ty().function(params, results);
fn_type_indices.push(next_type_idx);
next_type_idx += 1;
}
let import_count: u32 = match target {
super::TargetMode::AverBridge => effect_registry.import_count(),
super::TargetMode::Wasip2 => wasip2_imports.import_count(),
};
let mut next_builtin_fn_idx = import_count + 1 + (fn_defs.len() as u32);
builtin_registry.assign_slots(&mut next_builtin_fn_idx, &mut next_type_idx);
for name in builtin_registry.iter() {
let p = name.params(®istry)?;
let r = name.results(®istry)?;
types.ty().function(p, r);
}
let mut map_helpers = MapHelperRegistry::default();
map_helpers.assign_slots(
®istry.map_order,
®istry,
&mut next_builtin_fn_idx,
&mut next_type_idx,
)?;
map_helpers.emit_helper_types(&mut types, ®istry)?;
let needs_split_join = items_use_string_split_join(items);
let mut list_helpers = super::lists::ListHelperRegistry::default();
list_helpers.assign_slots(
®istry.list_order,
®istry.vector_order,
®istry.tuple_order,
needs_split_join,
®istry,
&mut next_builtin_fn_idx,
&mut next_type_idx,
)?;
list_helpers.emit_helper_types(&mut types, ®istry)?;
eq_helpers_registry.assign_slots(&mut next_builtin_fn_idx, &mut next_type_idx);
eq_helpers_registry.emit_helper_types(&mut types);
hash_helpers_registry.assign_slots(&mut next_builtin_fn_idx, &mut next_type_idx);
hash_helpers_registry.emit_helper_types(&mut types);
let handler_wrapper: Option<HandlerWrapper> = if let Some(name) = handler_name {
let user_idx = fn_defs
.iter()
.position(|fd| fd.name == name)
.ok_or_else(|| {
WasmGcError::Validation(format!(
"--handler `{name}` doesn't match any fn in this module"
))
})?;
types.ty().function([], []);
let wrapper_type = next_type_idx;
next_type_idx += 1;
let s_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"handler wrapper requires String slot".into(),
))?;
let list_idx = registry
.list_type_idx("List<String>")
.ok_or(WasmGcError::Validation(
"handler wrapper requires List<String> slot".into(),
))?;
let s_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(s_idx),
});
let l_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(list_idx),
});
types.ty().function([s_ref, l_ref], [l_ref]);
let list_cons_type = next_type_idx;
next_type_idx += 1;
let wrapper_fn = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
let list_cons_fn = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
Some(HandlerWrapper {
user_handler_idx: user_idx,
wrapper_type,
wrapper_fn,
list_cons_type,
list_cons_fn,
})
} else {
None
};
let bridge: Option<BridgeIndices> = if registry.string_array_type_idx.is_some() {
let idx = emit_bridge_types(&mut types, ®istry, &mut next_type_idx)?;
let mut next_fn = || {
let v = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
v
};
Some(BridgeIndices {
from_lm_type: idx.from_lm_type,
to_lm_type: idx.to_lm_type,
pages_type: idx.pages_type,
grow_type: idx.grow_type,
from_lm_fn: next_fn(),
to_lm_fn: next_fn(),
pages_fn: next_fn(),
grow_fn: next_fn(),
})
} else {
None
};
let cabi_realloc: Option<CabiReallocIndices> =
if matches!(target, super::TargetMode::Wasip2) && wasip2_imports.import_count() > 0 {
types.ty().function(
[ValType::I32, ValType::I32, ValType::I32, ValType::I32],
[ValType::I32],
);
let realloc_type = next_type_idx;
next_type_idx += 1;
let realloc_fn = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
Some(CabiReallocIndices {
fn_type: realloc_type,
fn_idx: realloc_fn,
})
} else {
None
};
let decode_list_string: Option<DecodeListStringIndices> = if cabi_realloc.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::CliEnvironmentGetArguments)
.is_some()
&& let (Some(string_idx), Some(list_string_idx)) = (
registry.string_array_type_idx,
registry.list_type_idx("List<String>"),
) {
let list_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(list_string_idx),
});
types.ty().function([ValType::I32], [list_ref]);
let decoder_type = next_type_idx;
next_type_idx += 1;
let decoder_fn = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
Some(DecodeListStringIndices {
fn_type: decoder_type,
fn_idx: decoder_fn,
string_type_idx: string_idx,
list_string_type_idx: list_string_idx,
})
} else {
None
};
let console_read_line: Option<ConsoleReadLineIndices> = if cabi_realloc.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::CliStdinGetStdin)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::InputStreamBlockingRead)
.is_some()
&& let Some(string_idx) = registry.string_array_type_idx
&& let Some(result_idx) = registry.result_type_idx("Result<String,String>")
{
let res_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(result_idx),
});
types.ty().function([], [res_ref]);
let fn_type = next_type_idx;
next_type_idx += 1;
let fn_idx = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
Some(ConsoleReadLineIndices {
fn_type,
fn_idx,
string_type_idx: string_idx,
result_string_string_type_idx: result_idx,
})
} else {
None
};
let time_sleep: Option<TimeSleepIndices> = if cabi_realloc.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::ClocksMonotonicSubscribeDuration,
)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::IoPollPoll)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::IoPollResourceDropPollable)
.is_some()
{
types.ty().function([ValType::I64], []);
let fn_type = next_type_idx;
next_type_idx += 1;
let fn_idx = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
Some(TimeSleepIndices { fn_type, fn_idx })
} else {
None
};
let disk_exists: Option<DiskExistsIndices> = if cabi_realloc.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesStatAt)
.is_some()
&& let Some(string_idx) = registry.string_array_type_idx
{
let s_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(string_idx),
});
types.ty().function([s_ref], [ValType::I32]);
let fn_type = next_type_idx;
next_type_idx += 1;
let fn_idx = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
Some(DiskExistsIndices { fn_type, fn_idx })
} else {
None
};
let disk_read_text: Option<DiskReadTextIndices> = if cabi_realloc.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesOpenAt)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesReadViaStream,
)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::InputStreamBlockingRead)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesResourceDropDescriptor,
)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::IoStreamsResourceDropInputStream,
)
.is_some()
&& let Some(string_idx) = registry.string_array_type_idx
&& let Some(result_idx) = registry.result_type_idx("Result<String,String>")
{
let r_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(result_idx),
});
let s_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(string_idx),
});
types.ty().function([s_ref], [r_ref]);
let fn_type = next_type_idx;
next_type_idx += 1;
let fn_idx = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
Some(DiskReadTextIndices {
fn_type,
fn_idx,
string_type_idx: string_idx,
result_string_string_type_idx: result_idx,
})
} else {
None
};
let disk_write_text: Option<DiskWriteTextIndices> = if cabi_realloc.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesOpenAt)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesWriteViaStream,
)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::OutputStreamBlockingWriteAndFlush,
)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesResourceDropDescriptor,
)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::IoStreamsResourceDropOutputStream,
)
.is_some()
&& let Some(string_idx) = registry.string_array_type_idx
&& let Some(result_idx) = registry.result_type_idx("Result<Unit,String>")
{
let r_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(result_idx),
});
let s_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(string_idx),
});
types.ty().function([s_ref, s_ref], [r_ref]);
let fn_type = next_type_idx;
next_type_idx += 1;
let fn_idx = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
Some(DiskWriteTextIndices {
fn_type,
fn_idx,
string_type_idx: string_idx,
result_unit_string_type_idx: result_idx,
})
} else {
None
};
let disk_append_text: Option<DiskWriteTextIndices> = if cabi_realloc.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesOpenAt)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesAppendViaStream,
)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::OutputStreamBlockingWriteAndFlush,
)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesResourceDropDescriptor,
)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::IoStreamsResourceDropOutputStream,
)
.is_some()
&& let Some(string_idx) = registry.string_array_type_idx
&& let Some(result_idx) = registry.result_type_idx("Result<Unit,String>")
{
let r_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(result_idx),
});
let s_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(string_idx),
});
types.ty().function([s_ref, s_ref], [r_ref]);
let fn_type = next_type_idx;
next_type_idx += 1;
let fn_idx = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
Some(DiskWriteTextIndices {
fn_type,
fn_idx,
string_type_idx: string_idx,
result_unit_string_type_idx: result_idx,
})
} else {
None
};
let alloc_path_op = |types: &mut wasm_encoder::TypeSection,
next_type_idx: &mut u32,
next_builtin_fn_idx: &mut u32,
op_slot: super::wasip2_imports::Wasip2ImportSlot|
-> Option<DiskSimplePathOpIndices> {
if cabi_realloc.is_none()
|| wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.is_none()
|| wasip2_imports.lookup_wasm_fn_idx(op_slot).is_none()
{
return None;
}
let string_idx = registry.string_array_type_idx?;
let result_idx = registry.result_type_idx("Result<Unit,String>")?;
let r_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(result_idx),
});
let s_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(string_idx),
});
types.ty().function([s_ref], [r_ref]);
let fn_type = *next_type_idx;
*next_type_idx += 1;
let fn_idx = *next_builtin_fn_idx;
*next_builtin_fn_idx += 1;
Some(DiskSimplePathOpIndices {
fn_type,
fn_idx,
string_type_idx: string_idx,
result_unit_string_type_idx: result_idx,
})
};
let disk_delete = alloc_path_op(
&mut types,
&mut next_type_idx,
&mut next_builtin_fn_idx,
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesUnlinkFileAt,
);
let disk_delete_dir = alloc_path_op(
&mut types,
&mut next_type_idx,
&mut next_builtin_fn_idx,
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesRemoveDirectoryAt,
);
let disk_make_dir = alloc_path_op(
&mut types,
&mut next_type_idx,
&mut next_builtin_fn_idx,
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesCreateDirectoryAt,
);
let disk_list_dir: Option<DiskListDirIndices> = if cabi_realloc.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesOpenAt)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesReadDirectory)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesDirectoryEntryStreamReadDirectoryEntry)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesResourceDropDescriptor)
.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesResourceDropDirectoryEntryStream)
.is_some()
&& let Some(string_idx) = registry.string_array_type_idx
&& let Some(list_string_idx) = registry.list_type_idx("List<String>")
&& let Some(result_idx) = registry.result_type_idx("Result<List<String>,String>")
{
let r_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(result_idx),
});
let s_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(string_idx),
});
types.ty().function([s_ref], [r_ref]);
let fn_type = next_type_idx;
next_type_idx += 1;
let fn_idx = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
Some(DiskListDirIndices {
fn_type,
fn_idx,
string_type_idx: string_idx,
list_string_type_idx: list_string_idx,
result_list_string_string_type_idx: result_idx,
})
} else {
None
};
let env_get_lookup: Option<EnvGetLookupIndices> = if cabi_realloc.is_some()
&& wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::CliEnvironmentGetEnvironment,
)
.is_some()
&& let Some(string_idx) = registry.string_array_type_idx
&& let Some(option_string_idx) = registry.option_type_idx("Option<String>")
{
let opt_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(option_string_idx),
});
types
.ty()
.function([ValType::I32, ValType::I32, ValType::I32], [opt_ref]);
let lookup_type = next_type_idx;
next_type_idx += 1;
let lookup_fn = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
Some(EnvGetLookupIndices {
fn_type: lookup_type,
fn_idx: lookup_fn,
string_type_idx: string_idx,
option_string_type_idx: option_string_idx,
})
} else {
None
};
let format_iso8601: Option<FormatIso8601Indices> =
if matches!(target, super::TargetMode::Wasip2)
&& wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::ClocksWallClockNow)
.is_some()
&& let Some(string_idx) = registry.string_array_type_idx
{
let s_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(string_idx),
});
types.ty().function([ValType::I64, ValType::I32], [s_ref]);
let fn_type = next_type_idx;
next_type_idx += 1;
let fn_idx = next_builtin_fn_idx;
next_builtin_fn_idx += 1;
Some(FormatIso8601Indices {
fn_type,
fn_idx,
string_type_idx: string_idx,
})
} else {
None
};
let factory_exports = allocate_factory_exports(
&mut types,
&mut next_type_idx,
&mut next_builtin_fn_idx,
®istry,
&effect_registry,
)?;
let caller_fn_table_types: Option<(u32, u32)> =
if let Some(string_type_idx) = registry.string_array_type_idx {
types.ty().function([], [ValType::I32]);
let count_type_idx = next_type_idx;
next_type_idx += 1;
let string_ref_ty = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(string_type_idx),
});
types.ty().function([ValType::I32], [string_ref_ty]);
let name_type_idx = next_type_idx;
Some((count_type_idx, name_type_idx))
} else {
None
};
module.section(&types);
match target {
super::TargetMode::AverBridge => {
if effect_registry.import_count() > 0 {
let mut imports = ImportSection::new();
for name in effect_registry.iter() {
let (module_, field) = name.import_pair();
let type_idx = effect_registry
.lookup_wasm_type_idx(name)
.expect("just-assigned effect type idx");
imports.import(module_, field, EntityType::Function(type_idx));
}
module.section(&imports);
}
}
super::TargetMode::Wasip2 => {
if wasip2_imports.import_count() > 0 {
let mut imports = ImportSection::new();
for slot in wasip2_imports.iter() {
let (module_, field) = slot.module_field_pair();
let type_idx = wasip2_imports
.lookup_wasm_type_idx(slot)
.expect("just-assigned wasip2 import type idx");
imports.import(module_, field, EntityType::Function(type_idx));
}
module.section(&imports);
}
}
}
let mut funcs = FunctionSection::new();
funcs.function(start_type_idx); for type_idx in &fn_type_indices {
funcs.function(*type_idx);
}
for name in builtin_registry.iter() {
let type_idx = builtin_registry
.lookup_wasm_type_idx(name)
.expect("just-assigned builtin type idx");
funcs.function(type_idx);
}
map_helpers.emit_function_section(&mut funcs);
list_helpers.emit_function_section(&mut funcs);
for (name, _kind) in eq_helpers_registry.iter() {
let t_idx = eq_helpers_registry
.lookup_type_idx(name)
.expect("registered eq helper has type idx after assign_slots");
funcs.function(t_idx);
}
for (name, _kind) in hash_helpers_registry.iter() {
let t_idx = hash_helpers_registry
.lookup_type_idx(name)
.expect("registered hash helper has type idx after assign_slots");
funcs.function(t_idx);
}
if let Some(hw) = &handler_wrapper {
funcs.function(hw.wrapper_type);
funcs.function(hw.list_cons_type);
}
if let Some(b) = &bridge {
funcs.function(b.from_lm_type);
funcs.function(b.to_lm_type);
funcs.function(b.pages_type);
funcs.function(b.grow_type);
}
if let Some(c) = &cabi_realloc {
funcs.function(c.fn_type);
}
if let Some(d) = &decode_list_string {
funcs.function(d.fn_type);
}
if let Some(c) = &console_read_line {
funcs.function(c.fn_type);
}
if let Some(t) = &time_sleep {
funcs.function(t.fn_type);
}
if let Some(d) = &disk_exists {
funcs.function(d.fn_type);
}
if let Some(d) = &disk_read_text {
funcs.function(d.fn_type);
}
if let Some(d) = &disk_write_text {
funcs.function(d.fn_type);
}
if let Some(d) = &disk_append_text {
funcs.function(d.fn_type);
}
if let Some(d) = &disk_delete {
funcs.function(d.fn_type);
}
if let Some(d) = &disk_delete_dir {
funcs.function(d.fn_type);
}
if let Some(d) = &disk_make_dir {
funcs.function(d.fn_type);
}
if let Some(d) = &disk_list_dir {
funcs.function(d.fn_type);
}
if let Some(e) = &env_get_lookup {
funcs.function(e.fn_type);
}
if let Some(fmt) = &format_iso8601 {
funcs.function(fmt.fn_type);
}
factory_exports.emit_function_entries(&mut funcs);
let caller_fn_table_fns: Option<(u32, u32)> = caller_fn_table_types.map(|(c_ty, n_ty)| {
let count_fn_idx = import_count + funcs.len();
funcs.function(c_ty);
let name_fn_idx = import_count + funcs.len();
funcs.function(n_ty);
(count_fn_idx, name_fn_idx)
});
module.section(&funcs);
let need_memory_for_wasip2 =
matches!(target, super::TargetMode::Wasip2) && wasip2_imports.import_count() > 0;
if bridge.is_some() || need_memory_for_wasip2 {
let mut memories = wasm_encoder::MemorySection::new();
memories.memory(wasm_encoder::MemoryType {
minimum: 1,
maximum: Some(2048),
memory64: false,
shared: false,
page_size_log2: None,
});
module.section(&memories);
}
let wasip2_globals: Option<Wasip2Globals> =
if matches!(target, super::TargetMode::Wasip2) && wasip2_imports.import_count() > 0 {
let mut globals = wasm_encoder::GlobalSection::new();
let mut next_global_idx: u32 = 0;
globals.global(
wasm_encoder::GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
},
&wasm_encoder::ConstExpr::i32_const(65536),
);
let bump_alloc_ptr = next_global_idx;
next_global_idx += 1;
let stdout_handle = if wasip2_imports
.lookup_wasm_type_idx(super::wasip2_imports::Wasip2ImportSlot::CliGetStdout)
.is_some()
{
globals.global(
wasm_encoder::GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
},
&wasm_encoder::ConstExpr::i32_const(-1),
);
let idx = next_global_idx;
next_global_idx += 1;
Some(idx)
} else {
None
};
let stderr_handle = if wasip2_imports
.lookup_wasm_type_idx(super::wasip2_imports::Wasip2ImportSlot::CliGetStderr)
.is_some()
{
globals.global(
wasm_encoder::GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
},
&wasm_encoder::ConstExpr::i32_const(-1),
);
let idx = next_global_idx;
next_global_idx += 1;
Some(idx)
} else {
None
};
let stdin_handle = if wasip2_imports
.lookup_wasm_type_idx(super::wasip2_imports::Wasip2ImportSlot::CliStdinGetStdin)
.is_some()
{
globals.global(
wasm_encoder::GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
},
&wasm_encoder::ConstExpr::i32_const(-1),
);
let idx = next_global_idx;
next_global_idx += 1;
Some(idx)
} else {
None
};
let disk_preopen_handle = if wasip2_imports
.lookup_wasm_type_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.is_some()
{
globals.global(
wasm_encoder::GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
},
&wasm_encoder::ConstExpr::i32_const(-1),
);
let idx = next_global_idx;
next_global_idx += 1;
Some(idx)
} else {
None
};
let _ = next_global_idx;
module.section(&globals);
Some(Wasip2Globals {
bump_alloc_ptr,
stdout_handle,
stderr_handle,
stdin_handle,
disk_preopen_handle,
})
} else {
None
};
let wasip2_lowering: Option<super::body::Wasip2Lowering> =
if matches!(target, super::TargetMode::Wasip2) && wasip2_imports.import_count() > 0 {
use super::wasip2_imports::Wasip2ImportSlot;
Some(super::body::Wasip2Lowering {
get_stdout_fn_idx: wasip2_imports
.lookup_wasm_fn_idx(Wasip2ImportSlot::CliGetStdout),
get_stderr_fn_idx: wasip2_imports
.lookup_wasm_fn_idx(Wasip2ImportSlot::CliGetStderr),
blocking_write_fn_idx: wasip2_imports
.lookup_wasm_fn_idx(Wasip2ImportSlot::OutputStreamBlockingWriteAndFlush),
stdout_handle_global: wasip2_globals.as_ref().and_then(|g| g.stdout_handle),
stderr_handle_global: wasip2_globals.as_ref().and_then(|g| g.stderr_handle),
str_to_lm_fn_idx: bridge.as_ref().map(|b| b.to_lm_fn),
clocks_now_fn_idx: wasip2_imports
.lookup_wasm_fn_idx(Wasip2ImportSlot::ClocksWallClockNow),
random_u64_fn_idx: wasip2_imports
.lookup_wasm_fn_idx(Wasip2ImportSlot::RandomGetRandomU64),
get_arguments_fn_idx: wasip2_imports
.lookup_wasm_fn_idx(Wasip2ImportSlot::CliEnvironmentGetArguments),
cabi_realloc_fn_idx: cabi_realloc.as_ref().map(|c| c.fn_idx),
decode_list_string_fn_idx: decode_list_string.as_ref().map(|d| d.fn_idx),
get_environment_fn_idx: wasip2_imports
.lookup_wasm_fn_idx(Wasip2ImportSlot::CliEnvironmentGetEnvironment),
env_get_lookup_fn_idx: env_get_lookup.as_ref().map(|e| e.fn_idx),
fmt_iso8601_fn_idx: format_iso8601.as_ref().map(|f| f.fn_idx),
console_read_line_fn_idx: console_read_line.as_ref().map(|c| c.fn_idx),
time_sleep_fn_idx: time_sleep.as_ref().map(|t| t.fn_idx),
disk_exists_fn_idx: disk_exists.as_ref().map(|d| d.fn_idx),
disk_read_text_fn_idx: disk_read_text.as_ref().map(|d| d.fn_idx),
disk_write_text_fn_idx: disk_write_text.as_ref().map(|d| d.fn_idx),
disk_append_text_fn_idx: disk_append_text.as_ref().map(|d| d.fn_idx),
disk_delete_fn_idx: disk_delete.as_ref().map(|d| d.fn_idx),
disk_delete_dir_fn_idx: disk_delete_dir.as_ref().map(|d| d.fn_idx),
disk_make_dir_fn_idx: disk_make_dir.as_ref().map(|d| d.fn_idx),
disk_list_dir_fn_idx: disk_list_dir.as_ref().map(|d| d.fn_idx),
})
} else {
None
};
let start_wasm_idx = import_count;
let mut by_name: HashMap<String, FnEntry> = HashMap::new();
for (i, fd) in fn_defs.iter().enumerate() {
by_name.insert(
fd.name.clone(),
FnEntry {
wasm_idx: import_count + 1 + (i as u32),
return_type: fd.return_type.clone(),
},
);
}
let mut builtin_idx_lookup: HashMap<String, u32> = HashMap::new();
for name in builtin_registry.iter() {
let idx = builtin_registry
.lookup_wasm_fn_idx(name)
.expect("registered builtin has wasm fn idx");
builtin_idx_lookup.insert(name.canonical().to_string(), idx);
}
let mut effect_idx_lookup: HashMap<String, u32> = HashMap::new();
if matches!(target, super::TargetMode::AverBridge) {
for name in effect_registry.iter() {
let idx = effect_registry
.lookup_wasm_fn_idx(name)
.expect("registered effect has wasm fn idx");
effect_idx_lookup.insert(name.canonical().to_string(), idx);
}
}
let mut map_helpers_lookup: HashMap<String, super::maps::MapKVHelpers> = HashMap::new();
for canonical in ®istry.map_order {
if let Some(h) = map_helpers.kv_helpers(canonical) {
map_helpers_lookup.insert(canonical.clone(), h);
}
}
let mut list_ops_lookup: HashMap<String, super::lists::ListOps> = HashMap::new();
for canonical in ®istry.list_order {
if let Some(o) = list_helpers.list_ops_for(canonical) {
list_ops_lookup.insert(canonical.clone(), o);
}
}
let mut vfl_ops_lookup: HashMap<String, super::lists::VectorFromListOps> = HashMap::new();
for canonical in ®istry.list_order {
if let Some(o) = list_helpers.vfl_ops_for(canonical) {
vfl_ops_lookup.insert(canonical.clone(), o);
}
}
let mut zip_ops_lookup: HashMap<String, u32> = HashMap::new();
for tup_canonical in ®istry.tuple_order {
if let Some(idx) = list_helpers.zip_op_for(tup_canonical) {
zip_ops_lookup.insert(tup_canonical.clone(), idx);
}
}
let string_split_ops = list_helpers.string_split_ops();
let mut eq_helpers_lookup: HashMap<String, u32> = HashMap::new();
for (name, _kind) in eq_helpers_registry.iter() {
if let Some(fn_idx) = eq_helpers_registry.lookup_fn_idx(name) {
eq_helpers_lookup.insert(name.to_string(), fn_idx);
}
}
for canonical in ®istry.map_order {
if let Some(h) = map_helpers.kv_helpers(canonical) {
eq_helpers_lookup.insert(canonical.clone(), h.eq);
}
}
let fn_map = FnMap {
by_name,
builtins: builtin_idx_lookup,
effects: effect_idx_lookup.clone(),
map_helpers: map_helpers_lookup,
list_ops: list_ops_lookup,
vfl_ops: vfl_ops_lookup,
zip_ops: zip_ops_lookup,
string_split_ops,
eq_helpers: eq_helpers_lookup,
};
let mut exports = ExportSection::new();
let start_export_name: &str = match target {
super::TargetMode::AverBridge => "_start",
super::TargetMode::Wasip2 => "wasi:cli/run@0.2.4#run",
};
exports.export(start_export_name, ExportKind::Func, start_wasm_idx);
for (i, fd) in fn_defs.iter().enumerate() {
let wasm_idx = import_count + 1 + (i as u32);
exports.export(&fd.name, ExportKind::Func, wasm_idx);
}
if let Some(b) = &bridge {
if matches!(target, super::TargetMode::AverBridge) {
exports.export("__rt_string_from_lm", ExportKind::Func, b.from_lm_fn);
exports.export("__rt_string_to_lm", ExportKind::Func, b.to_lm_fn);
exports.export("__rt_memory_pages", ExportKind::Func, b.pages_fn);
exports.export("__rt_memory_grow", ExportKind::Func, b.grow_fn);
}
exports.export("memory", ExportKind::Memory, 0);
} else if cabi_realloc.is_some() {
exports.export("memory", ExportKind::Memory, 0);
}
if let Some(c) = &cabi_realloc {
exports.export("cabi_realloc", ExportKind::Func, c.fn_idx);
}
factory_exports.emit_exports(&mut exports);
if let Some(hw) = &handler_wrapper {
exports.export("aver_http_handle", ExportKind::Func, hw.wrapper_fn);
exports.export("__rt_list_string_cons", ExportKind::Func, hw.list_cons_fn);
if let Some(map_h) = map_helpers.kv_helpers("Map<String,List<String>>") {
exports.export(
"__rt_map_string_list_string_empty",
ExportKind::Func,
map_h.empty,
);
exports.export(
"__rt_map_string_list_string_set",
ExportKind::Func,
map_h.set,
);
}
}
if let Some((count_fn_idx, name_fn_idx)) = caller_fn_table_fns {
exports.export("__caller_fn_count", ExportKind::Func, count_fn_idx);
exports.export("__caller_fn_name", ExportKind::Func, name_fn_idx);
}
module.section(&exports);
let wrapper_caller_fn_idx: Option<u32> = handler_wrapper.as_ref().map(|_| {
caller_fn_collector
.borrow_mut()
.register("aver_http_handle")
});
for (i, fd) in fn_defs.iter().enumerate() {
let self_wasm_idx = import_count + 1 + (i as u32);
let mut probe = Function::new([]);
let _ = emit_fn_body(
&mut probe,
fd,
&fn_map,
self_wasm_idx,
®istry,
&effect_idx_lookup,
&caller_fn_collector,
wasip2_lowering.as_ref(),
)?;
}
let caller_fn_segment_count = caller_fn_collector.borrow().names.len() as u32;
let total_segment_count = registry.string_literals.len() as u32 + caller_fn_segment_count;
if total_segment_count > 0 {
let count = DataCountSection {
count: total_segment_count,
};
module.section(&count);
}
let mut codes = CodeSection::new();
let mut start = Function::new([]);
if let Some(idx) = main_idx {
let main_idx_wasm = import_count + 1 + (idx as u32);
let main_returns_value = !fn_defs[idx].return_type.trim().eq("Unit");
start.instruction(&Instruction::Call(main_idx_wasm));
if main_returns_value {
start.instruction(&Instruction::Drop);
}
}
if start_returns_i32 {
start.instruction(&Instruction::I32Const(0));
}
start.instruction(&Instruction::End);
codes.function(&start);
for (i, fd) in fn_defs.iter().enumerate() {
let self_wasm_idx = import_count + 1 + (i as u32);
let mut probe = Function::new([]);
let extra_locals_dry = emit_fn_body(
&mut probe,
fd,
&fn_map,
self_wasm_idx,
®istry,
&effect_idx_lookup,
&caller_fn_collector,
wasip2_lowering.as_ref(),
)?;
let local_groups: Vec<(u32, ValType)> = extra_locals_dry.iter().map(|v| (1, *v)).collect();
let mut func = Function::new(local_groups);
let _ = emit_fn_body(
&mut func,
fd,
&fn_map,
self_wasm_idx,
®istry,
&effect_idx_lookup,
&caller_fn_collector,
wasip2_lowering.as_ref(),
)?;
codes.function(&func);
}
builtin_registry.emit_helper_bodies(&mut codes, ®istry)?;
let mut compound_eq_hash_lookup: HashMap<String, (u32, u32)> = HashMap::new();
for canonical in ®istry.list_order {
if let Some(o) = list_helpers.list_ops_for(canonical)
&& let (Some(eq_fn), Some(hash_fn)) = (o.eq, o.hash)
{
compound_eq_hash_lookup.insert(canonical.clone(), (eq_fn, hash_fn));
}
}
for canonical in ®istry.list_order {
if let Some(elem) = TypeRegistry::list_element_type(canonical)
&& let Some(o) = list_helpers.vfl_ops_for(canonical)
&& let (Some(eq_fn), Some(hash_fn)) = (o.eq, o.hash)
{
compound_eq_hash_lookup.insert(format!("Vector<{}>", elem.trim()), (eq_fn, hash_fn));
}
}
for canonical in ®istry.map_order {
if let Some(h) = map_helpers.kv_helpers(canonical) {
compound_eq_hash_lookup.insert(canonical.clone(), (h.eq, h.hash));
}
}
let mut carrier_eq_hash_lookup: HashMap<String, (u32, u32)> = HashMap::new();
for (name, kind) in eq_helpers_registry.iter() {
use super::body::eq_helpers::EqKind as EK;
if matches!(kind, EK::OptionEq | EK::ResultEq | EK::TupleEq)
&& let Some(eq_fn) = eq_helpers_registry.lookup_fn_idx(name)
&& let Some(hash_fn) = hash_helpers_registry.lookup_fn_idx(name)
{
carrier_eq_hash_lookup.insert(name.to_string(), (eq_fn, hash_fn));
}
}
map_helpers.emit_helper_bodies(
&mut codes,
®istry,
&compound_eq_hash_lookup,
&carrier_eq_hash_lookup,
)?;
let string_eq_fn_idx = builtin_registry.lookup_wasm_fn_idx(BuiltinName::StringEq);
let mut eq_helper_fn_idx_map: HashMap<String, u32> = eq_helpers_registry
.iter()
.filter_map(|(n, _k)| {
eq_helpers_registry
.lookup_fn_idx(n)
.map(|i| (n.to_string(), i))
})
.collect();
let mut hash_helper_fn_idx_map: HashMap<String, u32> = hash_helpers_registry
.iter()
.filter_map(|(n, _k)| {
hash_helpers_registry
.lookup_fn_idx(n)
.map(|i| (n.to_string(), i))
})
.collect();
for (canonical, (eq_fn, hash_fn)) in &compound_eq_hash_lookup {
eq_helper_fn_idx_map.insert(canonical.clone(), *eq_fn);
hash_helper_fn_idx_map.insert(canonical.clone(), *hash_fn);
}
list_helpers.emit_helper_bodies(
&mut codes,
®istry,
string_eq_fn_idx,
&eq_helper_fn_idx_map,
&hash_helper_fn_idx_map,
)?;
let compound_eq_lookup: HashMap<String, u32> = compound_eq_hash_lookup
.iter()
.map(|(n, (eq, _))| (n.clone(), *eq))
.collect();
let compound_hash_lookup: HashMap<String, u32> = compound_eq_hash_lookup
.iter()
.map(|(n, (_, h))| (n.clone(), *h))
.collect();
eq_helpers_registry.emit_helper_bodies(
&mut codes,
®istry,
string_eq_fn_idx,
&compound_eq_lookup,
)?;
hash_helpers_registry.emit_helper_bodies(
&mut codes,
®istry,
string_eq_fn_idx,
&compound_hash_lookup,
)?;
if let Some(hw) = &handler_wrapper {
let user_handler_wasm_idx = import_count + 1 + (hw.user_handler_idx as u32);
let wrapper_caller_fn_idx = wrapper_caller_fn_idx
.expect("handler_wrapper present implies wrapper_caller_fn_idx pre-registered");
codes.function(&emit_handler_wrapper(
®istry,
&fn_map,
user_handler_wasm_idx,
wrapper_caller_fn_idx,
)?);
codes.function(&emit_list_string_cons(®istry)?);
let _ = hw.list_cons_type; }
if bridge.is_some() {
emit_bridge_bodies(&mut codes, ®istry)?;
}
if cabi_realloc.is_some() {
let bump_global = wasip2_globals
.as_ref()
.expect("cabi_realloc emit requires Wasip2Globals (same gate)")
.bump_alloc_ptr;
codes.function(&emit_cabi_realloc(bump_global));
}
if let Some(d) = &decode_list_string {
codes.function(&emit_decode_list_string(
d.string_type_idx,
d.list_string_type_idx,
));
}
if let Some(c) = &console_read_line {
let stdin_global = wasip2_globals
.as_ref()
.and_then(|g| g.stdin_handle)
.expect("console_read_line emit requires stdin_handle global");
let cabi = cabi_realloc
.as_ref()
.expect("console_read_line emit requires cabi_realloc fn idx (gate matches)")
.fn_idx;
let get_stdin = wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::CliStdinGetStdin)
.expect("console_read_line emit requires CliStdinGetStdin fn idx (gate matches)");
let blocking_read = wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::InputStreamBlockingRead)
.expect("console_read_line emit requires InputStreamBlockingRead fn idx");
codes.function(&emit_console_read_line(
c.string_type_idx,
c.result_string_string_type_idx,
stdin_global,
cabi,
get_stdin,
blocking_read,
));
}
if time_sleep.is_some() {
let cabi = cabi_realloc
.as_ref()
.expect("time_sleep emit requires cabi_realloc fn idx (gate matches)")
.fn_idx;
let subscribe = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::ClocksMonotonicSubscribeDuration,
)
.expect("time_sleep emit requires subscribe-duration fn idx (gate matches)");
let poll = wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::IoPollPoll)
.expect("time_sleep emit requires poll fn idx (gate matches)");
let drop_pollable = wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::IoPollResourceDropPollable)
.expect("time_sleep emit requires drop-pollable fn idx (gate matches)");
codes.function(&emit_time_sleep(cabi, subscribe, poll, drop_pollable));
}
if disk_exists.is_some() {
let preopen_global = wasip2_globals
.as_ref()
.and_then(|g| g.disk_preopen_handle)
.expect("disk_exists emit requires disk_preopen_handle global (gate matches)");
let cabi = cabi_realloc
.as_ref()
.expect("disk_exists emit requires cabi_realloc fn idx (gate matches)")
.fn_idx;
let str_to_lm = bridge
.as_ref()
.expect("disk_exists emit requires bridge (string marshalling)")
.to_lm_fn;
let get_directories = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.expect("disk_exists emit requires get-directories fn idx (gate matches)");
let stat_at = wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesStatAt)
.expect("disk_exists emit requires stat-at fn idx (gate matches)");
codes.function(&emit_disk_exists(
preopen_global,
cabi,
str_to_lm,
get_directories,
stat_at,
));
}
if let Some(rt) = &disk_read_text {
let preopen_global = wasip2_globals
.as_ref()
.and_then(|g| g.disk_preopen_handle)
.expect("disk_read_text emit requires disk_preopen_handle global (gate matches)");
let cabi = cabi_realloc
.as_ref()
.expect("disk_read_text emit requires cabi_realloc fn idx (gate matches)")
.fn_idx;
let str_to_lm = bridge
.as_ref()
.expect("disk_read_text emit requires bridge (string marshalling)")
.to_lm_fn;
let get_directories = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.expect("disk_read_text emit requires get-directories fn idx");
let open_at = wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesOpenAt)
.expect("disk_read_text emit requires open-at fn idx");
let read_via_stream = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesReadViaStream,
)
.expect("disk_read_text emit requires read-via-stream fn idx");
let blocking_read = wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::InputStreamBlockingRead)
.expect("disk_read_text emit requires blocking-read fn idx");
let drop_descriptor = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesResourceDropDescriptor,
)
.expect("disk_read_text emit requires drop-descriptor fn idx");
let drop_input_stream = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::IoStreamsResourceDropInputStream,
)
.expect("disk_read_text emit requires drop-input-stream fn idx");
codes.function(&emit_disk_read_text(
rt.string_type_idx,
rt.result_string_string_type_idx,
preopen_global,
cabi,
str_to_lm,
get_directories,
open_at,
read_via_stream,
blocking_read,
drop_descriptor,
drop_input_stream,
));
}
if let Some(wt) = &disk_write_text {
let preopen_global = wasip2_globals
.as_ref()
.and_then(|g| g.disk_preopen_handle)
.expect("disk_write_text emit requires disk_preopen_handle global");
let cabi = cabi_realloc
.as_ref()
.expect("disk_write_text emit requires cabi_realloc fn idx")
.fn_idx;
let str_to_lm = bridge
.as_ref()
.expect("disk_write_text emit requires bridge")
.to_lm_fn;
let get_directories = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.expect("disk_write_text emit requires get-directories fn idx");
let open_at = wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesOpenAt)
.expect("disk_write_text emit requires open-at fn idx");
let write_via_stream = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesWriteViaStream,
)
.expect("disk_write_text emit requires write-via-stream fn idx");
let blocking_write = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::OutputStreamBlockingWriteAndFlush,
)
.expect("disk_write_text emit requires blocking-write-and-flush fn idx");
let drop_descriptor = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesResourceDropDescriptor,
)
.expect("disk_write_text emit requires drop-descriptor fn idx");
let drop_output_stream = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::IoStreamsResourceDropOutputStream,
)
.expect("disk_write_text emit requires drop-output-stream fn idx");
codes.function(&emit_disk_write_text(
wt.string_type_idx,
wt.result_unit_string_type_idx,
preopen_global,
cabi,
str_to_lm,
get_directories,
open_at,
write_via_stream,
blocking_write,
drop_descriptor,
drop_output_stream,
false, ));
}
if let Some(at) = &disk_append_text {
let preopen_global = wasip2_globals
.as_ref()
.and_then(|g| g.disk_preopen_handle)
.expect("disk_append_text emit requires disk_preopen_handle global");
let cabi = cabi_realloc
.as_ref()
.expect("disk_append_text emit requires cabi_realloc fn idx")
.fn_idx;
let str_to_lm = bridge
.as_ref()
.expect("disk_append_text emit requires bridge")
.to_lm_fn;
let get_directories = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.expect("disk_append_text emit requires get-directories fn idx");
let open_at = wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesOpenAt)
.expect("disk_append_text emit requires open-at fn idx");
let append_via_stream = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesAppendViaStream,
)
.expect("disk_append_text emit requires append-via-stream fn idx");
let blocking_write = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::OutputStreamBlockingWriteAndFlush,
)
.expect("disk_append_text emit requires blocking-write-and-flush fn idx");
let drop_descriptor = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesResourceDropDescriptor,
)
.expect("disk_append_text emit requires drop-descriptor fn idx");
let drop_output_stream = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::IoStreamsResourceDropOutputStream,
)
.expect("disk_append_text emit requires drop-output-stream fn idx");
codes.function(&emit_disk_write_text(
at.string_type_idx,
at.result_unit_string_type_idx,
preopen_global,
cabi,
str_to_lm,
get_directories,
open_at,
append_via_stream,
blocking_write,
drop_descriptor,
drop_output_stream,
true, ));
}
if let Some(d) = &disk_delete {
let preopen_global = wasip2_globals
.as_ref()
.and_then(|g| g.disk_preopen_handle)
.expect("disk_delete emit requires disk_preopen_handle global");
let cabi = cabi_realloc
.as_ref()
.expect("disk_delete emit requires cabi_realloc fn idx")
.fn_idx;
let str_to_lm = bridge
.as_ref()
.expect("disk_delete emit requires bridge")
.to_lm_fn;
let get_directories = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.expect("disk_delete emit requires get-directories fn idx");
let op_fn = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesUnlinkFileAt,
)
.expect("disk_delete emit requires unlink-file-at fn idx");
codes.function(&emit_disk_simple_path_op(
d.string_type_idx,
d.result_unit_string_type_idx,
preopen_global,
cabi,
str_to_lm,
get_directories,
op_fn,
b"delete failed",
));
}
if let Some(d) = &disk_delete_dir {
let preopen_global = wasip2_globals
.as_ref()
.and_then(|g| g.disk_preopen_handle)
.expect("disk_delete_dir emit requires disk_preopen_handle global");
let cabi = cabi_realloc
.as_ref()
.expect("disk_delete_dir emit requires cabi_realloc fn idx")
.fn_idx;
let str_to_lm = bridge
.as_ref()
.expect("disk_delete_dir emit requires bridge")
.to_lm_fn;
let get_directories = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.expect("disk_delete_dir emit requires get-directories fn idx");
let op_fn = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesRemoveDirectoryAt,
)
.expect("disk_delete_dir emit requires remove-directory-at fn idx");
codes.function(&emit_disk_simple_path_op(
d.string_type_idx,
d.result_unit_string_type_idx,
preopen_global,
cabi,
str_to_lm,
get_directories,
op_fn,
b"deleteDir failed",
));
}
if let Some(d) = &disk_make_dir {
let preopen_global = wasip2_globals
.as_ref()
.and_then(|g| g.disk_preopen_handle)
.expect("disk_make_dir emit requires disk_preopen_handle global");
let cabi = cabi_realloc
.as_ref()
.expect("disk_make_dir emit requires cabi_realloc fn idx")
.fn_idx;
let str_to_lm = bridge
.as_ref()
.expect("disk_make_dir emit requires bridge")
.to_lm_fn;
let get_directories = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.expect("disk_make_dir emit requires get-directories fn idx");
let op_fn = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesCreateDirectoryAt,
)
.expect("disk_make_dir emit requires create-directory-at fn idx");
codes.function(&emit_disk_simple_path_op(
d.string_type_idx,
d.result_unit_string_type_idx,
preopen_global,
cabi,
str_to_lm,
get_directories,
op_fn,
b"makeDir failed",
));
}
if let Some(ld) = &disk_list_dir {
let preopen_global = wasip2_globals
.as_ref()
.and_then(|g| g.disk_preopen_handle)
.expect("disk_list_dir emit requires disk_preopen_handle global");
let cabi = cabi_realloc
.as_ref()
.expect("disk_list_dir emit requires cabi_realloc fn idx")
.fn_idx;
let str_to_lm = bridge
.as_ref()
.expect("disk_list_dir emit requires bridge")
.to_lm_fn;
let get_directories = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemPreopensGetDirectories,
)
.expect("disk_list_dir emit requires get-directories fn idx");
let open_at = wasip2_imports
.lookup_wasm_fn_idx(super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesOpenAt)
.expect("disk_list_dir emit requires open-at fn idx");
let read_directory = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesReadDirectory,
)
.expect("disk_list_dir emit requires read-directory fn idx");
let read_dir_entry = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesDirectoryEntryStreamReadDirectoryEntry,
)
.expect("disk_list_dir emit requires read-directory-entry fn idx");
let drop_descriptor = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesResourceDropDescriptor,
)
.expect("disk_list_dir emit requires drop-descriptor fn idx");
let drop_dir_stream = wasip2_imports
.lookup_wasm_fn_idx(
super::wasip2_imports::Wasip2ImportSlot::FilesystemTypesResourceDropDirectoryEntryStream,
)
.expect("disk_list_dir emit requires drop-directory-entry-stream fn idx");
codes.function(&emit_disk_list_dir(
ld.string_type_idx,
ld.list_string_type_idx,
ld.result_list_string_string_type_idx,
preopen_global,
cabi,
str_to_lm,
get_directories,
open_at,
read_directory,
read_dir_entry,
drop_descriptor,
drop_dir_stream,
));
}
if let Some(e) = &env_get_lookup {
codes.function(&emit_env_get_lookup(
e.string_type_idx,
e.option_string_type_idx,
));
}
if let Some(fmt) = &format_iso8601 {
codes.function(&emit_format_iso8601(fmt.string_type_idx));
}
factory_exports.emit_bodies(&mut codes, ®istry)?;
if let Some((_count_fn_idx, _name_fn_idx)) = caller_fn_table_fns {
let names = caller_fn_collector.borrow();
let string_idx = registry
.string_array_type_idx
.expect("caller_fn name table requires the $string slot");
let segment_base = registry.string_literals.len() as u32;
let mut count_fn = Function::new([]);
count_fn.instruction(&Instruction::I32Const(names.names.len() as i32));
count_fn.instruction(&Instruction::End);
codes.function(&count_fn);
let string_ref_ty = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(string_idx),
});
let mut name_fn = Function::new([]);
let block_ty = wasm_encoder::BlockType::Result(string_ref_ty);
name_fn.instruction(&Instruction::Block(block_ty));
for (i, fn_name) in names.names.iter().enumerate() {
let bytes = fn_name.as_bytes();
name_fn.instruction(&Instruction::Block(wasm_encoder::BlockType::Empty));
name_fn.instruction(&Instruction::LocalGet(0));
name_fn.instruction(&Instruction::I32Const(i as i32));
name_fn.instruction(&Instruction::I32Ne);
name_fn.instruction(&Instruction::BrIf(0));
name_fn.instruction(&Instruction::I32Const(0));
name_fn.instruction(&Instruction::I32Const(bytes.len() as i32));
name_fn.instruction(&Instruction::ArrayNewData {
array_type_index: string_idx,
array_data_index: segment_base + i as u32,
});
name_fn.instruction(&Instruction::Br(1));
name_fn.instruction(&Instruction::End);
}
name_fn.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
string_idx,
)));
name_fn.instruction(&Instruction::End);
name_fn.instruction(&Instruction::End);
codes.function(&name_fn);
}
module.section(&codes);
if total_segment_count > 0 {
let mut data = DataSection::new();
for bytes in ®istry.string_literals {
data.passive(bytes.iter().copied());
}
let names = caller_fn_collector.borrow();
for fn_name in &names.names {
data.passive(fn_name.as_bytes().iter().copied());
}
module.section(&data);
}
let bytes = module.finish();
if let Err(e) = validate(&bytes) {
let _ = std::fs::write("/tmp/aver_wasm_gc_invalid.wasm", &bytes);
return Err(e);
}
Ok(bytes)
}
fn emit_user_types(
types: &mut TypeSection,
items: &[TopLevel],
registry: &TypeRegistry,
) -> Result<(), WasmGcError> {
use wasm_encoder::{ArrayType, CompositeInnerType, CompositeType, StructType, SubType};
let mut entries: Vec<(u32, SubType)> = Vec::new();
let mk_struct = |fields: Vec<wasm_encoder::FieldType>| SubType {
is_final: true,
supertype_idx: None,
composite_type: CompositeType {
inner: CompositeInnerType::Struct(StructType {
fields: fields.into_boxed_slice(),
}),
shared: false,
descriptor: None,
describes: None,
},
};
let mk_array = |elem: wasm_encoder::FieldType| SubType {
is_final: true,
supertype_idx: None,
composite_type: CompositeType {
inner: CompositeInnerType::Array(ArrayType(elem)),
shared: false,
descriptor: None,
describes: None,
},
};
for item in items {
match item {
TopLevel::TypeDef(TypeDef::Product { name, fields, .. }) => {
let st = record_struct_type(fields, registry)?;
let idx = registry
.record_type_idx(name)
.ok_or(WasmGcError::Validation(format!(
"record `{name}` not registered"
)))?;
entries.push((idx, mk_struct(st.fields.to_vec())));
}
TopLevel::TypeDef(TypeDef::Sum {
name: parent,
variants,
..
}) => {
for v in variants {
let mut fields = Vec::new();
for ty in &v.fields {
let val_ty = super::types::aver_to_wasm(ty, Some(registry))?.ok_or(
WasmGcError::Validation(format!(
"variant `{}` field of type {ty} has no wasm representation",
v.name
)),
)?;
fields.push(wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(val_ty),
mutable: false,
});
}
let info =
registry
.variant_in(parent, &v.name)
.ok_or(WasmGcError::Validation(format!(
"variant `{parent}.{}` not registered",
v.name
)))?;
entries.push((info.type_idx, mk_struct(fields)));
}
}
_ => {}
}
}
if let Some(idx) = registry.string_array_type_idx {
entries.push((
idx,
mk_array(wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::I8,
mutable: true,
}),
));
}
for canonical in ®istry.vector_order {
let element =
TypeRegistry::vector_element_type(canonical).ok_or(WasmGcError::Validation(
format!("registered vector `{canonical}` has no parsable element type"),
))?;
let elem_val =
super::types::aver_to_wasm(element, Some(registry))?.ok_or(WasmGcError::Validation(
format!("Vector element type `{element}` has no wasm representation"),
))?;
let idx = registry
.vector_type_idx(canonical)
.ok_or(WasmGcError::Validation(format!(
"vector `{canonical}` not registered"
)))?;
entries.push((
idx,
mk_array(wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(elem_val),
mutable: true,
}),
));
}
for canonical in ®istry.result_order {
let (t_aver, e_aver) =
TypeRegistry::result_te(canonical).ok_or(WasmGcError::Validation(format!(
"registered result `{canonical}` has no parsable T, E"
)))?;
let t_val = super::types::aver_to_wasm(t_aver, Some(registry))?.unwrap_or(ValType::I32);
let e_val = super::types::aver_to_wasm(e_aver, Some(registry))?.unwrap_or(ValType::I32);
let idx = registry
.result_type_idx(canonical)
.ok_or(WasmGcError::Validation(format!(
"result `{canonical}` not registered"
)))?;
entries.push((
idx,
mk_struct(vec![
wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(ValType::I32),
mutable: true,
},
wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(t_val),
mutable: true,
},
wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(e_val),
mutable: true,
},
]),
));
}
for canonical in ®istry.list_order {
let element = TypeRegistry::list_element_type(canonical).ok_or(WasmGcError::Validation(
format!("registered list `{canonical}` has no parsable element type"),
))?;
let elem_val =
super::types::aver_to_wasm(element, Some(registry))?.ok_or(WasmGcError::Validation(
format!("List element type `{element}` has no wasm representation"),
))?;
let own_idx = registry
.list_type_idx(canonical)
.expect("just-registered list slot");
let tail_ref = wasm_encoder::ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(own_idx),
});
entries.push((
own_idx,
mk_struct(vec![
wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(elem_val),
mutable: false,
},
wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(tail_ref),
mutable: false,
},
]),
));
}
for canonical in ®istry.option_order {
let element =
TypeRegistry::option_element_type(canonical).ok_or(WasmGcError::Validation(
format!("registered option `{canonical}` has no parsable element type"),
))?;
let elem_val =
super::types::aver_to_wasm(element, Some(registry))?.ok_or(WasmGcError::Validation(
format!("Option element type `{element}` has no wasm representation"),
))?;
let idx = registry
.option_type_idx(canonical)
.ok_or(WasmGcError::Validation(format!(
"option `{canonical}` not registered"
)))?;
entries.push((
idx,
mk_struct(vec![
wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(ValType::I32),
mutable: true,
},
wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(elem_val),
mutable: true,
},
]),
));
}
for canonical in ®istry.map_order {
let (k_aver, v_aver) = super::types::parse_map_kv(canonical).ok_or(
WasmGcError::Validation(format!("registered map `{canonical}` has no parsable K, V")),
)?;
let v_val =
super::types::aver_to_wasm(v_aver, Some(registry))?.ok_or(WasmGcError::Validation(
format!("Map value type `{v_aver}` has no wasm representation"),
))?;
let key_storage_val = if let Some(box_idx) = registry.primitive_key_box_idx(k_aver) {
ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::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"),
))?
};
let slots = registry
.map_slots(canonical)
.expect("just-registered map slots");
entries.push((
slots.keys_array,
mk_array(wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(key_storage_val),
mutable: true,
}),
));
entries.push((
slots.values_array,
mk_array(wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(v_val),
mutable: true,
}),
));
let keys_ref = wasm_encoder::ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(slots.keys_array),
});
let values_ref = wasm_encoder::ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(slots.values_array),
});
entries.push((
slots.map,
mk_struct(vec![
wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(ValType::I32),
mutable: true,
},
wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(ValType::I32),
mutable: true,
},
wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(keys_ref),
mutable: true,
},
wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(values_ref),
mutable: true,
},
]),
));
}
for k_aver in ®istry.primitive_key_box_order {
let k_val =
super::types::aver_to_wasm(k_aver, Some(registry))?.ok_or(WasmGcError::Validation(
format!("primitive key box: K=`{k_aver}` has no wasm representation"),
))?;
let idx = registry
.primitive_key_box_idx(k_aver)
.ok_or(WasmGcError::Validation(format!(
"primitive key box for `{k_aver}` not registered"
)))?;
entries.push((
idx,
mk_struct(vec![wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(k_val),
mutable: true,
}]),
));
}
for canonical in ®istry.tuple_order {
let elems = TypeRegistry::tuple_elements(canonical).ok_or(WasmGcError::Validation(
format!("registered tuple `{canonical}` has no parsable elements"),
))?;
let mut fields: Vec<wasm_encoder::FieldType> = Vec::with_capacity(elems.len());
for elem_aver in &elems {
let elem_val =
super::types::aver_to_wasm(elem_aver, Some(registry))?.unwrap_or(ValType::I32);
fields.push(wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(elem_val),
mutable: true,
});
}
let idx = registry
.tuple_type_idx(canonical)
.ok_or(WasmGcError::Validation(format!(
"tuple `{canonical}` not registered"
)))?;
entries.push((idx, mk_struct(fields)));
}
let user_record_names: std::collections::HashSet<&str> = items
.iter()
.filter_map(|item| match item {
TopLevel::TypeDef(TypeDef::Product { name, .. }) => Some(name.as_str()),
_ => None,
})
.collect();
for record in crate::codegen::builtin_records::BUILTIN_RECORDS {
if !registry.records.contains_key(record.aver_name) {
continue;
}
if user_record_names.contains(record.aver_name) {
continue;
}
let fields = registry
.record_fields
.get(record.aver_name)
.expect("builtin record registered without fields");
let st = super::types::record_struct_type(fields, registry)?;
let idx = registry
.record_type_idx(record.aver_name)
.ok_or(WasmGcError::Validation(format!(
"builtin record `{}` not registered",
record.aver_name
)))?;
entries.push((idx, mk_struct(st.fields.to_vec())));
}
entries.sort_by_key(|(idx, _)| *idx);
let subtypes: Vec<SubType> = entries.into_iter().map(|(_, t)| t).collect();
types.ty().rec(subtypes);
Ok(())
}
fn discover_builtins_in_fn(
fd: &FnDef,
builtins: &mut BuiltinRegistry,
effects: &mut EffectRegistry,
eq_helpers: &mut EqHelperRegistry,
type_registry: &TypeRegistry,
) {
let crate::ast::FnBody::Block(stmts) = fd.body.as_ref();
for stmt in stmts {
discover_builtins_in_stmt(stmt, builtins, effects, eq_helpers, type_registry);
}
}
fn discover_builtins_in_stmt(
stmt: &Stmt,
builtins: &mut BuiltinRegistry,
effects: &mut EffectRegistry,
eq_helpers: &mut EqHelperRegistry,
type_registry: &TypeRegistry,
) {
match stmt {
Stmt::Binding(_, _, e) | Stmt::Expr(e) => {
discover_builtins_in_expr(&e.node, builtins, effects, eq_helpers, type_registry)
}
}
}
fn register_nominal_in_type(
t: &AverType,
eq_helpers: &mut EqHelperRegistry,
type_registry: &super::types::TypeRegistry,
) {
let canonical: String = t.display().chars().filter(|c| !c.is_whitespace()).collect();
match t {
AverType::Named(name) => {
if type_registry.record_fields.contains_key(name) {
eq_helpers.register_transitive(name, EqKind::Record, type_registry);
} else if type_registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| &v.parent == name)
{
eq_helpers.register_transitive(name, EqKind::Sum, type_registry);
}
}
AverType::Option(inner) => {
eq_helpers.register_transitive(&canonical, EqKind::OptionEq, type_registry);
register_nominal_in_type(inner, eq_helpers, type_registry);
}
AverType::Result(ok, err) => {
eq_helpers.register_transitive(&canonical, EqKind::ResultEq, type_registry);
register_nominal_in_type(ok, eq_helpers, type_registry);
register_nominal_in_type(err, eq_helpers, type_registry);
}
AverType::Tuple(items) => {
eq_helpers.register_transitive(&canonical, EqKind::TupleEq, type_registry);
for item in items {
register_nominal_in_type(item, eq_helpers, type_registry);
}
}
AverType::List(inner) | AverType::Vector(inner) => {
register_nominal_in_type(inner, eq_helpers, type_registry);
}
AverType::Map(k, v) => {
register_nominal_in_type(k, eq_helpers, type_registry);
register_nominal_in_type(v, eq_helpers, type_registry);
}
_ => {}
}
}
fn discover_builtins_in_expr(
expr: &Expr,
builtins: &mut BuiltinRegistry,
effects: &mut EffectRegistry,
eq_helpers: &mut EqHelperRegistry,
type_registry: &TypeRegistry,
) {
use crate::ast::StrPart;
match expr {
Expr::FnCall(callee, args) => {
if let Expr::Attr(_parent, member) = &callee.node
&& let Some(parent_name) = expr_to_dotted_head(&callee.node)
{
let dotted = format!("{parent_name}.{member}");
if let Some(name) = BuiltinName::from_dotted(&dotted) {
builtins.register(name);
}
if let Some(name) = EffectName::from_dotted(&dotted) {
effects.register(name);
}
if dotted == "Args.get" && args.is_empty() {
effects.register(EffectName::ArgsLen);
effects.register(EffectName::ArgsGet);
}
if dotted == "Int.mod" {
builtins.register(BuiltinName::IntModEuclid);
}
}
discover_builtins_in_expr(&callee.node, builtins, effects, eq_helpers, type_registry);
for arg in args {
discover_builtins_in_expr(&arg.node, builtins, effects, eq_helpers, type_registry);
}
}
Expr::BinOp(op, l, r) => {
if let Some(t) = l.ty()
&& t.display().trim() == "String"
{
use crate::ast::BinOp as Op;
match op {
Op::Add => builtins.register(BuiltinName::StringConcatN),
Op::Eq | Op::Neq => builtins.register(BuiltinName::StringEq),
Op::Lt | Op::Gt | Op::Lte | Op::Gte => {
builtins.register(BuiltinName::StringCompare);
}
_ => {}
}
}
use crate::ast::BinOp as Op;
if matches!(op, Op::Eq | Op::Neq)
&& let Some(t) = l.ty()
&& let AverType::Named(name) = t
{
if type_registry.record_fields.contains_key(name) {
eq_helpers.register_transitive(name, EqKind::Record, type_registry);
} else if type_registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| &v.parent == name)
{
eq_helpers.register_transitive(name, EqKind::Sum, type_registry);
}
}
if matches!(op, Op::Eq | Op::Neq)
&& let Some(t) = l.ty()
{
register_nominal_in_type(t, eq_helpers, type_registry);
}
discover_builtins_in_expr(&l.node, builtins, effects, eq_helpers, type_registry);
discover_builtins_in_expr(&r.node, builtins, effects, eq_helpers, type_registry);
}
Expr::Match { subject, arms } => {
discover_builtins_in_expr(&subject.node, builtins, effects, eq_helpers, type_registry);
if arms.iter().any(|a| {
matches!(
&a.pattern,
crate::ast::Pattern::Literal(crate::ast::Literal::Str(_))
)
}) {
builtins.register(BuiltinName::StringEq);
}
for arm in arms {
discover_builtins_in_expr(
&arm.body.node,
builtins,
effects,
eq_helpers,
type_registry,
);
}
}
Expr::TailCall(boxed) => {
for arg in &boxed.args {
discover_builtins_in_expr(&arg.node, builtins, effects, eq_helpers, type_registry);
}
}
Expr::Attr(obj, _) => {
discover_builtins_in_expr(&obj.node, builtins, effects, eq_helpers, type_registry)
}
Expr::ErrorProp(inner) => {
discover_builtins_in_expr(&inner.node, builtins, effects, eq_helpers, type_registry)
}
Expr::Constructor(_, payload) => {
if let Some(p) = payload.as_deref() {
discover_builtins_in_expr(&p.node, builtins, effects, eq_helpers, type_registry);
}
}
Expr::RecordCreate { fields, .. } => {
for (_, e) in fields {
discover_builtins_in_expr(&e.node, builtins, effects, eq_helpers, type_registry);
}
}
Expr::RecordUpdate { base, updates, .. } => {
discover_builtins_in_expr(&base.node, builtins, effects, eq_helpers, type_registry);
for (_, e) in updates {
discover_builtins_in_expr(&e.node, builtins, effects, eq_helpers, type_registry);
}
}
Expr::InterpolatedStr(parts) => {
builtins.register(BuiltinName::StringConcatN);
builtins.register(BuiltinName::StringFromInt);
builtins.register(BuiltinName::StringFromFloat);
builtins.register(BuiltinName::StringFromBool);
for p in parts {
if let StrPart::Parsed(inner) = p {
discover_builtins_in_expr(
&inner.node,
builtins,
effects,
eq_helpers,
type_registry,
);
}
}
}
Expr::List(items) => {
for item in items {
discover_builtins_in_expr(&item.node, builtins, effects, eq_helpers, type_registry);
}
}
Expr::Tuple(items) => {
for item in items {
discover_builtins_in_expr(&item.node, builtins, effects, eq_helpers, type_registry);
}
}
Expr::IndependentProduct(items, _) => {
effects.register(EffectName::RecordEnterGroup);
effects.register(EffectName::RecordSetBranch);
effects.register(EffectName::RecordExitGroup);
for item in items {
discover_builtins_in_expr(&item.node, builtins, effects, eq_helpers, type_registry);
}
}
Expr::MapLiteral(entries) => {
for (k, v) in entries {
discover_builtins_in_expr(&k.node, builtins, effects, eq_helpers, type_registry);
discover_builtins_in_expr(&v.node, builtins, effects, eq_helpers, type_registry);
}
}
_ => {}
}
}
fn items_use_string_split_join(items: &[TopLevel]) -> bool {
use crate::ast::{Expr, FnBody, Stmt};
fn walk(e: &Expr) -> bool {
match e {
Expr::FnCall(callee, args) => {
if let Expr::Attr(_parent, member) = &callee.node
&& let Some(p) = expr_to_dotted_head(&callee.node)
&& p == "String"
&& (member == "split" || member == "join")
{
return true;
}
walk(&callee.node) || args.iter().any(|a| walk(&a.node))
}
Expr::BinOp(_, l, r) => walk(&l.node) || walk(&r.node),
Expr::Match { subject, arms } => {
walk(&subject.node) || arms.iter().any(|a| walk(&a.body.node))
}
Expr::TailCall(boxed) => boxed.args.iter().any(|a| walk(&a.node)),
Expr::Attr(obj, _) => walk(&obj.node),
Expr::RecordCreate { fields, .. } => fields.iter().any(|(_, e)| walk(&e.node)),
Expr::Constructor(_, payload) => {
payload.as_deref().map(|p| walk(&p.node)).unwrap_or(false)
}
Expr::List(items) => items.iter().any(|x| walk(&x.node)),
Expr::InterpolatedStr(_) => false,
_ => false,
}
}
for item in items {
if let TopLevel::FnDef(fd) = item {
let FnBody::Block(stmts) = fd.body.as_ref();
for stmt in stmts {
let e = match stmt {
Stmt::Binding(_, _, e) | Stmt::Expr(e) => &e.node,
};
if walk(e) {
return true;
}
}
}
}
false
}
fn expr_to_dotted_head(expr: &Expr) -> Option<&str> {
if let Expr::Attr(parent, _) = expr {
match &parent.node {
Expr::Ident(n) => Some(n.as_str()),
Expr::Resolved { name, .. } => Some(name.as_str()),
_ => None,
}
} else {
None
}
}
fn emit_list_string_cons(registry: &TypeRegistry) -> Result<wasm_encoder::Function, WasmGcError> {
let list_idx = registry
.list_type_idx("List<String>")
.ok_or(WasmGcError::Validation(
"list_cons helper requires List<String> slot".into(),
))?;
let mut f = wasm_encoder::Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructNew(list_idx));
f.instruction(&Instruction::End);
Ok(f)
}
#[derive(Default)]
struct FactoryExports {
opt_string_some: Option<FactorySlot>,
opt_string_none: Option<FactorySlot>,
terminal_size_make: Option<FactorySlot>,
result_string_string_ok: Option<FactorySlot>,
result_string_string_err: Option<FactorySlot>,
result_unit_string_ok: Option<FactorySlot>,
result_unit_string_err: Option<FactorySlot>,
result_list_string_string_ok: Option<FactorySlot>,
result_list_string_string_err: Option<FactorySlot>,
list_string_cons: Option<FactorySlot>,
list_string_nil: Option<FactorySlot>,
tcp_connection_make: Option<FactorySlot>,
tcp_connection_id: Option<FactorySlot>,
result_tcp_connection_string_ok: Option<FactorySlot>,
result_tcp_connection_string_err: Option<FactorySlot>,
http_response_make: Option<FactorySlot>,
result_http_response_string_ok: Option<FactorySlot>,
result_http_response_string_err: Option<FactorySlot>,
map_string_list_string_empty: Option<FactorySlot>,
}
#[derive(Clone, Copy)]
struct FactorySlot {
type_idx: u32,
fn_idx: u32,
}
fn allocate_factory_exports(
types: &mut TypeSection,
next_type_idx: &mut u32,
next_fn_idx: &mut u32,
registry: &TypeRegistry,
effect_registry: &EffectRegistry,
) -> Result<FactoryExports, WasmGcError> {
let mut fx = FactoryExports::default();
if effect_registry
.iter()
.any(|e| e == EffectName::TerminalReadKey)
{
let opt_idx = registry
.option_type_idx("Option<String>")
.ok_or(WasmGcError::Validation(
"Terminal.readKey factory requires Option<String> slot".into(),
))?;
let s_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"Terminal.readKey factory requires String slot".into(),
))?;
let s_ref = ref_null(s_idx);
let opt_ref = ref_null(opt_idx);
types.ty().function([s_ref], [opt_ref]);
fx.opt_string_some = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
types.ty().function([], [opt_ref]);
fx.opt_string_none = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
}
if effect_registry
.iter()
.any(|e| e == EffectName::TerminalSize)
{
let rec_idx = registry
.record_type_idx("Terminal.Size")
.ok_or(WasmGcError::Validation(
"Terminal.size factory requires Terminal.Size record slot".into(),
))?;
let rec_ref = ref_null(rec_idx);
types.ty().function([ValType::I64, ValType::I64], [rec_ref]);
fx.terminal_size_make = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
}
let needs_result_string_string = effect_registry.iter().any(|e| {
matches!(
e,
EffectName::ConsoleReadLine
| EffectName::DiskReadText
| EffectName::TcpReadLine
| EffectName::TcpSend
)
});
if needs_result_string_string {
let res_idx =
registry
.result_type_idx("Result<String,String>")
.ok_or(WasmGcError::Validation(
"Result<String,String> factory required but slot not registered".into(),
))?;
let s_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"Result<String,String> factory requires String slot".into(),
))?;
let s_ref = ref_null(s_idx);
let res_ref = ref_null(res_idx);
types.ty().function([s_ref], [res_ref]);
fx.result_string_string_ok = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
types.ty().function([s_ref], [res_ref]);
fx.result_string_string_err = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
}
let needs_result_unit_string = effect_registry.iter().any(|e| {
matches!(
e,
EffectName::DiskWriteText
| EffectName::DiskAppendText
| EffectName::DiskDelete
| EffectName::DiskDeleteDir
| EffectName::DiskMakeDir
| EffectName::TcpWriteLine
| EffectName::TcpClose
| EffectName::TcpPing
)
});
if needs_result_unit_string {
let res_idx =
registry
.result_type_idx("Result<Unit,String>")
.ok_or(WasmGcError::Validation(
"Result<Unit,String> factory required but slot not registered".into(),
))?;
let s_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"Result<Unit,String> factory requires String slot".into(),
))?;
let s_ref = ref_null(s_idx);
let res_ref = ref_null(res_idx);
types.ty().function([], [res_ref]);
fx.result_unit_string_ok = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
types.ty().function([s_ref], [res_ref]);
fx.result_unit_string_err = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
}
let needs_tcp_connection = effect_registry.iter().any(|e| {
matches!(
e,
EffectName::TcpConnect
| EffectName::TcpWriteLine
| EffectName::TcpReadLine
| EffectName::TcpClose
)
});
if needs_tcp_connection {
let rec_idx = registry
.record_type_idx("Tcp.Connection")
.ok_or(WasmGcError::Validation(
"Tcp.connect factory requires Tcp.Connection record slot".into(),
))?;
let s_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"Tcp.connect factory requires String slot".into(),
))?;
let s_ref = ref_null(s_idx);
let rec_ref = ref_null(rec_idx);
types.ty().function([s_ref, s_ref, ValType::I64], [rec_ref]);
fx.tcp_connection_make = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
types.ty().function([rec_ref], [s_ref]);
fx.tcp_connection_id = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
}
if effect_registry
.iter()
.any(|e| matches!(e, EffectName::TcpConnect))
{
let res_idx = registry
.result_type_idx("Result<Tcp.Connection,String>")
.ok_or(WasmGcError::Validation(
"Tcp.connect requires Result<Tcp.Connection,String> slot".into(),
))?;
let rec_idx = registry
.record_type_idx("Tcp.Connection")
.ok_or(WasmGcError::Validation(
"Tcp.connect requires Tcp.Connection record slot".into(),
))?;
let s_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"Tcp.connect requires String slot".into(),
))?;
let s_ref = ref_null(s_idx);
let rec_ref = ref_null(rec_idx);
let res_ref = ref_null(res_idx);
types.ty().function([rec_ref], [res_ref]);
fx.result_tcp_connection_string_ok = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
types.ty().function([s_ref], [res_ref]);
fx.result_tcp_connection_string_err = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
}
let needs_http_response = effect_registry.iter().any(|e| {
matches!(
e,
EffectName::HttpGet
| EffectName::HttpHead
| EffectName::HttpDelete
| EffectName::HttpPost
| EffectName::HttpPut
| EffectName::HttpPatch
)
});
if needs_http_response {
let res_idx = registry
.result_type_idx("Result<HttpResponse,String>")
.ok_or(WasmGcError::Validation(
"Http.* requires Result<HttpResponse,String> slot".into(),
))?;
let rec_idx = registry
.record_type_idx("HttpResponse")
.ok_or(WasmGcError::Validation(
"Http.* requires HttpResponse record slot".into(),
))?;
let s_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"Http.* requires String slot".into(),
))?;
let map_slots =
registry
.map_slots("Map<String,List<String>>")
.ok_or(WasmGcError::Validation(
"Http.* requires Map<String,List<String>> slot".into(),
))?;
let s_ref = ref_null(s_idx);
let rec_ref = ref_null(rec_idx);
let res_ref = ref_null(res_idx);
let map_ref = ref_null(map_slots.map);
let keys_ref = ref_null(map_slots.keys_array);
let values_ref = ref_null(map_slots.values_array);
let _ = (keys_ref, values_ref);
types
.ty()
.function([ValType::I64, s_ref, map_ref], [rec_ref]);
fx.http_response_make = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
types.ty().function([rec_ref], [res_ref]);
fx.result_http_response_string_ok = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
types.ty().function([s_ref], [res_ref]);
fx.result_http_response_string_err = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
types.ty().function([], [map_ref]);
fx.map_string_list_string_empty = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
}
let needs_list_string_pair = effect_registry
.iter()
.any(|e| matches!(e, EffectName::DiskListDir));
if needs_list_string_pair {
let res_idx = registry
.result_type_idx("Result<List<String>,String>")
.ok_or(WasmGcError::Validation(
"Result<List<String>,String> factory required but slot not registered".into(),
))?;
let list_idx = registry
.list_type_idx("List<String>")
.ok_or(WasmGcError::Validation(
"Result<List<String>,String> factory requires List<String> slot".into(),
))?;
let s_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"Result<List<String>,String> factory requires String slot".into(),
))?;
let s_ref = ref_null(s_idx);
let list_ref = ref_null(list_idx);
let res_ref = ref_null(res_idx);
types.ty().function([list_ref], [res_ref]);
fx.result_list_string_string_ok = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
types.ty().function([s_ref], [res_ref]);
fx.result_list_string_string_err = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
types.ty().function([s_ref, list_ref], [list_ref]);
fx.list_string_cons = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
types.ty().function([], [list_ref]);
fx.list_string_nil = Some(FactorySlot {
type_idx: *next_type_idx,
fn_idx: *next_fn_idx,
});
*next_type_idx += 1;
*next_fn_idx += 1;
}
Ok(fx)
}
fn ref_null(type_idx: u32) -> ValType {
ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(type_idx),
})
}
impl FactoryExports {
fn emit_function_entries(&self, funcs: &mut FunctionSection) {
for slot in self.iter_slots() {
funcs.function(slot.type_idx);
}
}
fn emit_exports(&self, exports: &mut ExportSection) {
if let Some(s) = self.opt_string_some {
exports.export("__rt_option_string_some", ExportKind::Func, s.fn_idx);
}
if let Some(s) = self.opt_string_none {
exports.export("__rt_option_string_none", ExportKind::Func, s.fn_idx);
}
if let Some(s) = self.terminal_size_make {
exports.export("__rt_record_terminal_size_make", ExportKind::Func, s.fn_idx);
}
if let Some(s) = self.result_string_string_ok {
exports.export("__rt_result_string_string_ok", ExportKind::Func, s.fn_idx);
}
if let Some(s) = self.result_string_string_err {
exports.export("__rt_result_string_string_err", ExportKind::Func, s.fn_idx);
}
if let Some(s) = self.result_unit_string_ok {
exports.export("__rt_result_unit_string_ok", ExportKind::Func, s.fn_idx);
}
if let Some(s) = self.result_unit_string_err {
exports.export("__rt_result_unit_string_err", ExportKind::Func, s.fn_idx);
}
if let Some(s) = self.result_list_string_string_ok {
exports.export(
"__rt_result_list_string_string_ok",
ExportKind::Func,
s.fn_idx,
);
}
if let Some(s) = self.result_list_string_string_err {
exports.export(
"__rt_result_list_string_string_err",
ExportKind::Func,
s.fn_idx,
);
}
if let Some(s) = self.list_string_cons {
exports.export("__rt_list_string_cons", ExportKind::Func, s.fn_idx);
}
if let Some(s) = self.list_string_nil {
exports.export("__rt_list_string_nil", ExportKind::Func, s.fn_idx);
}
if let Some(s) = self.tcp_connection_make {
exports.export(
"__rt_record_tcp_connection_make",
ExportKind::Func,
s.fn_idx,
);
}
if let Some(s) = self.tcp_connection_id {
exports.export("__rt_tcp_connection_id", ExportKind::Func, s.fn_idx);
}
if let Some(s) = self.result_tcp_connection_string_ok {
exports.export(
"__rt_result_tcp_connection_string_ok",
ExportKind::Func,
s.fn_idx,
);
}
if let Some(s) = self.result_tcp_connection_string_err {
exports.export(
"__rt_result_tcp_connection_string_err",
ExportKind::Func,
s.fn_idx,
);
}
if let Some(s) = self.http_response_make {
exports.export("__rt_record_http_response_make", ExportKind::Func, s.fn_idx);
}
if let Some(s) = self.result_http_response_string_ok {
exports.export(
"__rt_result_http_response_string_ok",
ExportKind::Func,
s.fn_idx,
);
}
if let Some(s) = self.result_http_response_string_err {
exports.export(
"__rt_result_http_response_string_err",
ExportKind::Func,
s.fn_idx,
);
}
if let Some(s) = self.map_string_list_string_empty {
exports.export(
"__rt_map_string_list_string_empty",
ExportKind::Func,
s.fn_idx,
);
}
}
fn emit_bodies(
&self,
codes: &mut CodeSection,
registry: &TypeRegistry,
) -> Result<(), WasmGcError> {
if self.opt_string_some.is_some() {
codes.function(&emit_factory_option_string_some(registry)?);
}
if self.opt_string_none.is_some() {
codes.function(&emit_factory_option_string_none(registry)?);
}
if self.terminal_size_make.is_some() {
codes.function(&emit_factory_terminal_size_make(registry)?);
}
if self.result_string_string_ok.is_some() {
codes.function(&emit_factory_result_string_string_ok(registry)?);
}
if self.result_string_string_err.is_some() {
codes.function(&emit_factory_result_string_string_err(registry)?);
}
if self.result_unit_string_ok.is_some() {
codes.function(&emit_factory_result_unit_string_ok(registry)?);
}
if self.result_unit_string_err.is_some() {
codes.function(&emit_factory_result_unit_string_err(registry)?);
}
if self.result_list_string_string_ok.is_some() {
codes.function(&emit_factory_result_list_string_string_ok(registry)?);
}
if self.result_list_string_string_err.is_some() {
codes.function(&emit_factory_result_list_string_string_err(registry)?);
}
if self.list_string_cons.is_some() {
codes.function(&emit_factory_list_string_cons(registry)?);
}
if self.list_string_nil.is_some() {
codes.function(&emit_factory_list_string_nil(registry)?);
}
if self.tcp_connection_make.is_some() {
codes.function(&emit_factory_tcp_connection_make(registry)?);
}
if self.tcp_connection_id.is_some() {
codes.function(&emit_factory_tcp_connection_id(registry)?);
}
if self.result_tcp_connection_string_ok.is_some() {
codes.function(&emit_factory_result_tcp_connection_string_ok(registry)?);
}
if self.result_tcp_connection_string_err.is_some() {
codes.function(&emit_factory_result_tcp_connection_string_err(registry)?);
}
if self.http_response_make.is_some() {
codes.function(&emit_factory_http_response_make(registry)?);
}
if self.result_http_response_string_ok.is_some() {
codes.function(&emit_factory_result_http_response_string_ok(registry)?);
}
if self.result_http_response_string_err.is_some() {
codes.function(&emit_factory_result_http_response_string_err(registry)?);
}
if self.map_string_list_string_empty.is_some() {
codes.function(&emit_factory_map_string_list_string_empty(registry)?);
}
Ok(())
}
fn iter_slots(&self) -> impl Iterator<Item = FactorySlot> + '_ {
[
self.opt_string_some,
self.opt_string_none,
self.terminal_size_make,
self.result_string_string_ok,
self.result_string_string_err,
self.result_unit_string_ok,
self.result_unit_string_err,
self.result_list_string_string_ok,
self.result_list_string_string_err,
self.list_string_cons,
self.list_string_nil,
self.tcp_connection_make,
self.tcp_connection_id,
self.result_tcp_connection_string_ok,
self.result_tcp_connection_string_err,
self.http_response_make,
self.result_http_response_string_ok,
self.result_http_response_string_err,
self.map_string_list_string_empty,
]
.into_iter()
.flatten()
}
}
fn emit_factory_option_string_some(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let opt_idx = registry
.option_type_idx("Option<String>")
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructNew(opt_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_option_string_none(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let opt_idx = registry
.option_type_idx("Option<String>")
.expect("checked at allocation");
let s_idx = registry
.string_array_type_idx
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
s_idx,
)));
f.instruction(&Instruction::StructNew(opt_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_terminal_size_make(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let rec_idx = registry
.record_type_idx("Terminal.Size")
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructNew(rec_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_result_string_string_ok(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let res_idx = registry
.result_type_idx("Result<String,String>")
.expect("checked at allocation");
let s_idx = registry
.string_array_type_idx
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
s_idx,
)));
f.instruction(&Instruction::StructNew(res_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_result_string_string_err(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let res_idx = registry
.result_type_idx("Result<String,String>")
.expect("checked at allocation");
let s_idx = registry
.string_array_type_idx
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
s_idx,
)));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructNew(res_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_result_unit_string_ok(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let res_idx = registry
.result_type_idx("Result<Unit,String>")
.expect("checked at allocation");
let s_idx = registry
.string_array_type_idx
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
s_idx,
)));
f.instruction(&Instruction::StructNew(res_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_result_unit_string_err(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let res_idx = registry
.result_type_idx("Result<Unit,String>")
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructNew(res_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_result_list_string_string_ok(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let res_idx = registry
.result_type_idx("Result<List<String>,String>")
.expect("checked at allocation");
let s_idx = registry
.string_array_type_idx
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
s_idx,
)));
f.instruction(&Instruction::StructNew(res_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_result_list_string_string_err(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let res_idx = registry
.result_type_idx("Result<List<String>,String>")
.expect("checked at allocation");
let list_idx = registry
.list_type_idx("List<String>")
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
list_idx,
)));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructNew(res_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_list_string_cons(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let list_idx = registry
.list_type_idx("List<String>")
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::StructNew(list_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_list_string_nil(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let list_idx = registry
.list_type_idx("List<String>")
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
list_idx,
)));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_tcp_connection_make(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let rec_idx = registry
.record_type_idx("Tcp.Connection")
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructNew(rec_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_tcp_connection_id(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let rec_idx = registry
.record_type_idx("Tcp.Connection")
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(rec_idx),
));
f.instruction(&Instruction::StructGet {
struct_type_index: rec_idx,
field_index: 0,
});
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_result_tcp_connection_string_ok(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let res_idx = registry
.result_type_idx("Result<Tcp.Connection,String>")
.expect("checked at allocation");
let s_idx = registry
.string_array_type_idx
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
s_idx,
)));
f.instruction(&Instruction::StructNew(res_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_result_tcp_connection_string_err(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let res_idx = registry
.result_type_idx("Result<Tcp.Connection,String>")
.expect("checked at allocation");
let rec_idx = registry
.record_type_idx("Tcp.Connection")
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
rec_idx,
)));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructNew(res_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_http_response_make(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let rec_idx = registry
.record_type_idx("HttpResponse")
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::StructNew(rec_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_result_http_response_string_ok(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let res_idx = registry
.result_type_idx("Result<HttpResponse,String>")
.expect("checked at allocation");
let s_idx = registry
.string_array_type_idx
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
s_idx,
)));
f.instruction(&Instruction::StructNew(res_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_result_http_response_string_err(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let res_idx = registry
.result_type_idx("Result<HttpResponse,String>")
.expect("checked at allocation");
let rec_idx = registry
.record_type_idx("HttpResponse")
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
rec_idx,
)));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::StructNew(res_idx));
f.instruction(&Instruction::End);
Ok(f)
}
fn emit_factory_map_string_list_string_empty(
registry: &TypeRegistry,
) -> Result<wasm_encoder::Function, WasmGcError> {
let slots = registry
.map_slots("Map<String,List<String>>")
.expect("checked at allocation");
let mut f = Function::new([]);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
slots.keys_array,
)));
f.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
slots.values_array,
)));
f.instruction(&Instruction::StructNew(slots.map));
f.instruction(&Instruction::End);
Ok(f)
}
struct HandlerWrapper {
user_handler_idx: usize,
wrapper_type: u32,
wrapper_fn: u32,
list_cons_type: u32,
list_cons_fn: u32,
}
fn emit_handler_wrapper(
registry: &TypeRegistry,
fn_map: &super::body::FnMap,
user_handler_wasm_idx: u32,
caller_fn_idx: u32,
) -> Result<wasm_encoder::Function, WasmGcError> {
use wasm_encoder::{BlockType, Function, HeapType, Instruction, RefType};
let s_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"aver_http_handle wrapper requires String slot".into(),
))?;
let req_idx = registry
.records
.get("HttpRequest")
.copied()
.ok_or(WasmGcError::Validation(
"aver_http_handle wrapper requires HttpRequest record slot".into(),
))?;
let resp_idx = registry
.records
.get("HttpResponse")
.copied()
.ok_or(WasmGcError::Validation(
"aver_http_handle wrapper requires HttpResponse record slot".into(),
))?;
let map_slots =
registry
.map_slots("Map<String,List<String>>")
.ok_or(WasmGcError::Validation(
"aver_http_handle wrapper requires `Map<String, List<String>>` slot".into(),
))?;
let list_idx = registry
.list_type_idx("List<String>")
.ok_or(WasmGcError::Validation(
"aver_http_handle wrapper requires `List<String>` slot".into(),
))?;
let request_method_fn =
fn_map
.effects
.get("Request.method")
.copied()
.ok_or(WasmGcError::Validation(
"Request.method effect not registered".into(),
))?;
let request_url_fn =
fn_map
.effects
.get("Request.url")
.copied()
.ok_or(WasmGcError::Validation(
"Request.url effect not registered".into(),
))?;
let request_query_fn =
fn_map
.effects
.get("Request.query")
.copied()
.ok_or(WasmGcError::Validation(
"Request.query effect not registered".into(),
))?;
let request_body_fn =
fn_map
.effects
.get("Request.body")
.copied()
.ok_or(WasmGcError::Validation(
"Request.body effect not registered".into(),
))?;
let request_headers_load_fn =
fn_map
.effects
.get("Request.headersLoad")
.copied()
.ok_or(WasmGcError::Validation(
"Request.headersLoad effect not registered".into(),
))?;
let response_text_fn =
fn_map
.effects
.get("Response.text")
.copied()
.ok_or(WasmGcError::Validation(
"Response.text effect not registered".into(),
))?;
let response_set_header_fn =
fn_map
.effects
.get("Response.setHeader")
.copied()
.ok_or(WasmGcError::Validation(
"Response.setHeader effect not registered".into(),
))?;
let s_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(s_idx),
});
let req_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(req_idx),
});
let resp_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(resp_idx),
});
let map_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(map_slots.map),
});
let keys_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(map_slots.keys_array),
});
let values_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(map_slots.values_array),
});
let list_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(list_idx),
});
let mut f = Function::new([
(4, s_ref),
(1, map_ref),
(1, req_ref),
(1, resp_ref),
(1, ValType::I64),
(1, s_ref),
(1, map_ref),
(1, keys_ref),
(1, values_ref),
(2, ValType::I32),
(1, s_ref),
(1, list_ref),
]);
let push_caller = |f: &mut Function| {
f.instruction(&Instruction::I32Const(caller_fn_idx as i32));
};
push_caller(&mut f);
f.instruction(&Instruction::Call(request_method_fn));
f.instruction(&Instruction::RefCastNullable(HeapType::Concrete(s_idx)));
f.instruction(&Instruction::LocalSet(0));
push_caller(&mut f);
f.instruction(&Instruction::Call(request_url_fn));
f.instruction(&Instruction::RefCastNullable(HeapType::Concrete(s_idx)));
f.instruction(&Instruction::LocalSet(1));
push_caller(&mut f);
f.instruction(&Instruction::Call(request_query_fn));
f.instruction(&Instruction::RefCastNullable(HeapType::Concrete(s_idx)));
f.instruction(&Instruction::LocalSet(2));
push_caller(&mut f);
f.instruction(&Instruction::Call(request_body_fn));
f.instruction(&Instruction::RefCastNullable(HeapType::Concrete(s_idx)));
f.instruction(&Instruction::LocalSet(3));
push_caller(&mut f);
f.instruction(&Instruction::Call(request_headers_load_fn));
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::StructNew(req_idx));
f.instruction(&Instruction::LocalSet(5));
f.instruction(&Instruction::LocalGet(5));
f.instruction(&Instruction::Call(user_handler_wasm_idx));
f.instruction(&Instruction::LocalSet(6));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::StructGet {
struct_type_index: resp_idx,
field_index: 0,
});
f.instruction(&Instruction::LocalSet(7));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::StructGet {
struct_type_index: resp_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(8));
f.instruction(&Instruction::LocalGet(6));
f.instruction(&Instruction::StructGet {
struct_type_index: resp_idx,
field_index: 2,
});
f.instruction(&Instruction::LocalSet(9));
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::StructGet {
struct_type_index: map_slots.map,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(12));
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::StructGet {
struct_type_index: map_slots.map,
field_index: 2,
});
f.instruction(&Instruction::LocalSet(10));
f.instruction(&Instruction::LocalGet(9));
f.instruction(&Instruction::StructGet {
struct_type_index: map_slots.map,
field_index: 3,
});
f.instruction(&Instruction::LocalSet(11));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(13));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(13));
f.instruction(&Instruction::LocalGet(12));
f.instruction(&Instruction::I32GeU);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(10));
f.instruction(&Instruction::LocalGet(13));
f.instruction(&Instruction::ArrayGet(map_slots.keys_array));
f.instruction(&Instruction::LocalSet(14));
f.instruction(&Instruction::LocalGet(14));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::I32Eqz);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(11));
f.instruction(&Instruction::LocalGet(13));
f.instruction(&Instruction::ArrayGet(map_slots.values_array));
f.instruction(&Instruction::LocalSet(15));
f.instruction(&Instruction::Block(BlockType::Empty));
f.instruction(&Instruction::Loop(BlockType::Empty));
f.instruction(&Instruction::LocalGet(15));
f.instruction(&Instruction::RefIsNull);
f.instruction(&Instruction::BrIf(1));
f.instruction(&Instruction::LocalGet(14));
f.instruction(&Instruction::LocalGet(15));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
push_caller(&mut f);
f.instruction(&Instruction::Call(response_set_header_fn));
f.instruction(&Instruction::LocalGet(15));
f.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
f.instruction(&Instruction::LocalSet(15));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End); f.instruction(&Instruction::End); f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(13));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(13));
f.instruction(&Instruction::Br(0));
f.instruction(&Instruction::End); f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(7));
f.instruction(&Instruction::LocalGet(8));
push_caller(&mut f);
f.instruction(&Instruction::Call(response_text_fn));
f.instruction(&Instruction::End);
Ok(f)
}
struct BridgeIndices {
from_lm_type: u32,
to_lm_type: u32,
pages_type: u32,
grow_type: u32,
from_lm_fn: u32,
to_lm_fn: u32,
pages_fn: u32,
grow_fn: u32,
}
struct BridgeTypeSlots {
from_lm_type: u32,
to_lm_type: u32,
pages_type: u32,
grow_type: u32,
}
fn emit_bridge_types(
types: &mut TypeSection,
registry: &TypeRegistry,
next_type_idx: &mut u32,
) -> Result<BridgeTypeSlots, WasmGcError> {
let s_idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"bridge helpers require String slot to be allocated".into(),
))?;
let s_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(s_idx),
});
types.ty().function([ValType::I32], [s_ref]);
let from_lm = *next_type_idx;
*next_type_idx += 1;
types.ty().function([s_ref], [ValType::I32]);
let to_lm = *next_type_idx;
*next_type_idx += 1;
types.ty().function([], [ValType::I32]);
let pages = *next_type_idx;
*next_type_idx += 1;
types.ty().function([ValType::I32], [ValType::I32]);
let grow = *next_type_idx;
*next_type_idx += 1;
Ok(BridgeTypeSlots {
from_lm_type: from_lm,
to_lm_type: to_lm,
pages_type: pages,
grow_type: grow,
})
}
fn emit_bridge_bodies(codes: &mut CodeSection, registry: &TypeRegistry) -> Result<(), WasmGcError> {
let s_idx = registry
.string_array_type_idx
.expect("bridge bodies emitted only when string slot exists");
let padding = wat_helper::padding_types(s_idx);
let from_lm_wat = format!(
r#"
(module
{padding}
(type $string (array (mut i8)))
(memory 1)
(func (export "helper") (param $len i32) (result (ref null $string))
(local $arr (ref null $string))
(local $i i32)
local.get $len
array.new_default $string
local.set $arr
i32.const 0
local.set $i
(block $break
(loop $next
local.get $i
local.get $len
i32.ge_u
br_if $break
local.get $arr
local.get $i
local.get $i
i32.load8_u
array.set $string
local.get $i
i32.const 1
i32.add
local.set $i
br $next))
local.get $arr)
)
"#
);
codes.function(&wat_helper::compile_wat_helper(&from_lm_wat)?);
let to_lm_wat = format!(
r#"
(module
{padding}
(type $string (array (mut i8)))
(memory 1)
(func (export "helper") (param $s (ref null $string)) (result i32)
(local $len i32)
(local $i i32)
(local $needed i32)
(local $current i32)
local.get $s
array.len
local.set $len
;; needed = (len + 65535) >> 16
local.get $len
i32.const 65535
i32.add
i32.const 16
i32.shr_u
local.set $needed
memory.size
local.set $current
local.get $needed
local.get $current
i32.gt_u
(if
(then
local.get $needed
local.get $current
i32.sub
memory.grow
drop))
i32.const 0
local.set $i
(block $break
(loop $next
local.get $i
local.get $len
i32.ge_u
br_if $break
local.get $i
local.get $s
local.get $i
array.get_u $string
i32.store8
local.get $i
i32.const 1
i32.add
local.set $i
br $next))
local.get $len)
)
"#
);
codes.function(&wat_helper::compile_wat_helper(&to_lm_wat)?);
let mut pages = wasm_encoder::Function::new([]);
pages.instruction(&Instruction::MemorySize(0));
pages.instruction(&Instruction::End);
codes.function(&pages);
let mut grow = wasm_encoder::Function::new([]);
grow.instruction(&Instruction::LocalGet(0));
grow.instruction(&Instruction::MemoryGrow(0));
grow.instruction(&Instruction::End);
codes.function(&grow);
Ok(())
}
fn validate(bytes: &[u8]) -> Result<(), WasmGcError> {
use wasmparser::{Validator, WasmFeatures};
let features = WasmFeatures::default()
| WasmFeatures::GC
| WasmFeatures::REFERENCE_TYPES
| WasmFeatures::FUNCTION_REFERENCES
| WasmFeatures::TAIL_CALL;
let mut validator = Validator::new_with_features(features);
validator
.validate_all(bytes)
.map_err(|e| WasmGcError::Validation(format!("{e}")))?;
Ok(())
}