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:
Joel Dice
2022-06-29 08:38:36 -06:00
committed by GitHub
parent eef1758d19
commit 22fb3ecbbf
14 changed files with 660 additions and 46 deletions

View File

@@ -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",
]

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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).

View File

@@ -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;

View File

@@ -1112,6 +1112,7 @@ impl<T> StoreInner<T> {
}
}
#[doc(hidden)]
impl StoreOpaque {
pub fn id(&self) -> StoreId {
self.store_data.id()