Implement module imports into components (#4208)

* Implement module imports into components

As a step towards implementing function imports into a component this
commit implements importing modules into a component. This fills out
missing pieces of functionality such as exporting modules as well. The
previous translation code had initial support for translating imported
modules but some of the AST type information was restructured with
feedback from this implementation, namely splitting the
`InstantiateModule` initializer into separate upvar/import variants to
clarify that the item orderings for imports are resolved differently at
runtime.

Much of this commit is also adding infrastructure for any imports at all
into a component. For example a `Linker` type (analagous to
`wasmtime::Linker`) was added here as well. For now this type is quite
limited due to the inability to define host functions (it can only work
with instances and instances-of-modules) but it's enough to start
writing `*.wast` tests which exercise lots of module-related functionality.

* Fix a warning
This commit is contained in:
Alex Crichton
2022-06-03 09:33:18 -05:00
committed by GitHub
parent 816aae6aca
commit b49c5c878e
12 changed files with 1254 additions and 250 deletions

View File

@@ -1,12 +1,14 @@
use crate::component::{Component, ComponentParams, ComponentValue, Func, TypedFunc};
use crate::instance::OwnedImports;
use crate::store::{StoreOpaque, Stored};
use crate::{AsContextMut, Module, StoreContextMut};
use crate::{AsContextMut, Module, StoreContext, StoreContextMut};
use anyhow::{anyhow, Context, Result};
use std::marker;
use std::sync::Arc;
use wasmtime_environ::component::{
ComponentTypes, CoreDef, CoreExport, Export, ExportItem, Initializer, ModuleToInstantiate,
RuntimeInstanceIndex, RuntimeMemoryIndex, RuntimeReallocIndex,
ComponentTypes, CoreDef, CoreExport, Export, ExportItem, Initializer, InstantiateModule,
RuntimeImportIndex, RuntimeInstanceIndex, RuntimeMemoryIndex, RuntimeModuleIndex,
RuntimeReallocIndex,
};
use wasmtime_environ::{EntityIndex, PrimaryMap};
@@ -26,6 +28,7 @@ pub(crate) struct InstanceData {
// alive and things like that, instead only the bare minimum necessary
// should be kept alive here (mostly just `wasmtime_environ::Component`.
component: Component,
exported_modules: PrimaryMap<RuntimeModuleIndex, Module>,
// TODO: move these to `VMComponentContext`
memories: PrimaryMap<RuntimeMemoryIndex, wasmtime_runtime::ExportMemory>,
@@ -33,21 +36,6 @@ pub(crate) struct InstanceData {
}
impl Instance {
/// Instantiates the `component` provided within the given `store`.
///
/// Does not support components which have imports at this time.
//
// FIXME: need to write more docs here.
pub fn new(mut store: impl AsContextMut, component: &Component) -> Result<Instance> {
let mut store = store.as_context_mut();
let mut instantiator = Instantiator::new(component);
instantiator.run(&mut store)?;
let data = Box::new(instantiator.data);
Ok(Instance(store.0.store_data_mut().insert(Some(data))))
}
/// Looks up a function by name within this [`Instance`].
///
/// The `store` specified must be the store that this instance lives within
@@ -101,6 +89,41 @@ impl Instance {
Ok(f.typed::<Params, Results, _>(store)
.with_context(|| format!("failed to convert function `{}` to given type", name))?)
}
/// Returns an iterator of all of the exported modules that this instance
/// contains.
//
// FIXME: this should probably be generalized in some form to something else
// that either looks like:
//
// * an iterator over all exports
// * an iterator for a `Component` with type information followed by a
// `get_module` function here
//
// For now this is just quick-and-dirty to get wast support for iterating
// over exported modules to work.
pub fn modules<'a, T: 'a>(
&'a self,
store: impl Into<StoreContext<'a, T>>,
) -> impl Iterator<Item = (&'a str, &'a Module)> + 'a {
let store = store.into();
self._modules(store.0)
}
fn _modules<'a>(
&'a self,
store: &'a StoreOpaque,
) -> impl Iterator<Item = (&'a str, &'a Module)> + '_ {
let data = store.store_data()[self.0].as_ref().unwrap();
data.component
.env_component()
.exports
.iter()
.filter_map(|(name, export)| match *export {
Export::Module(idx) => Some((name.as_str(), &data.exported_modules[idx])),
_ => None,
})
}
}
impl InstanceData {
@@ -109,6 +132,7 @@ impl InstanceData {
Export::LiftedFunction { ty, func, options } => {
Some(Func::from_lifted_func(store, self, *ty, func, options))
}
Export::Module(_) => None,
}
}
@@ -156,21 +180,30 @@ impl InstanceData {
struct Instantiator<'a> {
component: &'a Component,
data: InstanceData,
imports: OwnedImports,
core_imports: OwnedImports,
imports: &'a PrimaryMap<RuntimeImportIndex, RuntimeImport>,
}
pub enum RuntimeImport {
Module(Module),
}
impl<'a> Instantiator<'a> {
fn new(component: &'a Component) -> Instantiator<'a> {
fn new(
component: &'a Component,
imports: &'a PrimaryMap<RuntimeImportIndex, RuntimeImport>,
) -> Instantiator<'a> {
let env_component = component.env_component();
if env_component.imports.len() > 0 {
unimplemented!("component imports");
}
Instantiator {
component,
imports: OwnedImports::empty(),
imports,
core_imports: OwnedImports::empty(),
data: InstanceData {
instances: PrimaryMap::with_capacity(env_component.num_runtime_instances as usize),
component: component.clone(),
exported_modules: PrimaryMap::with_capacity(
env_component.num_runtime_modules as usize,
),
memories: Default::default(),
reallocs: Default::default(),
},
@@ -181,16 +214,27 @@ impl<'a> Instantiator<'a> {
let env_component = self.component.env_component();
for initializer in env_component.initializers.iter() {
match initializer {
Initializer::InstantiateModule {
instance,
module,
args,
} => {
let module = match module {
ModuleToInstantiate::Upvar(module) => self.component.upvar(*module),
ModuleToInstantiate::Import(idx) => {
drop(idx);
unimplemented!("component module imports");
Initializer::InstantiateModule(m) => {
let module;
let imports = match m {
// Since upvars are statically know we know that the
// `args` list is already in the right order.
InstantiateModule::Upvar(idx, args) => {
module = self.component.upvar(*idx);
self.build_imports(store.0, module, args.iter())
}
// With imports, unlike upvars, we need to do runtime
// lookups with strings to determine the order of the
// imports since it's whatever the actual module
// requires.
InstantiateModule::Import(idx, args) => {
module = match &self.imports[*idx] {
RuntimeImport::Module(m) => m,
};
let args = module
.imports()
.map(|import| &args[import.module()][import.name()]);
self.build_imports(store.0, module, args)
}
};
@@ -198,42 +242,51 @@ impl<'a> Instantiator<'a> {
// validity of the component means that type-checks have
// already been performed. This maens that the unsafety due
// to imports having the wrong type should not happen here.
let imports = self.build_imports(store.0, module, args);
let i =
unsafe { crate::Instance::new_started(store, module, imports.as_ref())? };
let idx = self.data.instances.push(i);
assert_eq!(idx, *instance);
self.data.instances.push(i);
}
Initializer::LowerImport(_) => unimplemented!(),
Initializer::ExtractMemory { index, export } => {
Initializer::ExtractMemory(export) => {
let memory = match self.data.lookup_export(store.0, export) {
wasmtime_runtime::Export::Memory(m) => m,
_ => unreachable!(),
};
assert_eq!(*index, self.data.memories.push(memory));
self.data.memories.push(memory);
}
Initializer::ExtractRealloc { index, def } => {
Initializer::ExtractRealloc(def) => {
let func = match self.data.lookup_def(store.0, def) {
wasmtime_runtime::Export::Function(f) => f,
_ => unreachable!(),
};
assert_eq!(*index, self.data.reallocs.push(func));
self.data.reallocs.push(func);
}
Initializer::SaveModuleUpvar(idx) => {
self.data
.exported_modules
.push(self.component.upvar(*idx).clone());
}
Initializer::SaveModuleImport(idx) => {
self.data.exported_modules.push(match &self.imports[*idx] {
RuntimeImport::Module(m) => m.clone(),
});
}
}
}
Ok(())
}
fn build_imports(
fn build_imports<'b>(
&mut self,
store: &mut StoreOpaque,
module: &Module,
args: &[CoreDef],
args: impl Iterator<Item = &'b CoreDef>,
) -> &OwnedImports {
self.imports.clear();
self.imports.reserve(module);
self.core_imports.clear();
self.core_imports.reserve(module);
for arg in args {
let export = self.data.lookup_def(store, arg);
@@ -242,10 +295,58 @@ impl<'a> Instantiator<'a> {
// directly from an instance which should only give us valid export
// items.
unsafe {
self.imports.push_export(&export);
self.core_imports.push_export(&export);
}
}
&self.imports
&self.core_imports
}
}
/// A "pre-instantiated" [`Instance`] which has all of its arguments already
/// supplied and is ready to instantiate.
///
/// This structure represents an efficient form of instantiation where import
/// type-checking and import lookup has all been resolved by the time that this
/// type is created. This type is primarily created through the
/// [`Linker::instance_pre`](crate::component::Linker::instance_pre) method.
pub struct InstancePre<T> {
component: Component,
imports: PrimaryMap<RuntimeImportIndex, RuntimeImport>,
_marker: marker::PhantomData<fn() -> T>,
}
impl<T> InstancePre<T> {
/// This function is `unsafe` since there's no guarantee that the
/// `RuntimeImport` items provided are guaranteed to work with the `T` of
/// the store.
///
/// Additionally there is no static guarantee that the `imports` provided
/// satisfy the imports of the `component` provided.
pub(crate) unsafe fn new_unchecked(
component: Component,
imports: PrimaryMap<RuntimeImportIndex, RuntimeImport>,
) -> InstancePre<T> {
InstancePre {
component,
imports,
_marker: marker::PhantomData,
}
}
/// Returns the underlying component that will be instantiated.
pub fn component(&self) -> &Component {
&self.component
}
/// Performs the instantiation process into the store specified.
//
// TODO: needs more docs
pub fn instantiate(&self, mut store: impl AsContextMut<Data = T>) -> Result<Instance> {
let mut store = store.as_context_mut();
let mut i = Instantiator::new(&self.component, &self.imports);
i.run(&mut store)?;
let data = Box::new(i.data);
Ok(Instance(store.0.store_data_mut().insert(Some(data))))
}
}

View File

@@ -0,0 +1,272 @@
use crate::component::instance::RuntimeImport;
use crate::component::matching::TypeChecker;
use crate::component::{Component, Instance, InstancePre};
use crate::{AsContextMut, Engine, Module};
use anyhow::{anyhow, bail, Context, Result};
use std::collections::hash_map::{Entry, HashMap};
use std::marker;
use std::sync::Arc;
use wasmtime_environ::PrimaryMap;
/// A type used to instantiate [`Component`]s.
///
/// This type is used to both link components together as well as supply host
/// functionality to components. Values are defined in a [`Linker`] by their
/// import name and then components are instantiated with a [`Linker`] using the
/// names provided for name resolution of the component's imports.
pub struct Linker<T> {
engine: Engine,
strings: Strings,
map: NameMap,
allow_shadowing: bool,
_marker: marker::PhantomData<fn() -> T>,
}
#[derive(Default)]
pub struct Strings {
string2idx: HashMap<Arc<str>, usize>,
strings: Vec<Arc<str>>,
}
/// Structure representing an "instance" being defined within a linker.
///
/// Instances do not need to be actual [`Instance`]s and instead are defined by
/// a "bag of named items", so each [`LinkerInstance`] can further define items
/// internally.
pub struct LinkerInstance<'a, T> {
strings: &'a mut Strings,
map: &'a mut NameMap,
allow_shadowing: bool,
_marker: marker::PhantomData<fn() -> T>,
}
pub type NameMap = HashMap<usize, Definition>;
#[derive(Clone)]
pub enum Definition {
Instance(NameMap),
Module(Module),
}
impl<T> Linker<T> {
/// Creates a new linker for the [`Engine`] specified with no items defined
/// within it.
pub fn new(engine: &Engine) -> Linker<T> {
Linker {
engine: engine.clone(),
strings: Strings::default(),
map: NameMap::default(),
allow_shadowing: false,
_marker: marker::PhantomData,
}
}
/// Returns the [`Engine`] this is connected to.
pub fn engine(&self) -> &Engine {
&self.engine
}
/// Configures whether or not name-shadowing is allowed.
///
/// By default name shadowing is not allowed and it's an error to redefine
/// the same name within a linker.
pub fn allow_shadowing(&mut self, allow: bool) -> &mut Self {
self.allow_shadowing = allow;
self
}
/// Returns the "root instance" of this linker, used to define names into
/// the root namespace.
pub fn root(&mut self) -> LinkerInstance<'_, T> {
LinkerInstance {
strings: &mut self.strings,
map: &mut self.map,
allow_shadowing: self.allow_shadowing,
_marker: self._marker,
}
}
/// Returns a builder for the named instance specified.
///
/// # Errors
///
/// Returns an error if `name` is already defined within the linker.
pub fn instance(&mut self, name: &str) -> Result<LinkerInstance<'_, T>> {
self.root().into_instance(name)
}
/// Performs a "pre-instantiation" to resolve the imports of the
/// [`Component`] specified with the items defined within this linker.
///
/// This method will perform as much work as possible short of actually
/// instnatiating an instance. Internally this will use the names defined
/// within this linker to satisfy the imports of the [`Component`] provided.
/// Additionally this will perform type-checks against the component's
/// imports against all items defined within this linker.
///
/// Note that unlike internally in components where subtyping at the
/// interface-types layer is supported this is not supported here. Items
/// defined in this linker must match the component's imports precisely.
///
/// # Errors
///
/// Returns an error if this linker doesn't define a name that the
/// `component` imports or if a name defined doesn't match the type of the
/// item imported by the `component` provided.
pub fn instantiate_pre(&self, component: &Component) -> Result<InstancePre<T>> {
let cx = TypeChecker {
types: component.types(),
strings: &self.strings,
};
// Walk over the component's list of import names and use that to lookup
// the definition within this linker that it corresponds to. When found
// perform a typecheck against the component's expected type.
let env_component = component.env_component();
for (_idx, (name, ty)) in env_component.import_types.iter() {
let import = self
.strings
.lookup(name)
.and_then(|name| self.map.get(&name))
.ok_or_else(|| anyhow!("import `{name}` not defined"))?;
cx.definition(ty, import)
.with_context(|| format!("import `{name}` has the wrong type"))?;
}
// Now that all imports are known to be defined and satisfied by this
// linker a list of "flat" import items (aka no instances) is created
// using the import map within the component created at
// component-compile-time.
let mut imports = PrimaryMap::with_capacity(env_component.imports.len());
for (idx, (import, names)) in env_component.imports.iter() {
let (root, _) = &env_component.import_types[*import];
let root = self.strings.lookup(root).unwrap();
// This is the flattening process where we go from a definition
// optionally through a list of exported names to get to the final
// item.
let mut cur = &self.map[&root];
for name in names {
let name = self.strings.lookup(name).unwrap();
cur = match cur {
Definition::Instance(map) => &map[&name],
_ => unreachable!(),
};
}
let import = match cur {
Definition::Module(m) => RuntimeImport::Module(m.clone()),
// This is guaranteed by the compilation process that "leaf"
// runtime imports are never instances.
Definition::Instance(_) => unreachable!(),
};
let i = imports.push(import);
assert_eq!(i, idx);
}
Ok(unsafe { InstancePre::new_unchecked(component.clone(), imports) })
}
/// Instantiates the [`Component`] provided into the `store` specified.
///
/// This function will use the items defined within this [`Linker`] to
/// satisfy the imports of the [`Component`] provided as necessary. For more
/// information about this see [`Linker::instantiate_pre`] as well.
///
/// # Errors
///
/// Returns an error if this [`Linker`] doesn't define an import that
/// `component` requires or if it is of the wrong type. Additionally this
/// can return an error if something goes wrong during instantiation such as
/// a runtime trap or a runtime limit being exceeded.
pub fn instantiate(
&self,
store: impl AsContextMut<Data = T>,
component: &Component,
) -> Result<Instance> {
self.instantiate_pre(component)?.instantiate(store)
}
}
impl<T> LinkerInstance<'_, T> {
fn as_mut(&mut self) -> LinkerInstance<'_, T> {
LinkerInstance {
strings: self.strings,
map: self.map,
allow_shadowing: self.allow_shadowing,
_marker: self._marker,
}
}
/// Defines a [`Module`] within this instance.
///
/// This can be used to provide a core wasm [`Module`] as an import to a
/// component. The [`Module`] provided is saved within the linker for the
/// specified `name` in this instance.
pub fn module(&mut self, name: &str, module: &Module) -> Result<()> {
let name = self.strings.intern(name);
self.insert(name, Definition::Module(module.clone()))
}
/// Defines a nested instance within this instance.
///
/// This can be used to describe arbitrarily nested levels of instances
/// within a linker to satisfy nested instance exports of components.
pub fn instance(&mut self, name: &str) -> Result<LinkerInstance<'_, T>> {
self.as_mut().into_instance(name)
}
/// Same as [`LinkerInstance::instance`] except with different liftime
/// parameters.
pub fn into_instance(mut self, name: &str) -> Result<Self> {
let name = self.strings.intern(name);
let item = Definition::Instance(NameMap::default());
let slot = match self.map.entry(name) {
Entry::Occupied(_) if !self.allow_shadowing => {
bail!("import of `{}` defined twice", self.strings.strings[name])
}
Entry::Occupied(o) => {
let slot = o.into_mut();
*slot = item;
slot
}
Entry::Vacant(v) => v.insert(item),
};
self.map = match slot {
Definition::Instance(map) => map,
_ => unreachable!(),
};
Ok(self)
}
fn insert(&mut self, key: usize, item: Definition) -> Result<()> {
match self.map.entry(key) {
Entry::Occupied(_) if !self.allow_shadowing => {
bail!("import of `{}` defined twice", self.strings.strings[key])
}
Entry::Occupied(mut e) => {
e.insert(item);
}
Entry::Vacant(v) => {
v.insert(item);
}
}
Ok(())
}
}
impl Strings {
fn intern(&mut self, string: &str) -> usize {
if let Some(idx) = self.string2idx.get(string) {
return *idx;
}
let string: Arc<str> = string.into();
let idx = self.strings.len();
self.strings.push(string.clone());
self.string2idx.insert(string, idx);
idx
}
pub fn lookup(&self, string: &str) -> Option<usize> {
self.string2idx.get(string).cloned()
}
}

View File

@@ -0,0 +1,84 @@
use crate::component::linker::{Definition, NameMap, Strings};
use crate::types::matching;
use crate::Module;
use anyhow::{anyhow, bail, Context, Result};
use wasmtime_environ::component::{ComponentInstanceType, ComponentTypes, ModuleType, TypeDef};
pub struct TypeChecker<'a> {
pub types: &'a ComponentTypes,
pub strings: &'a Strings,
}
impl TypeChecker<'_> {
pub fn definition(&self, expected: &TypeDef, actual: &Definition) -> Result<()> {
match *expected {
TypeDef::Module(t) => match actual {
Definition::Module(actual) => self.module(&self.types[t], actual),
_ => bail!("expected module found {}", actual.desc()),
},
TypeDef::ComponentInstance(t) => match actual {
Definition::Instance(actual) => self.instance(&self.types[t], actual),
_ => bail!("expected instance found {}", actual.desc()),
},
TypeDef::Func(_) => bail!("expected func found {}", actual.desc()),
TypeDef::Component(_) => bail!("expected component found {}", actual.desc()),
TypeDef::Interface(_) => bail!("expected type found {}", actual.desc()),
}
}
fn module(&self, expected: &ModuleType, actual: &Module) -> Result<()> {
let actual_types = actual.types();
let actual = actual.env_module();
// Every export that is expected should be in the actual module we have
for (name, expected) in expected.exports.iter() {
let idx = actual
.exports
.get(name)
.ok_or_else(|| anyhow!("module export `{name}` not defined"))?;
let actual = actual.type_of(*idx);
matching::entity_ty(expected, self.types.module_types(), &actual, actual_types)
.with_context(|| format!("module export `{name}` has the wrong type"))?;
}
// Note the opposite order of checks here. Every import that the actual
// module expects should be imported by the expected module since the
// expected module has the set of items given to the actual module.
// Additionally the "matches" check is inverted here.
for (module, name, actual) in actual.imports() {
// TODO: shouldn't need a `.to_string()` here ideally
let expected = expected
.imports
.get(&(module.to_string(), name.to_string()))
.ok_or_else(|| anyhow!("module import `{module}::{name}` not defined"))?;
matching::entity_ty(&actual, actual_types, expected, self.types.module_types())
.with_context(|| format!("module import `{module}::{name}` has the wrong type"))?;
}
Ok(())
}
fn instance(&self, expected: &ComponentInstanceType, actual: &NameMap) -> Result<()> {
// Like modules, every export in the expected type must be present in
// the actual type. It's ok, though, to have extra exports in the actual
// type.
for (name, expected) in expected.exports.iter() {
let actual = self
.strings
.lookup(name)
.and_then(|name| actual.get(&name))
.ok_or_else(|| anyhow!("instance export `{name}` not defined"))?;
self.definition(expected, actual)
.with_context(|| format!("instance export `{name}` has the wrong type"))?;
}
Ok(())
}
}
impl Definition {
fn desc(&self) -> &'static str {
match self {
Definition::Module(_) => "module",
Definition::Instance(_) => "instance",
}
}
}

View File

@@ -6,10 +6,13 @@
mod component;
mod func;
mod instance;
mod linker;
mod matching;
mod store;
pub use self::component::Component;
pub use self::func::{ComponentParams, ComponentValue, Func, Op, TypedFunc, WasmList, WasmStr};
pub use self::instance::Instance;
pub use self::instance::{Instance, InstancePre};
pub use self::linker::Linker;
// These items are expected to be used by an eventual
// `#[derive(ComponentValue)]`, they are not part of Wasmtime's API stability

View File

@@ -1,7 +1,7 @@
use crate::linker::Definition;
use crate::store::StoreOpaque;
use crate::{signatures::SignatureCollection, Engine, Extern};
use anyhow::{bail, Result};
use anyhow::{anyhow, bail, Result};
use wasmtime_environ::{
EntityType, Global, Memory, ModuleTypes, SignatureIndex, Table, WasmFuncType, WasmType,
};
@@ -16,84 +16,25 @@ pub struct MatchCx<'a> {
impl MatchCx<'_> {
pub fn global(&self, expected: &Global, actual: &crate::Global) -> Result<()> {
self.global_ty(expected, actual.wasmtime_ty(self.store.store_data()))
}
fn global_ty(&self, expected: &Global, actual: &Global) -> Result<()> {
match_ty(expected.wasm_ty, actual.wasm_ty, "global")?;
match_bool(
expected.mutability,
actual.mutability,
"global",
"mutable",
"immutable",
)?;
Ok(())
global_ty(expected, actual.wasmtime_ty(self.store.store_data()))
}
pub fn table(&self, expected: &Table, actual: &crate::Table) -> Result<()> {
self.table_ty(
table_ty(
expected,
actual.wasmtime_ty(self.store.store_data()),
Some(actual.internal_size(self.store)),
)
}
fn table_ty(
&self,
expected: &Table,
actual: &Table,
actual_runtime_size: Option<u32>,
) -> Result<()> {
match_ty(expected.wasm_ty, actual.wasm_ty, "table")?;
match_limits(
expected.minimum.into(),
expected.maximum.map(|i| i.into()),
actual_runtime_size.unwrap_or(actual.minimum).into(),
actual.maximum.map(|i| i.into()),
"table",
)?;
Ok(())
}
pub fn memory(&self, expected: &Memory, actual: &crate::Memory) -> Result<()> {
self.memory_ty(
memory_ty(
expected,
actual.wasmtime_ty(self.store.store_data()),
Some(actual.internal_size(self.store)),
)
}
fn memory_ty(
&self,
expected: &Memory,
actual: &Memory,
actual_runtime_size: Option<u64>,
) -> Result<()> {
match_bool(
expected.shared,
actual.shared,
"memory",
"shared",
"non-shared",
)?;
match_bool(
expected.memory64,
actual.memory64,
"memory",
"64-bit",
"32-bit",
)?;
match_limits(
expected.minimum,
expected.maximum,
actual_runtime_size.unwrap_or(actual.minimum),
actual.maximum,
"memory",
)?;
Ok(())
}
pub fn func(&self, expected: SignatureIndex, actual: &crate::Func) -> Result<()> {
self.vmshared_signature_index(expected, actual.sig_index(self.store.store_data()))
}
@@ -130,27 +71,7 @@ impl MatchCx<'_> {
}
};
let render = |ty: &WasmFuncType| {
let params = ty
.params()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", ");
let returns = ty
.returns()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", ");
format!("`({}) -> ({})`", params, returns)
};
bail!(
"{}: expected func of type {}, found func of type {}",
msg,
render(expected),
render(&actual)
)
Err(func_ty_mismatch(msg, expected, &actual))
}
/// Validates that the `expected` type matches the type of `actual`
@@ -188,6 +109,118 @@ impl MatchCx<'_> {
}
}
#[cfg_attr(not(feature = "component-model"), allow(dead_code))]
pub fn entity_ty(
expected: &EntityType,
expected_types: &ModuleTypes,
actual: &EntityType,
actual_types: &ModuleTypes,
) -> Result<()> {
match expected {
EntityType::Memory(expected) => match actual {
EntityType::Memory(actual) => memory_ty(expected, actual, None),
_ => bail!("expected memory found {}", entity_desc(actual)),
},
EntityType::Global(expected) => match actual {
EntityType::Global(actual) => global_ty(expected, actual),
_ => bail!("expected global found {}", entity_desc(actual)),
},
EntityType::Table(expected) => match actual {
EntityType::Table(actual) => table_ty(expected, actual, None),
_ => bail!("expected table found {}", entity_desc(actual)),
},
EntityType::Function(expected) => match actual {
EntityType::Function(actual) => {
let expected = &expected_types[*expected];
let actual = &actual_types[*actual];
if expected == actual {
Ok(())
} else {
Err(func_ty_mismatch(
"function types incompaible",
expected,
actual,
))
}
}
_ => bail!("expected func found {}", entity_desc(actual)),
},
EntityType::Tag(_) => unimplemented!(),
}
}
fn func_ty_mismatch(msg: &str, expected: &WasmFuncType, actual: &WasmFuncType) -> anyhow::Error {
let render = |ty: &WasmFuncType| {
let params = ty
.params()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", ");
let returns = ty
.returns()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", ");
format!("`({}) -> ({})`", params, returns)
};
anyhow!(
"{msg}: expected func of type {}, found func of type {}",
render(expected),
render(actual)
)
}
fn global_ty(expected: &Global, actual: &Global) -> Result<()> {
match_ty(expected.wasm_ty, actual.wasm_ty, "global")?;
match_bool(
expected.mutability,
actual.mutability,
"global",
"mutable",
"immutable",
)?;
Ok(())
}
fn table_ty(expected: &Table, actual: &Table, actual_runtime_size: Option<u32>) -> Result<()> {
match_ty(expected.wasm_ty, actual.wasm_ty, "table")?;
match_limits(
expected.minimum.into(),
expected.maximum.map(|i| i.into()),
actual_runtime_size.unwrap_or(actual.minimum).into(),
actual.maximum.map(|i| i.into()),
"table",
)?;
Ok(())
}
fn memory_ty(expected: &Memory, actual: &Memory, actual_runtime_size: Option<u64>) -> Result<()> {
match_bool(
expected.shared,
actual.shared,
"memory",
"shared",
"non-shared",
)?;
match_bool(
expected.memory64,
actual.memory64,
"memory",
"64-bit",
"32-bit",
)?;
match_limits(
expected.minimum,
expected.maximum,
actual_runtime_size.unwrap_or(actual.minimum),
actual.maximum,
"memory",
)?;
Ok(())
}
fn match_ty(expected: WasmType, actual: WasmType, desc: &str) -> Result<()> {
if expected == actual {
return Ok(());