Browse Source

Remove type information from `wasmtime::component::Val` (#8062)

This commit is a large refactor of the `Val` type as used with
components to remove inherent type information present currently. The
`Val` type is now only an AST of what a component model value looks like
and cannot fully describe the type that it is without further context.
For example enums only store the case that's being used, not the full
set of cases.

The motivation for this commit is to make it simpler to use and
construct `Val`, especially in the face of resources. Some problems
solved here are:

* With resources in play managing type information is not trivial and
  can often be surprising. For example if you learn the type of a
  function from a component and the instantiate the component twice the
  type information is not suitable to use with either function due to
  exported resources acquiring unique types on all instantiations.

* Functionally it's much easier to construct values when type
  information is not required as it no longer requires probing various
  pieces for type information here and there.

* API-wise there's far less for us to maintain as there's no need for a
  type-per-variant of component model types. Pieces now fit much more
  naturally into a `Val` shape without extra types.

* Functionally when working with `Val` there's now only one typecheck
  instead of two. Previously a typecheck was performed first when a
  `Val` was created and then again later when it was passed to wasm. Now
  the typecheck only happens when passed to wasm.

It's worth pointing out that `Val` as-is is a pretty inefficient
representation of component model values, for example flags are stored
as a list of strings. While semantically correct this is quite
inefficient for most purposes other than "get something working". To
that extent my goal is to, in the future, add traits that enable
building a custom user-defined `Val` (of sorts), but still dynamically.
This should enable embedders to opt-in to a more efficient
representation that relies on contextual knowledge.
pull/8071/head
Alex Crichton 8 months ago
committed by GitHub
parent
commit
9de6828e7b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      crates/component-macro/src/component.rs
  2. 72
      crates/environ/src/component/types.rs
  3. 40
      crates/environ/src/fact/trampoline.rs
  4. 96
      crates/fuzzing/src/generators/component_types.rs
  5. 5
      crates/wasmtime/src/runtime/component/func.rs
  6. 6
      crates/wasmtime/src/runtime/component/func/host.rs
  7. 8
      crates/wasmtime/src/runtime/component/func/typed.rs
  8. 2
      crates/wasmtime/src/runtime/component/mod.rs
  9. 119
      crates/wasmtime/src/runtime/component/types.rs
  10. 1326
      crates/wasmtime/src/runtime/component/values.rs
  11. 189
      crates/wast/src/component.rs
  12. 9
      crates/wast/src/wast.rs
  13. 266
      tests/all/component_model/dynamic.rs
  14. 4
      tests/all/component_model/import.rs

4
crates/component-macro/src/component.rs

@ -447,7 +447,7 @@ impl Expander for LiftExpander {
if let Some(ty) = ty {
let payload_ty = match style {
VariantStyle::Variant => {
quote!(ty.cases[#index].ty.unwrap_or_else(#internal::bad_type_info))
quote!(ty.cases[#index].unwrap_or_else(#internal::bad_type_info))
}
VariantStyle::Enum => unreachable!(),
};
@ -626,7 +626,7 @@ impl Expander for LowerExpander {
if ty.is_some() {
let ty = match style {
VariantStyle::Variant => {
quote!(ty.cases[#index].ty.unwrap_or_else(#internal::bad_type_info))
quote!(ty.cases[#index].unwrap_or_else(#internal::bad_type_info))
}
VariantStyle::Enum => unreachable!(),
};

72
crates/environ/src/component/types.rs

@ -8,7 +8,7 @@ use cranelift_entity::EntityRef;
use indexmap::{IndexMap, IndexSet};
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
use std::hash::Hash;
use std::hash::{Hash, Hasher};
use std::ops::Index;
use wasmparser::names::KebabString;
use wasmparser::types;
@ -757,19 +757,20 @@ impl ComponentTypesBuilder {
if case.refines.is_some() {
bail!("refines is not supported at this time");
}
Ok(VariantCase {
name: name.to_string(),
ty: match &case.ty.as_ref() {
Ok((
name.to_string(),
match &case.ty.as_ref() {
Some(ty) => Some(self.valtype(types, ty)?),
None => None,
},
})
))
})
.collect::<Result<Box<[_]>>>()?;
let (info, abi) = VariantInfo::new(cases.iter().map(|c| {
c.ty.as_ref()
.map(|ty| self.component_types.canonical_abi(ty))
}));
.collect::<Result<IndexMap<_, _>>>()?;
let (info, abi) = VariantInfo::new(
cases
.iter()
.map(|(_, c)| c.as_ref().map(|ty| self.component_types.canonical_abi(ty))),
);
Ok(self.add_variant_type(TypeVariant { cases, abi, info }))
}
@ -804,7 +805,10 @@ impl ComponentTypesBuilder {
}
fn enum_type(&mut self, variants: &IndexSet<KebabString>) -> TypeEnumIndex {
let names = variants.iter().map(|s| s.to_string()).collect::<Box<[_]>>();
let names = variants
.iter()
.map(|s| s.to_string())
.collect::<IndexSet<_>>();
let (info, abi) = VariantInfo::new(names.iter().map(|_| None));
self.add_enum_type(TypeEnum { names, abi, info })
}
@ -1485,24 +1489,23 @@ pub struct RecordField {
/// Variants are close to Rust `enum` declarations where a value is one of many
/// cases and each case has a unique name and an optional payload associated
/// with it.
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
pub struct TypeVariant {
/// The list of cases that this variant can take.
pub cases: Box<[VariantCase]>,
pub cases: IndexMap<String, Option<InterfaceType>>,
/// Byte information about this type in the canonical ABI.
pub abi: CanonicalAbiInfo,
/// Byte information about this variant type.
pub info: VariantInfo,
}
/// One case of a `variant` type which contains the name of the variant as well
/// as the payload.
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
pub struct VariantCase {
/// Name of the variant, unique amongst all cases in a variant.
pub name: String,
/// Optional type associated with this payload.
pub ty: Option<InterfaceType>,
impl Hash for TypeVariant {
fn hash<H: Hasher>(&self, h: &mut H) {
let TypeVariant { cases, abi, info } = self;
cases.as_slice().hash(h);
abi.hash(h);
info.hash(h);
}
}
/// Shape of a "tuple" type in interface types.
@ -1521,29 +1524,46 @@ pub struct TypeTuple {
///
/// This can be thought of as a record-of-bools, although the representation is
/// more efficient as bitflags.
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
pub struct TypeFlags {
/// The names of all flags, all of which are unique.
pub names: Box<[String]>,
pub names: IndexSet<String>,
/// Byte information about this type in the canonical ABI.
pub abi: CanonicalAbiInfo,
}
impl Hash for TypeFlags {
fn hash<H: Hasher>(&self, h: &mut H) {
let TypeFlags { names, abi } = self;
names.as_slice().hash(h);
abi.hash(h);
}
}
/// Shape of an "enum" type in interface types, not to be confused with a Rust
/// `enum` type.
///
/// In interface types enums are simply a bag of names, and can be seen as a
/// variant where all payloads are `Unit`.
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
pub struct TypeEnum {
/// The names of this enum, all of which are unique.
pub names: Box<[String]>,
pub names: IndexSet<String>,
/// Byte information about this type in the canonical ABI.
pub abi: CanonicalAbiInfo,
/// Byte information about this variant type.
pub info: VariantInfo,
}
impl Hash for TypeEnum {
fn hash<H: Hasher>(&self, h: &mut H) {
let TypeEnum { names, abi, info } = self;
names.as_slice().hash(h);
abi.hash(h);
info.hash(h);
}
}
/// Shape of an "option" interface type.
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
pub struct TypeOption {
@ -1909,7 +1929,7 @@ impl TypeInformation {
self.build_variant(
ty.cases
.iter()
.map(|c| c.ty.as_ref().map(|ty| types.type_information(ty))),
.map(|(_, c)| c.as_ref().map(|ty| types.type_information(ty))),
)
}

40
crates/environ/src/fact/trampoline.rs

@ -2179,25 +2179,29 @@ impl Compiler<'_, '_> {
_ => panic!("expected a variant"),
};
let src_info = variant_info(self.types, src_ty.cases.iter().map(|c| c.ty.as_ref()));
let dst_info = variant_info(self.types, dst_ty.cases.iter().map(|c| c.ty.as_ref()));
let src_info = variant_info(self.types, src_ty.cases.iter().map(|(_, c)| c.as_ref()));
let dst_info = variant_info(self.types, dst_ty.cases.iter().map(|(_, c)| c.as_ref()));
let iter = src_ty.cases.iter().enumerate().map(|(src_i, src_case)| {
let dst_i = dst_ty
.cases
.iter()
.position(|c| c.name == src_case.name)
.unwrap();
let dst_case = &dst_ty.cases[dst_i];
let src_i = u32::try_from(src_i).unwrap();
let dst_i = u32::try_from(dst_i).unwrap();
VariantCase {
src_i,
src_ty: src_case.ty.as_ref(),
dst_i,
dst_ty: dst_case.ty.as_ref(),
}
});
let iter = src_ty
.cases
.iter()
.enumerate()
.map(|(src_i, (src_case, src_case_ty))| {
let dst_i = dst_ty
.cases
.iter()
.position(|(c, _)| c == src_case)
.unwrap();
let dst_case_ty = &dst_ty.cases[dst_i];
let src_i = u32::try_from(src_i).unwrap();
let dst_i = u32::try_from(dst_i).unwrap();
VariantCase {
src_i,
src_ty: src_case_ty.as_ref(),
dst_i,
dst_ty: dst_case_ty.as_ref(),
}
});
self.convert_variant(src, &src_info, dst, &dst_info, iter);
}

96
crates/fuzzing/src/generators/component_types.rs

@ -46,77 +46,67 @@ pub fn arbitrary_val(ty: &component::Type, input: &mut Unstructured) -> arbitrar
Ok(ControlFlow::Continue(()))
})?;
list.new_val(values.into()).unwrap()
Val::List(values.into())
}
Type::Record(record) => record
.new_val(
record
.fields()
.map(|field| Ok((field.name, arbitrary_val(&field.ty, input)?)))
.collect::<arbitrary::Result<Vec<_>>>()?,
)
.unwrap(),
Type::Tuple(tuple) => tuple
.new_val(
tuple
.types()
.map(|ty| arbitrary_val(&ty, input))
.collect::<arbitrary::Result<_>>()?,
)
.unwrap(),
Type::Record(record) => Val::Record(
record
.fields()
.map(|field| Ok((field.name.to_string(), arbitrary_val(&field.ty, input)?)))
.collect::<arbitrary::Result<_>>()?,
),
Type::Tuple(tuple) => Val::Tuple(
tuple
.types()
.map(|ty| arbitrary_val(&ty, input))
.collect::<arbitrary::Result<_>>()?,
),
Type::Variant(variant) => {
let cases = variant.cases().collect::<Vec<_>>();
let case = input.choose(&cases)?;
let payload = match &case.ty {
Some(ty) => Some(arbitrary_val(ty, input)?),
Some(ty) => Some(Box::new(arbitrary_val(ty, input)?)),
None => None,
};
variant.new_val(case.name, payload).unwrap()
Val::Variant(case.name.to_string(), payload)
}
Type::Enum(en) => {
let names = en.names().collect::<Vec<_>>();
let name = input.choose(&names)?;
en.new_val(name).unwrap()
Val::Enum(name.to_string())
}
Type::Option(option) => {
let discriminant = input.int_in_range(0..=1)?;
option
.new_val(match discriminant {
0 => None,
1 => Some(arbitrary_val(&option.ty(), input)?),
_ => unreachable!(),
})
.unwrap()
Val::Option(match discriminant {
0 => None,
1 => Some(Box::new(arbitrary_val(&option.ty(), input)?)),
_ => unreachable!(),
})
}
Type::Result(result) => {
let discriminant = input.int_in_range(0..=1)?;
result
.new_val(match discriminant {
0 => Ok(match result.ok() {
Some(ty) => Some(arbitrary_val(&ty, input)?),
None => None,
}),
1 => Err(match result.err() {
Some(ty) => Some(arbitrary_val(&ty, input)?),
None => None,
}),
_ => unreachable!(),
})
.unwrap()
Val::Result(match discriminant {
0 => Ok(match result.ok() {
Some(ty) => Some(Box::new(arbitrary_val(&ty, input)?)),
None => None,
}),
1 => Err(match result.err() {
Some(ty) => Some(Box::new(arbitrary_val(&ty, input)?)),
None => None,
}),
_ => unreachable!(),
})
}
Type::Flags(flags) => flags
.new_val(
&flags
.names()
.filter_map(|name| {
input
.arbitrary()
.map(|p| if p { Some(name) } else { None })
.transpose()
})
.collect::<arbitrary::Result<Box<[_]>>>()?,
)
.unwrap(),
Type::Flags(flags) => Val::Flags(
flags
.names()
.filter_map(|name| {
input
.arbitrary()
.map(|p| if p { Some(name.to_string()) } else { None })
.transpose()
})
.collect::<arbitrary::Result<_>>()?,
),
// Resources aren't fuzzed at this time.
Type::Own(_) | Type::Borrow(_) => unreachable!(),

5
crates/wasmtime/src/runtime/component/func.rs

@ -395,11 +395,6 @@ impl Func {
);
}
for (param, ty) in params.iter().zip(param_tys.iter()) {
ty.is_supertype_of(param)
.context("type mismatch with parameters")?;
}
self.call_raw(
store,
params,

6
crates/wasmtime/src/runtime/component/func/host.rs

@ -1,7 +1,7 @@
use crate::component::func::{LiftContext, LowerContext, Options};
use crate::component::matching::InstanceType;
use crate::component::storage::slice_to_storage_mut;
use crate::component::{ComponentNamedList, ComponentType, Lift, Lower, Type, Val};
use crate::component::{ComponentNamedList, ComponentType, Lift, Lower, Val};
use crate::{AsContextMut, StoreContextMut, ValRaw};
use anyhow::{anyhow, bail, Context, Result};
use std::any::Any;
@ -383,10 +383,6 @@ where
flags.set_may_leave(false);
let mut cx = LowerContext::new(store, &options, types, instance);
let instance = cx.instance_type();
for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
Type::from(ty, &instance).is_supertype_of(val)?;
}
if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) {
let mut dst = storage[..cnt].iter_mut();
for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {

8
crates/wasmtime/src/runtime/component/func/typed.rs

@ -1787,12 +1787,12 @@ pub fn typecheck_variant(
);
}
for (case, &(name, check)) in cases.iter().zip(expected) {
if case.name != name {
bail!("expected variant case named {name}, found {}", case.name);
for ((case_name, case_ty), &(name, check)) in cases.iter().zip(expected) {
if *case_name != name {
bail!("expected variant case named {name}, found {case_name}");
}
match (check, &case.ty) {
match (check, case_ty) {
(Some(check), Some(ty)) => check(ty, types)
.with_context(|| format!("type mismatch for case {name}"))?,
(None, None) => {}

2
crates/wasmtime/src/runtime/component/mod.rs

@ -61,7 +61,7 @@ pub use self::linker::{Linker, LinkerInstance, ResourceImportIndex};
pub use self::resource_table::{ResourceTable, ResourceTableError};
pub use self::resources::{Resource, ResourceAny};
pub use self::types::{ResourceType, Type};
pub use self::values::{Enum, Flags, List, OptionVal, Record, ResultVal, Tuple, Val, Variant};
pub use self::values::Val;
pub(crate) use self::resources::HostResourceData;

119
crates/wasmtime/src/runtime/component/types.rs

@ -1,18 +1,15 @@
//! This module defines the `Type` type, representing the dynamic form of a component interface type.
use crate::component::matching::InstanceType;
use crate::component::values::{self, Val};
use crate::{Engine, ExternType, FuncType};
use anyhow::{anyhow, Result};
use std::fmt;
use std::mem;
use std::ops::Deref;
use std::sync::Arc;
use wasmtime_environ::component::{
CanonicalAbiInfo, ComponentTypes, InterfaceType, ResourceIndex, TypeComponentIndex,
TypeComponentInstanceIndex, TypeDef, TypeEnumIndex, TypeFlagsIndex, TypeFuncIndex,
TypeListIndex, TypeModuleIndex, TypeOptionIndex, TypeRecordIndex, TypeResourceTableIndex,
TypeResultIndex, TypeTupleIndex, TypeVariantIndex,
ComponentTypes, InterfaceType, ResourceIndex, TypeComponentIndex, TypeComponentInstanceIndex,
TypeDef, TypeEnumIndex, TypeFlagsIndex, TypeFuncIndex, TypeListIndex, TypeModuleIndex,
TypeOptionIndex, TypeRecordIndex, TypeResourceTableIndex, TypeResultIndex, TypeTupleIndex,
TypeVariantIndex,
};
use wasmtime_environ::PrimaryMap;
@ -183,18 +180,21 @@ impl TypeChecker<'_> {
if a.cases.len() != b.cases.len() {
return false;
}
a.cases.iter().zip(b.cases.iter()).all(|(a_case, b_case)| {
if a_case.name != b_case.name {
return false;
}
match (a_case.ty, b_case.ty) {
(Some(a_case_ty), Some(b_case_ty)) => {
self.interface_types_equal(a_case_ty, b_case_ty)
a.cases
.iter()
.zip(b.cases.iter())
.all(|((a_name, a_ty), (b_name, b_ty))| {
if a_name != b_name {
return false;
}
(None, None) => true,
_ => false,
}
})
match (a_ty, b_ty) {
(Some(a_case_ty), Some(b_case_ty)) => {
self.interface_types_equal(*a_case_ty, *b_case_ty)
}
(None, None) => true,
_ => false,
}
})
}
fn results_equal(&self, r1: TypeResultIndex, r2: TypeResultIndex) -> bool {
@ -259,11 +259,6 @@ impl PartialEq for List {
impl Eq for List {}
impl List {
/// Instantiate this type with the specified `values`.
pub fn new_val(&self, values: Box<[Val]>) -> Result<Val> {
Ok(Val::List(values::List::new(self, values)?))
}
pub(crate) fn from(index: TypeListIndex, ty: &InstanceType<'_>) -> Self {
List(Handle::new(index, ty))
}
@ -288,11 +283,6 @@ pub struct Field<'a> {
pub struct Record(Handle<TypeRecordIndex>);
impl Record {
/// Instantiate this type with the specified `values`.
pub fn new_val<'a>(&self, values: impl IntoIterator<Item = (&'a str, Val)>) -> Result<Val> {
Ok(Val::Record(values::Record::new(self, values)?))
}
pub(crate) fn from(index: TypeRecordIndex, ty: &InstanceType<'_>) -> Self {
Record(Handle::new(index, ty))
}
@ -319,11 +309,6 @@ impl Eq for Record {}
pub struct Tuple(Handle<TypeTupleIndex>);
impl Tuple {
/// Instantiate this type ith the specified `values`.
pub fn new_val(&self, values: Box<[Val]>) -> Result<Val> {
Ok(Val::Tuple(values::Tuple::new(self, values)?))
}
pub(crate) fn from(index: TypeTupleIndex, ty: &InstanceType<'_>) -> Self {
Tuple(Handle::new(index, ty))
}
@ -358,24 +343,19 @@ pub struct Case<'a> {
pub struct Variant(Handle<TypeVariantIndex>);
impl Variant {
/// Instantiate this type with the specified case `name` and `value`.
pub fn new_val(&self, name: &str, value: Option<Val>) -> Result<Val> {
Ok(Val::Variant(values::Variant::new(self, name, value)?))
}
pub(crate) fn from(index: TypeVariantIndex, ty: &InstanceType<'_>) -> Self {
Variant(Handle::new(index, ty))
}
/// Retrieve the cases of this `variant` in declaration order.
pub fn cases(&self) -> impl ExactSizeIterator<Item = Case> {
self.0.types[self.0.index].cases.iter().map(|case| Case {
name: &case.name,
ty: case
.ty
.as_ref()
.map(|ty| Type::from(ty, &self.0.instance())),
})
self.0.types[self.0.index]
.cases
.iter()
.map(|(name, ty)| Case {
name: name,
ty: ty.as_ref().map(|ty| Type::from(ty, &self.0.instance())),
})
}
}
@ -392,11 +372,6 @@ impl Eq for Variant {}
pub struct Enum(Handle<TypeEnumIndex>);
impl Enum {
/// Instantiate this type with the specified case `name`.
pub fn new_val(&self, name: &str) -> Result<Val> {
Ok(Val::Enum(values::Enum::new(self, name)?))
}
pub(crate) fn from(index: TypeEnumIndex, ty: &InstanceType<'_>) -> Self {
Enum(Handle::new(index, ty))
}
@ -423,11 +398,6 @@ impl Eq for Enum {}
pub struct OptionType(Handle<TypeOptionIndex>);
impl OptionType {
/// Instantiate this type with the specified `value`.
pub fn new_val(&self, value: Option<Val>) -> Result<Val> {
Ok(Val::Option(values::OptionVal::new(self, value)?))
}
pub(crate) fn from(index: TypeOptionIndex, ty: &InstanceType<'_>) -> Self {
OptionType(Handle::new(index, ty))
}
@ -451,11 +421,6 @@ impl Eq for OptionType {}
pub struct ResultType(Handle<TypeResultIndex>);
impl ResultType {
/// Instantiate this type with the specified `value`.
pub fn new_val(&self, value: Result<Option<Val>, Option<Val>>) -> Result<Val> {
Ok(Val::Result(values::ResultVal::new(self, value)?))
}
pub(crate) fn from(index: TypeResultIndex, ty: &InstanceType<'_>) -> Self {
ResultType(Handle::new(index, ty))
}
@ -490,11 +455,6 @@ impl Eq for ResultType {}
pub struct Flags(Handle<TypeFlagsIndex>);
impl Flags {
/// Instantiate this type with the specified flag `names`.
pub fn new_val(&self, names: &[&str]) -> Result<Val> {
Ok(Val::Flags(values::Flags::new(self, names)?))
}
pub(crate) fn from(index: TypeFlagsIndex, ty: &InstanceType<'_>) -> Self {
Flags(Handle::new(index, ty))
}
@ -506,10 +466,6 @@ impl Flags {
.iter()
.map(|name| name.deref())
}
pub(crate) fn canonical_abi(&self) -> &CanonicalAbiInfo {
&self.0.types[self.0.index].abi
}
}
impl PartialEq for Flags {
@ -678,31 +634,6 @@ impl Type {
}
}
/// Checks whether type of `value` is a subtype of `self`.
///
/// # Errors
///
/// Returns an error in case of a type mismatch
pub(crate) fn is_supertype_of(&self, value: &Val) -> Result<()> {
let other = &value.ty();
if self == other
|| matches!((self, other), (Self::Borrow(s), Self::Own(other)) if s == other)
{
Ok(())
} else if mem::discriminant(self) != mem::discriminant(other) {
Err(anyhow!(
"type mismatch: expected {}, got {}",
self.desc(),
other.desc()
))
} else {
Err(anyhow!(
"type mismatch for {}, possibly due to mixing distinct composite types",
self.desc()
))
}
}
/// Convert the specified `InterfaceType` to a `Type`.
pub(crate) fn from(ty: &InterfaceType, instance: &InstanceType<'_>) -> Self {
match ty {

1326
crates/wasmtime/src/runtime/component/values.rs

File diff suppressed because it is too large

189
crates/wast/src/component.rs

@ -1,13 +1,13 @@
use crate::core;
use anyhow::{anyhow, bail, Context, Result};
use std::collections::{BTreeSet, HashMap};
use anyhow::{bail, Context, Result};
use std::collections::BTreeSet;
use std::fmt::Debug;
use wast::component::WastVal;
use wast::core::NanPattern;
pub use wasmtime::component::*;
pub fn val(v: &WastVal<'_>, ty: &Type) -> Result<Val> {
pub fn val(v: &WastVal<'_>) -> Result<Val> {
Ok(match v {
WastVal::Bool(b) => Val::Bool(*b),
WastVal::U8(b) => Val::U8(*b),
@ -22,104 +22,44 @@ pub fn val(v: &WastVal<'_>, ty: &Type) -> Result<Val> {
WastVal::Float64(b) => Val::Float64(f64::from_bits(b.bits)),
WastVal::Char(b) => Val::Char(*b),
WastVal::String(s) => Val::String(s.to_string().into()),
WastVal::List(vals) => match ty {
Type::List(t) => {
let element = t.ty();
let vals = vals
.iter()
.map(|v| val(v, &element))
.collect::<Result<Vec<_>>>()?;
t.new_val(vals.into())?
}
_ => bail!("expected a list value"),
},
WastVal::Record(fields) => match ty {
Type::Record(t) => {
let mut fields_by_name = HashMap::new();
for (name, val) in fields {
let prev = fields_by_name.insert(*name, val);
if prev.is_some() {
bail!("field `{name}` specified twice");
}
}
let mut values = Vec::new();
for field in t.fields() {
let name = field.name;
let v = fields_by_name
.remove(name)
.ok_or_else(|| anyhow!("field `{name}` not specified"))?;
values.push((name, val(v, &field.ty)?));
}
if let Some((field, _)) = fields_by_name.iter().next() {
bail!("extra field `{field}` specified");
}
t.new_val(values)?
}
_ => bail!("expected a record value"),
},
WastVal::Tuple(vals) => match ty {
Type::Tuple(t) => {
if vals.len() != t.types().len() {
bail!("expected {} values got {}", t.types().len(), vals.len());
}
t.new_val(
vals.iter()
.zip(t.types())
.map(|(v, ty)| val(v, &ty))
.collect::<Result<Vec<_>>>()?
.into(),
)?
}
_ => bail!("expected a tuple value"),
},
WastVal::Enum(name) => match ty {
Type::Enum(t) => t.new_val(name)?,
_ => bail!("expected an enum value"),
},
WastVal::Variant(name, payload) => match ty {
Type::Variant(t) => {
let case = match t.cases().find(|c| c.name == *name) {
Some(case) => case,
None => bail!("no case named `{}", name),
};
let payload = payload_val(case.name, payload.as_deref(), case.ty.as_ref())?;
t.new_val(name, payload)?
}
_ => bail!("expected a variant value"),
},
WastVal::Option(v) => match ty {
Type::Option(t) => {
let v = match v {
Some(v) => Some(val(v, &t.ty())?),
None => None,
};
t.new_val(v)?
}
_ => bail!("expected an option value"),
},
WastVal::Result(v) => match ty {
Type::Result(t) => {
let v = match v {
Ok(v) => Ok(payload_val("ok", v.as_deref(), t.ok().as_ref())?),
Err(v) => Err(payload_val("err", v.as_deref(), t.err().as_ref())?),
};
t.new_val(v)?
WastVal::List(vals) => {
let vals = vals.iter().map(|v| val(v)).collect::<Result<Vec<_>>>()?;
Val::List(vals.into())
}
WastVal::Record(vals) => {
let mut fields = Vec::new();
for (name, v) in vals {
fields.push((name.to_string(), val(v)?));
}
_ => bail!("expected an expected value"),
},
WastVal::Flags(v) => match ty {
Type::Flags(t) => t.new_val(v)?,
_ => bail!("expected a flags value"),
},
Val::Record(fields.into())
}
WastVal::Tuple(vals) => Val::Tuple(
vals.iter()
.map(|v| val(v))
.collect::<Result<Vec<_>>>()?
.into(),
),
WastVal::Enum(name) => Val::Enum(name.to_string()),
WastVal::Variant(name, payload) => {
let payload = payload_val(payload.as_deref())?;
Val::Variant(name.to_string(), payload)
}
WastVal::Option(v) => Val::Option(match v {
Some(v) => Some(Box::new(val(v)?)),
None => None,
}),
WastVal::Result(v) => Val::Result(match v {
Ok(v) => Ok(payload_val(v.as_deref())?),
Err(v) => Err(payload_val(v.as_deref())?),
}),
WastVal::Flags(v) => Val::Flags(v.iter().map(|s| s.to_string()).collect()),
})
}
fn payload_val(name: &str, v: Option<&WastVal<'_>>, ty: Option<&Type>) -> Result<Option<Val>> {
match (v, ty) {
(Some(v), Some(ty)) => Ok(Some(val(v, ty)?)),
(None, None) => Ok(None),
(Some(_), None) => bail!("expected payload for case `{name}`"),
(None, Some(_)) => bail!("unexpected payload for case `{name}`"),
fn payload_val(v: Option<&WastVal<'_>>) -> Result<Option<Box<Val>>> {
match v {
Some(v) => Ok(Some(Box::new(val(v)?))),
None => Ok(None),
}
}
@ -192,22 +132,15 @@ pub fn match_val(expected: &WastVal<'_>, actual: &Val) -> Result<()> {
},
WastVal::Record(e) => match actual {
Val::Record(a) => {
let mut fields_by_name = HashMap::new();
for (name, val) in e {
let prev = fields_by_name.insert(*name, val);
if prev.is_some() {
bail!("field `{name}` specified twice");
}
}
for (name, actual) in a.fields() {
let expected = fields_by_name
.remove(name)
.ok_or_else(|| anyhow!("field `{name}` not specified"))?;
match_val(expected, actual)
.with_context(|| format!("failed to match field `{name}`"))?;
if e.len() != e.len() {
bail!("mismatched number of record fields");
}
if let Some((field, _)) = fields_by_name.iter().next() {
bail!("extra field `{field}` specified");
for ((e_name, e_val), (a_name, a_val)) in e.iter().zip(a.iter()) {
if e_name != a_name {
bail!("expected field `{e_name}` got `{a_name}`");
}
match_val(e_val, a_val)
.with_context(|| format!("failed to match field `{e_name}`"))?;
}
Ok(())
}
@ -215,14 +148,10 @@ pub fn match_val(expected: &WastVal<'_>, actual: &Val) -> Result<()> {
},
WastVal::Tuple(e) => match actual {
Val::Tuple(a) => {
if e.len() != a.values().len() {
bail!(
"expected {}-tuple, found {}-tuple",
e.len(),
a.values().len()
);
if e.len() != a.len() {
bail!("expected {}-tuple, found {}-tuple", e.len(), a.len());
}
for (i, (expected, actual)) in e.iter().zip(a.values()).enumerate() {
for (i, (expected, actual)) in e.iter().zip(a.iter()).enumerate() {
match_val(expected, actual)
.with_context(|| format!("failed to match tuple element {i}"))?;
}
@ -231,18 +160,18 @@ pub fn match_val(expected: &WastVal<'_>, actual: &Val) -> Result<()> {
_ => mismatch(expected, actual),
},
WastVal::Variant(name, e) => match actual {
Val::Variant(a) => {
if a.discriminant() != *name {
bail!("expected discriminant `{name}` got `{}`", a.discriminant());
Val::Variant(discr, payload) => {
if *discr != *name {
bail!("expected discriminant `{name}` got `{discr}`");
}
match_payload_val(name, e.as_deref(), a.payload())
match_payload_val(name, e.as_deref(), payload.as_deref())
}
_ => mismatch(expected, actual),
},
WastVal::Enum(name) => match actual {
Val::Enum(a) => {
if a.discriminant() != *name {
bail!("expected discriminant `{name}` got `{}`", a.discriminant());
if *a != *name {
bail!("expected discriminant `{name}` got `{a}`");
} else {
Ok(())
}
@ -250,7 +179,7 @@ pub fn match_val(expected: &WastVal<'_>, actual: &Val) -> Result<()> {
_ => mismatch(expected, actual),
},
WastVal::Option(e) => match actual {
Val::Option(a) => match (e, a.value()) {
Val::Option(a) => match (e, a) {
(None, None) => Ok(()),
(Some(expected), Some(actual)) => match_val(expected, actual),
(None, Some(_)) => bail!("expected `none`, found `some`"),
@ -259,18 +188,18 @@ pub fn match_val(expected: &WastVal<'_>, actual: &Val) -> Result<()> {
_ => mismatch(expected, actual),
},
WastVal::Result(e) => match actual {
Val::Result(a) => match (e, a.value()) {
Val::Result(a) => match (e, a) {
(Ok(_), Err(_)) => bail!("expected `ok`, found `err`"),
(Err(_), Ok(_)) => bail!("expected `err`, found `ok`"),
(Err(e), Err(a)) => match_payload_val("err", e.as_deref(), a),
(Ok(e), Ok(a)) => match_payload_val("ok", e.as_deref(), a),
(Err(e), Err(a)) => match_payload_val("err", e.as_deref(), a.as_deref()),
(Ok(e), Ok(a)) => match_payload_val("ok", e.as_deref(), a.as_deref()),
},
_ => mismatch(expected, actual),
},
WastVal::Flags(e) => match actual {
Val::Flags(a) => {
let expected = e.iter().copied().collect::<BTreeSet<_>>();
let actual = a.flags().collect::<BTreeSet<_>>();
let actual = a.iter().map(|s| s.as_str()).collect::<BTreeSet<_>>();
match_debug(&actual, &expected)
}
_ => mismatch(expected, actual),

9
crates/wast/src/wast.rs

@ -186,16 +186,11 @@ where
}
#[cfg(feature = "component-model")]
Export::Component(func) => {
let params = func.params(&self.store);
if exec.args.len() != params.len() {
bail!("mismatched number of parameters")
}
let values = exec
.args
.iter()
.zip(params.iter())
.map(|(v, t)| match v {
WastArg::Component(v) => component::val(v, t),
.map(|v| match v {
WastArg::Component(v) => component::val(v),
WastArg::Core(_) => bail!("expected core function, found component"),
})
.collect::<Result<Vec<_>>>()?;

266
tests/all/component_model/dynamic.rs

@ -4,7 +4,7 @@ use super::{make_echo_component, make_echo_component_with_params, Param, Type};
use anyhow::Result;
use component_test_util::FuncExt;
use wasmtime::component::types::{self, Case, ComponentItem, Field};
use wasmtime::component::{self, Component, Linker, ResourceType, Val};
use wasmtime::component::{Component, Linker, ResourceType, Val};
use wasmtime::{Module, Store};
use wasmtime_component_util::REALLOC_AND_FREE;
@ -102,7 +102,7 @@ fn strings() -> Result<()> {
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 input = Val::String("hello, component!".into());
let mut output = [Val::Bool(false)];
func.call_and_post_return(&mut store, &[input.clone()], &mut output)?;
assert_eq!(input, output[0]);
@ -118,12 +118,11 @@ fn lists() -> Result<()> {
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([
let input = Val::List(vec![
Val::U32(32343),
Val::U32(79023439),
Val::U32(2084037802),
]))?;
]);
let mut output = [Val::Bool(false)];
func.call_and_post_return(&mut store, &[input.clone()], &mut output)?;
@ -131,15 +130,14 @@ fn lists() -> Result<()> {
// Sad path: type mismatch
let err = ty
.unwrap_list()
.new_val(Box::new([
Val::U32(32343),
Val::U32(79023439),
Val::Float32(3.14159265),
]))
let err = Val::List(vec![
Val::U32(32343),
Val::U32(79023439),
Val::Float32(3.14159265),
]);
let err = func
.call_and_post_return(&mut store, &[err], &mut output)
.unwrap_err();
assert!(err.to_string().contains("type mismatch"), "{err}");
Ok(())
@ -175,18 +173,17 @@ fn records() -> Result<()> {
)?;
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)),
let input = Val::Record(vec![
("A".into(), Val::U32(32343)),
("B".into(), Val::Float64(3.14159265)),
(
"C",
inner_type
.unwrap_record()
.new_val([("D", Val::Bool(false)), ("E", Val::U32(2084037802))])?,
"C".into(),
Val::Record(vec![
("D".into(), Val::Bool(false)),
("E".into(), Val::U32(2084037802)),
]),
),
])?;
]);
let mut output = [Val::Bool(false)];
func.call_and_post_return(&mut store, &[input.clone()], &mut output)?;
@ -194,53 +191,61 @@ fn records() -> Result<()> {
// Sad path: type mismatch
let err = ty
.unwrap_record()
.new_val([
("A", Val::S32(32343)),
("B", Val::Float64(3.14159265)),
(
"C",
inner_type
.unwrap_record()
.new_val([("D", Val::Bool(false)), ("E", Val::U32(2084037802))])?,
),
])
let err = Val::Record(vec![
("A".into(), Val::S32(32343)),
("B".into(), Val::Float64(3.14159265)),
(
"C".into(),
Val::Record(vec![
("D".into(), Val::Bool(false)),
("E".into(), Val::U32(2084037802)),
]),
),
]);
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, &[err], &mut output)
.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)),
(
"C",
inner_type
.unwrap_record()
.new_val([("D", Val::Bool(false)), ("E", Val::U32(2084037802))])?,
),
("F", Val::Bool(true)),
])
let err = Val::Record(vec![
("A".into(), Val::U32(32343)),
("B".into(), Val::Float64(3.14159265)),
(
"C".into(),
Val::Record(vec![
("D".into(), Val::Bool(false)),
("E".into(), Val::U32(2084037802)),
]),
),
("F".into(), Val::Bool(true)),
]);
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, &[err], &mut output)
.unwrap_err();
assert!(
err.to_string().contains("expected 3 value(s); got 4"),
err.to_string().contains("expected 3 fields, got 4"),
"{err}"
);
// Sad path: too few fields
let err = ty
.unwrap_record()
.new_val([("A", Val::U32(32343)), ("B", Val::Float64(3.14159265))])
let err = Val::Record(vec![
("A".into(), Val::U32(32343)),
("B".into(), Val::Float64(3.14159265)),
]);
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, &[err], &mut output)
.unwrap_err();
assert!(
err.to_string().contains("expected 3 value(s); got 2"),
err.to_string().contains("expected 3 fields, got 2"),
"{err}"
);
@ -275,10 +280,7 @@ fn variants() -> Result<()> {
)?;
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", Some(Val::Float64(3.14159265)))?;
let input = Val::Variant("B".into(), Some(Box::new(Val::Float64(3.14159265))));
let mut output = [Val::Bool(false)];
func.call_and_post_return(&mut store, &[input.clone()], &mut output)?;
@ -299,28 +301,33 @@ fn variants() -> Result<()> {
)?;
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.unwrap();
let input = ty.unwrap_variant().new_val(
"C",
Some(
c_type
.unwrap_record()
.new_val([("D", Val::Bool(true)), ("E", Val::U32(314159265))])?,
),
)?;
let input = Val::Variant(
"C".into(),
Some(Box::new(Val::Record(vec![
("D".into(), Val::Bool(true)),
("E".into(), Val::U32(314159265)),
]))),
);
func.call_and_post_return(&mut store, &[input.clone()], &mut output)?;
assert_eq!(input, output[0]);
// Sad path: type mismatch
let err = ty
.unwrap_variant()
.new_val("B", Some(Val::U64(314159265)))
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let func = instance.get_func(&mut store, "echo").unwrap();
let err = Val::Variant("B".into(), Some(Box::new(Val::U64(314159265))));
let err = func
.call_and_post_return(&mut store, &[err], &mut output)
.unwrap_err();
assert!(err.to_string().contains("type mismatch"), "{err}");
let err = ty.unwrap_variant().new_val("B", None).unwrap_err();
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let func = instance.get_func(&mut store, "echo").unwrap();
let err = Val::Variant("B".into(), None);
let err = func
.call_and_post_return(&mut store, &[err], &mut output)
.unwrap_err();
assert!(
err.to_string().contains("expected a payload for case `B`"),
"{err}"
@ -328,12 +335,20 @@ fn variants() -> Result<()> {
// Sad path: unknown case
let err = ty
.unwrap_variant()
.new_val("D", Some(Val::U64(314159265)))
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let func = instance.get_func(&mut store, "echo").unwrap();
let err = Val::Variant("D".into(), Some(Box::new(Val::U64(314159265))));
let err = func
.call_and_post_return(&mut store, &[err], &mut output)
.unwrap_err();
assert!(err.to_string().contains("unknown variant case"), "{err}");
let err = ty.unwrap_variant().new_val("D", None).unwrap_err();
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let func = instance.get_func(&mut store, "echo").unwrap();
let err = Val::Variant("D".into(), None);
let err = func
.call_and_post_return(&mut store, &[err], &mut output)
.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
@ -365,17 +380,13 @@ fn variants() -> Result<()> {
)?;
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([
let input = Val::Record(vec![
(
"A",
a_type
.unwrap_variant()
.new_val("A", Some(Val::U32(314159265)))?,
"A".into(),
Val::Variant("A".into(), Some(Box::new(Val::U32(314159265)))),
),
("B", Val::U32(628318530)),
])?;
("B".into(), Val::U32(628318530)),
]);
func.call_and_post_return(&mut store, &[input.clone()], &mut output)?;
assert_eq!(input, output[0]);
@ -397,8 +408,7 @@ fn flags() -> Result<()> {
)?;
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 input = Val::Flags(vec!["B".into(), "D".into()]);
let mut output = [Val::Bool(false)];
func.call_and_post_return(&mut store, &[input.clone()], &mut output)?;
@ -406,8 +416,10 @@ fn flags() -> Result<()> {
// Sad path: unknown flags
let err = ty.unwrap_flags().new_val(&["B", "D", "F"]).unwrap_err();
let err = Val::Flags(vec!["B".into(), "D".into(), "F".into()]);
let err = func
.call_and_post_return(&mut store, &[err], &mut output)
.unwrap_err();
assert!(err.to_string().contains("unknown flag"), "{err}");
Ok(())
@ -490,62 +502,42 @@ fn everything() -> Result<()> {
)?;
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, aa_type, bb_type) = (
&types[1], &types[2], &types[3], &types[4], &types[13], &types[14], &types[15],
);
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("b")?),
(
"C",
c_type
.unwrap_record()
.new_val([("D", Val::Bool(false)), ("E", Val::U32(2084037802))])?,
),
let input = Val::Record(vec![
("A".into(), Val::U32(32343)),
("B".into(), Val::Enum("b".to_string())),
(
"F",
f_type.unwrap_list().new_val(Box::new([f_element_type
.unwrap_flags()
.new_val(&["G", "I"])?]))?,
"C".into(),
Val::Record(vec![
("D".to_string(), Val::Bool(false)),
("E".to_string(), Val::U32(2084037802)),
]),
),
(
"J",
j_type
.unwrap_variant()
.new_val("L", Some(Val::Float64(3.14159265)))?,
"F".into(),
Val::List(vec![Val::Flags(vec!["G".to_string(), "I".to_string()])]),
),
("P", Val::S8(42)),
("Q", Val::S16(4242)),
("R", Val::S32(42424242)),
("S", Val::S64(424242424242424242)),
("T", Val::Float32(3.14159265)),
("U", Val::Float64(3.14159265)),
("V", Val::String(Box::from("wow, nice types"))),
("W", Val::Char('🦀')),
(
"Y",
y_type
.unwrap_tuple()
.new_val(Box::new([Val::U32(42), Val::U32(24)]))?,
"J".into(),
Val::Variant("L".to_string(), Some(Box::new(Val::Float64(3.14159265)))),
),
("P".into(), Val::S8(42)),
("Q".into(), Val::S16(4242)),
("R".into(), Val::S32(42424242)),
("S".into(), Val::S64(424242424242424242)),
("T".into(), Val::Float32(3.14159265)),
("U".into(), Val::Float64(3.14159265)),
("V".into(), Val::String("wow, nice types".to_string())),
("W".into(), Val::Char('🦀')),
("Y".into(), Val::Tuple(vec![Val::U32(42), Val::U32(24)])),
(
"AA",
aa_type.unwrap_option().new_val(Some(Val::U32(314159265)))?,
"AA".into(),
Val::Option(Some(Box::new(Val::U32(314159265)))),
),
(
"BB",
bb_type
.unwrap_result()
.new_val(Ok(Some(Val::String(Box::from("no problem")))))?,
"BB".into(),
Val::Result(Ok(Some(Box::new(Val::String("no problem".to_string()))))),
),
])?;
]);
let mut output = [Val::Bool(false)];
func.call_and_post_return(&mut store, &[input.clone()], &mut output)?;

4
tests/all/component_model/import.rs

@ -706,7 +706,7 @@ fn stack_and_heap_args_and_rets() -> Result<()> {
.root()
.func_new(&component, "f2", |_, args, results| {
if let Val::Tuple(tuple) = &args[0] {
if let Val::String(s) = &tuple.values()[0] {
if let Val::String(s) = &tuple[0] {
assert_eq!(s.deref(), "abc");
results[0] = Val::U32(3);
Ok(())
@ -732,7 +732,7 @@ fn stack_and_heap_args_and_rets() -> Result<()> {
.root()
.func_new(&component, "f4", |_, args, results| {
if let Val::Tuple(tuple) = &args[0] {
if let Val::String(s) = &tuple.values()[0] {
if let Val::String(s) = &tuple[0] {
assert_eq!(s.deref(), "abc");
results[0] = Val::String("xyz".into());
Ok(())

Loading…
Cancel
Save