pub(super) mod blob;
pub(super) mod cells;
pub(super) mod layout;
pub(super) mod lift;
pub(super) mod schema;
pub(super) mod section_emit;
#[cfg(test)]
mod test_utils;
pub(super) mod wrapper_body;
use anyhow::{bail, Context, Result};
use wasm_encoder::Module;
use wit_component::{embed_component_metadata, ComponentEncoder, StringEncoding};
use wit_parser::abi::{AbiVariant, WasmSignature};
use wit_parser::{
Function as WitFunction, InterfaceId, Mangling, Resolve, Type, TypeId, WasmExport,
WasmExportKind, WasmImport, WorldKey,
};
use super::abi::emit::{
collect_borrow_drops, emit_data_section, emit_export_section, emit_memory_and_globals,
require_indirect_params_supported_shape, require_no_inline_resources,
synthesize_adapter_world_wit, BlobSlice,
};
use super::resolve::{decode_input_resolve, dispatch_mangling, find_target_interface};
use blob::NameInterner;
use layout::lay_out_static_memory;
use lift::{classify_func_params, classify_result_lift, ParamLift, ResultLift};
use schema::compute_schema;
use section_emit::{emit_code_section, emit_imports_and_funcs, emit_type_section, wrapper_exports};
use wrapper_body::{AfterHook, BeforeHook, WrapperCtx};
const TIER2_ADAPTER_WORLD_PACKAGE: &str = "splicer:adapter-tier2";
const TIER2_ADAPTER_WORLD_NAME: &str = "adapter";
pub(super) fn build_tier2_adapter(
target_interface: &str,
has_before: bool,
has_after: bool,
split_bytes: &[u8],
common_wit: &str,
tier2_wit: &str,
) -> Result<Vec<u8>> {
if !has_before && !has_after {
bail!(
"tier-2 adapter generation requires the middleware to export at least \
one of `splicer:tier2/before` or `splicer:tier2/after` — `trap`-only \
middleware is planned for a follow-up slice."
);
}
let mut resolve = decode_input_resolve(split_bytes)?;
let target_iface = find_target_interface(&resolve, target_interface)?;
require_supported_case(&resolve, target_iface)?;
resolve
.push_str("splicer-common.wit", common_wit)
.context("parse common WIT")?;
resolve
.push_str("splicer-tier2.wit", tier2_wit)
.context("parse tier2 WIT")?;
let world_pkg = resolve
.push_str(
"splicer-adapter-tier2.wit",
&synthesize_adapter_world_wit(
TIER2_ADAPTER_WORLD_PACKAGE,
TIER2_ADAPTER_WORLD_NAME,
target_interface,
&tier2_hook_imports(has_before, has_after),
),
)
.context("parse synthesized tier-2 adapter world WIT")?;
let world_id = resolve
.select_world(&[world_pkg], Some(TIER2_ADAPTER_WORLD_NAME))
.context("select tier-2 adapter world")?;
let funcs: Vec<&WitFunction> = resolve.interfaces[target_iface]
.functions
.values()
.collect();
let schema = compute_schema(&resolve, world_id, has_before, has_after)?;
let mut names = NameInterner::new();
let iface_name = names.intern(target_interface);
let classified = build_per_func_classified(&resolve, target_iface, &funcs, &mut names)?;
let (per_func, plan) = lay_out_static_memory(classified, &funcs, &schema, names, iface_name)?;
let mut core_module =
build_dispatch_module(&resolve, &schema, &per_func, &funcs, plan, iface_name);
embed_component_metadata(&mut core_module, &resolve, world_id, StringEncoding::UTF8)
.context("embed_component_metadata")?;
ComponentEncoder::default()
.validate(true)
.module(&core_module)
.context("ComponentEncoder::module")?
.encode()
.context("ComponentEncoder::encode")
}
fn require_supported_case(resolve: &Resolve, target_iface: InterfaceId) -> Result<()> {
let iface = &resolve.interfaces[target_iface];
if iface.functions.is_empty() {
bail!("interface has no functions");
}
require_no_inline_resources(resolve, target_iface)?;
for (name, func) in &iface.functions {
if func.kind.is_async() {
let import_sig = resolve.wasm_signature(AbiVariant::GuestImportAsync, func);
if import_sig.indirect_params {
require_indirect_params_supported_shape(resolve, name, func)?;
}
}
}
Ok(())
}
fn tier2_hook_imports(has_before: bool, has_after: bool) -> Vec<String> {
use crate::contract::{versioned_interface, TIER2_AFTER, TIER2_BEFORE, TIER2_VERSION};
let mut out = Vec::new();
if has_before {
out.push(versioned_interface(TIER2_BEFORE, TIER2_VERSION));
}
if has_after {
out.push(versioned_interface(TIER2_AFTER, TIER2_VERSION));
}
out
}
fn build_dispatch_module(
resolve: &Resolve,
schema: &schema::SchemaLayouts,
per_func: &[FuncDispatch],
funcs: &[&WitFunction],
plan: layout::StaticDataPlan,
iface_name: BlobSlice,
) -> Vec<u8> {
let mut module = Module::new();
let type_idx = emit_type_section(
&mut module,
per_func,
schema.before_hook.as_ref().map(|h| &h.import.sig),
schema.after_hook.as_ref().map(|h| &h.import.sig),
);
let func_idx = emit_imports_and_funcs(
&mut module,
resolve,
per_func,
&type_idx,
schema.before_hook.as_ref().map(|h| &h.import),
schema.after_hook.as_ref().map(|h| &h.import),
plan.event_ptr,
);
let globals = emit_memory_and_globals(&mut module, plan.bump_start);
let wrapper_exports = wrapper_exports(per_func, func_idx.init_idx);
emit_export_section(
&mut module,
&wrapper_exports,
func_idx.wrapper_base,
func_idx.init_idx,
func_idx.cabi_realloc_idx,
);
let before_hook = match (
schema.before_hook.as_ref(),
func_idx.before_hook_idx,
plan.hook_params_ptr,
) {
(Some(h), Some(idx), Some(params_ptr)) => Some(BeforeHook {
idx,
layout: &h.params_layout,
params_ptr: params_ptr as i32,
}),
(None, None, None) => None,
_ => unreachable!("before-hook schema, import idx, and params-ptr wired in lockstep"),
};
let after_hook = match (schema.after_hook.as_ref(), func_idx.after_hook_idx) {
(Some(h), Some(idx)) => Some(AfterHook {
idx,
layout: &h.params_layout,
}),
(None, None) => None,
_ => unreachable!("after-hook schema and import idx wired in lockstep"),
};
let wrapper_ctx = WrapperCtx {
schema,
resolve,
iface_name,
before_hook,
after_hook,
call_id_counter_global: globals.call_id_counter,
bump_global: globals.bump,
};
emit_code_section(
&mut module,
per_func,
funcs,
&func_idx,
&wrapper_ctx,
&globals,
);
emit_data_section(&mut module, &plan.data_segments);
module.finish()
}
pub(in crate::adapter::tier2) struct TaskReturnImport {
pub module: String,
pub name: String,
pub sig: WasmSignature,
}
pub(in crate::adapter::tier2) enum FuncShape {
Sync,
Async(TaskReturnImport),
}
impl FuncShape {
fn classify(resolve: &Resolve, target_world_key: &WorldKey, func: &WitFunction) -> Self {
if func.kind.is_async() {
let (module, name, sig) =
func.task_return_import(resolve, Some(target_world_key), Mangling::Legacy);
FuncShape::Async(TaskReturnImport { module, name, sig })
} else {
FuncShape::Sync
}
}
fn is_async(&self) -> bool {
matches!(self, FuncShape::Async(_))
}
pub fn task_return(&self) -> Option<&TaskReturnImport> {
match self {
FuncShape::Async(tr) => Some(tr),
FuncShape::Sync => None,
}
}
fn abi_variants(&self) -> (AbiVariant, AbiVariant) {
match self {
FuncShape::Async(_) => (
AbiVariant::GuestImportAsync,
AbiVariant::GuestExportAsyncStackful,
),
FuncShape::Sync => (AbiVariant::GuestImport, AbiVariant::GuestExport),
}
}
fn needs_cabi_post(&self, export_sig: &WasmSignature) -> bool {
match self {
FuncShape::Async(_) => false,
FuncShape::Sync => export_sig.retptr,
}
}
fn result_at_retptr(&self, export_sig: &WasmSignature, import_sig: &WasmSignature) -> bool {
match self {
FuncShape::Async(_) => import_sig.retptr,
FuncShape::Sync => export_sig.retptr,
}
}
}
pub(in crate::adapter::tier2) struct AfterSetup {
pub params_offset: i32,
}
pub(in crate::adapter::tier2) struct FuncClassified {
pub shape: FuncShape,
pub result_ty: Option<Type>,
pub import_module: String,
pub import_field: String,
pub export_name: String,
pub export_sig: WasmSignature,
pub import_sig: WasmSignature,
pub needs_cabi_post: bool,
pub fn_name_offset: i32,
pub fn_name_len: i32,
pub params: Vec<ParamLift>,
pub result_lift: Option<ResultLift>,
pub borrow_drops: Vec<(u32, TypeId)>,
}
pub(in crate::adapter::tier2) struct FuncDispatch {
pub shape: FuncShape,
pub result_ty: Option<Type>,
pub import_module: String,
pub import_field: String,
pub export_name: String,
pub export_sig: WasmSignature,
pub import_sig: WasmSignature,
pub needs_cabi_post: bool,
pub fn_name_offset: i32,
pub fn_name_len: i32,
pub params: Vec<lift::ParamLayout>,
pub fields_buf_offset: u32,
pub retptr_offset: Option<i32>,
pub params_record_offset: Option<i32>,
pub result_lift: Option<lift::ResultLayout>,
pub after: Option<AfterSetup>,
pub borrow_drops: Vec<(u32, TypeId)>,
}
fn build_per_func_classified(
resolve: &Resolve,
target_iface: InterfaceId,
funcs: &[&WitFunction],
names: &mut NameInterner,
) -> Result<Vec<FuncClassified>> {
let target_world_key = WorldKey::Interface(target_iface);
let mut per_func: Vec<FuncClassified> = Vec::with_capacity(funcs.len());
for func in funcs {
let fn_name_slice = names.intern(&func.name);
let params_lift = classify_func_params(resolve, func, names)?;
let shape = FuncShape::classify(resolve, &target_world_key, func);
let (import_variant, export_variant) = shape.abi_variants();
let mangling = dispatch_mangling(shape.is_async());
let (import_module, import_field) = resolve.wasm_import_name(
mangling,
WasmImport::Func {
interface: Some(&target_world_key),
func,
},
);
let export_name = resolve.wasm_export_name(
mangling,
WasmExport::Func {
interface: Some(&target_world_key),
func,
kind: WasmExportKind::Normal,
},
);
let export_sig = resolve.wasm_signature(export_variant, func);
let import_sig = resolve.wasm_signature(import_variant, func);
let needs_cabi_post = shape.needs_cabi_post(&export_sig);
let result_lift = classify_result_lift(
resolve,
func,
shape.result_at_retptr(&export_sig, &import_sig),
names,
)?;
let borrow_drops = collect_borrow_drops(resolve, func);
per_func.push(FuncClassified {
shape,
result_ty: func.result,
import_module,
import_field,
export_name,
export_sig,
import_sig,
needs_cabi_post,
fn_name_offset: fn_name_slice.off as i32,
fn_name_len: fn_name_slice.len as i32,
params: params_lift,
result_lift,
borrow_drops,
});
}
Ok(per_func)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dispatch_module_roundtrips_through_component_encoder() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "add") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
)
)
(core instance $i (instantiate $m))
(alias core export $i "add" (core func $add))
(type $add-ty (func (param "x" u32) (param "y" u32) (result u32)))
(func $add-lifted (type $add-ty) (canon lift (core func $add)))
(instance $api-inst (export "add" (func $add-lifted)))
(export "my:math/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:math/api@1.0.0" (instance $api "my:math/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:math/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_tuple_param_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "consume") (param i32 i32))
)
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(type $consume-ty (func (param "t" (tuple u32 s32))))
(func $consume-lifted (type $consume-ty) (canon lift (core func $consume)))
(instance $api-inst (export "consume" (func $consume-lifted)))
(export "my:tup/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:tup/api@1.0.0" (instance $api "my:tup/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:tup/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for tuple param");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_option_param_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "consume") (param i32 i32))
)
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(type $consume-ty (func (param "o" (option u32))))
(func $consume-lifted (type $consume-ty) (canon lift (core func $consume)))
(instance $api-inst (export "consume" (func $consume-lifted)))
(export "my:opt/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:opt/api@1.0.0" (instance $api "my:opt/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:opt/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for option param");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_single_slot_tuple_result_falls_through() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "one-val") (param i32) (result i32)
local.get 0
)
)
(core instance $i (instantiate $m))
(alias core export $i "one-val" (core func $one))
(type $one-ty (func (param "x" u32) (result (tuple u32))))
(func $one-lifted (type $one-ty) (canon lift (core func $one)))
(instance $api-inst (export "one-val" (func $one-lifted)))
(export "my:tup1/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:tup1/api@1.0.0" (instance $api "my:tup1/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:tup1/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("single-slot tuple result must fall through to no-lift, not panic");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted adapter component should validate");
}
#[test]
fn dispatch_module_with_tuple_result_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(memory (export "memory") 1)
(func (export "two-vals") (param i32) (result i32)
i32.const 0x1000
local.get 0
i32.store
i32.const 0x1000
i32.const -1
i32.store offset=4
i32.const 0x1000
)
(func (export "cabi_post_two-vals") (param i32))
)
(core instance $i (instantiate $m))
(alias core export $i "two-vals" (core func $two))
(alias core export $i "cabi_post_two-vals" (core func $two_post))
(alias core export $i "memory" (core memory $mem))
(type $two-ty (func (param "x" u32) (result (tuple u32 s32))))
(func $two-lifted (type $two-ty)
(canon lift (core func $two) (memory $mem)
(post-return (func $two_post))))
(instance $api-inst (export "two-vals" (func $two-lifted)))
(export "my:tup-ret/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:tup-ret/api@1.0.0" (instance $api "my:tup-ret/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:tup-ret/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for tuple result");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_option_result_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(memory (export "memory") 1)
(func (export "maybe-val") (param i32) (result i32)
i32.const 0x1000
i32.const 1
i32.store
i32.const 0x1000
local.get 0
i32.store offset=4
i32.const 0x1000
)
(func (export "cabi_post_maybe-val") (param i32))
)
(core instance $i (instantiate $m))
(alias core export $i "maybe-val" (core func $maybe))
(alias core export $i "cabi_post_maybe-val" (core func $maybe_post))
(alias core export $i "memory" (core memory $mem))
(type $maybe-ty (func (param "x" u32) (result (option u32))))
(func $maybe-lifted (type $maybe-ty)
(canon lift (core func $maybe) (memory $mem)
(post-return (func $maybe_post))))
(instance $api-inst (export "maybe-val" (func $maybe-lifted)))
(export "my:opt-ret/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:opt-ret/api@1.0.0" (instance $api "my:opt-ret/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:opt-ret/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for option result");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_flags_param_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "consume") (param i32))
)
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(type $perms (flags "read" "write" "exec"))
(export $perms-export "fperms" (type $perms))
(type $consume-ty (func (param "p" $perms-export)))
(func $consume-lifted (type $consume-ty) (canon lift (core func $consume)))
(instance $api-inst
(export "fperms" (type $perms-export))
(export "consume" (func $consume-lifted)))
(export "my:fl/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:fl/api@1.0.0" (instance $api "my:fl/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:fl/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for flags param");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_flags_param_and_flags_result_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "thru") (param i32) (result i32) local.get 0)
)
(core instance $i (instantiate $m))
(alias core export $i "thru" (core func $thru))
(type $perms (flags "read" "write" "exec"))
(export $perms-export "fperms" (type $perms))
(type $thru-ty (func (param "p" $perms-export) (result $perms-export)))
(func $thru-lifted (type $thru-ty) (canon lift (core func $thru)))
(instance $api-inst
(export "fperms" (type $perms-export))
(export "thru" (func $thru-lifted)))
(export "my:flio/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:flio/api@1.0.0" (instance $api "my:flio/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:flio/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect(
"tier-2 adapter generation should succeed for flags param + flags result on the same fn",
);
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_char_param_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "consume") (param i32))
)
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(type $consume-ty (func (param "c" char)))
(func $consume-lifted (type $consume-ty) (canon lift (core func $consume)))
(instance $api-inst (export "consume" (func $consume-lifted)))
(export "my:ch/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:ch/api@1.0.0" (instance $api "my:ch/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:ch/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for char param");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_char_list_param_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(memory (export "memory") 1)
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32)
i32.const 0x4000)
(func (export "consume") (param i32 i32))
)
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(alias core export $i "memory" (core memory $mem))
(alias core export $i "cabi_realloc" (core func $realloc))
(type $consume-ty (func (param "xs" (list char))))
(func $consume-lifted (type $consume-ty)
(canon lift (core func $consume) (memory $mem) (realloc (func $realloc))))
(instance $api-inst (export "consume" (func $consume-lifted)))
(export "my:lc/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:lc/api@1.0.0" (instance $api "my:lc/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:lc/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for list<char> param");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_variant_param_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "consume") (param i32 i32))
)
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(type $shape (variant (case "circle") (case "sq" u32) (case "tri" u32)))
(export $shape-export "shape" (type $shape))
(type $consume-ty (func (param "s" $shape-export)))
(func $consume-lifted (type $consume-ty) (canon lift (core func $consume)))
(instance $api-inst
(export "shape" (type $shape-export))
(export "consume" (func $consume-lifted)))
(export "my:vt/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:vt/api@1.0.0" (instance $api "my:vt/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:vt/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for variant param");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_inline_resource_bails() {
let wat = r#"(component
(component $inner
(core module $m (func (export "consume") (param i32)))
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(type $r (resource (rep i32)))
(export $r-export "my-res" (type $r))
(type $own-r (own $r-export))
(type $consume-ty (func (param "h" $own-r)))
(func $consume-lifted (type $consume-ty) (canon lift (core func $consume)))
(instance $api-inst
(export "my-res" (type $r-export))
(export "consume" (func $consume-lifted)))
(export "my:rh/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:rh/api@1.0.0" (instance $api "my:rh/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let err = build_tier2_adapter(
"my:rh/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect_err("inline-resource interface must bail");
let msg = err.to_string();
assert!(
msg.contains("declares resource `my-res` inline"),
"bail should call out the inline resource; got: {msg}",
);
assert!(
msg.contains("factored-types pattern"),
"bail should point at the factored-types fix; got: {msg}",
);
}
#[test]
fn dispatch_module_with_resource_handle_param_roundtrips() {
let wat = r#"(component
(core module $main
(func (export "my:rh/api@1.0.0#consume-own") (param i32))
(func (export "my:rh/api@1.0.0#consume-borrow") (param i32))
(func (export "my:rh/types@1.0.0#[resource-drop]my-res") (param i32))
(memory (export "memory") 1)
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) i32.const 0)
)
(type $my-res (resource (rep i32)))
(core instance $main (instantiate $main))
(alias core export $main "memory" (core memory $memory))
(component $types-shim
(import "import-type-my-res" (type $r (sub resource)))
(export "my-res" (type $r))
)
(instance $types-inst (instantiate $types-shim
(with "import-type-my-res" (type $my-res))
))
(export $types-export "my:rh/types@1.0.0" (instance $types-inst))
(type $own-r (own $my-res))
(type $consume-own-ty (func (param "h" $own-r)))
(alias core export $main "my:rh/api@1.0.0#consume-own" (core func $consume-own-core))
(alias core export $main "cabi_realloc" (core func $cabi_realloc))
(func $consume-own (type $consume-own-ty) (canon lift (core func $consume-own-core)))
(type $borrow-r (borrow $my-res))
(type $consume-borrow-ty (func (param "h" $borrow-r)))
(alias core export $main "my:rh/api@1.0.0#consume-borrow" (core func $consume-borrow-core))
(func $consume-borrow (type $consume-borrow-ty) (canon lift (core func $consume-borrow-core)))
(alias export $types-export "my-res" (type $r-aliased))
(component $api-shim
(import "import-type-my-res" (type $r (sub resource)))
(import "import-type-my-res0" (type $r0 (eq 0)))
(type $own-r0 (own 1))
(type $f-own (func (param "h" $own-r0)))
(import "import-func-consume-own" (func $consume-own (type $f-own)))
(type $borrow-r0 (borrow 1))
(type $f-borrow (func (param "h" $borrow-r0)))
(import "import-func-consume-borrow" (func $consume-borrow (type $f-borrow)))
(export $r-export "my-res" (type $r))
(type $own-out (own $r-export))
(type $f-own-out (func (param "h" $own-out)))
(export "consume-own" (func $consume-own) (func (type $f-own-out)))
(type $borrow-out (borrow $r-export))
(type $f-borrow-out (func (param "h" $borrow-out)))
(export "consume-borrow" (func $consume-borrow) (func (type $f-borrow-out)))
)
(instance $api-inst (instantiate $api-shim
(with "import-func-consume-own" (func $consume-own))
(with "import-func-consume-borrow" (func $consume-borrow))
(with "import-type-my-res" (type $r-aliased))
(with "import-type-my-res0" (type $my-res))
))
(export "my:rh/api@1.0.0" (instance $api-inst))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:rh/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect(
"tier-2 adapter generation should succeed for factored-types resource handle param",
);
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_resource_handle_result_roundtrips() {
let wat = r#"(component
(core module $main
(func (export "my:rhret/api@1.0.0#make") (result i32) i32.const 0)
(func (export "my:rhret/types@1.0.0#[resource-drop]my-res") (param i32))
(memory (export "memory") 1)
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) i32.const 0)
)
(type $my-res (resource (rep i32)))
(core instance $main (instantiate $main))
(alias core export $main "memory" (core memory $memory))
(component $types-shim
(import "import-type-my-res" (type $r (sub resource)))
(export "my-res" (type $r))
)
(instance $types-inst (instantiate $types-shim
(with "import-type-my-res" (type $my-res))
))
(export $types-export "my:rhret/types@1.0.0" (instance $types-inst))
(type $own-r (own $my-res))
(type $make-ty (func (result $own-r)))
(alias core export $main "my:rhret/api@1.0.0#make" (core func $make-core))
(alias core export $main "cabi_realloc" (core func $cabi_realloc))
(func $make (type $make-ty) (canon lift (core func $make-core)))
(alias export $types-export "my-res" (type $r-aliased))
(component $api-shim
(import "import-type-my-res" (type $r (sub resource)))
(import "import-type-my-res0" (type $r0 (eq 0)))
(type $own-in (own 1))
(type $f-in (func (result $own-in)))
(import "import-func-make" (func $make (type $f-in)))
(export $r-export "my-res" (type $r))
(type $own-out (own $r-export))
(type $f-out (func (result $own-out)))
(export "make" (func $make) (func (type $f-out)))
)
(instance $api-inst (instantiate $api-shim
(with "import-func-make" (func $make))
(with "import-type-my-res" (type $r-aliased))
(with "import-type-my-res0" (type $my-res))
))
(export "my:rhret/api@1.0.0" (instance $api-inst))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:rhret/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect(
"tier-2 adapter generation should succeed for factored-types resource handle result",
);
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_handle_param_and_handle_result_roundtrips() {
let wat = r#"(component
(core module $main
(func (export "my:rhio/api@1.0.0#thru") (param i32) (result i32) local.get 0)
(func (export "my:rhio/types@1.0.0#[resource-drop]my-res") (param i32))
(memory (export "memory") 1)
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32) i32.const 0)
)
(type $my-res (resource (rep i32)))
(core instance $main (instantiate $main))
(alias core export $main "memory" (core memory $memory))
(component $types-shim
(import "import-type-my-res" (type $r (sub resource)))
(export "my-res" (type $r))
)
(instance $types-inst (instantiate $types-shim
(with "import-type-my-res" (type $my-res))
))
(export $types-export "my:rhio/types@1.0.0" (instance $types-inst))
(type $own-r (own $my-res))
(type $thru-ty (func (param "h" $own-r) (result $own-r)))
(alias core export $main "my:rhio/api@1.0.0#thru" (core func $thru-core))
(alias core export $main "cabi_realloc" (core func $cabi_realloc))
(func $thru (type $thru-ty) (canon lift (core func $thru-core)))
(alias export $types-export "my-res" (type $r-aliased))
(component $api-shim
(import "import-type-my-res" (type $r (sub resource)))
(import "import-type-my-res0" (type $r0 (eq 0)))
(type $own-r0 (own 1))
(type $f-thru (func (param "h" $own-r0) (result $own-r0)))
(import "import-func-thru" (func $thru (type $f-thru)))
(export $r-export "my-res" (type $r))
(type $own-out (own $r-export))
(type $f-thru-out (func (param "h" $own-out) (result $own-out)))
(export "thru" (func $thru) (func (type $f-thru-out)))
)
(instance $api-inst (instantiate $api-shim
(with "import-func-thru" (func $thru))
(with "import-type-my-res" (type $r-aliased))
(with "import-type-my-res0" (type $my-res))
))
(export "my:rhio/api@1.0.0" (instance $api-inst))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:rhio/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect(
"tier-2 adapter generation should succeed for handle param + handle result on the same fn",
);
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_char_result_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "make") (result i32) i32.const 0x4E2D)
)
(core instance $i (instantiate $m))
(alias core export $i "make" (core func $make))
(type $make-ty (func (result char)))
(func $make-lifted (type $make-ty) (canon lift (core func $make)))
(instance $api-inst (export "make" (func $make-lifted)))
(export "my:chret/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:chret/api@1.0.0" (instance $api "my:chret/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:chret/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for char result");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_error_context_param_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "consume") (param i32))
)
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(type $consume-ty (func (param "e" error-context)))
(func $consume-lifted (type $consume-ty) (canon lift (core func $consume)))
(instance $api-inst (export "consume" (func $consume-lifted)))
(export "my:ec/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:ec/api@1.0.0" (instance $api "my:ec/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:ec/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for error-context param");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_error_context_result_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "make") (result i32) i32.const 0)
)
(core instance $i (instantiate $m))
(alias core export $i "make" (core func $make))
(type $make-ty (func (result error-context)))
(func $make-lifted (type $make-ty) (canon lift (core func $make)))
(instance $api-inst (export "make" (func $make-lifted)))
(export "my:ecret/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:ecret/api@1.0.0" (instance $api "my:ecret/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:ecret/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for error-context result");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_variant_result_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(memory (export "memory") 1)
(func (export "make") (result i32)
i32.const 0x1000
i32.const 2
i32.store
i32.const 0x1000
i32.const 42
i32.store offset=4
i32.const 0x1000
)
(func (export "cabi_post_make") (param i32))
)
(core instance $i (instantiate $m))
(alias core export $i "make" (core func $make))
(alias core export $i "cabi_post_make" (core func $make_post))
(alias core export $i "memory" (core memory $mem))
(type $shape (variant (case "circle") (case "sq" u32) (case "tri" u32)))
(export $shape-export "shape" (type $shape))
(type $make-ty (func (result $shape-export)))
(func $make-lifted (type $make-ty)
(canon lift (core func $make) (memory $mem)
(post-return (func $make_post))))
(instance $api-inst
(export "shape" (type $shape-export))
(export "make" (func $make-lifted)))
(export "my:vtret/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:vtret/api@1.0.0" (instance $api "my:vtret/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:vtret/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for variant result");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_list_result_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(memory (export "memory") 1)
(data (i32.const 0x2000) "\0a\00\00\00\14\00\00\00\1e\00\00\00")
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32)
i32.const 0x4000)
(func (export "make") (result i32)
i32.const 0x1000
i32.const 0x2000
i32.store
i32.const 0x1000
i32.const 3
i32.store offset=4
i32.const 0x1000
)
(func (export "cabi_post_make") (param i32))
)
(core instance $i (instantiate $m))
(alias core export $i "make" (core func $make))
(alias core export $i "cabi_post_make" (core func $make_post))
(alias core export $i "memory" (core memory $mem))
(alias core export $i "cabi_realloc" (core func $realloc))
(type $make-ty (func (result (list u32))))
(func $make-lifted (type $make-ty)
(canon lift (core func $make) (memory $mem) (realloc (func $realloc))
(post-return (func $make_post))))
(instance $api-inst (export "make" (func $make-lifted)))
(export "my:listret/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:listret/api@1.0.0" (instance $api "my:listret/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:listret/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for list<u32> result");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_option_list_param_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(memory (export "memory") 1)
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32)
i32.const 0x4000)
(func (export "consume") (param i32 i32))
)
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(alias core export $i "memory" (core memory $mem))
(alias core export $i "cabi_realloc" (core func $realloc))
(type $consume-ty (func (param "xs" (list (option u32)))))
(func $consume-lifted (type $consume-ty)
(canon lift (core func $consume) (memory $mem) (realloc (func $realloc))))
(instance $api-inst (export "consume" (func $consume-lifted)))
(export "my:lo/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:lo/api@1.0.0" (instance $api "my:lo/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:lo/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for list<option<u32>> param");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_result_list_param_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(memory (export "memory") 1)
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32)
i32.const 0x4000)
(func (export "consume") (param i32 i32))
)
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(alias core export $i "memory" (core memory $mem))
(alias core export $i "cabi_realloc" (core func $realloc))
(type $consume-ty (func (param "xs" (list (result u32 (error string))))))
(func $consume-lifted (type $consume-ty)
(canon lift (core func $consume) (memory $mem) (realloc (func $realloc))))
(instance $api-inst (export "consume" (func $consume-lifted)))
(export "my:lr/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:lr/api@1.0.0" (instance $api "my:lr/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:lr/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for list<result<u32, string>> param");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_tuple_list_param_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(memory (export "memory") 1)
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32)
i32.const 0x4000)
(func (export "consume") (param i32 i32))
)
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(alias core export $i "memory" (core memory $mem))
(alias core export $i "cabi_realloc" (core func $realloc))
(type $consume-ty (func (param "xs" (list (tuple u32 string)))))
(func $consume-lifted (type $consume-ty)
(canon lift (core func $consume) (memory $mem) (realloc (func $realloc))))
(instance $api-inst (export "consume" (func $consume-lifted)))
(export "my:lt/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:lt/api@1.0.0" (instance $api "my:lt/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:lt/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for list<tuple<u32, string>> param");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_char_list_result_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(memory (export "memory") 1)
(data (i32.const 0x2000) "\78\00\00\00")
(func (export "cabi_realloc") (param i32 i32 i32 i32) (result i32)
i32.const 0x4000)
(func (export "make") (result i32)
i32.const 0x1000
i32.const 0x2000
i32.store
i32.const 0x1000
i32.const 1
i32.store offset=4
i32.const 0x1000
)
(func (export "cabi_post_make") (param i32))
)
(core instance $i (instantiate $m))
(alias core export $i "make" (core func $make))
(alias core export $i "cabi_post_make" (core func $make_post))
(alias core export $i "memory" (core memory $mem))
(alias core export $i "cabi_realloc" (core func $realloc))
(type $make-ty (func (result (list char))))
(func $make-lifted (type $make-ty)
(canon lift (core func $make) (memory $mem) (realloc (func $realloc))
(post-return (func $make_post))))
(instance $api-inst (export "make" (func $make-lifted)))
(export "my:lcret/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:lcret/api@1.0.0" (instance $api "my:lcret/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:lcret/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for list<char> result");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_single_slot_variant_result_falls_through() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "noop") (result i32) i32.const 0)
)
(core instance $i (instantiate $m))
(alias core export $i "noop" (core func $noop))
(type $only (variant (case "only")))
(export $only-export "only" (type $only))
(type $noop-ty (func (result $only-export)))
(func $noop-lifted (type $noop-ty) (canon lift (core func $noop)))
(instance $api-inst
(export "only" (type $only-export))
(export "noop" (func $noop-lifted)))
(export "my:vt1/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:vt1/api@1.0.0" (instance $api "my:vt1/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:vt1/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("single-slot variant must fall through to no-lift, not panic");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted adapter component should validate");
}
#[test]
fn dispatch_module_with_flags_result_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "produce") (result i32) i32.const 5)
)
(core instance $i (instantiate $m))
(alias core export $i "produce" (core func $produce))
(type $perms (flags "read" "write" "exec"))
(export $perms-export "fperms" (type $perms))
(type $produce-ty (func (result $perms-export)))
(func $produce-lifted (type $produce-ty) (canon lift (core func $produce)))
(instance $api-inst
(export "fperms" (type $perms-export))
(export "produce" (func $produce-lifted)))
(export "my:flret/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:flret/api@1.0.0" (instance $api "my:flret/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:flret/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for flags result");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_result_param_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "consume") (param i32 i32))
)
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(type $consume-ty (func (param "r" (result u32 (error u32)))))
(func $consume-lifted (type $consume-ty) (canon lift (core func $consume)))
(instance $api-inst (export "consume" (func $consume-lifted)))
(export "my:res/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:res/api@1.0.0" (instance $api "my:res/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:res/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for result param");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_widening_result_param_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "consume") (param i32 i64))
)
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(type $consume-ty (func (param "r" (result u32 (error u64)))))
(func $consume-lifted (type $consume-ty) (canon lift (core func $consume)))
(instance $api-inst (export "consume" (func $consume-lifted)))
(export "my:res/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:res/api@1.0.0" (instance $api "my:res/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:res/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation must succeed for widening result param");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted adapter must validate (joined-flat bitcast on ok-arm read)");
}
#[test]
fn dispatch_module_with_widening_variant_param_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "consume") (param i32 i64))
)
(core instance $i (instantiate $m))
(alias core export $i "consume" (core func $consume))
(type $tri (variant (case "a" u32) (case "b" u64) (case "c" f64)))
(export $tri-export "tri" (type $tri))
(type $consume-ty (func (param "v" $tri-export)))
(func $consume-lifted (type $consume-ty) (canon lift (core func $consume)))
(instance $api-inst
(export "tri" (type $tri-export))
(export "consume" (func $consume-lifted)))
(export "my:vt/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:vt/api@1.0.0" (instance $api "my:vt/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:vt/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation must succeed for mixed-width variant param");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted adapter must validate (per-arm bitcasts on a/c arms)");
}
#[test]
fn dispatch_module_with_result_result_roundtrips() {
let wat = r#"(component
(component $inner
(core module $m
(memory (export "memory") 1)
(func (export "either") (param i32) (result i32)
i32.const 0x1000
i32.const 0
i32.store
i32.const 0x1000
local.get 0
i32.store offset=4
i32.const 0x1000
)
(func (export "cabi_post_either") (param i32))
)
(core instance $i (instantiate $m))
(alias core export $i "either" (core func $either))
(alias core export $i "cabi_post_either" (core func $either_post))
(alias core export $i "memory" (core memory $mem))
(type $either-ty (func (param "x" u32) (result (result u32 (error u32)))))
(func $either-lifted (type $either-ty)
(canon lift (core func $either) (memory $mem)
(post-return (func $either_post))))
(instance $api-inst (export "either" (func $either-lifted)))
(export "my:res-ret/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:res-ret/api@1.0.0" (instance $api "my:res-ret/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:res-ret/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for result result");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn dispatch_module_with_single_slot_result_result_falls_through() {
let wat = r#"(component
(component $inner
(core module $m
(func (export "noop") (result i32)
i32.const 0
)
)
(core instance $i (instantiate $m))
(alias core export $i "noop" (core func $noop))
(type $noop-ty (func (result (result))))
(func $noop-lifted (type $noop-ty) (canon lift (core func $noop)))
(instance $api-inst (export "noop" (func $noop-lifted)))
(export "my:res1/api@1.0.0" (instance $api-inst))
)
(instance $api (instantiate $inner))
(export "my:res1/api@1.0.0" (instance $api "my:res1/api@1.0.0"))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"my:res1/api@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("single-slot result must fall through to no-lift, not panic");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted adapter component should validate");
}
#[test]
fn async_5_u32_params_validates() {
let wat = r#"(component
(type (;0;) (instance
(type (;0;) (func async
(param "a" u32) (param "b" u32) (param "c" u32)
(param "d" u32) (param "e" u32) (result u32)))
(export "many" (func (type 0)))
))
(import "test:pkg/many@1.0.0" (instance (type 0)))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"test:pkg/many@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for 5×u32 async params");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn async_mixed_primitives_indirect_params_validates() {
let wat = r#"(component
(type (;0;) (instance
(type (;0;) (func async
(param "a" u32) (param "b" u64) (param "c" f32)
(param "d" f64) (param "e" bool) (param "f" char)
(result u32)))
(export "mixed" (func (type 0)))
))
(import "test:pkg/mixed-async@1.0.0" (instance (type 0)))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"test:pkg/mixed-async@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for mixed-primitive async params");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn async_aggregates_indirect_params_validates() {
let wat = r#"(component
(type (;0;) (instance
(type (;0;) (record
(field "a" u32) (field "b" u32) (field "c" u32)
(field "d" u32) (field "e" u32)))
(export "rec5" (type (eq 0)))
(type (;2;) (tuple u32 u64 f32 f64 bool))
(type (;3;) (enum "red" "green" "blue"))
(export "color" (type (eq 3)))
(type (;5;) (flags "read" "write" "exec"))
(export "perms" (type (eq 5)))
(type (;7;) (func async
(param "r" 1) (param "t" 2) (param "c" 4) (param "f" 6)
(result u32)))
(export "many" (func (type 7)))
))
(import "test:pkg/agg-async@1.0.0" (instance (type 0)))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"test:pkg/agg-async@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for aggregate async params");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
#[test]
fn async_dispatch_shapes_indirect_params_validates() {
let wat = r#"(component
(type (;0;) (instance
(type (;0;) (list u32))
(type (;1;) (option u32))
(type (;2;) (result u32 (error u32)))
(type (;3;) (func async
(param "s" string)
(param "l" 0)
(param "o" 1)
(param "r" 2)
(param "a" u32)
(param "b" u32)
(result u32)))
(export "many" (func (type 3)))
))
(import "test:pkg/disp-async@1.0.0" (instance (type 0)))
)"#;
let split_bytes = wat::parse_str(wat).expect("WAT must parse");
let common_wit = include_str!("../../../wit/common/world.wit");
let tier2_wit = include_str!("../../../wit/tier2/world.wit");
let bytes = build_tier2_adapter(
"test:pkg/disp-async@1.0.0",
true,
true,
&split_bytes,
common_wit,
tier2_wit,
)
.expect("tier-2 adapter generation should succeed for dispatch shapes");
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
.validate_all(&bytes)
.expect("emitted tier-2 adapter component should validate");
}
}