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:
Joel Dice
2022-07-25 12:38:48 -06:00
committed by GitHub
parent ee7e4f4c6b
commit 7c67e620c4
16 changed files with 2796 additions and 453 deletions

View File

@@ -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"))
)
)
)"#
)
}