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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user