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};
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::wat_helper;
use crate::types::Type as AverType;
use crate::ast::{Expr, FnDef, Stmt, TopLevel, TypeDef};
pub(super) fn emit_module(
items: &[TopLevel],
handler_name: Option<&str>,
) -> Result<Vec<u8>, WasmGcError> {
let registry = TypeRegistry::build_with_handler(items, handler_name.is_some());
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();
for fd in &fn_defs {
discover_builtins_in_fn(
fd,
&mut builtin_registry,
&mut effect_registry,
&mut eq_helpers_registry,
®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;
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);
}
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 = effect_registry.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);
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 factory_exports = allocate_factory_exports(
&mut types,
&mut next_type_idx,
&mut next_builtin_fn_idx,
®istry,
&effect_registry,
)?;
module.section(&types);
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);
}
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);
}
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);
}
factory_exports.emit_function_entries(&mut funcs);
let init_globals_fn_idx: Option<u32> = if !registry.caller_fn_global_order.is_empty() {
let idx = import_count + funcs.len();
funcs.function(start_type_idx);
Some(idx)
} else {
None
};
module.section(&funcs);
if bridge.is_some() {
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);
}
if !registry.caller_fn_global_order.is_empty() {
let string_idx = registry
.string_array_type_idx
.expect("caller_fn globals require the $string slot — TypeRegistry forces it on");
let mut globals = wasm_encoder::GlobalSection::new();
for _ in ®istry.caller_fn_global_order {
let init = wasm_encoder::ConstExpr::extended([Instruction::RefNull(
wasm_encoder::HeapType::Concrete(string_idx),
)]);
globals.global(
wasm_encoder::GlobalType {
val_type: wasm_encoder::ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(string_idx),
}),
mutable: true,
shared: false,
},
&init,
);
}
module.section(&globals);
}
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();
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);
}
}
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();
exports.export("_start", 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 {
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);
}
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,
);
}
}
module.section(&exports);
if let Some(idx) = init_globals_fn_idx {
module.section(&wasm_encoder::StartSection {
function_index: idx,
});
}
if !registry.string_literals.is_empty() {
let count = DataCountSection {
count: registry.string_literals.len() as u32,
};
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);
}
}
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,
)?;
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,
)?;
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));
}
}
map_helpers.emit_helper_bodies(&mut codes, ®istry, &compound_eq_hash_lookup)?;
let string_eq_fn_idx = builtin_registry.lookup_wasm_fn_idx(BuiltinName::StringEq);
list_helpers.emit_helper_bodies(&mut codes, ®istry, string_eq_fn_idx)?;
eq_helpers_registry.emit_helper_bodies(&mut codes, ®istry, string_eq_fn_idx)?;
if let Some(hw) = &handler_wrapper {
let user_handler_wasm_idx = import_count + 1 + (hw.user_handler_idx as u32);
codes.function(&emit_handler_wrapper(
®istry,
&fn_map,
user_handler_wasm_idx,
)?);
codes.function(&emit_list_string_cons(®istry)?);
let _ = hw.list_cons_type; }
if bridge.is_some() {
emit_bridge_bodies(&mut codes, ®istry)?;
}
factory_exports.emit_bodies(&mut codes, ®istry)?;
if init_globals_fn_idx.is_some() {
let string_idx = registry
.string_array_type_idx
.expect("caller_fn globals require the $string slot");
let mut init = Function::new([]);
for (idx, fn_name) in registry.caller_fn_global_order.iter().enumerate() {
let bytes = fn_name.as_bytes();
let segment_idx = registry
.string_literal_segment(bytes)
.expect("fn-name passive segment registered alongside the global");
init.instruction(&Instruction::I32Const(0));
init.instruction(&Instruction::I32Const(bytes.len() as i32));
init.instruction(&Instruction::ArrayNewData {
array_type_index: string_idx,
array_data_index: segment_idx,
});
init.instruction(&Instruction::GlobalSet(idx as u32));
}
init.instruction(&Instruction::End);
codes.function(&init);
}
module.section(&codes);
if !registry.string_literals.is_empty() {
let mut data = DataSection::new();
for bytes in ®istry.string_literals {
data.passive(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,
},
};
let mk_array = |elem: wasm_encoder::FieldType| SubType {
is_final: true,
supertype_idx: None,
composite_type: CompositeType {
inner: CompositeInnerType::Array(ArrayType(elem)),
shared: false,
},
};
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)));
}
for record in crate::codegen::builtin_records::BUILTIN_RECORDS {
if !registry.records.contains_key(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 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);
}
}
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(name, EqKind::Record);
} else if type_registry
.variants
.values()
.flat_map(|v| v.iter())
.any(|v| &v.parent == name)
{
eq_helpers.register(name, EqKind::Sum);
}
}
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::IntToString);
builtins.register(BuiltinName::FloatToString);
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,
) -> 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),
]);
f.instruction(&Instruction::Call(request_method_fn));
f.instruction(&Instruction::RefCastNullable(HeapType::Concrete(s_idx)));
f.instruction(&Instruction::LocalSet(0));
f.instruction(&Instruction::Call(request_url_fn));
f.instruction(&Instruction::RefCastNullable(HeapType::Concrete(s_idx)));
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::Call(request_query_fn));
f.instruction(&Instruction::RefCastNullable(HeapType::Concrete(s_idx)));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::Call(request_body_fn));
f.instruction(&Instruction::RefCastNullable(HeapType::Concrete(s_idx)));
f.instruction(&Instruction::LocalSet(3));
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,
});
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));
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(())
}