use cviz::model::{FuncSignature, InterfaceType, ValueTypeId};
use wasm_encoder::ValType;
use wit_parser::abi::{AbiVariant, WasmSignature};
use wit_parser::{Docs, Function, FunctionKind, Param, Span, Stability};
use super::abi::{wasm_to_val, WitBridge};
use super::build::MemoryLayoutBuilder;
pub(crate) struct AdapterFunc {
pub name: String,
pub is_async: bool,
pub param_names: Vec<String>,
pub param_type_ids: Vec<ValueTypeId>,
pub result_type_id: Option<ValueTypeId>,
pub result_is_complex: bool,
pub core_params: Vec<ValType>,
pub core_results: Vec<ValType>,
pub name_offset: u32,
pub name_len: u32,
pub async_result_mem_offset: Option<u32>,
pub sync_result_mem_offset: Option<u32>,
pub has_strings: bool,
pub has_lists: bool,
}
impl AdapterFunc {
pub fn canon_needs_memory(&self) -> bool {
self.has_strings
|| self.has_lists
|| self.result_is_complex
|| (self.is_async && self.result_type_id.is_some())
}
pub fn canon_needs_realloc(&self) -> bool {
self.has_strings || self.has_lists
}
pub fn canon_needs_utf8(&self) -> bool {
self.has_strings
}
pub fn uses_async_pointer_params(&self, bridge: &WitBridge) -> bool {
self.is_async
&& self
.wasm_signature(bridge, AbiVariant::GuestImportAsync)
.indirect_params
}
pub fn handler_import_sig(&self, bridge: &WitBridge) -> (Vec<ValType>, Vec<ValType>) {
let variant = if self.is_async {
AbiVariant::GuestImportAsync
} else {
AbiVariant::GuestImport
};
self.core_sig(bridge, variant)
}
pub fn wrapper_export_sig(&self, bridge: &WitBridge) -> (Vec<ValType>, Vec<ValType>) {
let variant = if self.is_async {
AbiVariant::GuestExportAsyncStackful
} else {
AbiVariant::GuestExport
};
self.core_sig(bridge, variant)
}
fn core_sig(&self, bridge: &WitBridge, variant: AbiVariant) -> (Vec<ValType>, Vec<ValType>) {
let ws = self.wasm_signature(bridge, variant);
let params = ws.params.into_iter().map(wasm_to_val).collect();
let results = ws.results.into_iter().map(wasm_to_val).collect();
(params, results)
}
pub fn wasm_signature(&self, bridge: &WitBridge, variant: AbiVariant) -> WasmSignature {
let name = &self.name;
let is_async = self.is_async;
let param_names = self
.param_names
.iter()
.cloned()
.chain(std::iter::repeat(String::new()));
let params_iter: Vec<(String, ValueTypeId)> = self
.param_type_ids
.iter()
.copied()
.zip(param_names)
.map(|(id, n)| (n, id))
.collect();
let func = build_wit_function_from_parts(
name,
is_async,
¶ms_iter,
self.result_type_id,
bridge,
);
bridge.resolve.wasm_signature(variant, &func)
}
}
fn build_wit_function(name: &str, sig: &FuncSignature, bridge: &WitBridge) -> Function {
let params: Vec<(String, ValueTypeId)> = sig
.params
.iter()
.enumerate()
.map(|(i, &id)| {
let n = sig
.param_names
.get(i)
.cloned()
.unwrap_or_else(|| format!("p{i}"));
(n, id)
})
.collect();
build_wit_function_from_parts(
name,
sig.is_async,
¶ms,
sig.results.first().copied(),
bridge,
)
}
fn build_wit_function_from_parts(
name: &str,
is_async: bool,
params: &[(String, ValueTypeId)],
result_type_id: Option<ValueTypeId>,
bridge: &WitBridge,
) -> Function {
let kind = if is_async {
FunctionKind::AsyncFreestanding
} else {
FunctionKind::Freestanding
};
let wit_params: Vec<Param> = params
.iter()
.map(|(pname, id)| Param {
name: pname.clone(),
ty: bridge.get(*id),
span: Span::default(),
})
.collect();
Function {
name: name.to_string(),
kind,
params: wit_params,
result: result_type_id.map(|id| bridge.get(id)),
docs: Docs::default(),
stability: Stability::Unknown,
span: Span::default(),
}
}
pub(crate) fn extract_adapter_funcs(
iface_ty: &InterfaceType,
bridge: &WitBridge,
) -> anyhow::Result<(Vec<AdapterFunc>, MemoryLayoutBuilder)> {
let inst = match iface_ty {
InterfaceType::Instance(i) => i,
InterfaceType::Func(_) => anyhow::bail!(
"Expected an instance-type interface for tier-1 adapter generation; \
bare function-type interfaces are not yet supported. If you need this, \
please open an issue with a repro at https://github.com/ejrgilbert/splicer/issues"
),
};
let total_name_bytes: u32 = inst.functions.keys().map(|n| n.len() as u32).sum();
let mut layout = MemoryLayoutBuilder::new(total_name_bytes);
let mut funcs = Vec::with_capacity(inst.functions.len());
for (name, sig) in &inst.functions {
let extracted = extract_func_sig(name, sig, bridge)?;
let name_len = name.len() as u32;
let name_offset = layout.alloc_name(name_len);
let has_result = extracted.result_type_id.is_some();
let result_align = extracted
.result_type_id
.map(|id| bridge.align_bytes(id))
.unwrap_or(1);
let async_result_mem_offset = (extracted.is_async && has_result)
.then(|| layout.alloc_async_result(extracted.result_byte_size, result_align));
let sync_result_mem_offset = (!extracted.is_async && extracted.result_is_complex)
.then(|| layout.alloc_sync_result(extracted.result_byte_size, result_align));
let param_ids = extracted.param_type_ids.iter().copied();
let result_id = extracted.result_type_id.into_iter();
let all_ids: Vec<ValueTypeId> = param_ids.chain(result_id).collect();
let has_strings = all_ids.iter().any(|&id| bridge.has_strings(id));
let has_lists = all_ids.iter().any(|&id| bridge.has_lists(id));
funcs.push(AdapterFunc {
name: name.clone(),
is_async: extracted.is_async,
param_names: extracted.param_names,
param_type_ids: extracted.param_type_ids,
result_type_id: extracted.result_type_id,
result_is_complex: extracted.result_is_complex,
core_params: extracted.core_params,
core_results: extracted.core_results,
name_offset,
name_len,
async_result_mem_offset,
sync_result_mem_offset,
has_strings,
has_lists,
});
}
Ok((funcs, layout))
}
struct ExtractedSig {
is_async: bool,
param_names: Vec<String>,
param_type_ids: Vec<ValueTypeId>,
result_type_id: Option<ValueTypeId>,
result_is_complex: bool,
core_params: Vec<ValType>,
core_results: Vec<ValType>,
result_byte_size: u32,
}
fn extract_func_sig(
name: &str,
sig: &FuncSignature,
bridge: &WitBridge,
) -> anyhow::Result<ExtractedSig> {
const MAX_FLAT: usize = 16;
let mut param_names = Vec::with_capacity(sig.params.len());
let mut param_type_ids = Vec::with_capacity(sig.params.len());
let mut core_params = Vec::new();
for (i, &id) in sig.params.iter().enumerate() {
let pname = if i < sig.param_names.len() {
sig.param_names[i].clone()
} else {
format!("p{i}")
};
param_names.push(pname);
param_type_ids.push(id);
core_params.extend(bridge.flat_types(id));
}
if core_params.len() > MAX_FLAT {
anyhow::bail!(
"Function '{name}' has {} flat parameter values (exceeds the \
canonical-ABI limit of {MAX_FLAT}). The pointer-form lowering \
required for >{MAX_FLAT} flat params is not yet implemented.",
core_params.len()
);
}
let wit_func = build_wit_function(name, sig, bridge);
let import_variant = if sig.is_async {
AbiVariant::GuestImportAsync
} else {
AbiVariant::GuestImport
};
let import_sig = bridge.resolve.wasm_signature(import_variant, &wit_func);
if sig.is_async && import_sig.indirect_params {
anyhow::bail!(
"Function '{name}' is async with a param shape that wit-parser \
lowers via pointer form ({} flat values). Pointer-form async \
param dispatch is not yet implemented in the adapter body.",
core_params.len()
);
}
if sig.results.len() > 1 {
anyhow::bail!(
"Function '{}' has {} results; only 0 or 1 results are supported \
for tier-1 adapter generation. If you need multi-result support, \
please open an issue with a repro at https://github.com/ejrgilbert/splicer/issues",
name,
sig.results.len()
);
}
let (result_type_id, result_is_complex, core_results, result_byte_size) =
if sig.results.is_empty() {
(None, false, vec![], 0)
} else {
let rid = sig.results[0];
let flat = bridge.flat_types(rid);
if flat.len() > MAX_FLAT {
anyhow::bail!(
"Function '{name}' has a result that flattens to {} core \
values (exceeds {MAX_FLAT}). The pointer-form lowering \
required for >{MAX_FLAT} flat results is not yet \
implemented.",
flat.len()
);
}
let is_complex = if sig.is_async {
flat.len() > 1
} else {
import_sig.retptr
};
let total_bytes = bridge.size_bytes(rid);
(Some(rid), is_complex, flat, total_bytes)
};
Ok(ExtractedSig {
is_async: sig.is_async,
param_names,
param_type_ids,
result_type_id,
result_is_complex,
core_params,
core_results,
result_byte_size,
})
}