add ComponentType/Lift/Lower derive macro for record types (#4337)
This is the first stage of implementing https://github.com/bytecodealliance/wasmtime/issues/4308, i.e. derive macros for `ComponentType`, `Lift`, and `Lower` for composite types in the component model. This stage only covers records; I expect the other composite types will follow a similar pattern. It borrows heavily from the work Jamey Sharp did in https://github.com/bytecodealliance/wasmtime/pull/4217. Thanks for that, and thanks to both Jamey and Alex Crichton for their excellent review feedback. Thanks also to Brian for pairing up on the initial draft. Signed-off-by: Joel Dice <joel.dice@fermyon.com>
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -3332,6 +3332,7 @@ dependencies = [
|
||||
"wasi-cap-std-sync",
|
||||
"wasmparser",
|
||||
"wasmtime-cache",
|
||||
"wasmtime-component-macro",
|
||||
"wasmtime-cranelift",
|
||||
"wasmtime-environ",
|
||||
"wasmtime-fiber",
|
||||
@@ -3457,6 +3458,15 @@ dependencies = [
|
||||
"wasmtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-component-macro"
|
||||
version = "0.39.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-cranelift"
|
||||
version = "0.39.0"
|
||||
|
||||
22
crates/component-macro/Cargo.toml
Normal file
22
crates/component-macro/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "wasmtime-component-macro"
|
||||
version = "0.39.0"
|
||||
authors = ["The Wasmtime Project Developers"]
|
||||
description = "Macros for deriving component interface types from Rust types"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
repository = "https://github.com/bytecodealliance/wasmtime"
|
||||
documentation = "https://docs.rs/wasmtime-component-macro/"
|
||||
categories = ["wasm"]
|
||||
keywords = ["webassembly", "wasm"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0", features = ["extra-traits"] }
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
382
crates/component-macro/src/lib.rs
Normal file
382
crates/component-macro/src/lib.rs
Normal file
@@ -0,0 +1,382 @@
|
||||
use proc_macro2::{Literal, TokenStream, TokenTree};
|
||||
use quote::{format_ident, quote};
|
||||
use std::collections::HashSet;
|
||||
use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Error, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Style {
|
||||
Record,
|
||||
Variant,
|
||||
Enum,
|
||||
Union,
|
||||
}
|
||||
|
||||
fn find_style(input: &DeriveInput) -> Result<Style> {
|
||||
let mut style = None;
|
||||
|
||||
for attribute in &input.attrs {
|
||||
if attribute.path.leading_colon.is_some() || attribute.path.segments.len() != 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ident = &attribute.path.segments[0].ident;
|
||||
|
||||
if "component" != &ident.to_string() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let syntax_error = || {
|
||||
Err(Error::new_spanned(
|
||||
&attribute.tokens,
|
||||
"expected `component(<style>)` syntax",
|
||||
))
|
||||
};
|
||||
|
||||
let style_string = if let [TokenTree::Group(group)] =
|
||||
&attribute.tokens.clone().into_iter().collect::<Vec<_>>()[..]
|
||||
{
|
||||
if let [TokenTree::Ident(style)] = &group.stream().into_iter().collect::<Vec<_>>()[..] {
|
||||
style.to_string()
|
||||
} else {
|
||||
return syntax_error();
|
||||
}
|
||||
} else {
|
||||
return syntax_error();
|
||||
};
|
||||
|
||||
if style.is_some() {
|
||||
return Err(Error::new(ident.span(), "duplicate `component` attribute"));
|
||||
}
|
||||
|
||||
style = Some(match style_string.as_ref() {
|
||||
"record" => Style::Record,
|
||||
"variant" => Style::Variant,
|
||||
"enum" => Style::Enum,
|
||||
"union" => Style::Union,
|
||||
"flags" => {
|
||||
return Err(Error::new_spanned(
|
||||
&attribute.tokens,
|
||||
"`flags` not allowed here; \
|
||||
use `wasmtime::component::flags!` macro to define `flags` types",
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
&attribute.tokens,
|
||||
"unrecognized component type keyword \
|
||||
(expected `record`, `variant`, `enum`, or `union`)",
|
||||
))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
style.ok_or_else(|| Error::new_spanned(input, "missing `component` attribute"))
|
||||
}
|
||||
|
||||
fn find_rename(field: &syn::Field) -> Result<Option<Literal>> {
|
||||
let mut name = None;
|
||||
|
||||
for attribute in &field.attrs {
|
||||
if attribute.path.leading_colon.is_some() || attribute.path.segments.len() != 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ident = &attribute.path.segments[0].ident;
|
||||
|
||||
if "component" != &ident.to_string() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let syntax_error = || {
|
||||
Err(Error::new_spanned(
|
||||
&attribute.tokens,
|
||||
"expected `component(name = <name literal>)` syntax",
|
||||
))
|
||||
};
|
||||
|
||||
let name_literal = if let [TokenTree::Group(group)] =
|
||||
&attribute.tokens.clone().into_iter().collect::<Vec<_>>()[..]
|
||||
{
|
||||
match &group.stream().into_iter().collect::<Vec<_>>()[..] {
|
||||
[TokenTree::Ident(key), TokenTree::Punct(op), TokenTree::Literal(literal)]
|
||||
if "name" == &key.to_string() && '=' == op.as_char() =>
|
||||
{
|
||||
literal.clone()
|
||||
}
|
||||
_ => return syntax_error(),
|
||||
}
|
||||
} else {
|
||||
return syntax_error();
|
||||
};
|
||||
|
||||
if name.is_some() {
|
||||
return Err(Error::new(ident.span(), "duplicate field rename attribute"));
|
||||
}
|
||||
|
||||
name = Some(name_literal);
|
||||
}
|
||||
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
fn add_trait_bounds(generics: &syn::Generics) -> syn::Generics {
|
||||
let mut generics = generics.clone();
|
||||
for param in &mut generics.params {
|
||||
if let syn::GenericParam::Type(ref mut type_param) = *param {
|
||||
type_param
|
||||
.bounds
|
||||
.push(parse_quote!(wasmtime::component::ComponentType));
|
||||
}
|
||||
}
|
||||
generics
|
||||
}
|
||||
|
||||
trait Expander {
|
||||
fn expand_record(&self, input: &DeriveInput, fields: &syn::FieldsNamed) -> Result<TokenStream>;
|
||||
|
||||
// TODO: expand_variant, expand_enum, etc.
|
||||
}
|
||||
|
||||
fn expand(expander: &dyn Expander, input: DeriveInput) -> Result<TokenStream> {
|
||||
match find_style(&input)? {
|
||||
Style::Record => expand_record(expander, input),
|
||||
|
||||
style => Err(Error::new_spanned(input, format!("todo: expand {style:?}"))),
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_record(expander: &dyn Expander, input: DeriveInput) -> Result<TokenStream> {
|
||||
let name = &input.ident;
|
||||
|
||||
let body = if let Data::Struct(body) = &input.data {
|
||||
body
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
name.span(),
|
||||
"`record` component types can only be derived for `struct`s",
|
||||
));
|
||||
};
|
||||
|
||||
match &body.fields {
|
||||
syn::Fields::Named(fields) => expander.expand_record(&input, fields),
|
||||
|
||||
syn::Fields::Unnamed(_) | syn::Fields::Unit => Err(Error::new(
|
||||
name.span(),
|
||||
"`record` component types can only be derived for `struct`s with named fields",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Lift, attributes(component))]
|
||||
pub fn lift(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
expand(&LiftExpander, parse_macro_input!(input as DeriveInput))
|
||||
.unwrap_or_else(Error::into_compile_error)
|
||||
.into()
|
||||
}
|
||||
|
||||
struct LiftExpander;
|
||||
|
||||
impl Expander for LiftExpander {
|
||||
fn expand_record(&self, input: &DeriveInput, fields: &syn::FieldsNamed) -> Result<TokenStream> {
|
||||
let internal = quote!(wasmtime::component::__internal);
|
||||
|
||||
let mut lifts = TokenStream::new();
|
||||
let mut loads = TokenStream::new();
|
||||
|
||||
for syn::Field { ty, ident, .. } in &fields.named {
|
||||
lifts.extend(quote!(#ident: <#ty as wasmtime::component::Lift>::lift(
|
||||
store, options, &src.#ident
|
||||
)?,));
|
||||
|
||||
loads.extend(quote!(#ident: <#ty as wasmtime::component::Lift>::load(
|
||||
memory,
|
||||
&bytes
|
||||
[#internal::next_field::<#ty>(&mut offset)..]
|
||||
[..<#ty as wasmtime::component::ComponentType>::size()]
|
||||
)?,));
|
||||
}
|
||||
|
||||
let name = &input.ident;
|
||||
let generics = add_trait_bounds(&input.generics);
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
let expanded = quote! {
|
||||
unsafe impl #impl_generics wasmtime::component::Lift for #name #ty_generics #where_clause {
|
||||
#[inline]
|
||||
fn lift(
|
||||
store: &#internal::StoreOpaque,
|
||||
options: &#internal::Options,
|
||||
src: &Self::Lower,
|
||||
) -> #internal::anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
#lifts
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn load(memory: &#internal::Memory, bytes: &[u8]) -> #internal::anyhow::Result<Self> {
|
||||
debug_assert!(
|
||||
(bytes.as_ptr() as usize)
|
||||
% (<Self as wasmtime::component::ComponentType>::align() as usize)
|
||||
== 0
|
||||
);
|
||||
let mut offset = 0;
|
||||
Ok(Self {
|
||||
#loads
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(expanded)
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_derive(Lower, attributes(component))]
|
||||
pub fn lower(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
expand(&LowerExpander, parse_macro_input!(input as DeriveInput))
|
||||
.unwrap_or_else(Error::into_compile_error)
|
||||
.into()
|
||||
}
|
||||
|
||||
struct LowerExpander;
|
||||
|
||||
impl Expander for LowerExpander {
|
||||
fn expand_record(&self, input: &DeriveInput, fields: &syn::FieldsNamed) -> Result<TokenStream> {
|
||||
let internal = quote!(wasmtime::component::__internal);
|
||||
|
||||
let mut lowers = TokenStream::new();
|
||||
let mut stores = TokenStream::new();
|
||||
|
||||
for syn::Field { ty, ident, .. } in &fields.named {
|
||||
lowers.extend(quote!(wasmtime::component::Lower::lower(
|
||||
&self.#ident, store, options, #internal::map_maybe_uninit!(dst.#ident)
|
||||
)?;));
|
||||
|
||||
stores.extend(quote!(wasmtime::component::Lower::store(
|
||||
&self.#ident, memory, #internal::next_field::<#ty>(&mut offset)
|
||||
)?;));
|
||||
}
|
||||
|
||||
let name = &input.ident;
|
||||
let generics = add_trait_bounds(&input.generics);
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
let expanded = quote! {
|
||||
unsafe impl #impl_generics wasmtime::component::Lower for #name #ty_generics #where_clause {
|
||||
#[inline]
|
||||
fn lower<T>(
|
||||
&self,
|
||||
store: &mut wasmtime::StoreContextMut<T>,
|
||||
options: &#internal::Options,
|
||||
dst: &mut std::mem::MaybeUninit<Self::Lower>,
|
||||
) -> #internal::anyhow::Result<()> {
|
||||
#lowers
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn store<T>(
|
||||
&self,
|
||||
memory: &mut #internal::MemoryMut<'_, T>,
|
||||
mut offset: usize
|
||||
) -> #internal::anyhow::Result<()> {
|
||||
#stores
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(expanded)
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ComponentType, attributes(component))]
|
||||
pub fn component_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
expand(
|
||||
&ComponentTypeExpander,
|
||||
parse_macro_input!(input as DeriveInput),
|
||||
)
|
||||
.unwrap_or_else(Error::into_compile_error)
|
||||
.into()
|
||||
}
|
||||
|
||||
struct ComponentTypeExpander;
|
||||
|
||||
impl Expander for ComponentTypeExpander {
|
||||
fn expand_record(&self, input: &DeriveInput, fields: &syn::FieldsNamed) -> Result<TokenStream> {
|
||||
let internal = quote!(wasmtime::component::__internal);
|
||||
|
||||
let mut field_names_and_checks = TokenStream::new();
|
||||
let mut lower_field_declarations = TokenStream::new();
|
||||
let mut sizes = TokenStream::new();
|
||||
let mut unique_types = HashSet::new();
|
||||
|
||||
for field @ syn::Field { ty, ident, .. } in &fields.named {
|
||||
lower_field_declarations
|
||||
.extend(quote!(#ident: <#ty as wasmtime::component::ComponentType>::Lower,));
|
||||
|
||||
let literal = find_rename(field)?
|
||||
.unwrap_or_else(|| Literal::string(&ident.as_ref().unwrap().to_string()));
|
||||
|
||||
field_names_and_checks.extend(
|
||||
quote!((#literal, <#ty as wasmtime::component::ComponentType>::typecheck),),
|
||||
);
|
||||
|
||||
sizes.extend(quote!(#internal::next_field::<#ty>(&mut size);));
|
||||
|
||||
unique_types.insert(ty);
|
||||
}
|
||||
|
||||
let alignments = unique_types
|
||||
.into_iter()
|
||||
.map(|ty| {
|
||||
quote!(align = align.max(
|
||||
<#ty as wasmtime::component::ComponentType>::align()
|
||||
);)
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
let name = &input.ident;
|
||||
let generics = add_trait_bounds(&input.generics);
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
let lower = format_ident!("_Lower{}", name);
|
||||
|
||||
let expanded = quote! {
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct #lower {
|
||||
#lower_field_declarations
|
||||
}
|
||||
|
||||
unsafe impl #impl_generics wasmtime::component::ComponentType for #name #ty_generics #where_clause {
|
||||
type Lower = #lower;
|
||||
|
||||
#[inline]
|
||||
fn typecheck(
|
||||
ty: &#internal::InterfaceType,
|
||||
types: &#internal::ComponentTypes,
|
||||
) -> #internal::anyhow::Result<()> {
|
||||
#internal::typecheck_record(ty, types, &[#field_names_and_checks])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size() -> usize {
|
||||
let mut size = 0;
|
||||
#sizes
|
||||
size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn align() -> u32 {
|
||||
let mut align = 1;
|
||||
#alignments
|
||||
align
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(quote!(const _: () = { #expanded };))
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ wasmtime-jit = { path = "../jit", version = "=0.39.0" }
|
||||
wasmtime-cache = { path = "../cache", version = "=0.39.0", optional = true }
|
||||
wasmtime-fiber = { path = "../fiber", version = "=0.39.0", optional = true }
|
||||
wasmtime-cranelift = { path = "../cranelift", version = "=0.39.0", optional = true }
|
||||
wasmtime-component-macro = { path = "../component-macro", version = "=0.39.0", optional = true }
|
||||
target-lexicon = { version = "0.12.0", default-features = false }
|
||||
wasmparser = "0.86.0"
|
||||
anyhow = "1.0.19"
|
||||
@@ -115,4 +116,5 @@ component-model = [
|
||||
"wasmtime-environ/component-model",
|
||||
"wasmtime-cranelift?/component-model",
|
||||
"wasmtime-runtime/component-model",
|
||||
"dep:wasmtime-component-macro",
|
||||
]
|
||||
|
||||
@@ -34,9 +34,11 @@ const MAX_STACK_RESULTS: usize = 1;
|
||||
/// let initial: &mut MaybeUninit<[u32; 2]> = ...;
|
||||
/// let element: &mut MaybeUninit<u32> = map_maybe_uninit!(initial[1]);
|
||||
/// ```
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! map_maybe_uninit {
|
||||
($maybe_uninit:ident $($field:tt)*) => (#[allow(unused_unsafe)] unsafe {
|
||||
use crate::component::func::MaybeUninitExt;
|
||||
use $crate::component::__internal::MaybeUninitExt;
|
||||
|
||||
let m: &mut std::mem::MaybeUninit<_> = $maybe_uninit;
|
||||
// Note the usage of `addr_of_mut!` here which is an attempt to "stay
|
||||
@@ -47,7 +49,8 @@ macro_rules! map_maybe_uninit {
|
||||
})
|
||||
}
|
||||
|
||||
trait MaybeUninitExt<T> {
|
||||
#[doc(hidden)]
|
||||
pub trait MaybeUninitExt<T> {
|
||||
/// Maps `MaybeUninit<T>` to `MaybeUninit<U>` using the closure provided.
|
||||
///
|
||||
/// Note that this is `unsafe` as there is no guarantee that `U` comes from
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::component::func::{MAX_STACK_PARAMS, MAX_STACK_RESULTS};
|
||||
use crate::component::{ComponentParams, ComponentType, Lift, Lower, Memory, MemoryMut, Options};
|
||||
use crate::component::func::{Memory, MemoryMut, Options, MAX_STACK_PARAMS, MAX_STACK_RESULTS};
|
||||
use crate::component::{ComponentParams, ComponentType, Lift, Lower};
|
||||
use crate::{AsContextMut, StoreContextMut, ValRaw};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::any::Any;
|
||||
|
||||
@@ -196,7 +196,7 @@ impl<'a, T> MemoryMut<'a, T> {
|
||||
/// bounds-checks.
|
||||
pub fn get<const N: usize>(&mut self, offset: usize) -> &mut [u8; N] {
|
||||
// FIXME: this bounds check shouldn't actually be necessary, all
|
||||
// callers of `ComponentValue::store` have already performed a bounds
|
||||
// callers of `ComponentType::store` have already performed a bounds
|
||||
// check so we're guaranteed that `offset..offset+N` is in-bounds. That
|
||||
// being said we at least should do bounds checks in debug mode and
|
||||
// it's not clear to me how to easily structure this so that it's
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::component::func::{
|
||||
};
|
||||
use crate::store::StoreOpaque;
|
||||
use crate::{AsContext, AsContextMut, StoreContext, StoreContextMut, ValRaw};
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::borrow::Cow;
|
||||
use std::marker;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
@@ -348,8 +348,8 @@ where
|
||||
// are all valid since they're coming from our store, and the
|
||||
// `params_and_results` should have the correct layout for the core
|
||||
// wasm function we're calling. Note that this latter point relies
|
||||
// on the correctness of this module and `ComponentValue`
|
||||
// implementations, hence `ComponentValue` being an `unsafe` trait.
|
||||
// on the correctness of this module and `ComponentType`
|
||||
// implementations, hence `ComponentType` being an `unsafe` trait.
|
||||
crate::Func::call_unchecked_raw(
|
||||
store,
|
||||
export.anyfunc,
|
||||
@@ -615,14 +615,8 @@ pub unsafe trait ComponentType {
|
||||
#[doc(hidden)]
|
||||
fn align() -> u32;
|
||||
|
||||
/// Performs a type-check to see whether this comopnent value type matches
|
||||
/// Performs a type-check to see whether this component value type matches
|
||||
/// the interface type `ty` provided.
|
||||
///
|
||||
/// The `op` provided is the operations which could be performed with this
|
||||
/// type if the typecheck passes, either lifting or lowering. Some Rust
|
||||
/// types are only valid for one operation and we can't prevent the wrong
|
||||
/// one from being used at compile time so we rely on the runtime check
|
||||
/// here.
|
||||
#[doc(hidden)]
|
||||
fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()>;
|
||||
}
|
||||
@@ -754,7 +748,7 @@ forward_impls! {
|
||||
(T: Lower) Vec<T> => [T],
|
||||
}
|
||||
|
||||
// Macro to help generate `ComponentValue` implementations for primitive types
|
||||
// Macro to help generate `ComponentType` implementations for primitive types
|
||||
// such as integers, char, bool, etc.
|
||||
macro_rules! integers {
|
||||
($($primitive:ident = $ty:ident in $field:ident/$get:ident,)*) => ($(
|
||||
@@ -1006,7 +1000,7 @@ unsafe impl Lift for char {
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this is similar to `ComponentValue for WasmStr` except it can only
|
||||
// Note that this is similar to `ComponentType for WasmStr` except it can only
|
||||
// be used for lowering, not lifting.
|
||||
unsafe impl ComponentType for str {
|
||||
type Lower = [ValRaw; 2];
|
||||
@@ -1171,7 +1165,7 @@ impl WasmStr {
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this is similar to `ComponentValue for str` except it can only be
|
||||
// Note that this is similar to `ComponentType for str` except it can only be
|
||||
// used for lifting, not lowering.
|
||||
unsafe impl ComponentType for WasmStr {
|
||||
type Lower = <str as ComponentType>::Lower;
|
||||
@@ -1431,7 +1425,7 @@ raw_wasm_list_accessors! {
|
||||
u8 u16 u32 u64
|
||||
}
|
||||
|
||||
// Note that this is similar to `ComponentValue for str` except it can only be
|
||||
// Note that this is similar to `ComponentType for str` except it can only be
|
||||
// used for lifting, not lowering.
|
||||
unsafe impl<T: ComponentType> ComponentType for WasmList<T> {
|
||||
type Lower = <[T] as ComponentType>::Lower;
|
||||
@@ -1491,7 +1485,6 @@ pub fn next_field<T: ComponentType>(offset: &mut usize) -> usize {
|
||||
}
|
||||
|
||||
/// Verify that the given wasm type is a tuple with the expected fields in the right order.
|
||||
#[inline]
|
||||
fn typecheck_tuple(
|
||||
ty: &InterfaceType,
|
||||
types: &ComponentTypes,
|
||||
@@ -1526,6 +1519,40 @@ fn typecheck_tuple(
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify that the given wasm type is a record with the expected fields in the right order and with the right
|
||||
/// names.
|
||||
pub fn typecheck_record(
|
||||
ty: &InterfaceType,
|
||||
types: &ComponentTypes,
|
||||
expected: &[(&str, fn(&InterfaceType, &ComponentTypes) -> Result<()>)],
|
||||
) -> Result<()> {
|
||||
match ty {
|
||||
InterfaceType::Record(index) => {
|
||||
let fields = &types[*index].fields;
|
||||
|
||||
if fields.len() != expected.len() {
|
||||
bail!(
|
||||
"expected record of {} fields, found {} fields",
|
||||
expected.len(),
|
||||
fields.len()
|
||||
);
|
||||
}
|
||||
|
||||
for (field, &(name, check)) in fields.iter().zip(expected) {
|
||||
check(&field.ty, types)
|
||||
.with_context(|| format!("type mismatch for field {}", name))?;
|
||||
|
||||
if field.name != name {
|
||||
bail!("expected record field named {}, found {}", name, field.name);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
other => bail!("expected `record` found `{}`", desc(other)),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> ComponentType for Option<T>
|
||||
where
|
||||
T: ComponentType,
|
||||
@@ -1565,7 +1592,7 @@ where
|
||||
map_maybe_uninit!(dst.A1).write(ValRaw::i32(0));
|
||||
// Note that this is unsafe as we're writing an arbitrary
|
||||
// bit-pattern to an arbitrary type, but part of the unsafe
|
||||
// contract of the `ComponentValue` trait is that we can assign
|
||||
// contract of the `ComponentType` trait is that we can assign
|
||||
// any bit-pattern. By writing all zeros here we're ensuring
|
||||
// that the core wasm arguments this translates to will all be
|
||||
// zeros (as the canonical ABI requires).
|
||||
|
||||
@@ -16,14 +16,21 @@ pub use self::func::{
|
||||
};
|
||||
pub use self::instance::{Instance, InstancePre};
|
||||
pub use self::linker::{Linker, LinkerInstance};
|
||||
pub use wasmtime_component_macro::{ComponentType, Lift, Lower};
|
||||
|
||||
// These items are expected to be used by an eventual
|
||||
// `#[derive(ComponentValue)]`, they are not part of Wasmtime's API stability
|
||||
// `#[derive(ComponentType)]`, they are not part of Wasmtime's API stability
|
||||
// guarantees
|
||||
#[doc(hidden)]
|
||||
pub use {
|
||||
self::func::{Memory, MemoryMut, Options},
|
||||
wasmtime_environ,
|
||||
pub mod __internal {
|
||||
pub use super::func::{
|
||||
next_field, typecheck_record, MaybeUninitExt, Memory, MemoryMut, Options,
|
||||
};
|
||||
pub use crate::map_maybe_uninit;
|
||||
pub use crate::store::StoreOpaque;
|
||||
pub use anyhow;
|
||||
pub use wasmtime_environ;
|
||||
pub use wasmtime_environ::component::{ComponentTypes, InterfaceType};
|
||||
}
|
||||
|
||||
pub(crate) use self::store::ComponentStoreData;
|
||||
|
||||
@@ -1112,6 +1112,7 @@ impl<T> StoreInner<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl StoreOpaque {
|
||||
pub fn id(&self) -> StoreId {
|
||||
self.store_data.id()
|
||||
|
||||
@@ -40,6 +40,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[
|
||||
"wiggle-generate",
|
||||
"wiggle-macro",
|
||||
// wasmtime
|
||||
"wasmtime-component-macro",
|
||||
"wasmtime-jit-debug",
|
||||
"wasmtime-fiber",
|
||||
"wasmtime-environ",
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
use anyhow::Result;
|
||||
use wasmtime::component::Component;
|
||||
use wasmtime::{Config, Engine};
|
||||
use wasmtime::component::{Component, ComponentParams, Lift, Lower, TypedFunc};
|
||||
use wasmtime::{AsContextMut, Config, Engine};
|
||||
|
||||
mod func;
|
||||
mod import;
|
||||
mod macros;
|
||||
mod nested;
|
||||
mod post_return;
|
||||
|
||||
trait TypedFuncExt<P, R> {
|
||||
fn call_and_post_return(&self, store: impl AsContextMut, params: P) -> Result<R>;
|
||||
}
|
||||
|
||||
impl<P, R> TypedFuncExt<P, R> for TypedFunc<P, R>
|
||||
where
|
||||
P: ComponentParams + Lower,
|
||||
R: Lift,
|
||||
{
|
||||
fn call_and_post_return(&self, mut store: impl AsContextMut, params: P) -> Result<R> {
|
||||
let result = self.call(&mut store, params)?;
|
||||
self.post_return(&mut store)?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
// A simple bump allocator which can be used with modules
|
||||
const REALLOC_AND_FREE: &str = r#"
|
||||
(global $last (mut i32) (i32.const 8))
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::REALLOC_AND_FREE;
|
||||
use super::{TypedFuncExt, REALLOC_AND_FREE};
|
||||
use anyhow::Result;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use wasmtime::component::*;
|
||||
use wasmtime::{AsContextMut, Store, StoreContextMut, Trap, TrapCode};
|
||||
use wasmtime::{Store, StoreContextMut, Trap, TrapCode};
|
||||
|
||||
const CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000;
|
||||
const CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000;
|
||||
@@ -400,22 +400,6 @@ fn integers() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
trait TypedFuncExt<P, R> {
|
||||
fn call_and_post_return(&self, store: impl AsContextMut, params: P) -> Result<R>;
|
||||
}
|
||||
|
||||
impl<P, R> TypedFuncExt<P, R> for TypedFunc<P, R>
|
||||
where
|
||||
P: ComponentParams + Lower,
|
||||
R: Lift,
|
||||
{
|
||||
fn call_and_post_return(&self, mut store: impl AsContextMut, params: P) -> Result<R> {
|
||||
let result = self.call(&mut store, params)?;
|
||||
self.post_return(&mut store)?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_layers() -> Result<()> {
|
||||
let component = r#"
|
||||
|
||||
158
tests/all/component_model/macros.rs
Normal file
158
tests/all/component_model/macros.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use super::TypedFuncExt;
|
||||
use anyhow::Result;
|
||||
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)]
|
||||
#[component(record)]
|
||||
struct Foo {
|
||||
#[component(name = "foo-bar-baz")]
|
||||
a: i32,
|
||||
b: u32,
|
||||
}
|
||||
|
||||
let engine = super::engine();
|
||||
let mut store = Store::new(&engine, ());
|
||||
let input = Foo { a: -42, b: 73 };
|
||||
|
||||
// Happy path: component type matches field count, names, and types
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
r#"(type $Foo (record (field "foo-bar-baz" s32) (field "b" u32)))"#,
|
||||
8,
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
let output = instance
|
||||
.get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")?
|
||||
.call_and_post_return(&mut store, (input,))?;
|
||||
|
||||
assert_eq!(input, output);
|
||||
|
||||
// Sad path: field count mismatch (too few)
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (record (field "foo-bar-baz" s32)))"#, 4),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
assert!(instance
|
||||
.get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")
|
||||
.is_err());
|
||||
|
||||
// Sad path: field count mismatch (too many)
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
r#"(type $Foo (record (field "foo-bar-baz" s32) (field "b" u32) (field "c" u32)))"#,
|
||||
12,
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
assert!(instance
|
||||
.get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")
|
||||
.is_err());
|
||||
|
||||
// Sad path: field name mismatch
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(r#"(type $Foo (record (field "a" s32) (field "b" u32)))"#, 8),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
assert!(instance
|
||||
.get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")
|
||||
.is_err());
|
||||
|
||||
// Sad path: field type mismatch
|
||||
|
||||
let component = Component::new(
|
||||
&engine,
|
||||
make_echo_component(
|
||||
r#"(type $Foo (record (field "foo-bar-baz" s32) (field "b" s32)))"#,
|
||||
8,
|
||||
),
|
||||
)?;
|
||||
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
|
||||
|
||||
assert!(instance
|
||||
.get_typed_func::<(Foo,), Foo, _>(&mut store, "echo")
|
||||
.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user