Update support for the module linking proposal

This commit updates the various tooling used by wasmtime which has new
updates to the module linking proposal. This is done primarily to sync
with WebAssembly/module-linking#26. The main change implemented here is
that wasmtime now supports creating instances from a set of values, nott
just from instantiating a module. Additionally subtyping handling of
modules with respect to imports is now properly handled by desugaring
two-level imports to imports of instances.

A number of small refactorings are included here as well, but most of
them are in accordance with the changes to `wasmparser` and the updated
binary format for module linking.
This commit is contained in:
Alex Crichton
2021-01-12 10:44:11 -08:00
parent 705af0ac41
commit 703762c49e
45 changed files with 1041 additions and 747 deletions

View File

@@ -16,7 +16,7 @@ wasmtime-jit = { path = "../jit", version = "0.22.0" }
wasmtime-cache = { path = "../cache", version = "0.22.0", optional = true }
wasmtime-profiling = { path = "../profiling", version = "0.22.0" }
target-lexicon = { version = "0.11.0", default-features = false }
wasmparser = "0.71"
wasmparser = "0.72"
anyhow = "1.0.19"
region = "2.2.0"
libc = "0.2"

View File

@@ -112,26 +112,25 @@ impl Extern {
}
}
pub(crate) fn from_wasmtime_export(
wasmtime_export: wasmtime_runtime::Export,
instance: StoreInstanceHandle,
pub(crate) unsafe fn from_wasmtime_export(
wasmtime_export: &wasmtime_runtime::Export,
store: &Store,
) -> Extern {
match wasmtime_export {
wasmtime_runtime::Export::Function(f) => {
Extern::Func(Func::from_wasmtime_function(f, instance))
Extern::Func(Func::from_wasmtime_function(f, store))
}
wasmtime_runtime::Export::Memory(m) => {
Extern::Memory(Memory::from_wasmtime_memory(m, instance))
Extern::Memory(Memory::from_wasmtime_memory(m, store))
}
wasmtime_runtime::Export::Global(g) => {
Extern::Global(Global::from_wasmtime_global(g, instance))
Extern::Global(Global::from_wasmtime_global(g, store))
}
wasmtime_runtime::Export::Table(t) => {
Extern::Table(Table::from_wasmtime_table(t, instance))
Extern::Table(Table::from_wasmtime_table(t, store))
}
wasmtime_runtime::Export::Instance(i) => {
let handle = unsafe { instance.store.existing_instance_handle(i.clone()) };
Extern::Instance(Instance::from_wasmtime(handle))
Extern::Instance(Instance::from_wasmtime(i, store))
}
wasmtime_runtime::Export::Module(m) => {
Extern::Module(m.downcast_ref::<Module>().unwrap().clone())
@@ -335,13 +334,13 @@ impl Global {
Ok(())
}
pub(crate) fn from_wasmtime_global(
wasmtime_export: wasmtime_runtime::ExportGlobal,
instance: StoreInstanceHandle,
pub(crate) unsafe fn from_wasmtime_global(
wasmtime_export: &wasmtime_runtime::ExportGlobal,
store: &Store,
) -> Global {
Global {
instance,
wasmtime_export,
instance: store.existing_vmctx(wasmtime_export.vmctx),
wasmtime_export: wasmtime_export.clone(),
}
}
@@ -354,6 +353,10 @@ impl Global {
from: self.wasmtime_export.definition,
}
}
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::ExportGlobal {
&self.wasmtime_export
}
}
/// A WebAssembly `table`, or an array of values.
@@ -576,13 +579,13 @@ impl Table {
Ok(())
}
pub(crate) fn from_wasmtime_table(
wasmtime_export: wasmtime_runtime::ExportTable,
instance: StoreInstanceHandle,
pub(crate) unsafe fn from_wasmtime_table(
wasmtime_export: &wasmtime_runtime::ExportTable,
store: &Store,
) -> Table {
Table {
instance,
wasmtime_export,
instance: store.existing_vmctx(wasmtime_export.vmctx),
wasmtime_export: wasmtime_export.clone(),
}
}
@@ -596,6 +599,10 @@ impl Table {
vmctx: self.wasmtime_export.vmctx,
}
}
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::ExportTable {
&self.wasmtime_export
}
}
/// A WebAssembly linear memory.
@@ -987,13 +994,13 @@ impl Memory {
.ok_or_else(|| anyhow!("failed to grow memory"))
}
pub(crate) fn from_wasmtime_memory(
wasmtime_export: wasmtime_runtime::ExportMemory,
instance: StoreInstanceHandle,
pub(crate) unsafe fn from_wasmtime_memory(
wasmtime_export: &wasmtime_runtime::ExportMemory,
store: &Store,
) -> Memory {
Memory {
instance,
wasmtime_export,
instance: store.existing_vmctx(wasmtime_export.vmctx),
wasmtime_export: wasmtime_export.clone(),
}
}
@@ -1007,6 +1014,10 @@ impl Memory {
vmctx: self.wasmtime_export.vmctx,
}
}
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::ExportMemory {
&self.wasmtime_export
}
}
/// A linear memory. This trait provides an interface for raw memory buffers which are used

View File

@@ -1,6 +1,6 @@
use crate::store::StoreInner;
use crate::trampoline::StoreInstanceHandle;
use crate::{Extern, ExternRef, FuncType, Memory, Store, Trap, Val, ValType};
use crate::{Extern, ExternRef, FuncType, Store, Trap, Val, ValType};
use anyhow::{bail, ensure, Context as _, Result};
use smallvec::{smallvec, SmallVec};
use std::cmp::max;
@@ -9,8 +9,9 @@ use std::mem;
use std::panic::{self, AssertUnwindSafe};
use std::ptr::{self, NonNull};
use std::rc::Weak;
use wasmtime_environ::wasm::EntityIndex;
use wasmtime_runtime::{
raise_user_trap, Export, InstanceHandle, VMContext, VMFunctionBody, VMSharedSignatureIndex,
raise_user_trap, InstanceHandle, VMContext, VMFunctionBody, VMSharedSignatureIndex,
VMTrampoline,
};
@@ -331,11 +332,8 @@ impl Func {
debug_assert!(
anyfunc.as_ref().type_index != wasmtime_runtime::VMSharedSignatureIndex::default()
);
let instance_handle = wasmtime_runtime::InstanceHandle::from_vmctx(anyfunc.as_ref().vmctx);
let export = wasmtime_runtime::ExportFunction { anyfunc };
let instance = store.existing_instance_handle(instance_handle);
let f = Func::from_wasmtime_function(export, instance);
let f = Func::from_wasmtime_function(&export, store);
Some(f)
}
@@ -649,24 +647,24 @@ impl Func {
self.export.anyfunc
}
pub(crate) fn from_wasmtime_function(
export: wasmtime_runtime::ExportFunction,
instance: StoreInstanceHandle,
pub(crate) unsafe fn from_wasmtime_function(
export: &wasmtime_runtime::ExportFunction,
store: &Store,
) -> Self {
// Each function signature in a module should have a trampoline stored
// on that module as well, so unwrap the result here since otherwise
// it's a bug in wasmtime.
let trampoline = instance
.store
let anyfunc = export.anyfunc.as_ref();
let trampoline = store
.signatures()
.borrow()
.lookup_shared(unsafe { export.anyfunc.as_ref().type_index })
.lookup_shared(anyfunc.type_index)
.expect("failed to retrieve trampoline from module")
.1;
Func {
instance,
export,
instance: store.existing_vmctx(anyfunc.vmctx),
export: export.clone(),
trampoline,
}
}
@@ -807,6 +805,10 @@ impl Func {
}
}
}
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::ExportFunction {
&self.export
}
}
impl fmt::Debug for Func {
@@ -1506,10 +1508,16 @@ impl Caller<'_> {
debug_assert!(self.store.upgrade().is_some());
let handle =
Store::from_inner(self.store.upgrade()?).existing_instance_handle(instance);
let export = handle.lookup(name)?;
match export {
Export::Memory(m) => Some(Extern::Memory(Memory::from_wasmtime_memory(m, handle))),
Export::Function(f) => Some(Extern::Func(Func::from_wasmtime_function(f, handle))),
let index = handle.module().exports.get(name)?;
match index {
// Only allow memory/functions for now to emulate what interface
// types will once provide
EntityIndex::Memory(_) | EntityIndex::Function(_) => {
Some(Extern::from_wasmtime_export(
&handle.lookup_by_declaration(&index),
&handle.store,
))
}
_ => None,
}
}

View File

@@ -1,21 +1,17 @@
use crate::trampoline::StoreInstanceHandle;
use crate::types::matching;
use crate::{
Engine, Export, Extern, ExternType, Func, Global, InstanceType, Memory, Module, Store, Table,
Trap,
Engine, Export, Extern, Func, Global, InstanceType, Memory, Module, Store, Table, Trap,
};
use anyhow::{bail, Context, Error, Result};
use std::mem;
use std::sync::Arc;
use std::rc::Rc;
use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::wasm::{
EntityIndex, EntityType, FuncIndex, GlobalIndex, InstanceIndex, MemoryIndex, ModuleIndex,
TableIndex,
EntityIndex, FuncIndex, GlobalIndex, InstanceIndex, MemoryIndex, ModuleIndex, TableIndex,
};
use wasmtime_environ::Initializer;
use wasmtime_jit::TypeTables;
use wasmtime_runtime::{
Imports, InstanceHandle, InstantiationError, StackMapRegistry, VMContext,
Imports, InstantiationError, RuntimeInstance, StackMapRegistry, VMContext,
VMExternRefActivationsTable, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport,
VMTableImport,
};
@@ -36,8 +32,6 @@ use wasmtime_runtime::{
/// itself.
/// * `type` - the type tables produced during compilation which
/// `compiled_module`'s metadata references.
/// * `parent_modules` - this is the list of compiled modules the parent has.
/// This is only applicable on recursive instantiations.
/// * `define_import` - this function, like the name implies, defines an import
/// into the provided builder. The expected entity that it's defining is also
/// passed in for the top-level case where type-checking is performed. This is
@@ -45,9 +39,13 @@ use wasmtime_runtime::{
fn instantiate(
store: &Store,
module: &Module,
parent_modules: &PrimaryMap<ModuleIndex, Module>,
define_import: &mut dyn FnMut(&EntityIndex, &mut ImportsBuilder<'_>) -> Result<()>,
) -> Result<StoreInstanceHandle, Error> {
define_import: &mut dyn FnMut(
&str,
Option<&str>,
&EntityIndex,
&mut ImportsBuilder<'_>,
) -> Result<()>,
) -> Result<RuntimeInstance, Error> {
let compiled_module = module.compiled_module();
let env_module = compiled_module.module();
@@ -59,21 +57,9 @@ fn instantiate(
// to fetching from the import list for the top-level module and
// otherwise fetching from each nested instance's argument list for
// submodules.
Initializer::Import {
index,
module,
field,
} => {
define_import(index, &mut imports).with_context(|| match field {
Some(name) => format!("incompatible import type for `{}::{}`", module, name),
None => format!("incompatible import type for `{}`", module),
})?;
}
// This one's pretty easy, we're just picking up our parent's module
// and putting it into our own index space.
Initializer::AliasParentModule(idx) => {
imports.modules.push(parent_modules[*idx].clone());
Initializer::Import { index, name, field } => {
define_import(name, field.as_deref(), index, &mut imports)
.with_context(|| format!("incompatible import type for `{}`", name))?;
}
// Turns out defining any kind of module is pretty easy, we're just
@@ -95,18 +81,8 @@ fn instantiate(
// handle comes from our same store, but this should be true because
// we acquired the handle from an instance in the store.
Initializer::AliasInstanceExport { instance, export } => {
let instance_ty = env_module.instances[*instance];
let export_name = module.types().instance_signatures[instance_ty]
.exports
.get_index(*export)
.expect("validation bug - should be valid")
.0;
let handle = &imports.instances[*instance];
let entity_index = &handle.module().exports[export_name];
let item = Extern::from_wasmtime_export(
handle.lookup_by_declaration(entity_index),
unsafe { store.existing_instance_handle(handle.clone()) },
);
let export = &imports.instances[*instance][export];
let item = unsafe { Extern::from_wasmtime_export(export, store) };
imports.push_extern(&item);
}
@@ -127,13 +103,13 @@ fn instantiate(
// we're doing all of this in the context of our `Store` argument
// above so we should be safe here.
Initializer::Instantiate { module, args } => {
let mut args = args.iter();
let handle = instantiate(
store,
&imports.modules[*module],
&imports.modules,
&mut |_, builder| {
match *args.next().unwrap() {
&mut |name, field, _, builder| {
debug_assert!(field.is_none());
let index = args.get(name).expect("should be present after validation");
match *index {
EntityIndex::Global(i) => {
builder.globals.push(imports.globals[i]);
}
@@ -150,24 +126,17 @@ fn instantiate(
builder.modules.push(imports.modules[i].clone());
}
EntityIndex::Instance(i) => {
builder
.instances
.push(unsafe { imports.instances[i].clone() });
builder.instances.push(imports.instances[i].clone());
}
}
Ok(())
},
)?;
imports.instances.push(unsafe { (*handle).clone() });
imports.instances.push(handle);
}
}
}
// With the above initialization done we've now acquired the final set of
// imports in all the right index spaces and everything. Time to carry on
// with the creation of our own instance.
let imports = imports.build();
// Register the module just before instantiation to ensure we have a
// trampoline registered for every signature and to preserve the module's
// compiled JIT code within the `Store`.
@@ -176,11 +145,11 @@ fn instantiate(
let config = store.engine().config();
let instance = unsafe {
let instance = compiled_module.instantiate(
imports,
imports.build(),
&store.lookup_shared_signature(module.types()),
config.memory_creator.as_ref().map(|a| a as _),
store.interrupts(),
Box::new(module.types().clone()),
Box::new(()),
store.externref_activations_table() as *const VMExternRefActivationsTable as *mut _,
store.stack_map_registry() as *const StackMapRegistry as *mut _,
)?;
@@ -233,7 +202,29 @@ fn instantiate(
}
}
Ok(instance)
let exports = instance
.handle
.module()
.exports
.iter()
.map(|(name, 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.
let item = match index {
EntityIndex::Instance(i) => {
wasmtime_runtime::Export::Instance(imports.instances[*i].clone())
}
EntityIndex::Module(i) => {
wasmtime_runtime::Export::Module(Box::new(imports.modules[*i].clone()))
}
index => instance.handle.lookup_by_declaration(index),
};
(name.clone(), item)
})
.collect();
Ok(Rc::new(exports))
}
/// An instantiated WebAssembly module.
@@ -254,7 +245,8 @@ fn instantiate(
/// call any code or execute anything!
#[derive(Clone)]
pub struct Instance {
pub(crate) handle: StoreInstanceHandle,
pub(crate) store: Store,
pub(crate) items: RuntimeInstance,
}
impl Instance {
@@ -316,7 +308,7 @@ impl Instance {
bail!("cross-`Engine` instantiation is not currently supported");
}
// Perform some pre-flight checks before we get into the meat of
// Perform some pre-flight checks before we geet into the meat of
// instantiation.
let expected = module.compiled_module().module().imports().count();
if expected != imports.len() {
@@ -329,32 +321,26 @@ impl Instance {
}
let mut imports = imports.iter();
let handle = instantiate(store, module, &PrimaryMap::new(), &mut |idx, builder| {
let items = instantiate(store, module, &mut |_name, _field, idx, builder| {
let import = imports.next().expect("already checked the length");
builder.define_extern(idx, import)
builder.define_extern(idx, &import)
})?;
Ok(Instance { handle })
Ok(Instance::from_wasmtime(&items, store))
}
pub(crate) fn from_wasmtime(handle: StoreInstanceHandle) -> Instance {
Instance { handle }
pub(crate) fn from_wasmtime(handle: &RuntimeInstance, store: &Store) -> Instance {
Instance {
items: handle.clone(),
store: store.clone(),
}
}
/// Returns the type signature of this instance.
pub fn ty(&self) -> InstanceType {
let mut ty = InstanceType::new();
let module = self.handle.module();
let types = self
.handle
.host_state()
.downcast_ref::<Arc<TypeTables>>()
.unwrap();
for (name, index) in module.exports.iter() {
ty.add_named_export(
name,
ExternType::from_wasmtime(types, &module.type_of(*index)),
);
for export in self.exports() {
ty.add_named_export(export.name(), export.ty());
}
ty
}
@@ -364,16 +350,15 @@ impl Instance {
/// This is the [`Store`] that generally serves as a sort of global cache
/// for various instance-related things.
pub fn store(&self) -> &Store {
&self.handle.store
&self.store
}
/// Returns the list of exported items from this [`Instance`].
pub fn exports<'instance>(
&'instance self,
) -> impl ExactSizeIterator<Item = Export<'instance>> + 'instance {
self.handle.exports().map(move |(name, entity_index)| {
let export = self.handle.lookup_by_declaration(entity_index);
let extern_ = Extern::from_wasmtime_export(export, self.handle.clone());
self.items.iter().map(move |(name, item)| {
let extern_ = unsafe { Extern::from_wasmtime_export(item, &self.store) };
Export::new(name, extern_)
})
}
@@ -385,8 +370,8 @@ impl Instance {
///
/// Returns `None` if there was no export named `name`.
pub fn get_export(&self, name: &str) -> Option<Extern> {
let export = self.handle.lookup(&name)?;
Some(Extern::from_wasmtime_export(export, self.handle.clone()))
let export = self.items.get(name)?;
Some(unsafe { Extern::from_wasmtime_export(export, &self.store) })
}
/// Looks up an exported [`Func`] value by name.
@@ -427,7 +412,7 @@ struct ImportsBuilder<'a> {
tables: PrimaryMap<TableIndex, VMTableImport>,
memories: PrimaryMap<MemoryIndex, VMMemoryImport>,
globals: PrimaryMap<GlobalIndex, VMGlobalImport>,
instances: PrimaryMap<InstanceIndex, InstanceHandle>,
instances: PrimaryMap<InstanceIndex, RuntimeInstance>,
modules: PrimaryMap<ModuleIndex, Module>,
module: &'a wasmtime_environ::Module,
@@ -452,36 +437,7 @@ impl<'a> ImportsBuilder<'a> {
fn define_extern(&mut self, expected: &EntityIndex, actual: &Extern) -> Result<()> {
let expected_ty = self.module.type_of(*expected);
let compatible = match &expected_ty {
EntityType::Table(i) => match actual {
Extern::Table(e) => self.matcher.table(i, e),
_ => bail!("expected table, but found {}", actual.desc()),
},
EntityType::Memory(i) => match actual {
Extern::Memory(e) => self.matcher.memory(i, e),
_ => bail!("expected memory, but found {}", actual.desc()),
},
EntityType::Global(i) => match actual {
Extern::Global(e) => self.matcher.global(i, e),
_ => bail!("expected global, but found {}", actual.desc()),
},
EntityType::Function(i) => match actual {
Extern::Func(e) => self.matcher.func(*i, e),
_ => bail!("expected func, but found {}", actual.desc()),
},
EntityType::Instance(i) => match actual {
Extern::Instance(e) => self.matcher.instance(*i, e),
_ => bail!("expected instance, but found {}", actual.desc()),
},
EntityType::Module(i) => match actual {
Extern::Module(e) => self.matcher.module(*i, e),
_ => bail!("expected module, but found {}", actual.desc()),
},
EntityType::Event(_) => unimplemented!(),
};
if !compatible {
bail!("{} types incompatible", actual.desc());
}
self.matcher.extern_(&expected_ty, actual)?;
self.push_extern(actual);
Ok(())
}
@@ -502,7 +458,7 @@ impl<'a> ImportsBuilder<'a> {
}
Extern::Instance(i) => {
debug_assert!(Store::same(i.store(), self.matcher.store));
self.instances.push(unsafe { (*i.handle).clone() });
self.instances.push(i.items.clone());
}
Extern::Module(m) => {
self.modules.push(m.clone());
@@ -516,11 +472,40 @@ impl<'a> ImportsBuilder<'a> {
globals: self.globals.values().as_slice(),
memories: self.memories.values().as_slice(),
functions: self.functions.values().as_slice(),
instances: mem::take(&mut self.instances),
modules: mem::take(&mut self.modules)
.into_iter()
.map(|(_, m)| Box::new(m) as Box<_>)
.collect(),
}
}
}
/// An internal structure to this crate to build an `Instance` from a list of
/// items with names. This is intended to stay private for now, it'll need an
/// audit of APIs if publicly exported.
#[derive(Default)]
pub(crate) struct InstanceBuilder {
items: RuntimeInstance,
}
impl InstanceBuilder {
pub(crate) fn new() -> InstanceBuilder {
InstanceBuilder::default()
}
pub(crate) fn insert(&mut self, name: &str, item: impl Into<Extern>) {
let items = Rc::get_mut(&mut self.items).unwrap();
let export = match item.into() {
Extern::Func(i) => wasmtime_runtime::Export::Function(i.wasmtime_export().clone()),
Extern::Memory(i) => wasmtime_runtime::Export::Memory(i.wasmtime_export().clone()),
Extern::Table(i) => wasmtime_runtime::Export::Table(i.wasmtime_export().clone()),
Extern::Global(i) => wasmtime_runtime::Export::Global(i.wasmtime_export().clone()),
Extern::Instance(i) => wasmtime_runtime::Export::Instance(i.items.clone()),
Extern::Module(i) => wasmtime_runtime::Export::Module(Box::new(i.clone())),
};
items.insert(name.to_string(), export);
}
pub(crate) fn finish(self, store: &Store) -> Instance {
Instance {
store: store.clone(),
items: self.items,
}
}
}

View File

@@ -1,3 +1,4 @@
use crate::instance::InstanceBuilder;
use crate::{
Extern, ExternType, Func, FuncType, GlobalType, ImportType, Instance, IntoFunc, Module, Store,
Trap,
@@ -654,7 +655,37 @@ impl Linker {
},
kind: self.import_kind(import.ty()),
};
self.map.get(&key).cloned()
if let Some(result) = self.map.get(&key).cloned() {
return Some(result);
}
// 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.
if let ExternType::Instance(t) = import.ty() {
if import.name().is_none() {
let mut builder = InstanceBuilder::new();
for export in t.exports() {
let item = self.get(&export.as_import(import.module()))?;
builder.insert(export.name(), item);
}
return Some(builder.finish(&self.store).into());
}
}
None
}
/// Returns all items defined for the `module` and `name` pair.

View File

@@ -245,12 +245,14 @@ impl Module {
/// ```
pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Module> {
#[cfg(feature = "cache")]
let (artifacts, types) = ModuleCacheEntry::new("wasmtime", engine.cache_config())
.get_data((engine.compiler(), binary), |(compiler, binary)| {
CompilationArtifacts::build(compiler, binary)
})?;
let (main_module, artifacts, types) =
ModuleCacheEntry::new("wasmtime", engine.cache_config())
.get_data((engine.compiler(), binary), |(compiler, binary)| {
CompilationArtifacts::build(compiler, binary)
})?;
#[cfg(not(feature = "cache"))]
let (artifacts, types) = CompilationArtifacts::build(engine.compiler(), binary)?;
let (main_module, artifacts, types) =
CompilationArtifacts::build(engine.compiler(), binary)?;
let modules = CompiledModule::from_artifacts_list(
artifacts,
@@ -261,7 +263,7 @@ impl Module {
let types = Arc::new(types);
Ok(Module {
engine: engine.clone(),
index: 0,
index: main_module,
data: Arc::new(ModuleData { types, modules }),
})
}

View File

@@ -13,8 +13,8 @@ use std::sync::Arc;
use wasmtime_environ::wasm;
use wasmtime_jit::{CompiledModule, ModuleCode, TypeTables};
use wasmtime_runtime::{
InstanceHandle, RuntimeMemoryCreator, SignalHandler, StackMapRegistry, TrapInfo, VMExternRef,
VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex,
InstanceHandle, RuntimeMemoryCreator, SignalHandler, StackMapRegistry, TrapInfo, VMContext,
VMExternRef, VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex,
};
/// A `Store` is a collection of WebAssembly instances and host-defined items.
@@ -234,6 +234,10 @@ impl Store {
}
}
pub(crate) unsafe fn existing_vmctx(&self, cx: *mut VMContext) -> StoreInstanceHandle {
self.existing_instance_handle(InstanceHandle::from_vmctx(cx))
}
pub(crate) fn weak(&self) -> Weak<StoreInner> {
Rc::downgrade(&self.inner)
}

View File

@@ -48,7 +48,7 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreIn
module
.initializers
.push(wasmtime_environ::Initializer::Import {
module: "".into(),
name: "".into(),
field: None,
index: wasm::EntityIndex::Function(func_index),
});
@@ -80,7 +80,7 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreIn
)?;
if let Some(x) = externref_init {
match handle.lookup("").unwrap() {
match handle.lookup_by_declaration(&wasm::EntityIndex::Global(global_id)) {
wasmtime_runtime::Export::Global(g) => unsafe {
*(*g.definition).as_externref_mut() = Some(x.inner);
},

View File

@@ -16,6 +16,7 @@ use crate::{FuncType, GlobalType, MemoryType, Store, TableType, Trap, Val};
use anyhow::Result;
use std::any::Any;
use std::ops::Deref;
use wasmtime_environ::wasm;
use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody, VMTrampoline};
/// A wrapper around `wasmtime_runtime::InstanceHandle` which pairs it with the
@@ -55,7 +56,8 @@ pub fn generate_func_export(
VMTrampoline,
)> {
let (instance, trampoline) = create_handle_with_function(ft, func, store)?;
match instance.lookup("").expect("trampoline export") {
let idx = wasm::EntityIndex::Function(wasm::FuncIndex::from_u32(0));
match instance.lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Function(f) => Ok((instance, f, trampoline)),
_ => unreachable!(),
}
@@ -72,7 +74,8 @@ pub unsafe fn generate_raw_func_export(
state: Box<dyn Any>,
) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportFunction)> {
let instance = func::create_handle_with_raw_function(ft, func, trampoline, store, state)?;
match instance.lookup("").expect("trampoline export") {
let idx = wasm::EntityIndex::Function(wasm::FuncIndex::from_u32(0));
match instance.lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Function(f) => Ok((instance, f)),
_ => unreachable!(),
}
@@ -84,7 +87,8 @@ pub fn generate_global_export(
val: Val,
) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportGlobal)> {
let instance = create_global(store, gt, val)?;
match instance.lookup("").expect("global export") {
let idx = wasm::EntityIndex::Global(wasm::GlobalIndex::from_u32(0));
match instance.lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Global(g) => Ok((instance, g)),
_ => unreachable!(),
}
@@ -95,7 +99,8 @@ pub fn generate_memory_export(
m: &MemoryType,
) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportMemory)> {
let instance = create_handle_with_memory(store, m)?;
match instance.lookup("").expect("memory export") {
let idx = wasm::EntityIndex::Memory(wasm::MemoryIndex::from_u32(0));
match instance.lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Memory(m) => Ok((instance, m)),
_ => unreachable!(),
}
@@ -106,7 +111,8 @@ pub fn generate_table_export(
t: &TableType,
) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportTable)> {
let instance = create_handle_with_table(store, t)?;
match instance.lookup("").expect("table export") {
let idx = wasm::EntityIndex::Table(wasm::TableIndex::from_u32(0));
match instance.lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Table(t) => Ok((instance, t)),
_ => unreachable!(),
}

View File

@@ -23,6 +23,7 @@ pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result<Stor
let table_plan = wasmtime_environ::TablePlan::for_table(table, &tunable);
let table_id = module.table_plans.push(table_plan);
// TODO: can this `exports.insert` get removed?
module
.exports
.insert(String::new(), wasm::EntityIndex::Table(table_id));

View File

@@ -466,21 +466,21 @@ impl ModuleType {
}
/// Adds a new export to this `ModuleType`.
pub fn add_named_export(&mut self, name: &str, ty: ExternType) {
pub(crate) 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) {
pub(crate) 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(|(module, name, ty)| ImportType {
module,
name: name.as_deref(),
self.imports.iter().map(|(name, field, ty)| ImportType {
module: name,
name: field.as_deref(),
ty: EntityOrExtern::Extern(ty),
})
}
@@ -506,13 +506,7 @@ impl ModuleType {
imports: ty
.imports
.iter()
.map(|(m, name, ty)| {
(
m.to_string(),
name.as_ref().map(|n| n.to_string()),
ExternType::from_wasmtime(types, ty),
)
})
.map(|(m, ty)| (m.to_string(), None, ExternType::from_wasmtime(types, ty)))
.collect(),
}
}
@@ -615,8 +609,9 @@ impl<'module> ImportType<'module> {
/// Returns the field name of the module that this import is expected to
/// come from.
///
/// Note that the name can be `None` for the module linking proposal. If the
/// module linking proposal is not enabled it's safe to unwrap this.
/// 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> {
self.name
}
@@ -683,6 +678,17 @@ impl<'module> ExportType<'module> {
EntityOrExtern::Extern(e) => (*e).clone(),
}
}
pub(crate) fn as_import<'a>(&self, module: &'a str) -> ImportType<'a>
where
'module: 'a,
{
ImportType {
module,
name: Some(self.name),
ty: self.ty.clone(),
}
}
}
impl<'module> fmt::Debug for ExportType<'module> {

View File

@@ -1,5 +1,5 @@
use crate::Store;
use std::sync::Arc;
use crate::{Extern, Store};
use anyhow::{bail, Context, Result};
use wasmtime_environ::wasm::{
EntityType, Global, InstanceTypeIndex, Memory, ModuleTypeIndex, SignatureIndex, Table,
};
@@ -11,22 +11,27 @@ pub struct MatchCx<'a> {
}
impl MatchCx<'_> {
pub fn global(&self, expected: &Global, actual: &crate::Global) -> bool {
pub fn global(&self, expected: &Global, actual: &crate::Global) -> Result<()> {
self.global_ty(expected, actual.wasmtime_ty())
}
fn global_ty(&self, expected: &Global, actual: &Global) -> bool {
expected.ty == actual.ty
fn global_ty(&self, expected: &Global, actual: &Global) -> Result<()> {
if expected.ty == actual.ty
&& expected.wasm_ty == actual.wasm_ty
&& expected.mutability == actual.mutability
{
Ok(())
} else {
bail!("global types incompatible")
}
}
pub fn table(&self, expected: &Table, actual: &crate::Table) -> bool {
pub fn table(&self, expected: &Table, actual: &crate::Table) -> Result<()> {
self.table_ty(expected, actual.wasmtime_ty())
}
fn table_ty(&self, expected: &Table, actual: &Table) -> bool {
expected.wasm_ty == actual.wasm_ty
fn table_ty(&self, expected: &Table, actual: &Table) -> Result<()> {
if expected.wasm_ty == actual.wasm_ty
&& expected.ty == actual.ty
&& expected.minimum <= actual.minimum
&& match expected.maximum {
@@ -36,14 +41,19 @@ impl MatchCx<'_> {
},
None => true,
}
{
Ok(())
} else {
bail!("table types incompatible")
}
}
pub fn memory(&self, expected: &Memory, actual: &crate::Memory) -> bool {
pub fn memory(&self, expected: &Memory, actual: &crate::Memory) -> Result<()> {
self.memory_ty(expected, actual.wasmtime_ty())
}
fn memory_ty(&self, expected: &Memory, actual: &Memory) -> bool {
expected.shared == actual.shared
fn memory_ty(&self, expected: &Memory, actual: &Memory) -> Result<()> {
if expected.shared == actual.shared
&& expected.minimum <= actual.minimum
&& match expected.maximum {
Some(expected) => match actual.maximum {
@@ -52,10 +62,15 @@ impl MatchCx<'_> {
},
None => true,
}
{
Ok(())
} else {
bail!("memory types incompatible")
}
}
pub fn func(&self, expected: SignatureIndex, actual: &crate::Func) -> bool {
match self
pub fn func(&self, expected: SignatureIndex, actual: &crate::Func) -> Result<()> {
let matches = match self
.store
.signatures()
.borrow()
@@ -65,31 +80,50 @@ impl MatchCx<'_> {
// If our expected signature isn't registered, then there's no way
// that `actual` can match it.
None => false,
};
if matches {
Ok(())
} else {
bail!("function types incompatible")
}
}
pub fn instance(&self, expected: InstanceTypeIndex, actual: &crate::Instance) -> bool {
let module = actual.handle.module();
self.exports_match(
expected,
actual
.handle
.host_state()
.downcast_ref::<Arc<TypeTables>>()
.unwrap(),
|name| module.exports.get(name).map(|idx| module.type_of(*idx)),
)
pub fn instance(&self, expected: InstanceTypeIndex, actual: &crate::Instance) -> Result<()> {
for (name, expected) in self.types.instance_signatures[expected].exports.iter() {
match actual.items.get(name) {
Some(item) => {
let item = unsafe { Extern::from_wasmtime_export(item, self.store) };
self.extern_(expected, &item)
.with_context(|| format!("instance export {:?} incompatible", name))?;
}
None => bail!("instance type missing export {:?}", name),
}
}
Ok(())
}
/// Validates that the type signature of `actual` matches the `expected`
/// module type signature.
pub fn module(&self, expected: ModuleTypeIndex, actual: &crate::Module) -> bool {
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.store.engine().config().features.module_linking);
let expected_sig = &self.types.module_signatures[expected];
let module = actual.compiled_module().module();
self.imports_match(expected, actual.types(), module.imports())
&& self.exports_match(expected_sig.exports, actual.types(), |name| {
module.exports.get(name).map(|idx| module.type_of(*idx))
})
self.imports_match(
expected,
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.types(), |name| {
module.exports.get(name).map(|idx| module.type_of(*idx))
})?;
Ok(())
}
/// Validates that the `actual_imports` list of module imports matches the
@@ -100,19 +134,25 @@ impl MatchCx<'_> {
&self,
expected: ModuleTypeIndex,
actual_types: &TypeTables,
mut actual_imports: impl Iterator<Item = (&'a str, Option<&'a str>, EntityType)>,
) -> bool {
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 (_, _, expected) in expected_sig.imports.iter() {
let (_, _, ty) = match actual_imports.next() {
Some(e) => e,
None => return false,
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),
};
if !self.extern_ty_matches(expected, &ty, actual_types) {
return false;
MatchCx {
types: actual_types,
store: self.store,
}
.extern_ty_matches(&actual_ty, expected_ty, self.types)
.with_context(|| format!("module import {:?} incompatible", name))?;
}
actual_imports.next().is_none()
Ok(())
}
/// Validates that all exports in `expected` are defined by `lookup` within
@@ -122,16 +162,19 @@ impl MatchCx<'_> {
expected: InstanceTypeIndex,
actual_types: &TypeTables,
lookup: impl Fn(&str) -> Option<EntityType>,
) -> bool {
) -> 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.
self.types.instance_signatures[expected].exports.iter().all(
|(name, expected)| match lookup(name) {
Some(ty) => self.extern_ty_matches(expected, &ty, actual_types),
None => false,
},
)
for (name, expected) in self.types.instance_signatures[expected].exports.iter() {
match lookup(name) {
Some(ty) => self
.extern_ty_matches(expected, &ty, 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
@@ -141,34 +184,49 @@ impl MatchCx<'_> {
expected: &EntityType,
actual_ty: &EntityType,
actual_types: &TypeTables,
) -> bool {
) -> Result<()> {
let actual_desc = match actual_ty {
EntityType::Global(_) => "global",
EntityType::Module(_) => "module",
EntityType::Memory(_) => "memory",
EntityType::Event(_) => "event",
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),
_ => false,
_ => bail!("expected global, but found {}", actual_desc),
},
EntityType::Table(expected) => match actual_ty {
EntityType::Table(actual) => self.table_ty(expected, actual),
_ => false,
_ => bail!("expected table, but found {}", actual_desc),
},
EntityType::Memory(expected) => match actual_ty {
EntityType::Memory(actual) => self.memory_ty(expected, actual),
_ => false,
_ => bail!("expected memory, but found {}", actual_desc),
},
EntityType::Function(expected) => match *actual_ty {
EntityType::Function(actual) => {
self.types.wasm_signatures[*expected] == actual_types.wasm_signatures[actual]
if self.types.wasm_signatures[*expected] == actual_types.wasm_signatures[actual]
{
Ok(())
} else {
bail!("function types incompatible")
}
}
_ => false,
_ => 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_types, |name| {
sig.exports.get(name).cloned()
})
})?;
Ok(())
}
_ => false,
_ => bail!("expected instance, but found {}", actual_desc),
},
EntityType::Module(expected) => match actual_ty {
EntityType::Module(actual) => {
@@ -180,14 +238,48 @@ impl MatchCx<'_> {
self.imports_match(
*expected,
actual_types,
actual_module_sig.imports.iter().map(|(module, field, ty)| {
(module.as_str(), field.as_deref(), ty.clone())
}),
) && self.exports_match(expected_module_sig.exports, actual_types, |name| {
actual_module_sig
.imports
.iter()
.map(|(module, ty)| (module.as_str(), ty.clone())),
)?;
self.exports_match(expected_module_sig.exports, actual_types, |name| {
actual_instance_sig.exports.get(name).cloned()
})
})?;
Ok(())
}
_ => false,
_ => bail!("expected module, but found {}", actual_desc),
},
EntityType::Event(_) => unimplemented!(),
}
}
/// Validates that the `expected` type matches the type of `actual`
pub fn extern_(&self, expected: &EntityType, actual: &Extern) -> Result<()> {
match expected {
EntityType::Global(expected) => match actual {
Extern::Global(actual) => self.global(expected, actual),
_ => bail!("expected global, but found {}", actual.desc()),
},
EntityType::Table(expected) => match actual {
Extern::Table(actual) => self.table(expected, actual),
_ => bail!("expected table, but found {}", actual.desc()),
},
EntityType::Memory(expected) => match actual {
Extern::Memory(actual) => self.memory(expected, actual),
_ => bail!("expected memory, but found {}", actual.desc()),
},
EntityType::Function(expected) => match actual {
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::Event(_) => unimplemented!(),
}