support dynamic function calls in component model (#4442)
* support dynamic function calls in component model This addresses #4310, introducing a new `component::values::Val` type for representing component values dynamically, as well as `component::types::Type` for representing the corresponding interface types. It also adds a `call` method to `component::func::Func`, which takes a slice of `Val`s as parameters and returns a `Result<Val>` representing the result. Note that I've moved `post_return` and `call_raw` from `TypedFunc` to `Func` since there was nothing specific to `TypedFunc` about them, and I wanted to reuse them. The code in both is unchanged beyond the trivial tweaks to make them fit in their new home. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * order variants and match cases more consistently Signed-off-by: Joel Dice <joel.dice@fermyon.com> * implement lift for String, Box<str>, etc. This also removes the redundant `store` parameter from `Type::load`. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * implement code review feedback This fixes a few issues: - Bad offset calculation when lowering - Missing variant padding - Style issues regarding `types::Handle` - Missed opportunities to reuse `Lift` and `Lower` impls It also adds forwarding `Lift` impls for `Box<[T]>`, `Vec<T>`, etc. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * move `new_*` methods to specific `types` structs Per review feedback, I've moved `Type::new_record` to `Record::new_val` and added a `Type::unwrap_record` method; likewise for the other kinds of types. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * make tuple, option, and expected type comparisons recursive These types should compare as equal across component boundaries as long as their type parameters are equal. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * improve error diagnostic in `Type::check` We now distinguish between more failure cases to provide an informative error message. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * address review feedback - Remove `WasmStr::to_str_from_memory` and `WasmList::get_from_memory` - add `try_new` methods to various `values` types - avoid using `ExactSizeIterator::len` where we can't trust it - fix over-constrained bounds on forwarded `ComponentType` impls Signed-off-by: Joel Dice <joel.dice@fermyon.com> * rearrange code per review feedback - Move functions from `types` to `values` module so we can make certain struct fields private - Rename `try_new` to just `new` Signed-off-by: Joel Dice <joel.dice@fermyon.com> * remove special-case equality test for tuples, options, and expecteds Instead, I've added a FIXME comment and will open an issue to do recursive structural equality testing. Signed-off-by: Joel Dice <joel.dice@fermyon.com>
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use std::fmt::Write;
|
||||
use std::iter;
|
||||
use wasmtime::component::{Component, ComponentParams, Lift, Lower, TypedFunc};
|
||||
use wasmtime::{AsContextMut, Config, Engine};
|
||||
|
||||
mod dynamic;
|
||||
mod func;
|
||||
mod import;
|
||||
mod instance;
|
||||
@@ -148,3 +151,128 @@ fn components_importing_modules() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
enum Type {
|
||||
S8,
|
||||
U8,
|
||||
S16,
|
||||
U16,
|
||||
I32,
|
||||
I64,
|
||||
F32,
|
||||
F64,
|
||||
}
|
||||
|
||||
impl Type {
|
||||
fn store(&self) -> &'static str {
|
||||
match self {
|
||||
Self::S8 | Self::U8 => "store8",
|
||||
Self::S16 | Self::U16 => "store16",
|
||||
Self::I32 | Self::F32 | Self::I64 | Self::F64 => "store",
|
||||
}
|
||||
}
|
||||
|
||||
fn primitive(&self) -> &'static str {
|
||||
match self {
|
||||
Self::S8 | Self::U8 | Self::S16 | Self::U16 | Self::I32 => "i32",
|
||||
Self::I64 => "i64",
|
||||
Self::F32 => "f32",
|
||||
Self::F64 => "f64",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
struct Param(Type, Option<usize>);
|
||||
|
||||
fn make_echo_component(type_definition: &str, type_size: u32) -> String {
|
||||
let mut offset = 0;
|
||||
make_echo_component_with_params(
|
||||
type_definition,
|
||||
&iter::repeat(Type::I32)
|
||||
.map(|ty| {
|
||||
let param = Param(ty, Some(offset));
|
||||
offset += 4;
|
||||
param
|
||||
})
|
||||
.take(usize::try_from(type_size).unwrap() / 4)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn make_echo_component_with_params(type_definition: &str, params: &[Param]) -> String {
|
||||
let func = if params.len() == 1 || params.len() > 16 {
|
||||
let primitive = if params.len() == 1 {
|
||||
params[0].0.primitive()
|
||||
} else {
|
||||
"i32"
|
||||
};
|
||||
|
||||
format!(
|
||||
r#"
|
||||
(func (export "echo") (param {primitive}) (result {primitive})
|
||||
local.get 0
|
||||
)"#,
|
||||
)
|
||||
} else {
|
||||
let mut param_string = String::new();
|
||||
let mut store = String::new();
|
||||
let mut size = 8;
|
||||
|
||||
for (index, Param(ty, offset)) in params.iter().enumerate() {
|
||||
let primitive = ty.primitive();
|
||||
|
||||
write!(&mut param_string, " {primitive}").unwrap();
|
||||
if let Some(offset) = offset {
|
||||
write!(
|
||||
&mut store,
|
||||
"({primitive}.{} offset={offset} (local.get $base) (local.get {index}))",
|
||||
ty.store(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
size = size.max(offset + 8);
|
||||
}
|
||||
}
|
||||
|
||||
format!(
|
||||
r#"
|
||||
(func (export "echo") (param{param_string}) (result i32)
|
||||
(local $base i32)
|
||||
(local.set $base
|
||||
(call $realloc
|
||||
(i32.const 0)
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
(i32.const {size})))
|
||||
{store}
|
||||
local.get $base
|
||||
)"#
|
||||
)
|
||||
};
|
||||
|
||||
format!(
|
||||
r#"
|
||||
(component
|
||||
(core module $m
|
||||
{func}
|
||||
|
||||
(memory (export "memory") 1)
|
||||
{REALLOC_AND_FREE}
|
||||
)
|
||||
|
||||
(core instance $i (instantiate $m))
|
||||
|
||||
(type $Foo {type_definition})
|
||||
|
||||
(func (export "echo") (param $Foo) (result $Foo)
|
||||
(canon lift
|
||||
(core func $i "echo")
|
||||
(memory $i "memory")
|
||||
(realloc (func $i "realloc"))
|
||||
)
|
||||
)
|
||||
)"#
|
||||
)
|
||||
}
|
||||
|
||||
511
tests/all/component_model/dynamic.rs
Normal file
511
tests/all/component_model/dynamic.rs
Normal file
@@ -0,0 +1,511 @@
|
||||
use super::{make_echo_component, make_echo_component_with_params, Param, Type};
|
||||
use anyhow::Result;
|
||||
use wasmtime::component::{self, Component, Func, Linker, Val};
|
||||
use wasmtime::{AsContextMut, Store};
|
||||
|
||||
trait FuncExt {
|
||||
fn call_and_post_return(&self, store: impl AsContextMut, args: &[Val]) -> Result<Val>;
|
||||
}
|
||||
|
||||
impl FuncExt for Func {
|
||||
fn call_and_post_return(&self, mut store: impl AsContextMut, args: &[Val]) -> Result<Val> {
|
||||
let result = self.call(&mut store, args)?;
|
||||
self.post_return(&mut store)?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn primitives() -> Result<()> {
|
||||
let engine = super::engine();
|
||||
let mut store = Store::new(&engine, ());
|
||||
|
||||
for (input, ty, param) in [
|
||||
(Val::Bool(true), "bool", Param(Type::U8, Some(0))),
|
||||
(Val::S8(-42), "s8", Param(Type::S8, Some(0))),
|
||||
(Val::U8(42), "u8", Param(Type::U8, Some(0))),
|
||||
(Val::S16(-4242), "s16", Param(Type::S16, Some(0))),
|
||||
(Val::U16(4242), "u16", Param(Type::U16, Some(0))),
|
||||
(Val::S32(-314159265), "s32", Param(Type::I32, Some(0))),
|
||||
(Val::U32(314159265), "u32", Param(Type::I32, Some(0))),
|
||||
(Val::S64(-31415926535897), "s64", Param(Type::I64, Some(0))),
|
||||
(Val::U64(31415926535897), "u64", Param(Type::I64, Some(0))),
|
||||
(
|
||||
Val::Float32(3.14159265_f32.to_bits()),
|
||||
"float32",
|
||||
Param(Type::F32, Some(0)),
|
||||
),
|
||||
(
|
||||
Val::Float64(3.14159265_f64.to_bits()),
|
||||
"float64",
|
||||
Param(Type::F64, Some(0)),
|
||||
),
|
||||
(Val::Char('🦀'), "char", Param(Type::I32, Some(0))),
|
||||
] {
|
||||
let component = Component::new(&engine, make_echo_component_with_params(ty, &[param]))?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_func(&mut store, "echo").unwrap();
|
||||
let output = func.call_and_post_return(&mut store, &[input.clone()])?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
}
|
||||
|
||||
// Sad path: type mismatch
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component_with_params("float64", &[Param(Type::F64, Some(0))]),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_func(&mut store, "echo").unwrap();
|
||||
let err = func
|
||||
.call_and_post_return(&mut store, &[Val::U64(42)])
|
||||
.unwrap_err();
|
||||
|
||||
assert!(err.to_string().contains("type mismatch"), "{err}");
|
||||
|
||||
// Sad path: arity mismatch (too many)
|
||||
|
||||
let err = func
|
||||
.call_and_post_return(
|
||||
&mut store,
|
||||
&[
|
||||
Val::Float64(3.14159265_f64.to_bits()),
|
||||
Val::Float64(3.14159265_f64.to_bits()),
|
||||
],
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert!(
|
||||
err.to_string().contains("expected 1 argument(s), got 2"),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
// Sad path: arity mismatch (too few)
|
||||
|
||||
let err = func.call_and_post_return(&mut store, &[]).unwrap_err();
|
||||
|
||||
assert!(
|
||||
err.to_string().contains("expected 1 argument(s), got 0"),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strings() -> Result<()> {
|
||||
let engine = super::engine();
|
||||
let mut store = Store::new(&engine, ());
|
||||
|
||||
let component = Component::new(&engine, make_echo_component("string", 8))?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_func(&mut store, "echo").unwrap();
|
||||
let input = Val::String(Box::from("hello, component!"));
|
||||
let output = func.call_and_post_return(&mut store, &[input.clone()])?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lists() -> Result<()> {
|
||||
let engine = super::engine();
|
||||
let mut store = Store::new(&engine, ());
|
||||
|
||||
let component = Component::new(&engine, make_echo_component("(list u32)", 8))?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_func(&mut store, "echo").unwrap();
|
||||
let ty = &func.params(&store)[0];
|
||||
let input = ty.unwrap_list().new_val(Box::new([
|
||||
Val::U32(32343),
|
||||
Val::U32(79023439),
|
||||
Val::U32(2084037802),
|
||||
]))?;
|
||||
let output = func.call_and_post_return(&mut store, &[input.clone()])?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
|
||||
// Sad path: type mismatch
|
||||
|
||||
let err = ty
|
||||
.unwrap_list()
|
||||
.new_val(Box::new([
|
||||
Val::U32(32343),
|
||||
Val::U32(79023439),
|
||||
Val::Float32(3.14159265_f32.to_bits()),
|
||||
]))
|
||||
.unwrap_err();
|
||||
|
||||
assert!(err.to_string().contains("type mismatch"), "{err}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn records() -> Result<()> {
|
||||
let engine = super::engine();
|
||||
let mut store = Store::new(&engine, ());
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component_with_params(
|
||||
r#"(record (field "A" u32) (field "B" float64) (field "C" (record (field "D" bool) (field "E" u32))))"#,
|
||||
&[
|
||||
Param(Type::I32, Some(0)),
|
||||
Param(Type::F64, Some(8)),
|
||||
Param(Type::U8, Some(16)),
|
||||
Param(Type::I32, Some(20)),
|
||||
],
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_func(&mut store, "echo").unwrap();
|
||||
let ty = &func.params(&store)[0];
|
||||
let inner_type = &ty.unwrap_record().fields().nth(2).unwrap().ty;
|
||||
let input = ty.unwrap_record().new_val([
|
||||
("A", Val::U32(32343)),
|
||||
("B", Val::Float64(3.14159265_f64.to_bits())),
|
||||
(
|
||||
"C",
|
||||
inner_type
|
||||
.unwrap_record()
|
||||
.new_val([("D", Val::Bool(false)), ("E", Val::U32(2084037802))])?,
|
||||
),
|
||||
])?;
|
||||
let output = func.call_and_post_return(&mut store, &[input.clone()])?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
|
||||
// Sad path: type mismatch
|
||||
|
||||
let err = ty
|
||||
.unwrap_record()
|
||||
.new_val([
|
||||
("A", Val::S32(32343)),
|
||||
("B", Val::Float64(3.14159265_f64.to_bits())),
|
||||
(
|
||||
"C",
|
||||
inner_type
|
||||
.unwrap_record()
|
||||
.new_val([("D", Val::Bool(false)), ("E", Val::U32(2084037802))])?,
|
||||
),
|
||||
])
|
||||
.unwrap_err();
|
||||
|
||||
assert!(err.to_string().contains("type mismatch"), "{err}");
|
||||
|
||||
// Sad path: too many fields
|
||||
|
||||
let err = ty
|
||||
.unwrap_record()
|
||||
.new_val([
|
||||
("A", Val::U32(32343)),
|
||||
("B", Val::Float64(3.14159265_f64.to_bits())),
|
||||
(
|
||||
"C",
|
||||
inner_type
|
||||
.unwrap_record()
|
||||
.new_val([("D", Val::Bool(false)), ("E", Val::U32(2084037802))])?,
|
||||
),
|
||||
("F", Val::Unit),
|
||||
])
|
||||
.unwrap_err();
|
||||
|
||||
assert!(
|
||||
err.to_string().contains("expected 3 value(s); got 4"),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
// Sad path: too few fields
|
||||
|
||||
let err = ty
|
||||
.unwrap_record()
|
||||
.new_val([
|
||||
("A", Val::U32(32343)),
|
||||
("B", Val::Float64(3.14159265_f64.to_bits())),
|
||||
])
|
||||
.unwrap_err();
|
||||
|
||||
assert!(
|
||||
err.to_string().contains("expected 3 value(s); got 2"),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variants() -> Result<()> {
|
||||
let engine = super::engine();
|
||||
let mut store = Store::new(&engine, ());
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component_with_params(
|
||||
r#"(variant (case "A" u32) (case "B" float64) (case "C" (record (field "D" bool) (field "E" u32))))"#,
|
||||
&[
|
||||
Param(Type::U8, Some(0)),
|
||||
Param(Type::I64, Some(8)),
|
||||
Param(Type::I32, None),
|
||||
],
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_func(&mut store, "echo").unwrap();
|
||||
let ty = &func.params(&store)[0];
|
||||
let input = ty
|
||||
.unwrap_variant()
|
||||
.new_val("B", Val::Float64(3.14159265_f64.to_bits()))?;
|
||||
let output = func.call_and_post_return(&mut store, &[input.clone()])?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
|
||||
// Do it again, this time using case "C"
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
dbg!(make_echo_component_with_params(
|
||||
r#"(variant (case "A" u32) (case "B" float64) (case "C" (record (field "D" bool) (field "E" u32))))"#,
|
||||
&[
|
||||
Param(Type::U8, Some(0)),
|
||||
Param(Type::I64, Some(8)),
|
||||
Param(Type::I32, Some(12)),
|
||||
],
|
||||
)),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_func(&mut store, "echo").unwrap();
|
||||
let ty = &func.params(&store)[0];
|
||||
let c_type = &ty.unwrap_variant().cases().nth(2).unwrap().ty;
|
||||
let input = ty.unwrap_variant().new_val(
|
||||
"C",
|
||||
c_type
|
||||
.unwrap_record()
|
||||
.new_val([("D", Val::Bool(true)), ("E", Val::U32(314159265))])?,
|
||||
)?;
|
||||
let output = func.call_and_post_return(&mut store, &[input.clone()])?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
|
||||
// Sad path: type mismatch
|
||||
|
||||
let err = ty
|
||||
.unwrap_variant()
|
||||
.new_val("B", Val::U64(314159265))
|
||||
.unwrap_err();
|
||||
|
||||
assert!(err.to_string().contains("type mismatch"), "{err}");
|
||||
|
||||
// Sad path: unknown case
|
||||
|
||||
let err = ty
|
||||
.unwrap_variant()
|
||||
.new_val("D", Val::U64(314159265))
|
||||
.unwrap_err();
|
||||
|
||||
assert!(err.to_string().contains("unknown variant case"), "{err}");
|
||||
|
||||
// Make sure we lift variants which have cases of different sizes with the correct alignment
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component_with_params(
|
||||
r#"
|
||||
(record
|
||||
(field "A" (variant
|
||||
(case "A" u32)
|
||||
(case "B" float64)
|
||||
(case "C" (record (field "D" bool) (field "E" u32)))))
|
||||
(field "B" u32))"#,
|
||||
&[
|
||||
Param(Type::U8, Some(0)),
|
||||
Param(Type::I64, Some(8)),
|
||||
Param(Type::I32, None),
|
||||
Param(Type::I32, Some(16)),
|
||||
],
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_func(&mut store, "echo").unwrap();
|
||||
let ty = &func.params(&store)[0];
|
||||
let a_type = &ty.unwrap_record().fields().nth(0).unwrap().ty;
|
||||
let input = ty.unwrap_record().new_val([
|
||||
(
|
||||
"A",
|
||||
a_type.unwrap_variant().new_val("A", Val::U32(314159265))?,
|
||||
),
|
||||
("B", Val::U32(628318530)),
|
||||
])?;
|
||||
let output = func.call_and_post_return(&mut store, &[input.clone()])?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flags() -> Result<()> {
|
||||
let engine = super::engine();
|
||||
let mut store = Store::new(&engine, ());
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component_with_params(
|
||||
r#"(flags "A" "B" "C" "D" "E")"#,
|
||||
&[Param(Type::U8, Some(0))],
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_func(&mut store, "echo").unwrap();
|
||||
let ty = &func.params(&store)[0];
|
||||
let input = ty.unwrap_flags().new_val(&["B", "D"])?;
|
||||
let output = func.call_and_post_return(&mut store, &[input.clone()])?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
|
||||
// Sad path: unknown flags
|
||||
|
||||
let err = ty.unwrap_flags().new_val(&["B", "D", "F"]).unwrap_err();
|
||||
|
||||
assert!(err.to_string().contains("unknown flag"), "{err}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn everything() -> Result<()> {
|
||||
// This serves to test both nested types and storing parameters on the heap (i.e. exceeding `MAX_STACK_PARAMS`)
|
||||
|
||||
let engine = super::engine();
|
||||
let mut store = Store::new(&engine, ());
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component_with_params(
|
||||
r#"
|
||||
(record
|
||||
(field "A" u32)
|
||||
(field "B" (enum "1" "2"))
|
||||
(field "C" (record (field "D" bool) (field "E" u32)))
|
||||
(field "F" (list (flags "G" "H" "I")))
|
||||
(field "J" (variant
|
||||
(case "K" u32)
|
||||
(case "L" float64)
|
||||
(case "M" (record (field "N" bool) (field "O" u32)))))
|
||||
(field "P" s8)
|
||||
(field "Q" s16)
|
||||
(field "R" s32)
|
||||
(field "S" s64)
|
||||
(field "T" float32)
|
||||
(field "U" float64)
|
||||
(field "V" string)
|
||||
(field "W" char)
|
||||
(field "X" unit)
|
||||
(field "Y" (tuple u32 u32))
|
||||
(field "Z" (union u32 float64))
|
||||
(field "AA" (option u32))
|
||||
(field "BB" (expected string string))
|
||||
)"#,
|
||||
&[
|
||||
Param(Type::I32, Some(0)),
|
||||
Param(Type::U8, Some(4)),
|
||||
Param(Type::U8, Some(5)),
|
||||
Param(Type::I32, Some(8)),
|
||||
Param(Type::I32, Some(12)),
|
||||
Param(Type::I32, Some(16)),
|
||||
Param(Type::U8, Some(20)),
|
||||
Param(Type::I64, Some(28)),
|
||||
Param(Type::I32, Some(32)),
|
||||
Param(Type::S8, Some(36)),
|
||||
Param(Type::S16, Some(38)),
|
||||
Param(Type::I32, Some(40)),
|
||||
Param(Type::I64, Some(48)),
|
||||
Param(Type::F32, Some(56)),
|
||||
Param(Type::F64, Some(64)),
|
||||
Param(Type::I32, Some(72)),
|
||||
Param(Type::I32, Some(76)),
|
||||
Param(Type::I32, Some(80)),
|
||||
Param(Type::I32, Some(84)),
|
||||
Param(Type::I32, Some(88)),
|
||||
Param(Type::I64, Some(96)),
|
||||
Param(Type::U8, Some(104)),
|
||||
Param(Type::I32, Some(108)),
|
||||
Param(Type::U8, Some(112)),
|
||||
Param(Type::I32, Some(116)),
|
||||
Param(Type::I32, Some(120)),
|
||||
],
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_func(&mut store, "echo").unwrap();
|
||||
let ty = &func.params(&store)[0];
|
||||
let types = ty
|
||||
.unwrap_record()
|
||||
.fields()
|
||||
.map(|field| field.ty)
|
||||
.collect::<Box<[component::Type]>>();
|
||||
let (b_type, c_type, f_type, j_type, y_type, z_type, aa_type, bb_type) = (
|
||||
&types[1], &types[2], &types[3], &types[4], &types[14], &types[15], &types[16], &types[17],
|
||||
);
|
||||
let f_element_type = &f_type.unwrap_list().ty();
|
||||
let input = ty.unwrap_record().new_val([
|
||||
("A", Val::U32(32343)),
|
||||
("B", b_type.unwrap_enum().new_val("2")?),
|
||||
(
|
||||
"C",
|
||||
c_type
|
||||
.unwrap_record()
|
||||
.new_val([("D", Val::Bool(false)), ("E", Val::U32(2084037802))])?,
|
||||
),
|
||||
(
|
||||
"F",
|
||||
f_type.unwrap_list().new_val(Box::new([f_element_type
|
||||
.unwrap_flags()
|
||||
.new_val(&["G", "I"])?]))?,
|
||||
),
|
||||
(
|
||||
"J",
|
||||
j_type
|
||||
.unwrap_variant()
|
||||
.new_val("L", Val::Float64(3.14159265_f64.to_bits()))?,
|
||||
),
|
||||
("P", Val::S8(42)),
|
||||
("Q", Val::S16(4242)),
|
||||
("R", Val::S32(42424242)),
|
||||
("S", Val::S64(424242424242424242)),
|
||||
("T", Val::Float32(3.14159265_f32.to_bits())),
|
||||
("U", Val::Float64(3.14159265_f64.to_bits())),
|
||||
("V", Val::String(Box::from("wow, nice types"))),
|
||||
("W", Val::Char('🦀')),
|
||||
("X", Val::Unit),
|
||||
(
|
||||
"Y",
|
||||
y_type
|
||||
.unwrap_tuple()
|
||||
.new_val(Box::new([Val::U32(42), Val::U32(24)]))?,
|
||||
),
|
||||
(
|
||||
"Z",
|
||||
z_type
|
||||
.unwrap_union()
|
||||
.new_val(1, Val::Float64(3.14159265_f64.to_bits()))?,
|
||||
),
|
||||
(
|
||||
"AA",
|
||||
aa_type.unwrap_option().new_val(Some(Val::U32(314159265)))?,
|
||||
),
|
||||
(
|
||||
"BB",
|
||||
bb_type
|
||||
.unwrap_expected()
|
||||
.new_val(Ok(Val::String(Box::from("no problem"))))?,
|
||||
),
|
||||
])?;
|
||||
let output = func.call_and_post_return(&mut store, &[input.clone()])?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,75 +1,9 @@
|
||||
use super::TypedFuncExt;
|
||||
use super::{make_echo_component, TypedFuncExt};
|
||||
use anyhow::Result;
|
||||
use component_macro_test::{add_variants, flags_test};
|
||||
use std::fmt::Write;
|
||||
use wasmtime::component::{Component, ComponentType, Lift, Linker, Lower};
|
||||
use wasmtime::Store;
|
||||
|
||||
fn make_echo_component(type_definition: &str, type_size: u32) -> String {
|
||||
if type_size <= 4 {
|
||||
format!(
|
||||
r#"
|
||||
(component
|
||||
(core module $m
|
||||
(func (export "echo") (param i32) (result i32)
|
||||
local.get 0
|
||||
)
|
||||
|
||||
(memory (export "memory") 1)
|
||||
)
|
||||
|
||||
(core instance $i (instantiate $m))
|
||||
|
||||
{}
|
||||
|
||||
(func (export "echo") (param $Foo) (result $Foo)
|
||||
(canon lift (core func $i "echo") (memory $i "memory"))
|
||||
)
|
||||
)"#,
|
||||
type_definition
|
||||
)
|
||||
} else {
|
||||
let mut params = String::new();
|
||||
let mut store = String::new();
|
||||
|
||||
for index in 0..(type_size / 4) {
|
||||
params.push_str(" i32");
|
||||
write!(
|
||||
&mut store,
|
||||
"(i32.store offset={} (local.get $base) (local.get {}))",
|
||||
index * 4,
|
||||
index,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
format!(
|
||||
r#"
|
||||
(component
|
||||
(core module $m
|
||||
(func (export "echo") (param{}) (result i32)
|
||||
(local $base i32)
|
||||
(local.set $base (i32.const 0))
|
||||
{}
|
||||
local.get $base
|
||||
)
|
||||
|
||||
(memory (export "memory") 1)
|
||||
)
|
||||
|
||||
(core instance $i (instantiate $m))
|
||||
|
||||
{}
|
||||
|
||||
(func (export "echo") (param $Foo) (result $Foo)
|
||||
(canon lift (core func $i "echo") (memory $i "memory"))
|
||||
)
|
||||
)"#,
|
||||
params, store, type_definition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_derive() -> Result<()> {
|
||||
#[derive(ComponentType, Lift, Lower, PartialEq, Eq, Debug, Copy, Clone)]
|
||||
@@ -87,10 +21,7 @@ fn record_derive() -> Result<()> {
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
r#"(type $Foo (record (field "foo-bar-baz" s32) (field "b" u32)))"#,
|
||||
8,
|
||||
),
|
||||
make_echo_component(r#"(record (field "foo-bar-baz" s32) (field "b" u32))"#, 8),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
@@ -105,7 +36,7 @@ fn record_derive() -> Result<()> {
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (record (field "foo-bar-baz" s32)))"#, 4),
|
||||
make_echo_component(r#"(record (field "foo-bar-baz" s32))"#, 4),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
@@ -118,7 +49,7 @@ fn record_derive() -> Result<()> {
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
r#"(type $Foo (record (field "foo-bar-baz" s32) (field "b" u32) (field "c" u32)))"#,
|
||||
r#"(record (field "foo-bar-baz" s32) (field "b" u32) (field "c" u32))"#,
|
||||
12,
|
||||
),
|
||||
)?;
|
||||
@@ -132,7 +63,7 @@ fn record_derive() -> Result<()> {
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (record (field "a" s32) (field "b" u32)))"#, 8),
|
||||
make_echo_component(r#"(record (field "a" s32) (field "b" u32))"#, 8),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
@@ -144,10 +75,7 @@ fn record_derive() -> Result<()> {
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
r#"(type $Foo (record (field "foo-bar-baz" s32) (field "b" s32)))"#,
|
||||
8,
|
||||
),
|
||||
make_echo_component(r#"(record (field "foo-bar-baz" s32) (field "b" s32))"#, 8),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
@@ -172,10 +100,7 @@ fn record_derive() -> Result<()> {
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
r#"(type $Foo (record (field "foo-bar-baz" s32) (field "b" u32)))"#,
|
||||
8,
|
||||
),
|
||||
make_echo_component(r#"(record (field "foo-bar-baz" s32) (field "b" u32))"#, 8),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
@@ -203,10 +128,7 @@ fn union_derive() -> Result<()> {
|
||||
|
||||
// Happy path: component type matches case count and types
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (union s32 u32 s32))"#, 8),
|
||||
)?;
|
||||
let component = Component::new(&engine, make_echo_component("(union s32 u32 s32)", 8))?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")?;
|
||||
|
||||
@@ -218,10 +140,7 @@ fn union_derive() -> Result<()> {
|
||||
|
||||
// Sad path: case count mismatch (too few)
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (union s32 u32))"#, 8),
|
||||
)?;
|
||||
let component = Component::new(&engine, make_echo_component("(union s32 u32)", 8))?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
assert!(instance
|
||||
@@ -232,7 +151,7 @@ fn union_derive() -> Result<()> {
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (union s32 u32 s32 s32))"#, 8),
|
||||
make_echo_component(r#"(union s32 u32 s32 s32)"#, 8),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
@@ -246,10 +165,7 @@ fn union_derive() -> Result<()> {
|
||||
|
||||
// Sad path: case type mismatch
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (union s32 s32 s32))"#, 8),
|
||||
)?;
|
||||
let component = Component::new(&engine, make_echo_component("(union s32 s32 s32)", 8))?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
assert!(instance
|
||||
@@ -266,10 +182,7 @@ fn union_derive() -> Result<()> {
|
||||
C(C),
|
||||
}
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (union s32 u32 s32))"#, 8),
|
||||
)?;
|
||||
let component = Component::new(&engine, make_echo_component("(union s32 u32 s32)", 8))?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_typed_func::<(Generic<i32, u32, i32>,), Generic<i32, u32, i32>, _>(
|
||||
&mut store, "echo",
|
||||
@@ -307,7 +220,7 @@ fn variant_derive() -> Result<()> {
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
r#"(type $Foo (variant (case "foo-bar-baz" s32) (case "B" u32) (case "C" unit)))"#,
|
||||
r#"(variant (case "foo-bar-baz" s32) (case "B" u32) (case "C" unit))"#,
|
||||
8,
|
||||
),
|
||||
)?;
|
||||
@@ -324,10 +237,7 @@ fn variant_derive() -> Result<()> {
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
r#"(type $Foo (variant (case "foo-bar-baz" s32) (case "B" u32)))"#,
|
||||
8,
|
||||
),
|
||||
make_echo_component(r#"(variant (case "foo-bar-baz" s32) (case "B" u32))"#, 8),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
@@ -340,7 +250,7 @@ fn variant_derive() -> Result<()> {
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
r#"(type $Foo (variant (case "foo-bar-baz" s32) (case "B" u32) (case "C" unit) (case "D" u32)))"#,
|
||||
r#"(variant (case "foo-bar-baz" s32) (case "B" u32) (case "C" unit) (case "D" u32))"#,
|
||||
8,
|
||||
),
|
||||
)?;
|
||||
@@ -355,7 +265,7 @@ fn variant_derive() -> Result<()> {
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
r#"(type $Foo (variant (case "A" s32) (case "B" u32) (case "C" unit)))"#,
|
||||
r#"(variant (case "A" s32) (case "B" u32) (case "C" unit))"#,
|
||||
8,
|
||||
),
|
||||
)?;
|
||||
@@ -370,7 +280,7 @@ fn variant_derive() -> Result<()> {
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
r#"(type $Foo (variant (case "foo-bar-baz" s32) (case "B" s32) (case "C" unit)))"#,
|
||||
r#"(variant (case "foo-bar-baz" s32) (case "B" s32) (case "C" unit))"#,
|
||||
8,
|
||||
),
|
||||
)?;
|
||||
@@ -394,7 +304,7 @@ fn variant_derive() -> Result<()> {
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
r#"(type $Foo (variant (case "foo-bar-baz" s32) (case "B" u32) (case "C" unit)))"#,
|
||||
r#"(variant (case "foo-bar-baz" s32) (case "B" u32) (case "C" unit))"#,
|
||||
8,
|
||||
),
|
||||
)?;
|
||||
@@ -429,7 +339,7 @@ fn enum_derive() -> Result<()> {
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (enum "foo-bar-baz" "B" "C"))"#, 4),
|
||||
make_echo_component(r#"(enum "foo-bar-baz" "B" "C")"#, 4),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")?;
|
||||
@@ -444,7 +354,7 @@ fn enum_derive() -> Result<()> {
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (enum "foo-bar-baz" "B"))"#, 4),
|
||||
make_echo_component(r#"(enum "foo-bar-baz" "B")"#, 4),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
@@ -456,7 +366,7 @@ fn enum_derive() -> Result<()> {
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (enum "foo-bar-baz" "B" "C" "D"))"#, 4),
|
||||
make_echo_component(r#"(enum "foo-bar-baz" "B" "C" "D")"#, 4),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
@@ -466,10 +376,7 @@ fn enum_derive() -> Result<()> {
|
||||
|
||||
// Sad path: case name mismatch
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (enum "A" "B" "C"))"#, 4),
|
||||
)?;
|
||||
let component = Component::new(&engine, make_echo_component(r#"(enum "A" "B" "C")"#, 4))?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
assert!(instance
|
||||
@@ -487,7 +394,7 @@ fn enum_derive() -> Result<()> {
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (enum {}))"#,
|
||||
"(enum {})",
|
||||
(0..257)
|
||||
.map(|index| format!(r#""V{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
@@ -542,7 +449,7 @@ fn flags() -> Result<()> {
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (flags "foo-bar-baz" "B" "C"))"#, 4),
|
||||
make_echo_component(r#"(flags "foo-bar-baz" "B" "C")"#, 4),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
let func = instance.get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")?;
|
||||
@@ -568,7 +475,7 @@ fn flags() -> Result<()> {
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (flags "foo-bar-baz" "B"))"#, 4),
|
||||
make_echo_component(r#"(flags "foo-bar-baz" "B")"#, 4),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
@@ -580,7 +487,7 @@ fn flags() -> Result<()> {
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (flags "foo-bar-baz" "B" "C" "D"))"#, 4),
|
||||
make_echo_component(r#"(flags "foo-bar-baz" "B" "C" "D")"#, 4),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
@@ -590,10 +497,7 @@ fn flags() -> Result<()> {
|
||||
|
||||
// Sad path: flag name mismatch
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (flags "A" "B" "C"))"#, 4),
|
||||
)?;
|
||||
let component = Component::new(&engine, make_echo_component(r#"(flags "A" "B" "C")"#, 4))?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
assert!(instance
|
||||
@@ -633,7 +537,7 @@ fn flags() -> Result<()> {
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
r#"(flags {})"#,
|
||||
(0..8)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
@@ -682,7 +586,7 @@ fn flags() -> Result<()> {
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
"(flags {})",
|
||||
(0..9)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
@@ -730,7 +634,7 @@ fn flags() -> Result<()> {
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
r#"(flags {})"#,
|
||||
(0..16)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
@@ -769,7 +673,7 @@ fn flags() -> Result<()> {
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
"(flags {})",
|
||||
(0..17)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
@@ -817,7 +721,7 @@ fn flags() -> Result<()> {
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
r#"(flags {})"#,
|
||||
(0..32)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
@@ -856,7 +760,7 @@ fn flags() -> Result<()> {
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
"(flags {})",
|
||||
(0..33)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
@@ -889,7 +793,7 @@ fn flags() -> Result<()> {
|
||||
&engine,
|
||||
make_echo_component(
|
||||
&format!(
|
||||
r#"(type $Foo (flags {}))"#,
|
||||
"(flags {})",
|
||||
(0..65)
|
||||
.map(|index| format!(r#""F{}""#, index))
|
||||
.collect::<Vec<_>>()
|
||||
|
||||
Reference in New Issue
Block a user