Remove the module linking implementation in Wasmtime (#3958)

* Remove the module linking implementation in Wasmtime

This commit removes the experimental implementation of the module
linking WebAssembly proposal from Wasmtime. The module linking is no
longer intended for core WebAssembly but is instead incorporated into
the component model now at this point. This means that very large parts
of Wasmtime's implementation of module linking are no longer applicable
and would change greatly with an implementation of the component model.

The main purpose of this is to remove Wasmtime's reliance on the support
for module-linking in `wasmparser` and tooling crates. With this
reliance removed we can move over to the `component-model` branch of
`wasmparser` and use the updated support for the component model.
Additionally given the trajectory of the component model proposal the
embedding API of Wasmtime will not look like what it looks like today
for WebAssembly. For example the core wasm `Instance` will not change
and instead a `Component` is likely to be added instead.

Some more rationale for this is in #3941, but the basic idea is that I
feel that it's not going to be viable to develop support for the
component model on a non-`main` branch of Wasmtime. Additionaly I don't
think it's viable, for the same reasons as `wasm-tools`, to support the
old module linking proposal and the new component model at the same
time.

This commit takes a moment to not only delete the existing module
linking implementation but some abstractions are also simplified. For
example module serialization is a bit simpler that there's only one
module. Additionally instantiation is much simpler since the only
initializer we have to deal with are imports and nothing else.

Closes #3941

* Fix doc link

* Update comments
This commit is contained in:
Alex Crichton
2022-03-23 14:57:34 -05:00
committed by GitHub
parent 6a60e8363f
commit 76b82910c9
51 changed files with 400 additions and 4555 deletions

View File

@@ -602,20 +602,6 @@ impl Config {
self
}
/// Configures whether the WebAssembly module linking [proposal] will
/// be enabled for compilation.
///
/// Note that development of this feature is still underway, so enabling
/// this is likely to be full of bugs.
///
/// This is `false` by default.
///
/// [proposal]: https://github.com/webassembly/module-linking
pub fn wasm_module_linking(&mut self, enable: bool) -> &mut Self {
self.features.module_linking = enable;
self
}
/// Configures whether the WebAssembly memory64 [proposal] will
/// be enabled for compilation.
///
@@ -1374,7 +1360,6 @@ impl fmt::Debug for Config {
.field("wasm_bulk_memory", &self.features.bulk_memory)
.field("wasm_simd", &self.features.simd)
.field("wasm_multi_value", &self.features.multi_value)
.field("wasm_module_linking", &self.features.module_linking)
.field(
"static_memory_maximum_size",
&(u64::from(self.tunables.static_memory_bound)

View File

@@ -193,9 +193,8 @@ impl Engine {
pub fn precompile_module(&self, bytes: &[u8]) -> Result<Vec<u8>> {
#[cfg(feature = "wat")]
let bytes = wat::parse_bytes(&bytes)?;
let (_, artifacts, types) = crate::Module::build_artifacts(self, &bytes)?;
let artifacts = artifacts.into_iter().map(|i| i.0).collect::<Vec<_>>();
crate::module::SerializedModule::from_artifacts(self, &artifacts, &types)
let (mmap, _, types) = crate::Module::build_artifacts(self, &bytes)?;
crate::module::SerializedModule::from_artifacts(self, &mmap, &types)
.to_bytes(&self.config().module_version)
}

View File

@@ -1,8 +1,8 @@
use crate::store::{StoreData, StoreOpaque, Stored};
use crate::trampoline::{generate_global_export, generate_table_export};
use crate::{
AsContext, AsContextMut, ExternRef, ExternType, Func, GlobalType, Instance, Memory, Module,
Mutability, TableType, Trap, Val, ValType,
AsContext, AsContextMut, ExternRef, ExternType, Func, GlobalType, Memory, Mutability,
TableType, Trap, Val, ValType,
};
use anyhow::{anyhow, bail, Result};
use std::mem;
@@ -29,10 +29,6 @@ pub enum Extern {
Table(Table),
/// A WebAssembly linear memory.
Memory(Memory),
/// A WebAssembly instance.
Instance(Instance),
/// A WebAssembly module.
Module(Module),
}
impl Extern {
@@ -76,26 +72,6 @@ impl Extern {
}
}
/// Returns the underlying `Instance`, if this external is a instance.
///
/// Returns `None` if this is not a instance.
pub fn into_instance(self) -> Option<Instance> {
match self {
Extern::Instance(instance) => Some(instance),
_ => None,
}
}
/// Returns the underlying `Module`, if this external is a module.
///
/// Returns `None` if this is not a module.
pub fn into_module(self) -> Option<Module> {
match self {
Extern::Module(module) => Some(module),
_ => None,
}
}
/// Returns the type associated with this `Extern`.
///
/// The `store` argument provided must own this `Extern` and is used to look
@@ -111,8 +87,6 @@ impl Extern {
Extern::Memory(ft) => ExternType::Memory(ft.ty(store)),
Extern::Table(tt) => ExternType::Table(tt.ty(store)),
Extern::Global(gt) => ExternType::Global(gt.ty(store)),
Extern::Instance(i) => ExternType::Instance(i.ty(store)),
Extern::Module(m) => ExternType::Module(m.ty()),
}
}
@@ -142,10 +116,6 @@ impl Extern {
Extern::Global(g) => store.store_data().contains(g.0),
Extern::Memory(m) => m.comes_from_same_store(store),
Extern::Table(t) => store.store_data().contains(t.0),
Extern::Instance(i) => i.comes_from_same_store(store),
// Modules don't live in stores right now, so they're compatible
// with all stores.
Extern::Module(_) => true,
}
}
@@ -155,8 +125,6 @@ impl Extern {
Extern::Table(_) => "table",
Extern::Memory(_) => "memory",
Extern::Global(_) => "global",
Extern::Instance(_) => "instance",
Extern::Module(_) => "module",
}
}
}
@@ -185,18 +153,6 @@ impl From<Table> for Extern {
}
}
impl From<Instance> for Extern {
fn from(r: Instance) -> Self {
Extern::Instance(r)
}
}
impl From<Module> for Extern {
fn from(r: Module) -> Self {
Extern::Module(r)
}
}
/// A WebAssembly `global` value which can be read and written to.
///
/// A `global` in WebAssembly is sort of like a global variable within an
@@ -205,9 +161,10 @@ impl From<Module> for Extern {
/// can either be imported or exported from wasm modules.
///
/// A [`Global`] "belongs" to the store that it was originally created within
/// (either via [`Global::new`] or via instantiating a [`Module`]). Operations
/// on a [`Global`] only work with the store it belongs to, and if another store
/// is passed in by accident then methods will panic.
/// (either via [`Global::new`] or via instantiating a
/// [`Module`](crate::Module)). Operations on a [`Global`] only work with the
/// store it belongs to, and if another store is passed in by accident then
/// methods will panic.
#[derive(Copy, Clone, Debug)]
#[repr(transparent)] // here for the C API
pub struct Global(Stored<wasmtime_runtime::ExportGlobal>);
@@ -385,9 +342,10 @@ impl Global {
/// `funcref` table), where each element has the `ValType::FuncRef` type.
///
/// A [`Table`] "belongs" to the store that it was originally created within
/// (either via [`Table::new`] or via instantiating a [`Module`]). Operations
/// on a [`Table`] only work with the store it belongs to, and if another store
/// is passed in by accident then methods will panic.
/// (either via [`Table::new`] or via instantiating a
/// [`Module`](crate::Module)). Operations on a [`Table`] only work with the
/// store it belongs to, and if another store is passed in by accident then
/// methods will panic.
#[derive(Copy, Clone, Debug)]
#[repr(transparent)] // here for the C API
pub struct Table(Stored<wasmtime_runtime::ExportTable>);
@@ -787,16 +745,4 @@ impl<'instance> Export<'instance> {
pub fn into_global(self) -> Option<Global> {
self.definition.into_global()
}
/// Consume this `Export` and return the contained `Instance`, if it's a
/// instance, or `None` otherwise.
pub fn into_instance(self) -> Option<Instance> {
self.definition.into_instance()
}
/// Consume this `Export` and return the contained `Module`, if it's a
/// module, or `None` otherwise.
pub fn into_module(self) -> Option<Module> {
self.definition.into_module()
}
}

View File

@@ -1,19 +1,16 @@
use crate::linker::Definition;
use crate::signatures::SignatureCollection;
use crate::store::{InstanceId, StoreData, StoreOpaque, Stored};
use crate::store::{InstanceId, StoreOpaque, Stored};
use crate::types::matching;
use crate::{
AsContext, AsContextMut, Engine, Export, Extern, ExternType, Func, Global, InstanceType,
Memory, Module, StoreContextMut, Table, Trap, TypedFunc,
AsContextMut, Engine, Export, Extern, Func, Global, Memory, Module, StoreContextMut, Table,
Trap, TypedFunc,
};
use anyhow::{anyhow, bail, Context, Error, Result};
use std::mem;
use std::sync::Arc;
use wasmtime_environ::{
EntityIndex, EntityType, FuncIndex, GlobalIndex, Initializer, InstanceIndex, MemoryIndex,
ModuleIndex, PrimaryMap, TableIndex,
EntityIndex, EntityType, FuncIndex, GlobalIndex, MemoryIndex, PrimaryMap, TableIndex,
};
use wasmtime_jit::TypeTables;
use wasmtime_runtime::{
Imports, InstanceAllocationRequest, InstantiationError, StorePtr, VMContext, VMFunctionBody,
VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport,
@@ -36,25 +33,14 @@ use wasmtime_runtime::{
#[repr(transparent)]
pub struct Instance(Stored<InstanceData>);
pub(crate) enum InstanceData {
/// This variant is used for instances created through instantiation of a
/// module, e.g. `Instance::new` or various linker methods.
Instantiated {
/// The id of the instance within the store, used to find the original
/// `InstanceHandle`.
id: InstanceId,
/// A lazily-populated list of exports of this instance. The order of
/// exports here matches the order of the exports in the the original
/// module.
exports: Vec<Option<Extern>>,
/// The type information of the module that this was instantiated with.
types: Arc<TypeTables>,
signatures: Arc<SignatureCollection>,
},
/// This variant is used for synthetically created instances via `Linker`
/// APIs. This is only used for the module linking proposal at this time.
Synthetic(Arc<indexmap::IndexMap<String, Extern>>),
pub(crate) struct InstanceData {
/// The id of the instance within the store, used to find the original
/// `InstanceHandle`.
id: InstanceId,
/// A lazily-populated list of exports of this instance. The order of
/// exports here matches the order of the exports in the the original
/// module.
exports: Vec<Option<Extern>>,
}
impl Instance {
@@ -181,39 +167,6 @@ impl Instance {
Instance(store.store_data_mut().insert(handle))
}
/// Returns the type signature of this instance.
///
/// # Panics
///
/// Panics if `store` does not own this instance.
pub fn ty(&self, store: impl AsContext) -> InstanceType {
let store = store.as_context();
let mut ty = InstanceType::new();
match &store[self.0] {
InstanceData::Synthetic(items) => {
for (name, item) in items.iter() {
ty.add_named_export(name, item.ty(&store));
}
}
InstanceData::Instantiated { id, types, .. } => {
let module = store.0.instance(*id).module();
for (name, idx) in module.exports.iter() {
let export_ty = module.type_of(*idx);
ty.add_named_export(name, ExternType::from_wasmtime(types, &export_ty));
}
}
}
ty
}
pub(crate) fn data<'a>(&self, store: &'a StoreData) -> &'a InstanceData {
&store[self.0]
}
pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
store.store_data().contains(self.0)
}
/// Returns the list of exported items from this [`Instance`].
///
/// # Panics
@@ -232,70 +185,21 @@ impl Instance {
) -> impl ExactSizeIterator<Item = Export<'a>> + 'a {
// If this is an `Instantiated` instance then all the `exports` may not
// be filled in. Fill them all in now if that's the case.
if let InstanceData::Instantiated { exports, id, .. } = &store[self.0] {
if exports.iter().any(|e| e.is_none()) {
let module = Arc::clone(store.instance(*id).module());
for name in module.exports.keys() {
self._get_export(store, name);
}
let InstanceData { exports, id, .. } = &store[self.0];
if exports.iter().any(|e| e.is_none()) {
let module = Arc::clone(store.instance(*id).module());
for name in module.exports.keys() {
self._get_export(store, name);
}
}
return match &store.store_data()[self.0] {
InstanceData::Synthetic(names) => {
Either::A(names.iter().map(|(k, v)| Export::new(k, v.clone())))
}
InstanceData::Instantiated { exports, id, .. } => {
let module = store.instance(*id).module();
Either::B(
module
.exports
.iter()
.zip(exports)
.map(|((name, _), export)| Export::new(name, export.clone().unwrap())),
)
}
};
enum Either<A, B> {
A(A),
B(B),
}
impl<A, B> Iterator for Either<A, B>
where
A: Iterator,
B: Iterator<Item = A::Item>,
{
type Item = A::Item;
fn next(&mut self) -> Option<A::Item> {
match self {
Either::A(a) => a.next(),
Either::B(b) => b.next(),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
Either::A(a) => a.size_hint(),
Either::B(b) => b.size_hint(),
}
}
}
impl<A, B> ExactSizeIterator for Either<A, B>
where
A: ExactSizeIterator,
B: ExactSizeIterator<Item = A::Item>,
{
fn len(&self) -> usize {
match self {
Either::A(a) => a.len(),
Either::B(b) => b.len(),
}
}
}
let data = &store.store_data()[self.0];
let module = store.instance(data.id).module();
module
.exports
.iter()
.zip(&data.exports)
.map(|((name, _), export)| Export::new(name, export.clone().unwrap()))
}
/// Looks up an exported [`Extern`] value by name.
@@ -320,33 +224,23 @@ impl Instance {
}
fn _get_export(&self, store: &mut StoreOpaque, name: &str) -> Option<Extern> {
match &store[self.0] {
// Synthetic instances always have their entire list of exports
// already specified.
InstanceData::Synthetic(names) => names.get(name).cloned(),
// Instantiated instances will lazily fill in exports, so we process
// all that lazy logic here.
let data = &store[self.0];
// Instantiated instances will lazily fill in exports, so we process
// all that lazy logic here.
InstanceData::Instantiated { id, exports, .. } => {
let id = *id;
let instance = store.instance(id);
let (i, _, &index) = instance.module().exports.get_full(name)?;
if let Some(export) = &exports[i] {
return Some(export.clone());
}
let instance = store.instance_mut(id); // reborrow the &mut Instancehandle
let item = unsafe {
Extern::from_wasmtime_export(instance.lookup_by_declaration(&index), store)
};
let exports = match &mut store[self.0] {
InstanceData::Instantiated { exports, .. } => exports,
_ => unreachable!(),
};
exports[i] = Some(item.clone());
Some(item)
}
let instance = store.instance(data.id);
let (i, _, &index) = instance.module().exports.get_full(name)?;
if let Some(export) = &data.exports[i] {
return Some(export.clone());
}
let id = data.id;
let instance = store.instance_mut(id); // reborrow the &mut Instancehandle
let item =
unsafe { Extern::from_wasmtime_export(instance.lookup_by_declaration(&index), store) };
let data = &mut store[self.0];
data.exports[i] = Some(item.clone());
Some(item)
}
/// Looks up an exported [`Func`] value by name.
@@ -428,26 +322,17 @@ impl Instance {
}
struct Instantiator<'a> {
in_progress: Vec<ImportsBuilder<'a>>,
cur: ImportsBuilder<'a>,
}
struct ImportsBuilder<'a> {
src: ImportSource<'a>,
imports: ImportSource<'a>,
functions: PrimaryMap<FuncIndex, VMFunctionImport>,
tables: PrimaryMap<TableIndex, VMTableImport>,
memories: PrimaryMap<MemoryIndex, VMMemoryImport>,
globals: PrimaryMap<GlobalIndex, VMGlobalImport>,
instances: PrimaryMap<InstanceIndex, Instance>,
modules: PrimaryMap<ModuleIndex, Module>,
initializer: usize,
module: Module,
module: &'a Module,
}
enum ImportSource<'a> {
Externs(&'a [Extern]),
Definitions(&'a [Definition]),
Outer { initializer: usize },
}
impl<'a> Instantiator<'a> {
@@ -473,225 +358,77 @@ impl<'a> Instantiator<'a> {
/// * The `imports` must all come from the `store` specified.
unsafe fn new(
store: &StoreOpaque,
module: &Module,
module: &'a Module,
imports: ImportSource<'a>,
) -> Result<Instantiator<'a>> {
if !Engine::same(store.engine(), module.engine()) {
bail!("cross-`Engine` instantiation is not currently supported");
}
let raw = module.compiled_module().module();
Ok(Instantiator {
in_progress: Vec::new(),
cur: ImportsBuilder::new(module, imports),
imports,
functions: PrimaryMap::with_capacity(raw.num_imported_funcs),
tables: PrimaryMap::with_capacity(raw.num_imported_tables),
memories: PrimaryMap::with_capacity(raw.num_imported_memories),
globals: PrimaryMap::with_capacity(raw.num_imported_globals),
module,
})
}
fn run<T>(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<Instance, Error> {
loop {
if let Some((instance, start, toplevel)) = self.step(store.0)? {
if let Some(start) = start {
Instantiator::start_raw(store, instance, start)?;
}
if toplevel {
break Ok(instance);
}
}
let (instance, start) = self.resolve_imports(store.0)?;
if let Some(start) = start {
Instantiator::start_raw(store, instance, start)?;
}
Ok(instance)
}
/// Processes the next initializer for the next instance being created
/// without running any wasm code.
///
/// This function will process module initializers, handling recursive
/// instantiations of modules for module linking if necessary as well. This
/// does not actually execute any WebAssembly code, which means that it
/// will return whenever an instance is created (because its `start`
/// function may need to be executed).
///
/// If this function returns `None`, then it simply needs to be called
/// again to execute the next initializer. Otherwise this function has two
/// return values:
///
/// * The first is the raw handle to the instance that was just created.
/// This instance must have its start function executed by the caller.
/// * The second is an optional list of items to get wrapped up in an
/// `Instance`. This is only `Some` for the outermost instance that was
/// created. If this is `None` callers need to keep calling this function
/// since the instance created was simply for a recursive instance
/// defined here.
fn step(
/// Resolve all the imports for the module being instantiated, extracting
/// the raw representations and building up the `PrimaryMap` instance for
/// each set of exports.
fn resolve_imports(
&mut self,
store: &mut StoreOpaque,
) -> Result<Option<(Instance, Option<FuncIndex>, bool)>> {
if self.cur.initializer == 0 {
store.bump_resource_counts(&self.cur.module)?;
}
) -> Result<(Instance, Option<FuncIndex>)> {
store.bump_resource_counts(&self.module)?;
// Read the current module's initializer and move forward the
// initializer pointer as well.
self.cur.initializer += 1;
match self
.cur
.module
.env_module()
.initializers
.get(self.cur.initializer - 1)
{
Some(Initializer::Import { name, field, .. }) => {
match &mut self.cur.src {
// If imports are coming from the runtime-provided list
// (e.g. the root module being instantiated) then we
// need to typecheck each item here before recording it.
//
// Note the `unwrap` here should be ok given the validation
// above in `Instantiation::new`.
ImportSource::Externs(list) => {
let (head, remaining) = list.split_first().unwrap();
*list = remaining;
self.cur.push(head.clone(), store);
}
ImportSource::Definitions(list) => {
let (head, remaining) = list.split_first().unwrap();
*list = remaining;
// This unsafety is encapsulated with
// `Instantiator::new`, documented above.
self.cur.push(unsafe { head.to_extern(store) }, store);
}
// Otherwise if arguments are coming from our outer
// instance due to a recursive instantiation then we
// look in the previous initializer's mapping of
// arguments to figure out where to load the item from.
// Note that no typechecking is necessary here due to
// validation.
ImportSource::Outer { initializer } => {
debug_assert!(field.is_none());
let outer = self.in_progress.last().unwrap();
let args = match &outer.module.env_module().initializers[*initializer] {
Initializer::Instantiate { args, .. } => args,
_ => unreachable!(),
};
let index = args.get(name).expect("should be present after validation");
match *index {
EntityIndex::Global(i) => {
self.cur.globals.push(outer.globals[i]);
}
EntityIndex::Function(i) => {
self.cur.functions.push(outer.functions[i]);
}
EntityIndex::Table(i) => {
self.cur.tables.push(outer.tables[i]);
}
EntityIndex::Memory(i) => {
self.cur.memories.push(outer.memories[i]);
}
EntityIndex::Module(i) => {
self.cur.modules.push(outer.modules[i].clone());
}
EntityIndex::Instance(i) => {
self.cur.instances.push(outer.instances[i].clone());
}
}
}
let num_imports = self.module.env_module().initializers.len();
match &self.imports {
ImportSource::Externs(list) => {
assert_eq!(list.len(), num_imports);
for item in list.iter() {
self.push(item.clone(), store);
}
}
// Here we lookup our instance handle, find the right export,
// and then push that item into our own index space. We eschew
// type-checking since only valid modules should reach this point.
Some(Initializer::AliasInstanceExport { instance, export }) => {
let instance = self.cur.instances[*instance];
let export = instance._get_export(store, export).unwrap();
self.cur.push(export, store);
}
// A recursive instantiation of an instance.
//
// The `module` argument is used to create an import builder
// object, and we specify that the source of imports for the builder is
// this initializer's position so we can look at the `args` payload
// later.
//
// Once that's set up we save off `self.cur` into
// `self.in_progress` and start the instantiation of the child
// instance on the next execution of this function.
Some(Initializer::Instantiate { module, args: _ }) => {
let module = &self.cur.modules[*module];
let imports = ImportsBuilder::new(
module,
ImportSource::Outer {
initializer: self.cur.initializer - 1,
},
);
let prev = mem::replace(&mut self.cur, imports);
self.in_progress.push(prev);
}
// A new module is being defined, and the source of this module is
// our module's list of closed-over-modules.
//
// This is used for outer aliases.
Some(Initializer::DefineModule(upvar_index)) => {
self.cur
.modules
.push(self.cur.module.module_upvar(*upvar_index).clone());
}
// A new module is defined, created from a set of compiled
// artifacts. The new module value will be created with the
// specified artifacts being closed over as well as the specified
// set of module values in our index/upvar index spaces being closed
// over.
//
// This is used for defining submodules.
Some(Initializer::CreateModule {
artifact_index,
artifacts,
modules,
}) => {
let submodule = self.cur.module.create_submodule(
*artifact_index,
artifacts,
modules,
&self.cur.modules,
)?;
self.cur.modules.push(submodule);
}
// All initializers have been processed, which means we're ready to
// perform the actual raw instantiation with the raw import values.
// Once that's done if there's an in-progress module we record the
// instance in the index space. Otherwise this is the final module
// and we return the items out.
//
// Note that in all cases we return the raw instance handle to get
// the start function executed by the outer context.
None => {
let (instance, start) = self.instantiate_raw(store)?;
let toplevel = match self.in_progress.pop() {
Some(imports) => {
self.cur = imports;
self.cur.instances.push(instance);
false
}
None => true,
};
return Ok(Some((instance, start, toplevel)));
ImportSource::Definitions(list) => {
assert_eq!(list.len(), num_imports);
for item in list.iter() {
// This unsafety is encapsulated with
// `Instantiator::new`, documented above.
self.push(unsafe { item.to_extern(store) }, store);
}
}
}
Ok(None)
// All initializers have been processed, which means we're ready to
// perform the actual raw instantiation with the raw import values. This
// will register everything except the start function's completion and
// the finished instance will be returned.
self.instantiate_raw(store)
}
fn instantiate_raw(
&mut self,
store: &mut StoreOpaque,
) -> Result<(Instance, Option<FuncIndex>)> {
let compiled_module = self.cur.module.compiled_module();
let compiled_module = self.module.compiled_module();
// Register the module just before instantiation to ensure we keep the module
// properly referenced while in use by the store.
store.modules_mut().register(&self.cur.module);
store.modules_mut().register(&self.module);
unsafe {
// The first thing we do is issue an instance allocation request
@@ -710,8 +447,8 @@ impl<'a> Instantiator<'a> {
.engine()
.allocator()
.allocate(InstanceAllocationRequest {
runtime_info: &self.cur.module.runtime_info(),
imports: self.cur.build(),
runtime_info: &self.module.runtime_info(),
imports: self.build(),
host_state: Box::new(Instance(instance_to_be)),
store: StorePtr::new(store.traitobj()),
})?;
@@ -747,28 +484,9 @@ impl<'a> Instantiator<'a> {
.module()
.exports
.values()
.map(|index| {
// Note that instances and modules are not handled by
// `wasmtime_runtime`, they're handled by us in this crate. That
// means we need to handle that here, otherwise we defer to the
// instance to load the values.
match *index {
EntityIndex::Instance(i) => {
Some(Extern::Instance(self.cur.instances[i].clone()))
}
EntityIndex::Module(i) => {
Some(Extern::Module(self.cur.modules[i].clone()))
}
_ => None,
}
})
.map(|_index| None)
.collect();
let data = InstanceData::Instantiated {
id,
exports,
types: Arc::clone(self.cur.module.types()),
signatures: Arc::clone(self.cur.module.signatures()),
};
let data = InstanceData { id, exports };
Instance::from_wasmtime(data, store)
};
@@ -810,10 +528,7 @@ impl<'a> Instantiator<'a> {
instance: Instance,
start: FuncIndex,
) -> Result<()> {
let id = match &store.0.store_data()[instance.0] {
InstanceData::Instantiated { id, .. } => *id,
InstanceData::Synthetic(_) => return Ok(()),
};
let id = store.0.store_data()[instance.0].id;
// If a start function is present, invoke it. Make sure we use all the
// trap-handling configuration in `store` as well.
let instance = store.0.instance_mut(id);
@@ -834,23 +549,6 @@ impl<'a> Instantiator<'a> {
}
Ok(())
}
}
impl<'a> ImportsBuilder<'a> {
fn new(module: &Module, src: ImportSource<'a>) -> ImportsBuilder<'a> {
let raw = module.compiled_module().module();
ImportsBuilder {
src,
functions: PrimaryMap::with_capacity(raw.num_imported_funcs),
tables: PrimaryMap::with_capacity(raw.num_imported_tables),
memories: PrimaryMap::with_capacity(raw.num_imported_memories),
globals: PrimaryMap::with_capacity(raw.num_imported_globals),
instances: PrimaryMap::with_capacity(raw.instances.len()),
modules: PrimaryMap::with_capacity(raw.modules.len()),
module: module.clone(),
initializer: 0,
}
}
fn push(&mut self, item: Extern, store: &mut StoreOpaque) {
match item {
@@ -866,12 +564,6 @@ impl<'a> ImportsBuilder<'a> {
Extern::Memory(i) => {
self.memories.push(i.vmimport(store));
}
Extern::Instance(i) => {
self.instances.push(i);
}
Extern::Module(m) => {
self.modules.push(m);
}
}
}
@@ -1067,13 +759,8 @@ fn typecheck<I>(
engine: store.engine(),
};
for ((name, field, expected_ty), actual) in env_module.imports().zip(imports) {
check(&cx, &expected_ty, actual).with_context(|| {
let extra = match field {
Some(name) => format!("::{}", name),
None => String::new(),
};
format!("incompatible import type for `{}{}`", name, extra)
})?;
check(&cx, &expected_ty, actual)
.with_context(|| format!("incompatible import type for `{name}::{field}`"))?;
}
Ok(())
}

View File

@@ -1,9 +1,9 @@
use crate::func::HostFunc;
use crate::instance::{InstanceData, InstancePre};
use crate::instance::InstancePre;
use crate::store::StoreOpaque;
use crate::{
AsContextMut, Caller, Engine, Extern, ExternType, Func, FuncType, ImportType, Instance,
IntoFunc, Module, StoreContextMut, Trap, Val, ValRaw,
AsContextMut, Caller, Engine, Extern, Func, FuncType, ImportType, Instance, IntoFunc, Module,
StoreContextMut, Trap, Val, ValRaw,
};
use anyhow::{anyhow, bail, Context, Result};
use log::warn;
@@ -115,7 +115,6 @@ struct ImportKey {
pub(crate) enum Definition {
Extern(Extern),
HostFunc(Arc<HostFunc>),
Instance(Arc<indexmap::IndexMap<String, Definition>>),
}
macro_rules! generate_wrap_async_func {
@@ -622,7 +621,7 @@ impl<T> Linker<T> {
/// "#;
/// let module = Module::new(&engine, wat)?;
/// linker.module(&mut store, "", &module)?;
/// let run = linker.get(&mut store, "", Some("run")).unwrap().into_func().unwrap();
/// let run = linker.get(&mut store, "", "run").unwrap().into_func().unwrap();
/// let count = run.typed::<(), i32, _>(&store)?.call(&mut store, ())?;
/// assert_eq!(count, 0, "a Command should get a fresh instance on each invocation");
///
@@ -1093,20 +1092,17 @@ impl<T> Linker<T> {
&self,
mut store: impl AsContextMut<Data = T>,
module: &str,
name: Option<&str>,
name: &str,
) -> Option<Extern> {
let store = store.as_context_mut().0;
// Should be safe since `T` is connecting the linker and store
Some(unsafe { self._get(module, name)?.to_extern(store) })
}
fn _get(&self, module: &str, name: Option<&str>) -> Option<&Definition> {
fn _get(&self, module: &str, name: &str) -> Option<&Definition> {
let key = ImportKey {
module: *self.string2idx.get(module)?,
name: match name {
Some(name) => *self.string2idx.get(name)?,
None => usize::max_value(),
},
name: *self.string2idx.get(name)?,
};
self.map.get(&key)
}
@@ -1139,37 +1135,11 @@ impl<T> Linker<T> {
return Ok(item.clone());
}
if let Some(name) = import.name() {
return Err(undef_err(&format!("{}::{}", import.module(), name)));
}
if let ExternType::Instance(t) = import.ty() {
// This is a key location where the module linking proposal is
// implemented. This logic allows single-level imports of an instance to
// get satisfied by multiple definitions of items within this `Linker`.
//
// The instance being import is iterated over to load the names from
// this `Linker` (recursively calling `get`). If anything isn't defined
// we return `None` since the entire value isn't defined. Otherwise when
// all values are loaded it's assembled into an `Instance` and
// returned`.
//
// Note that this isn't exactly the speediest implementation in the
// world. Ideally we would pre-create the `Instance` instead of creating
// it each time a module is instantiated. For now though while the
// module linking proposal is under development this should hopefully
// suffice.
let mut map = indexmap::IndexMap::new();
for export in t.exports() {
let item = self
._get(import.module(), Some(export.name()))
.ok_or_else(|| undef_err(&format!("{}::{}", import.module(), export.name())))?;
map.insert(export.name().to_string(), item.clone());
}
return Ok(Definition::Instance(Arc::new(map)));
}
Err(undef_err(&import.module()))
Err(undef_err(&format!(
"{}::{}",
import.module(),
import.name()
)))
}
/// Returns the "default export" of a module.
@@ -1187,7 +1157,7 @@ impl<T> Linker<T> {
mut store: impl AsContextMut<Data = T>,
module: &str,
) -> Result<Func> {
if let Some(external) = self.get(&mut store, module, Some("")) {
if let Some(external) = self.get(&mut store, module, "") {
if let Extern::Func(func) = external {
return Ok(func.clone());
}
@@ -1195,7 +1165,7 @@ impl<T> Linker<T> {
}
// For compatibility, also recognize "_start".
if let Some(external) = self.get(&mut store, module, Some("_start")) {
if let Some(external) = self.get(&mut store, module, "_start") {
if let Extern::Func(func) = external {
return Ok(func.clone());
}
@@ -1221,14 +1191,6 @@ impl Definition {
match self {
Definition::Extern(e) => e.clone(),
Definition::HostFunc(func) => func.to_func(store).into(),
Definition::Instance(i) => {
let items = Arc::new(
i.iter()
.map(|(name, item)| (name.clone(), item.to_extern(store)))
.collect(),
);
Instance::from_wasmtime(InstanceData::Synthetic(items), store).into()
}
}
}
@@ -1236,7 +1198,6 @@ impl Definition {
match self {
Definition::Extern(e) => e.comes_from_same_store(store),
Definition::HostFunc(_func) => true,
Definition::Instance(i) => i.values().all(|e| e.comes_from_same_store(store)),
}
}
}

View File

@@ -1,8 +1,8 @@
use crate::Engine;
use crate::{
signatures::SignatureCollection,
types::{ExportType, ExternType, ImportType},
};
use crate::{Engine, ModuleType};
use anyhow::{bail, Context, Result};
use once_cell::sync::OnceCell;
use std::fs;
@@ -12,7 +12,7 @@ use std::path::Path;
use std::sync::Arc;
use wasmparser::{Parser, ValidPayload, Validator};
use wasmtime_environ::{
DefinedFuncIndex, DefinedMemoryIndex, FunctionInfo, ModuleEnvironment, ModuleIndex, PrimaryMap,
DefinedFuncIndex, DefinedMemoryIndex, FunctionInfo, ModuleEnvironment, PrimaryMap,
SignatureIndex,
};
use wasmtime_jit::{CompiledModule, CompiledModuleInfo, TypeTables};
@@ -104,14 +104,7 @@ struct ModuleInner {
/// The compiled artifacts for this module that will be instantiated and
/// executed.
module: Arc<CompiledModule>,
/// Closed-over compilation artifacts used to create submodules when this
/// module is instantiated.
artifact_upvars: Vec<Arc<CompiledModule>>,
/// Closed-over module values which are used when this module is
/// instantiated.
module_upvars: Vec<Module>,
/// Type information of this module and all `artifact_upvars` compiled
/// modules.
/// Type information of this module.
types: Arc<TypeTables>,
/// Registered shared signature for the module.
signatures: Arc<SignatureCollection>,
@@ -307,7 +300,7 @@ impl Module {
cfg_if::cfg_if! {
if #[cfg(feature = "cache")] {
let state = (HashedEngineCompileEnv(engine), binary);
let (main_module, artifacts, types) = wasmtime_cache::ModuleCacheEntry::new(
let (mmap, info, types) = wasmtime_cache::ModuleCacheEntry::new(
"wasmtime",
engine.cache_config(),
)
@@ -318,40 +311,35 @@ impl Module {
|(engine, wasm)| Module::build_artifacts(engine.0, wasm),
// Implementation of how to serialize artifacts
|(engine, _wasm), (_, artifacts, types)| {
|(engine, _wasm), (mmap, _info, types)| {
SerializedModule::from_artifacts(
engine.0,
artifacts.iter().map(|p| &p.0),
mmap,
types,
).to_bytes(&engine.0.config().module_version).ok()
},
// Cache hit, deserialize the provided artifacts
|(engine, _wasm), serialized_bytes| {
let (i, m, t, upvars) = SerializedModule::from_bytes(&serialized_bytes, &engine.0.config().module_version)
SerializedModule::from_bytes(&serialized_bytes, &engine.0.config().module_version)
.ok()?
.into_parts(engine.0)
.ok()?;
// This upvars list is always empty for top-level modules
assert!(upvars.is_empty());
Some((i, m, t))
.ok()
},
)?;
} else {
let (main_module, artifacts, types) = Module::build_artifacts(engine, binary)?;
let (mmap, info, types) = Module::build_artifacts(engine, binary)?;
}
};
let modules = engine.run_maybe_parallel(artifacts, |(a, b)| {
CompiledModule::from_artifacts(
a,
b,
&*engine.config().profiler,
engine.unique_id_allocator(),
)
})?;
let module = CompiledModule::from_artifacts(
mmap,
info,
&*engine.config().profiler,
engine.unique_id_allocator(),
)?;
Self::from_parts(engine, modules, main_module, Arc::new(types), &[])
Self::from_parts(engine, module, Arc::new(types))
}
/// Converts an input binary-encoded WebAssembly module to compilation
@@ -375,79 +363,66 @@ impl Module {
pub(crate) fn build_artifacts(
engine: &Engine,
wasm: &[u8],
) -> Result<(
usize,
Vec<(MmapVec, Option<CompiledModuleInfo>)>,
TypeTables,
)> {
) -> Result<(MmapVec, Option<CompiledModuleInfo>, TypeTables)> {
let tunables = &engine.config().tunables;
// First a `ModuleEnvironment` is created which records type information
// about the wasm module. This is where the WebAssembly is parsed and
// validated. Afterwards `types` will have all the type information for
// this module.
let (main_module, translations, types) =
ModuleEnvironment::new(tunables, &engine.config().features)
.translate(wasm)
.context("failed to parse WebAssembly module")?;
let (mut translation, types) = ModuleEnvironment::new(tunables, &engine.config().features)
.translate(wasm)
.context("failed to parse WebAssembly module")?;
// Perform a two-level map/reduce here to get the final list of
// compilation artifacts. The first level of map/reduce maps over all
// modules found and reduces to collection into a vector. The second
// level of map/reduce here maps over all functions within each wasm
// module found and collects into an ELF image via `emit_obj`.
let list = engine.run_maybe_parallel(translations, |mut translation| -> Result<_> {
let functions = mem::take(&mut translation.function_body_inputs);
let functions = functions.into_iter().collect::<Vec<_>>();
let funcs = engine
.run_maybe_parallel(functions, |(index, func)| {
engine
.compiler()
.compile_function(&translation, index, func, tunables, &types)
})?
.into_iter()
.collect();
let mut obj = engine.compiler().object()?;
let (funcs, trampolines) =
// Next compile all functions in parallel using rayon. This will perform
// the actual validation of all the function bodies.
let functions = mem::take(&mut translation.function_body_inputs);
let functions = functions.into_iter().collect::<Vec<_>>();
let funcs = engine
.run_maybe_parallel(functions, |(index, func)| {
engine
.compiler()
.emit_obj(&translation, &types, funcs, tunables, &mut obj)?;
.compile_function(&translation, index, func, tunables, &types)
})?
.into_iter()
.collect();
// If configured, attempt to use paged memory initialization
// instead of the default mode of memory initialization
if engine.config().paged_memory_initialization {
translation.try_paged_init();
}
// Collect all the function results into a final ELF object.
let mut obj = engine.compiler().object()?;
let (funcs, trampolines) =
engine
.compiler()
.emit_obj(&translation, &types, funcs, tunables, &mut obj)?;
// If configured attempt to use static memory initialization which
// can either at runtime be implemented as a single memcpy to
// initialize memory or otherwise enabling virtual-memory-tricks
// such as mmap'ing from a file to get copy-on-write.
if engine.config().memory_init_cow {
let align = engine.compiler().page_size_align();
let max_always_allowed = engine.config().memory_guaranteed_dense_image_size;
translation.try_static_init(align, max_always_allowed);
}
// If configured, attempt to use paged memory initialization
// instead of the default mode of memory initialization
if engine.config().paged_memory_initialization {
translation.try_paged_init();
}
// Attempt to convert table initializer segments to
// FuncTable representation where possible, to enable
// table lazy init.
translation.try_func_table_init();
// If configured attempt to use static memory initialization which
// can either at runtime be implemented as a single memcpy to
// initialize memory or otherwise enabling virtual-memory-tricks
// such as mmap'ing from a file to get copy-on-write.
if engine.config().memory_init_cow {
let align = engine.compiler().page_size_align();
let max_always_allowed = engine.config().memory_guaranteed_dense_image_size;
translation.try_static_init(align, max_always_allowed);
}
let (mmap, info) =
wasmtime_jit::finish_compile(translation, obj, funcs, trampolines, tunables)?;
Ok((mmap, Some(info)))
})?;
// Attempt to convert table initializer segments to
// FuncTable representation where possible, to enable
// table lazy init.
translation.try_func_table_init();
let (mmap, info) =
wasmtime_jit::finish_compile(translation, obj, funcs, trampolines, tunables)?;
Ok((
main_module,
list,
mmap,
Some(info),
TypeTables {
wasm_signatures: types.wasm_signatures,
module_signatures: types.module_signatures,
instance_signatures: types.instance_signatures,
},
))
}
@@ -529,91 +504,27 @@ impl Module {
fn from_parts(
engine: &Engine,
mut modules: Vec<Arc<CompiledModule>>,
main_module: usize,
module: Arc<CompiledModule>,
types: Arc<TypeTables>,
module_upvars: &[serialization::SerializedModuleUpvar],
) -> Result<Self> {
// Validate all modules can be used with the current allocator
for module in modules.iter() {
engine.allocator().validate(module.module())?;
}
// Validate the module can be used with the current allocator
engine.allocator().validate(module.module())?;
let signatures = Arc::new(SignatureCollection::new_for_module(
engine.signatures(),
&types.wasm_signatures,
modules
.iter()
.flat_map(|m| m.trampolines().map(|(idx, f, _)| (idx, f))),
module.trampolines().map(|(idx, f, _)| (idx, f)),
));
let module = modules.remove(main_module);
let module_upvars = module_upvars
.iter()
.map(|m| {
mk(
engine,
&modules,
&types,
m.index,
&m.artifact_upvars,
&m.module_upvars,
&signatures,
)
})
.collect();
return Ok(Self {
Ok(Self {
inner: Arc::new(ModuleInner {
engine: engine.clone(),
types,
artifact_upvars: modules,
module_upvars,
signatures,
memory_images: OnceCell::new(),
module,
}),
});
fn mk(
engine: &Engine,
artifacts: &[Arc<CompiledModule>],
types: &Arc<TypeTables>,
module_index: usize,
artifact_upvars: &[usize],
module_upvars: &[serialization::SerializedModuleUpvar],
signatures: &Arc<SignatureCollection>,
) -> Module {
let module = artifacts[module_index].clone();
Module {
inner: Arc::new(ModuleInner {
engine: engine.clone(),
types: types.clone(),
memory_images: OnceCell::new(),
module,
artifact_upvars: artifact_upvars
.iter()
.map(|i| artifacts[*i].clone())
.collect(),
module_upvars: module_upvars
.into_iter()
.map(|m| {
mk(
engine,
artifacts,
types,
m.index,
&m.artifact_upvars,
&m.module_upvars,
signatures,
)
})
.collect(),
signatures: signatures.clone(),
}),
}
}
})
}
/// Validates `binary` input data as a WebAssembly binary given the
@@ -650,23 +561,6 @@ impl Module {
Ok(())
}
/// Returns the type signature of this module.
pub fn ty(&self) -> ModuleType {
let mut sig = ModuleType::new();
let env_module = self.compiled_module().module();
let types = self.types();
for (module, field, ty) in env_module.imports() {
sig.add_named_import(module, field, ExternType::from_wasmtime(types, &ty));
}
for (name, index) in env_module.exports.iter() {
sig.add_named_export(
name,
ExternType::from_wasmtime(types, &env_module.type_of(*index)),
);
}
sig
}
/// Serializes this module to a vector of bytes.
///
/// This function is similar to the [`Engine::precompile_module`] method
@@ -682,60 +576,6 @@ impl Module {
SerializedModule::new(self).to_bytes(&self.inner.engine.config().module_version)
}
/// Creates a submodule `Module` value from the specified parameters.
///
/// This is used for creating submodules as part of module instantiation.
///
/// * `artifact_index` - the index in `artifact_upvars` that we're creating
/// a module for
/// * `artifact_upvars` - the mapping of indices of what artifact upvars are
/// needed for the submodule. The length of this array is the length of
/// the upvars array in the submodule to be created, and each element of
/// this array is an index into this module's upvar array.
/// * `module_upvars` - similar to `artifact_upvars` this is a mapping of
/// how to create the `module_upvars` of the submodule being created.
/// Each entry in this array is either an index into this module's own
/// module upvars array or it's an index into `modules`, the list of
/// modules so far for the instance where this submodule is being
/// created.
/// * `modules` - array indexed by `module_upvars`.
///
/// Note that the real meat of this happens in `ModuleEnvironment`
/// translation inside of `wasmtime_environ`. This just does the easy thing
/// of handling all the indices, over there is where the indices are
/// actually calculated and such.
pub(crate) fn create_submodule(
&self,
artifact_index: usize,
artifact_upvars: &[usize],
module_upvars: &[wasmtime_environ::ModuleUpvar],
modules: &PrimaryMap<ModuleIndex, Module>,
) -> Result<Module> {
let module = self.inner.artifact_upvars[artifact_index].clone();
Ok(Module {
inner: Arc::new(ModuleInner {
types: self.inner.types.clone(),
engine: self.inner.engine.clone(),
memory_images: OnceCell::new(),
module,
artifact_upvars: artifact_upvars
.iter()
.map(|i| self.inner.artifact_upvars[*i].clone())
.collect(),
module_upvars: module_upvars
.iter()
.map(|i| match *i {
wasmtime_environ::ModuleUpvar::Inherit(i) => {
self.inner.module_upvars[i].clone()
}
wasmtime_environ::ModuleUpvar::Local(i) => modules[i].clone(),
})
.collect(),
signatures: self.inner.signatures.clone(),
}),
})
}
pub(crate) fn compiled_module(&self) -> &Arc<CompiledModule> {
&self.inner.module
}
@@ -752,14 +592,6 @@ impl Module {
&self.inner.signatures
}
/// Looks up the module upvar value at the `index` specified.
///
/// Note that this panics if `index` is out of bounds since this should
/// only be called for valid indices as part of instantiation.
pub(crate) fn module_upvar(&self, index: usize) -> &Module {
&self.inner.module_upvars[index]
}
/// Returns identifier/name that this [`Module`] has. This name
/// is used in traps/backtrace details.
///
@@ -829,7 +661,7 @@ impl Module {
/// assert_eq!(module.imports().len(), 1);
/// let import = module.imports().next().unwrap();
/// assert_eq!(import.module(), "host");
/// assert_eq!(import.name(), Some("foo"));
/// assert_eq!(import.name(), "foo");
/// match import.ty() {
/// ExternType::Func(_) => { /* ... */ }
/// _ => panic!("unexpected import type!"),

View File

@@ -6,36 +6,22 @@
//!
//! There are two main pieces of data associated with a binary artifact:
//!
//! 1. A list of compiled modules. The reason this is a list as opposed to one
//! singular module is that a module-linking module may encompass a number
//! of other modules.
//! 2. Compilation metadata shared by all modules, including the global
//! `TypeTables` information. This metadata is validated for compilation
//! settings and also has information shared by all modules (such as the
//! shared `TypeTables`).
//! 1. The compiled module image, currently an ELF file.
//! 2. Compilation metadata for the module, including the `TypeTables`
//! information. This metadata is validated for compilation settings.
//!
//! Compiled modules are, at this time, represented as an ELF file. This ELF
//! file contains all the necessary data needed to decode each individual
//! module, and conveniently also handles things like alignment so we can
//! actually directly `mmap` compilation artifacts from disk.
//! file contains all the necessary data needed to decode a module, and
//! conveniently also handles things like alignment so we can actually directly
//! `mmap` compilation artifacts from disk.
//!
//! With all this in mind, the current serialization format is as follows:
//! With this in mind, the current serialization format is as follows:
//!
//! * The first, primary, module starts the final artifact. This means that the
//! final artifact is actually, and conveniently, a valid ELF file. ELF files
//! don't place any restrictions on data coming after the ELF file itself,
//! so that's where everything else will go. Another reason for using this
//! format is that our compilation artifacts are then consumable by standard
//! debugging tools like `objdump` to poke around and see what's what.
//! * First the ELF image for the compiled module starts the artifact. This
//! helps developers use standard ELF-reading utilities like `objdump` to poke
//! around and see what's inside the compiled image.
//!
//! * Next, all other modules are encoded. Each module has its own alignment,
//! though, so modules aren't simply concatenated. Instead directly after an
//! ELF file there is a 64-bit little-endian integer which is the offset,
//! from the end of the previous ELF file, to the next ELF file.
//!
//! * Finally, once all modules have been encoded (there's always at least
//! one), the 8-byte value `u64::MAX` is encoded. Following this is a
//! number of fields:
//! * After the ELF file is a number of fields:
//!
//! 1. The `HEADER` value
//! 2. A byte indicating how long the next field is
@@ -46,15 +32,19 @@
//! other random ELF files, as well as provide better error messages for
//! using wasmtime artifacts across versions.
//!
//! Note that the structure of the ELF format is what enables this
//! representation. We can have trailing data after an ELF file which isn't read
//! by any parsing of the ELF itself, which provides a convenient location for
//! the metadata information to go.
//!
//! This format is implemented by the `to_bytes` and `from_mmap` function.
use crate::{Engine, Module, ModuleVersionStrategy};
use anyhow::{anyhow, bail, Context, Result};
use object::read::elf::FileHeader;
use object::{Bytes, File, Object, ObjectSection};
use object::Bytes;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::path::Path;
use std::str::FromStr;
use std::sync::Arc;
@@ -165,50 +155,8 @@ impl<'a, 'b, T: Deserialize<'a>> Deserialize<'a> for MyCow<'b, T> {
}
}
/// A small helper struct for serialized module upvars.
#[derive(Serialize, Deserialize)]
pub struct SerializedModuleUpvar {
/// The module's index into the compilation artifact.
pub index: usize,
/// Indexes into the list of all compilation artifacts for this module.
pub artifact_upvars: Vec<usize>,
/// Closed-over module values that are also needed for this module.
pub module_upvars: Vec<SerializedModuleUpvar>,
}
impl SerializedModuleUpvar {
pub fn new(module: &Module, artifacts: &[Arc<CompiledModule>]) -> Self {
// TODO: improve upon the linear searches in the artifact list
let index = artifacts
.iter()
.position(|a| Arc::as_ptr(a) == Arc::as_ptr(&module.inner.module))
.expect("module should be in artifacts list");
SerializedModuleUpvar {
index,
artifact_upvars: module
.inner
.artifact_upvars
.iter()
.map(|m| {
artifacts
.iter()
.position(|a| Arc::as_ptr(a) == Arc::as_ptr(m))
.expect("artifact should be in artifacts list")
})
.collect(),
module_upvars: module
.inner
.module_upvars
.iter()
.map(|m| SerializedModuleUpvar::new(m, artifacts))
.collect(),
}
}
}
pub struct SerializedModule<'a> {
artifacts: Vec<MyCow<'a, MmapVec>>,
artifacts: MyCow<'a, MmapVec>,
metadata: Metadata<'a>,
}
@@ -219,54 +167,28 @@ struct Metadata<'a> {
isa_flags: BTreeMap<String, FlagValue>,
tunables: Tunables,
features: WasmFeatures,
module_upvars: Vec<SerializedModuleUpvar>,
types: MyCow<'a, TypeTables>,
}
impl<'a> SerializedModule<'a> {
#[cfg(compiler)]
pub fn new(module: &'a Module) -> Self {
let artifacts = module
.inner
.artifact_upvars
.iter()
.map(|m| MyCow::Borrowed(m.mmap()))
.chain(Some(MyCow::Borrowed(module.inner.module.mmap())))
.collect::<Vec<_>>();
let module_upvars = module
.inner
.module_upvars
.iter()
.map(|m| SerializedModuleUpvar::new(m, &module.inner.artifact_upvars))
.collect::<Vec<_>>();
Self::with_data(
module.engine(),
artifacts,
module_upvars,
MyCow::Borrowed(module.compiled_module().mmap()),
MyCow::Borrowed(module.types()),
)
}
#[cfg(compiler)]
pub fn from_artifacts(
engine: &Engine,
artifacts: impl IntoIterator<Item = &'a MmapVec>,
types: &'a TypeTables,
) -> Self {
Self::with_data(
engine,
artifacts.into_iter().map(MyCow::Borrowed).collect(),
Vec::new(),
MyCow::Borrowed(types),
)
pub fn from_artifacts(engine: &Engine, artifacts: &'a MmapVec, types: &'a TypeTables) -> Self {
Self::with_data(engine, MyCow::Borrowed(artifacts), MyCow::Borrowed(types))
}
#[cfg(compiler)]
fn with_data(
engine: &Engine,
artifacts: Vec<MyCow<'a, MmapVec>>,
module_upvars: Vec<SerializedModuleUpvar>,
artifacts: MyCow<'a, MmapVec>,
types: MyCow<'a, TypeTables>,
) -> Self {
Self {
@@ -277,35 +199,27 @@ impl<'a> SerializedModule<'a> {
isa_flags: engine.compiler().isa_flags(),
tunables: engine.config().tunables.clone(),
features: (&engine.config().features).into(),
module_upvars,
types,
},
}
}
pub fn into_module(self, engine: &Engine) -> Result<Module> {
let (main_module, modules, types, upvars) = self.into_parts(engine)?;
let modules = engine.run_maybe_parallel(modules, |(i, m)| {
CompiledModule::from_artifacts(
i,
m,
&*engine.config().profiler,
engine.unique_id_allocator(),
)
})?;
let (mmap, info, types) = self.into_parts(engine)?;
let module = CompiledModule::from_artifacts(
mmap,
info,
&*engine.config().profiler,
engine.unique_id_allocator(),
)?;
Module::from_parts(engine, modules, main_module, Arc::new(types), &upvars)
Module::from_parts(engine, module, Arc::new(types))
}
pub fn into_parts(
mut self,
engine: &Engine,
) -> Result<(
usize,
Vec<(MmapVec, Option<CompiledModuleInfo>)>,
TypeTables,
Vec<SerializedModuleUpvar>,
)> {
) -> Result<(MmapVec, Option<CompiledModuleInfo>, TypeTables)> {
// Verify that the compilation settings in the engine match the
// compilation settings of the module that's being loaded.
self.check_triple(engine)?;
@@ -315,45 +229,18 @@ impl<'a> SerializedModule<'a> {
self.check_tunables(&engine.config().tunables)?;
self.check_features(&engine.config().features)?;
assert!(!self.artifacts.is_empty());
let modules = self.artifacts.into_iter().map(|i| (i.unwrap_owned(), None));
let module = self.artifacts.unwrap_owned();
let main_module = modules.len() - 1;
Ok((
main_module,
modules.collect(),
self.metadata.types.unwrap_owned(),
self.metadata.module_upvars,
))
Ok((module, None, self.metadata.types.unwrap_owned()))
}
pub fn to_bytes(&self, version_strat: &ModuleVersionStrategy) -> Result<Vec<u8>> {
// First up, create a linked-ish list of ELF files. For more
// information on this format, see the doc comment on this module.
// The only semi-tricky bit here is that we leave an
// offset-to-the-next-file between each set of ELF files. The list
// is then terminated with `u64::MAX`.
let mut ret = Vec::new();
for (i, obj) in self.artifacts.iter().enumerate() {
// Anything after the first object needs to respect the alignment of
// the object's sections, so insert padding as necessary. Note that
// the +8 to the length here is to accomodate the size we'll write
// to get to the next object.
if i > 0 {
let obj = File::parse(&obj.as_ref()[..])?;
let align = obj.sections().map(|s| s.align()).max().unwrap_or(0).max(1);
let align = usize::try_from(align).unwrap();
let new_size = align_to(ret.len() + 8, align);
ret.extend_from_slice(&(new_size as u64).to_le_bytes());
ret.resize(new_size, 0);
}
ret.extend_from_slice(obj.as_ref());
}
ret.extend_from_slice(&[0xff; 8]);
// Start off with a copy of the ELF image.
let mut ret = self.artifacts.as_ref().to_vec();
// The last part of our artifact is the bincode-encoded `Metadata`
// section with a few other guards to help give better error messages.
// Append the bincode-encoded `Metadata` section with a few other guards
// to help give better error messages during deserialization if
// something goes wrong.
ret.extend_from_slice(HEADER);
let version = match version_strat {
ModuleVersionStrategy::WasmtimeVersion => env!("CARGO_PKG_VERSION"),
@@ -385,37 +272,14 @@ impl<'a> SerializedModule<'a> {
)
}
pub fn from_mmap(mut mmap: MmapVec, version_strat: &ModuleVersionStrategy) -> Result<Self> {
// Artifacts always start with an ELF file, so read that first.
// Afterwards we continually read ELF files until we see the `u64::MAX`
// marker, meaning we've reached the end.
let first_module = read_file(&mut mmap)?;
let mut pos = first_module.len();
let mut artifacts = vec![MyCow::Owned(first_module)];
pub fn from_mmap(mmap: MmapVec, version_strat: &ModuleVersionStrategy) -> Result<Self> {
// First validate that this is at least somewhat an elf file within
// `mmap` and additionally skip to the end of the elf file to find our
// metadata.
let metadata = data_after_elf(&mmap)?;
let metadata = loop {
if mmap.len() < 8 {
bail!("invalid serialized data");
}
let next_file_start = u64::from_le_bytes([
mmap[0], mmap[1], mmap[2], mmap[3], mmap[4], mmap[5], mmap[6], mmap[7],
]);
if next_file_start == u64::MAX {
mmap.drain(..8);
break mmap;
}
// Remove padding leading up to the next file
let next_file_start = usize::try_from(next_file_start).unwrap();
let _padding = mmap.drain(..next_file_start - pos);
let data = read_file(&mut mmap)?;
pos = next_file_start + data.len();
artifacts.push(MyCow::Owned(data));
};
// Once we've reached the end we parse a `Metadata` object. This has a
// few guards up front which we process first, and eventually this
// bottoms out in a `bincode::deserialize` call.
// The metadata has a few guards up front which we process first, and
// eventually this bottoms out in a `bincode::deserialize` call.
let metadata = metadata
.strip_prefix(HEADER)
.ok_or_else(|| anyhow!("bytes are not a compatible serialized wasmtime module"))?;
@@ -453,18 +317,13 @@ impl<'a> SerializedModule<'a> {
.context("deserialize compilation artifacts")?;
return Ok(SerializedModule {
artifacts,
artifacts: MyCow::Owned(mmap),
metadata,
});
/// This function will drain the beginning contents of `mmap` which
/// correspond to an ELF object file. The ELF file is only very lightly
/// validated.
///
/// The `mmap` passed in will be reset to just after the ELF file, and
/// the `MmapVec` returned represents the extend of the ELF file
/// itself.
fn read_file(mmap: &mut MmapVec) -> Result<MmapVec> {
/// This function will return the trailing data behind the ELF file
/// parsed from `data` which is where we find our metadata section.
fn data_after_elf(data: &[u8]) -> Result<&[u8]> {
use object::NativeEndian as NE;
// There's not actually a great utility for figuring out where
// the end of an ELF file is in the `object` crate. In lieu of that
@@ -472,7 +331,7 @@ impl<'a> SerializedModule<'a> {
// is that the header comes first, that tells us where the section
// headers are, and for our ELF files the end of the file is the
// end of the section headers.
let mut bytes = Bytes(mmap);
let mut bytes = Bytes(data);
let header = bytes
.read::<object::elf::FileHeader64<NE>>()
.map_err(|()| anyhow!("artifact truncated, can't read header"))?;
@@ -480,10 +339,10 @@ impl<'a> SerializedModule<'a> {
bail!("invalid elf header");
}
let sections = header
.section_headers(NE, &mmap[..])
.section_headers(NE, data)
.context("failed to read section headers")?;
let range = subslice_range(object::bytes_of_slice(sections), mmap);
Ok(mmap.drain(..range.end))
let range = subslice_range(object::bytes_of_slice(sections), data);
Ok(&data[range.end..])
}
}
@@ -697,12 +556,6 @@ impl<'a> SerializedModule<'a> {
}
}
/// Aligns the `val` specified up to `align`, which must be a power of two
fn align_to(val: usize, align: usize) -> usize {
debug_assert!(align.is_power_of_two());
(val + (align - 1)) & (!(align - 1))
}
#[cfg(test)]
mod test {
use super::*;

View File

@@ -49,7 +49,7 @@ pub fn create_global(store: &mut StoreOpaque, gt: &GlobalType, val: Val) -> Resu
.initializers
.push(wasmtime_environ::Initializer::Import {
name: "".into(),
field: None,
field: "".into(),
index: EntityIndex::Function(func_index),
});

View File

@@ -115,10 +115,6 @@ pub enum ExternType {
Table(TableType),
/// This external type is the type of a WebAssembly memory.
Memory(MemoryType),
/// This external type is the type of a WebAssembly instance.
Instance(InstanceType),
/// This external type is the type of a WebAssembly module.
Module(ModuleType),
}
macro_rules! accessors {
@@ -151,8 +147,6 @@ impl ExternType {
(Global(GlobalType) global unwrap_global)
(Table(TableType) table unwrap_table)
(Memory(MemoryType) memory unwrap_memory)
(Module(ModuleType) module unwrap_module)
(Instance(InstanceType) instance unwrap_instance)
}
pub(crate) fn from_wasmtime(types: &TypeTables, ty: &EntityType) -> ExternType {
@@ -163,14 +157,6 @@ impl ExternType {
EntityType::Global(ty) => GlobalType::from_wasmtime_global(ty).into(),
EntityType::Memory(ty) => MemoryType::from_wasmtime_memory(ty).into(),
EntityType::Table(ty) => TableType::from_wasmtime_table(ty).into(),
EntityType::Module(ty) => {
let ty = &types.module_signatures[*ty];
ModuleType::from_wasmtime(types, ty).into()
}
EntityType::Instance(ty) => {
let ty = &types.instance_signatures[*ty];
InstanceType::from_wasmtime(types, ty).into()
}
EntityType::Tag(_) => unimplemented!("wasm tag support"),
}
}
@@ -200,18 +186,6 @@ impl From<TableType> for ExternType {
}
}
impl From<ModuleType> for ExternType {
fn from(ty: ModuleType) -> ExternType {
ExternType::Module(ty)
}
}
impl From<InstanceType> for ExternType {
fn from(ty: InstanceType) -> ExternType {
ExternType::Instance(ty)
}
}
/// A descriptor for a function in a WebAssembly module.
///
/// WebAssembly functions can have 0 or more parameters and results.
@@ -439,122 +413,6 @@ impl MemoryType {
}
}
// Module Types
/// A descriptor for a WebAssembly module type.
///
/// This is a part of the [WebAssembly module-linking proposal][proposal].
///
/// [proposal]: https://github.com/webassembly/module-linking
#[derive(Debug, Clone)]
pub struct ModuleType {
imports: Vec<(String, Option<String>, ExternType)>,
exports: Vec<(String, ExternType)>,
}
impl ModuleType {
/// Creates a new empty module type.
pub fn new() -> ModuleType {
ModuleType {
imports: Vec::new(),
exports: Vec::new(),
}
}
/// Adds a new export to this `ModuleType`.
pub fn add_named_export(&mut self, name: &str, ty: ExternType) {
self.exports.push((name.to_string(), ty));
}
/// Adds a new import to this `ModuleType`.
pub fn add_named_import(&mut self, module: &str, field: Option<&str>, ty: ExternType) {
self.imports
.push((module.to_string(), field.map(|f| f.to_string()), ty));
}
/// Returns the list of imports associated with this module type.
pub fn imports(&self) -> impl ExactSizeIterator<Item = ImportType<'_>> {
self.imports.iter().map(|(name, field, ty)| ImportType {
module: name,
name: field.as_deref(),
ty: EntityOrExtern::Extern(ty),
})
}
/// Returns the list of exports associated with this module type.
pub fn exports(&self) -> impl ExactSizeIterator<Item = ExportType<'_>> {
self.exports.iter().map(|(name, ty)| ExportType {
name,
ty: EntityOrExtern::Extern(ty),
})
}
pub(crate) fn from_wasmtime(
types: &TypeTables,
ty: &wasmtime_environ::ModuleSignature,
) -> ModuleType {
let exports = &types.instance_signatures[ty.exports].exports;
ModuleType {
exports: exports
.iter()
.map(|(name, ty)| (name.to_string(), ExternType::from_wasmtime(types, ty)))
.collect(),
imports: ty
.imports
.iter()
.map(|(m, ty)| (m.to_string(), None, ExternType::from_wasmtime(types, ty)))
.collect(),
}
}
}
// Instance Types
/// A descriptor for a WebAssembly instance type.
///
/// This is a part of the [WebAssembly module-linking proposal][proposal].
///
/// [proposal]: https://github.com/webassembly/module-linking
#[derive(Debug, Clone)]
pub struct InstanceType {
exports: Vec<(String, ExternType)>,
}
impl InstanceType {
/// Creates a new empty instance type.
pub fn new() -> InstanceType {
InstanceType {
exports: Vec::new(),
}
}
/// Adds a new export to this `ModuleType`.
pub fn add_named_export(&mut self, name: &str, ty: ExternType) {
self.exports.push((name.to_string(), ty));
}
/// Returns the list of exports associated with this module type.
pub fn exports(&self) -> impl ExactSizeIterator<Item = ExportType<'_>> {
self.exports.iter().map(|(name, ty)| ExportType {
name,
ty: EntityOrExtern::Extern(ty),
})
}
pub(crate) fn from_wasmtime(
types: &TypeTables,
ty: &wasmtime_environ::InstanceSignature,
) -> InstanceType {
InstanceType {
exports: ty
.exports
.iter()
.map(|(name, ty)| (name.to_string(), ExternType::from_wasmtime(types, ty)))
.collect(),
}
}
}
// Import Types
/// A descriptor for an imported value into a wasm module.
@@ -569,16 +427,11 @@ pub struct ImportType<'module> {
module: &'module str,
/// The field of the import.
name: Option<&'module str>,
name: &'module str,
/// The type of the import.
ty: EntityOrExtern<'module>,
}
#[derive(Clone)]
enum EntityOrExtern<'a> {
Entity(EntityType, &'a TypeTables),
Extern(&'a ExternType),
ty: EntityType,
types: &'module TypeTables,
}
impl<'module> ImportType<'module> {
@@ -586,14 +439,15 @@ impl<'module> ImportType<'module> {
/// is of type `ty`.
pub(crate) fn new(
module: &'module str,
name: Option<&'module str>,
name: &'module str,
ty: EntityType,
types: &'module TypeTables,
) -> ImportType<'module> {
ImportType {
module,
name,
ty: EntityOrExtern::Entity(ty, types),
ty,
types,
}
}
@@ -604,20 +458,13 @@ impl<'module> ImportType<'module> {
/// Returns the field name of the module that this import is expected to
/// come from.
///
/// Note that this is optional due to the module linking proposal. If the
/// module linking proposal is enabled this is always `None`, otherwise this
/// is always `Some`.
pub fn name(&self) -> Option<&'module str> {
pub fn name(&self) -> &'module str {
self.name
}
/// Returns the expected type of this import.
pub fn ty(&self) -> ExternType {
match &self.ty {
EntityOrExtern::Entity(e, types) => ExternType::from_wasmtime(types, e),
EntityOrExtern::Extern(e) => (*e).clone(),
}
ExternType::from_wasmtime(self.types, &self.ty)
}
}
@@ -645,7 +492,8 @@ pub struct ExportType<'module> {
name: &'module str,
/// The type of the export.
ty: EntityOrExtern<'module>,
ty: EntityType,
types: &'module TypeTables,
}
impl<'module> ExportType<'module> {
@@ -656,10 +504,7 @@ impl<'module> ExportType<'module> {
ty: EntityType,
types: &'module TypeTables,
) -> ExportType<'module> {
ExportType {
name,
ty: EntityOrExtern::Entity(ty, types),
}
ExportType { name, ty, types }
}
/// Returns the name by which this export is known.
@@ -669,10 +514,7 @@ impl<'module> ExportType<'module> {
/// Returns the type of this export.
pub fn ty(&self) -> ExternType {
match &self.ty {
EntityOrExtern::Entity(e, types) => ExternType::from_wasmtime(types, e),
EntityOrExtern::Extern(e) => (*e).clone(),
}
ExternType::from_wasmtime(self.types, &self.ty)
}
}

View File

@@ -1,12 +1,8 @@
use crate::instance::InstanceData;
use crate::linker::Definition;
use crate::store::StoreOpaque;
use crate::{signatures::SignatureCollection, Engine, Extern};
use anyhow::{bail, Context, Result};
use wasmtime_environ::{
EntityType, Global, InstanceTypeIndex, Memory, ModuleTypeIndex, SignatureIndex, Table,
WasmFuncType, WasmType,
};
use anyhow::{bail, Result};
use wasmtime_environ::{EntityType, Global, Memory, SignatureIndex, Table, WasmFuncType, WasmType};
use wasmtime_jit::TypeTables;
use wasmtime_runtime::VMSharedSignatureIndex;
@@ -156,206 +152,6 @@ impl MatchCx<'_> {
)
}
pub fn instance(&self, expected: InstanceTypeIndex, actual: &crate::Instance) -> Result<()> {
for (name, expected) in self.types.instance_signatures[expected].exports.iter() {
match actual.data(self.store.store_data()) {
InstanceData::Synthetic(names) => match names.get(name) {
Some(item) => {
self.extern_(expected, item)
.with_context(|| format!("instance export {:?} incompatible", name))?;
}
None => bail!("instance type missing export {:?}", name),
},
InstanceData::Instantiated {
id,
types,
signatures,
..
} => {
let module = self.store.instance(*id).module();
match module.exports.get(name) {
Some(index) => {
let actual_ty = module.type_of(*index);
self.extern_ty_matches(expected, &actual_ty, signatures, types)
.with_context(|| {
format!("instance export {:?} incompatible", name)
})?;
}
None => bail!("instance type missing export {:?}", name),
}
// let
}
}
}
Ok(())
}
/// Validates that the type signature of `actual` matches the `expected`
/// module type signature.
pub fn module(&self, expected: ModuleTypeIndex, actual: &crate::Module) -> Result<()> {
// This should only ever be invoked with module linking, and this is an
// early check that our `field` assertion below should always work as
// well.
assert!(self.engine.config().features.module_linking);
let expected_sig = &self.types.module_signatures[expected];
let module = actual.compiled_module().module();
self.imports_match(
expected,
actual.signatures(),
actual.types(),
module.imports().map(|(name, field, ty)| {
assert!(field.is_none()); // should be true if module linking is enabled
(name, ty)
}),
)?;
self.exports_match(
expected_sig.exports,
actual.signatures(),
actual.types(),
|name| module.exports.get(name).map(|idx| module.type_of(*idx)),
)?;
Ok(())
}
/// Validates that the `actual_imports` list of module imports matches the
/// `expected` module type signature.
///
/// Types specified in `actual_imports` are relative to `actual_types`.
fn imports_match<'a>(
&self,
expected: ModuleTypeIndex,
actual_signatures: &SignatureCollection,
actual_types: &TypeTables,
actual_imports: impl Iterator<Item = (&'a str, EntityType)>,
) -> Result<()> {
// Imports match if all of the actual imports are satisfied by the
// expected set of imports. Note that we're reversing the order of the
// subtytpe matching here too.
let expected_sig = &self.types.module_signatures[expected];
for (name, actual_ty) in actual_imports {
let expected_ty = match expected_sig.imports.get(name) {
Some(ty) => ty,
None => bail!("expected type doesn't import {:?}", name),
};
MatchCx {
signatures: actual_signatures,
types: actual_types,
store: self.store,
engine: self.engine,
}
.extern_ty_matches(&actual_ty, expected_ty, self.signatures, self.types)
.with_context(|| format!("module import {:?} incompatible", name))?;
}
Ok(())
}
/// Validates that all exports in `expected` are defined by `lookup` within
/// `actual_types`.
fn exports_match(
&self,
expected: InstanceTypeIndex,
actual_signatures: &SignatureCollection,
actual_types: &TypeTables,
lookup: impl Fn(&str) -> Option<EntityType>,
) -> Result<()> {
// The `expected` type must be a subset of `actual`, meaning that all
// names in `expected` must be present in `actual`. Note that we do
// name-based lookup here instead of index-based lookup.
for (name, expected) in self.types.instance_signatures[expected].exports.iter() {
match lookup(name) {
Some(ty) => self
.extern_ty_matches(expected, &ty, actual_signatures, actual_types)
.with_context(|| format!("export {:?} incompatible", name))?,
None => bail!("failed to find export {:?}", name),
}
}
Ok(())
}
/// Validates that the `expected` entity matches the `actual_ty` defined
/// within `actual_types`.
fn extern_ty_matches(
&self,
expected: &EntityType,
actual_ty: &EntityType,
actual_signatures: &SignatureCollection,
actual_types: &TypeTables,
) -> Result<()> {
let actual_desc = match actual_ty {
EntityType::Global(_) => "global",
EntityType::Module(_) => "module",
EntityType::Memory(_) => "memory",
EntityType::Tag(_) => "tag",
EntityType::Instance(_) => "instance",
EntityType::Table(_) => "table",
EntityType::Function(_) => "function",
};
match expected {
EntityType::Global(expected) => match actual_ty {
EntityType::Global(actual) => self.global_ty(expected, actual),
_ => bail!("expected global, but found {}", actual_desc),
},
EntityType::Table(expected) => match actual_ty {
EntityType::Table(actual) => self.table_ty(expected, actual, None),
_ => bail!("expected table, but found {}", actual_desc),
},
EntityType::Memory(expected) => match actual_ty {
EntityType::Memory(actual) => self.memory_ty(expected, actual, None),
_ => bail!("expected memory, but found {}", actual_desc),
},
EntityType::Function(expected) => match *actual_ty {
EntityType::Function(actual) => {
if self.types.wasm_signatures[*expected] == actual_types.wasm_signatures[actual]
{
Ok(())
} else {
bail!("function types incompatible")
}
}
_ => bail!("expected function, but found {}", actual_desc),
},
EntityType::Instance(expected) => match actual_ty {
EntityType::Instance(actual) => {
let sig = &actual_types.instance_signatures[*actual];
self.exports_match(*expected, actual_signatures, actual_types, |name| {
sig.exports.get(name).cloned()
})?;
Ok(())
}
_ => bail!("expected instance, but found {}", actual_desc),
},
EntityType::Module(expected) => match actual_ty {
EntityType::Module(actual) => {
let expected_module_sig = &self.types.module_signatures[*expected];
let actual_module_sig = &actual_types.module_signatures[*actual];
let actual_instance_sig =
&actual_types.instance_signatures[actual_module_sig.exports];
self.imports_match(
*expected,
actual_signatures,
actual_types,
actual_module_sig
.imports
.iter()
.map(|(module, ty)| (module.as_str(), ty.clone())),
)?;
self.exports_match(
expected_module_sig.exports,
actual_signatures,
actual_types,
|name| actual_instance_sig.exports.get(name).cloned(),
)?;
Ok(())
}
_ => bail!("expected module, but found {}", actual_desc),
},
EntityType::Tag(_) => unimplemented!(),
}
}
/// Validates that the `expected` type matches the type of `actual`
pub fn extern_(&self, expected: &EntityType, actual: &Extern) -> Result<()> {
match expected {
@@ -375,14 +171,6 @@ impl MatchCx<'_> {
Extern::Func(actual) => self.func(*expected, actual),
_ => bail!("expected func, but found {}", actual.desc()),
},
EntityType::Instance(expected) => match actual {
Extern::Instance(actual) => self.instance(*expected, actual),
_ => bail!("expected instance, but found {}", actual.desc()),
},
EntityType::Module(expected) => match actual {
Extern::Module(actual) => self.module(*expected, actual),
_ => bail!("expected module, but found {}", actual.desc()),
},
EntityType::Tag(_) => unimplemented!(),
}
}
@@ -395,23 +183,6 @@ impl MatchCx<'_> {
EntityType::Function(expected) => self.host_func(*expected, f),
_ => bail!("expected {}, but found func", entity_desc(expected)),
},
Definition::Instance(items) => match expected {
EntityType::Instance(expected) => {
for (name, expected) in self.types.instance_signatures[*expected].exports.iter()
{
match items.get(name) {
Some(item) => {
self.definition(expected, item).with_context(|| {
format!("instance export {:?} incompatible", name)
})?;
}
None => bail!("instance type missing export {:?}", name),
}
}
Ok(())
}
_ => bail!("expected {}, but found instance", entity_desc(expected)),
},
}
}
}
@@ -485,8 +256,6 @@ fn entity_desc(ty: &EntityType) -> &'static str {
EntityType::Table(_) => "table",
EntityType::Memory(_) => "memory",
EntityType::Function(_) => "func",
EntityType::Instance(_) => "instance",
EntityType::Module(_) => "module",
EntityType::Tag(_) => "tag",
}
}