diff --git a/Cargo.toml b/Cargo.toml index 796d74dbdff1..ed703cbe3341 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,3 +96,6 @@ harness = false [profile.dev.package.backtrace] debug = false # FIXME(#1813) + +[patch.crates-io] +wasmparser = { git = 'https://github.com/alexcrichton/wasm-tools', branch = 'fix-validate-submodule' } diff --git a/cranelift/wasm/src/translation_utils.rs b/cranelift/wasm/src/translation_utils.rs index e16103323884..c8493d4b51e5 100644 --- a/cranelift/wasm/src/translation_utils.rs +++ b/cranelift/wasm/src/translation_utils.rs @@ -110,7 +110,7 @@ pub struct InstanceTypeIndex(u32); entity_impl!(InstanceTypeIndex); /// An index of an entity. -#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum EntityIndex { /// Function index. diff --git a/crates/c-api/src/extern.rs b/crates/c-api/src/extern.rs index 0b395dd2fa76..6a08a6e13ea2 100644 --- a/crates/c-api/src/extern.rs +++ b/crates/c-api/src/extern.rs @@ -16,6 +16,10 @@ pub extern "C" fn wasm_extern_kind(e: &wasm_extern_t) -> wasm_externkind_t { Extern::Global(_) => crate::WASM_EXTERN_GLOBAL, Extern::Table(_) => crate::WASM_EXTERN_TABLE, Extern::Memory(_) => crate::WASM_EXTERN_MEMORY, + + // FIXME(#2094) + Extern::Instance(_) => unimplemented!(), + Extern::Module(_) => unimplemented!(), } } diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index d78efadf0f33..c555a4435b8f 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -346,6 +346,30 @@ impl Module { pub fn is_imported_global(&self, index: GlobalIndex) -> bool { index.index() < self.num_imported_globals } + + /// Test whether the given global index is for an imported global. + pub fn imports(&self) -> impl Iterator<Item = (&str, Option<&str>, EntityType)> { + self.initializers.iter().filter_map(move |i| match i { + Initializer::Import { + module, + field, + index, + } => Some((module.as_str(), field.as_deref(), self.type_of(*index))), + _ => None, + }) + } + + /// Returns the type of an item based on its index + pub fn type_of(&self, index: EntityIndex) -> EntityType { + match index { + EntityIndex::Global(i) => EntityType::Global(self.globals[i]), + EntityIndex::Table(i) => EntityType::Table(self.table_plans[i].table), + EntityIndex::Memory(i) => EntityType::Memory(self.memory_plans[i].memory), + EntityIndex::Function(i) => EntityType::Function(self.functions[i]), + EntityIndex::Instance(i) => EntityType::Instance(self.instances[i]), + EntityIndex::Module(i) => EntityType::Module(self.modules[i]), + } + } } /// All types which are recorded for the entirety of a translation. @@ -376,7 +400,7 @@ pub struct ModuleSignature { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InstanceSignature { /// The name of what's being exported as well as its type signature. - pub exports: Vec<(String, EntityType)>, + pub exports: IndexMap<String, EntityType>, } mod passive_data_serde { diff --git a/crates/environ/src/module_environ.rs b/crates/environ/src/module_environ.rs index f1af5c4ea023..6d36b4e7a9f7 100644 --- a/crates/environ/src/module_environ.rs +++ b/crates/environ/src/module_environ.rs @@ -857,7 +857,7 @@ and for re-adding support for interface types you can see this issue: // instance. Alias::Child { instance, export } => { let ty = self.result.module.instances[instance]; - match &self.types.instance_signatures[ty].exports[export].1 { + match &self.types.instance_signatures[ty].exports[export] { EntityType::Global(g) => { self.result.module.globals.push(g.clone()); self.result.module.num_imported_globals += 1; diff --git a/crates/runtime/src/export.rs b/crates/runtime/src/export.rs index edfa51e1f290..0161e56a6809 100644 --- a/crates/runtime/src/export.rs +++ b/crates/runtime/src/export.rs @@ -1,13 +1,14 @@ use crate::vmcontext::{ VMCallerCheckedAnyfunc, VMContext, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition, }; +use crate::InstanceHandle; +use std::any::Any; use std::ptr::NonNull; use wasmtime_environ::wasm::Global; use wasmtime_environ::{MemoryPlan, TablePlan}; /// The value of an export passed from one instance to another. -#[derive(Debug, Clone)] -pub enum Export { +pub enum Export<'a> { /// A function export value. Function(ExportFunction), @@ -19,6 +20,12 @@ pub enum Export { /// A global export value. Global(ExportGlobal), + + /// An instance + Instance(&'a InstanceHandle), + + /// A module + Module(&'a dyn Any), } /// A function export value. @@ -31,8 +38,8 @@ pub struct ExportFunction { pub anyfunc: NonNull<VMCallerCheckedAnyfunc>, } -impl From<ExportFunction> for Export { - fn from(func: ExportFunction) -> Export { +impl<'a> From<ExportFunction> for Export<'a> { + fn from(func: ExportFunction) -> Export<'a> { Export::Function(func) } } @@ -48,8 +55,8 @@ pub struct ExportTable { pub table: TablePlan, } -impl From<ExportTable> for Export { - fn from(func: ExportTable) -> Export { +impl<'a> From<ExportTable> for Export<'a> { + fn from(func: ExportTable) -> Export<'a> { Export::Table(func) } } @@ -65,8 +72,8 @@ pub struct ExportMemory { pub memory: MemoryPlan, } -impl From<ExportMemory> for Export { - fn from(func: ExportMemory) -> Export { +impl<'a> From<ExportMemory> for Export<'a> { + fn from(func: ExportMemory) -> Export<'a> { Export::Memory(func) } } @@ -82,8 +89,8 @@ pub struct ExportGlobal { pub global: Global, } -impl From<ExportGlobal> for Export { - fn from(func: ExportGlobal) -> Export { +impl<'a> From<ExportGlobal> for Export<'a> { + fn from(func: ExportGlobal) -> Export<'a> { Export::Global(func) } } diff --git a/crates/runtime/src/imports.rs b/crates/runtime/src/imports.rs index 2f85ba220199..19696307503a 100644 --- a/crates/runtime/src/imports.rs +++ b/crates/runtime/src/imports.rs @@ -1,12 +1,21 @@ use crate::vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport}; +use crate::InstanceHandle; +use std::any::Any; +use wasmtime_environ::entity::PrimaryMap; +use wasmtime_environ::wasm::{InstanceIndex, ModuleIndex}; /// Resolved import pointers. /// -/// Note that each of these fields are slices, not `PrimaryMap`. They should be +/// Note that some of these fields are slices, not `PrimaryMap`. They should be /// stored in index-order as with the module that we're providing the imports /// for, and indexing is all done the same way as the main module's index /// spaces. -#[derive(Clone, Default)] +/// +/// Also note that the way we compile modules means that for the module linking +/// proposal all `alias` directives should map to imported items. This means +/// that each of these items aren't necessarily directly imported, but may be +/// aliased. +#[derive(Default)] pub struct Imports<'a> { /// Resolved addresses for imported functions. pub functions: &'a [VMFunctionImport], @@ -19,4 +28,15 @@ pub struct Imports<'a> { /// Resolved addresses for imported globals. pub globals: &'a [VMGlobalImport], + + /// Resolved imported instances. + pub instances: PrimaryMap<InstanceIndex, InstanceHandle>, + + /// Resolved imported modules. + /// + /// Note that `Box<Any>` here is chosen to allow the embedder of this crate + /// to pick an appropriate representation of what module type should be. For + /// example for the `wasmtime` crate it's `wasmtime::Module` but that's not + /// defined way down here in this low crate. + pub modules: PrimaryMap<ModuleIndex, Box<dyn Any>>, } diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 1f39b171a032..9ff7c4ee6db6 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -28,8 +28,8 @@ use thiserror::Error; use wasmtime_environ::entity::{packed_option::ReservedValue, BoxedSlice, EntityRef, PrimaryMap}; use wasmtime_environ::wasm::{ DataIndex, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, - ElemIndex, EntityIndex, FuncIndex, GlobalIndex, GlobalInit, MemoryIndex, SignatureIndex, - TableElementType, TableIndex, WasmType, + ElemIndex, EntityIndex, FuncIndex, GlobalIndex, GlobalInit, InstanceIndex, MemoryIndex, + ModuleIndex, SignatureIndex, TableElementType, TableIndex, WasmType, }; use wasmtime_environ::{ir, DataInitializer, Module, ModuleType, TableElements, VMOffsets}; @@ -50,6 +50,15 @@ pub(crate) struct Instance { /// WebAssembly table data. tables: BoxedSlice<DefinedTableIndex, Table>, + /// Instances our module defined and their handles. + instances: PrimaryMap<InstanceIndex, InstanceHandle>, + + /// Modules that are located in our index space. + /// + /// For now these are `Box<Any>` so the caller can define the type of what a + /// module looks like. + modules: PrimaryMap<ModuleIndex, Box<dyn Any>>, + /// Passive elements in this instantiation. As `elem.drop`s happen, these /// entries get removed. A missing entry is considered equivalent to an /// empty slice. @@ -268,7 +277,7 @@ impl Instance { } /// Lookup an export with the given export declaration. - pub fn lookup_by_declaration(&self, export: &EntityIndex) -> Export { + pub fn lookup_by_declaration(&self, export: &EntityIndex) -> Export<'_> { match export { EntityIndex::Function(index) => { let anyfunc = self.get_caller_checked_anyfunc(*index).unwrap(); @@ -317,9 +326,8 @@ impl Instance { } .into(), - // FIXME(#2094) - EntityIndex::Instance(_index) => unimplemented!(), - EntityIndex::Module(_index) => unimplemented!(), + EntityIndex::Instance(index) => Export::Instance(&self.instances[*index]), + EntityIndex::Module(index) => Export::Module(&*self.modules[*index]), } } @@ -847,6 +855,8 @@ impl InstanceHandle { passive_elements: Default::default(), passive_data, host_state, + instances: imports.instances, + modules: imports.modules, vmctx: VMContext {}, }; let layout = instance.alloc_layout(); diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 28de26001ebc..c0d0ddb579d5 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -29,6 +29,7 @@ wat = { version = "1.0.18", optional = true } smallvec = "1.4.0" serde = { version = "1.0.94", features = ["derive"] } bincode = "1.2.1" +indexmap = "1.6" [target.'cfg(target_os = "windows")'.dependencies] winapi = "0.3.7" diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs index 63f2e2678f4d..9a075e308efc 100644 --- a/crates/wasmtime/src/externals.rs +++ b/crates/wasmtime/src/externals.rs @@ -3,8 +3,8 @@ use crate::trampoline::{ }; use crate::values::{from_checked_anyfunc, into_checked_anyfunc, Val}; use crate::{ - ExternRef, ExternType, Func, GlobalType, MemoryType, Mutability, Store, TableType, Trap, - ValType, + ExternRef, ExternType, Func, GlobalType, Instance, MemoryType, Module, Mutability, Store, + TableType, Trap, ValType, }; use anyhow::{anyhow, bail, Result}; use std::mem; @@ -33,6 +33,10 @@ pub enum Extern { Table(Table), /// A WebAssembly linear memory. Memory(Memory), + /// A WebAssembly instance. + Instance(Instance), + /// A WebAssembly module. + Module(Module), } impl Extern { @@ -76,6 +80,26 @@ 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`. pub fn ty(&self) -> ExternType { match self { @@ -83,6 +107,8 @@ impl Extern { Extern::Memory(ft) => ExternType::Memory(ft.ty()), Extern::Table(tt) => ExternType::Table(tt.ty()), Extern::Global(gt) => ExternType::Global(gt.ty()), + Extern::Instance(i) => ExternType::Instance(i.ty()), + Extern::Module(m) => ExternType::Module(m.ty()), } } @@ -103,6 +129,13 @@ impl Extern { wasmtime_runtime::Export::Table(t) => { Extern::Table(Table::from_wasmtime_table(t, instance)) } + wasmtime_runtime::Export::Instance(i) => { + let handle = unsafe { instance.store.existing_instance_handle(i.clone()) }; + Extern::Instance(Instance::from_wasmtime(handle)) + } + wasmtime_runtime::Export::Module(m) => { + Extern::Module(m.downcast_ref::<Module>().unwrap().clone()) + } } } @@ -112,6 +145,10 @@ impl Extern { Extern::Global(g) => &g.instance.store, Extern::Memory(m) => &m.instance.store, Extern::Table(t) => &t.instance.store, + Extern::Instance(i) => i.store(), + // Modules don't live in stores right now, so they're compatible + // with all stores. + Extern::Module(_) => return true, }; Store::same(my_store, store) } @@ -122,6 +159,8 @@ impl Extern { Extern::Table(_) => "table", Extern::Memory(_) => "memory", Extern::Global(_) => "global", + Extern::Instance(_) => "instance", + Extern::Module(_) => "module", } } } @@ -150,6 +189,18 @@ 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 @@ -294,11 +345,8 @@ impl Global { } } - pub(crate) fn matches_expected(&self, expected: &wasmtime_environ::wasm::Global) -> bool { - let actual = &self.wasmtime_export.global; - expected.ty == actual.ty - && expected.wasm_ty == actual.wasm_ty - && expected.mutability == actual.mutability + pub(crate) fn wasmtime_ty(&self) -> &wasmtime_environ::wasm::Global { + &self.wasmtime_export.global } pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMGlobalImport { @@ -538,19 +586,8 @@ impl Table { } } - pub(crate) fn matches_expected(&self, ty: &wasmtime_environ::TablePlan) -> bool { - let expected = &ty.table; - let actual = &self.wasmtime_export.table.table; - expected.wasm_ty == actual.wasm_ty - && expected.ty == actual.ty - && expected.minimum <= actual.minimum - && match expected.maximum { - Some(expected) => match actual.maximum { - Some(actual) => expected >= actual, - None => false, - }, - None => true, - } + pub(crate) fn wasmtime_ty(&self) -> &wasmtime_environ::wasm::Table { + &self.wasmtime_export.table.table } pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMTableImport { @@ -960,18 +997,8 @@ impl Memory { } } - pub(crate) fn matches_expected(&self, ty: &wasmtime_environ::MemoryPlan) -> bool { - let expected = &ty.memory; - let actual = &self.wasmtime_export.memory.memory; - expected.shared == actual.shared - && expected.minimum <= actual.minimum - && match expected.maximum { - Some(expected) => match actual.maximum { - Some(actual) => expected >= actual, - None => false, - }, - None => true, - } + pub(crate) fn wasmtime_ty(&self) -> &wasmtime_environ::wasm::Memory { + &self.wasmtime_export.memory.memory } pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMMemoryImport { diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index b57936d67d98..630c5f736d24 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -798,10 +798,6 @@ impl Func { &self.instance.store } - pub(crate) fn matches_expected(&self, expected: VMSharedSignatureIndex) -> bool { - self.sig_index() == expected - } - pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMFunctionImport { unsafe { let f = self.caller_checked_anyfunc(); @@ -1503,7 +1499,6 @@ impl Caller<'_> { return None; } let instance = InstanceHandle::from_vmctx(self.caller_vmctx); - let export = instance.lookup(name)?; // Our `Weak` pointer is used only to break a cycle where `Store` // stores instance handles which have this weak pointer as their // custom host data. This function should only be invoke-able while @@ -1511,6 +1506,7 @@ 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))), diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs index 066a61864036..678ced4f52bd 100644 --- a/crates/wasmtime/src/instance.rs +++ b/crates/wasmtime/src/instance.rs @@ -1,16 +1,23 @@ use crate::trampoline::StoreInstanceHandle; -use crate::{Engine, Export, Extern, Func, Global, Memory, Module, Store, Table, Trap}; +use crate::types::matching; +use crate::{ + Engine, Export, Extern, ExternType, Func, Global, InstanceType, Memory, Module, Store, Table, + Trap, +}; use anyhow::{bail, Context, Error, Result}; use std::mem; +use std::sync::Arc; use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::wasm::{ - EntityIndex, FuncIndex, GlobalIndex, InstanceIndex, MemoryIndex, ModuleIndex, TableIndex, + EntityIndex, EntityType, FuncIndex, GlobalIndex, InstanceIndex, MemoryIndex, ModuleIndex, + TableIndex, }; use wasmtime_environ::Initializer; -use wasmtime_jit::{CompiledModule, TypeTables}; +use wasmtime_jit::TypeTables; use wasmtime_runtime::{ - Imports, InstantiationError, StackMapRegistry, VMContext, VMExternRefActivationsTable, - VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport, + Imports, InstanceHandle, InstantiationError, StackMapRegistry, VMContext, + VMExternRefActivationsTable, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport, + VMTableImport, }; /// Performs all low-level steps necessary for instantiation. @@ -35,17 +42,16 @@ use wasmtime_runtime::{ /// 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 /// fallible because type checks may fail. -fn instantiate<'a>( - store: &'a Store, - compiled_module: &'a CompiledModule, - all_modules: &'a [CompiledModule], - types: &'a TypeTables, - parent_modules: &PrimaryMap<ModuleIndex, &'a CompiledModule>, - define_import: &mut dyn FnMut(&EntityIndex, &mut ImportsBuilder<'a>) -> Result<()>, +fn instantiate( + store: &Store, + module: &Module, + parent_modules: &PrimaryMap<ModuleIndex, Module>, + define_import: &mut dyn FnMut(&EntityIndex, &mut ImportsBuilder<'_>) -> Result<()>, ) -> Result<StoreInstanceHandle, Error> { + let compiled_module = module.compiled_module(); let env_module = compiled_module.module(); - let mut imports = ImportsBuilder::new(env_module, types, store); + let mut imports = ImportsBuilder::new(store, module); for initializer in env_module.initializers.iter() { match initializer { // Definition of an import depends on how our parent is providing @@ -67,24 +73,28 @@ fn instantiate<'a>( // 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]); + imports.modules.push(parent_modules[*idx].clone()); } // Turns out defining any kind of module is pretty easy, we're just // slinging around pointers. Initializer::DefineModule(idx) => { - imports.modules.push(&all_modules[*idx]); + imports.modules.push(module.submodule(*idx)); } // Here we lookup our instance handle, ask it for the nth export, // and then push that item into our own index space. We eschew // type-checking since only valid modules reach this point. + // + // Note that the unsafety here is because we're asserting that the + // handle comes from our same store, but this should be true because + // we acquired fthe handle from an instance in the store. Initializer::AliasInstanceExport { instance, export } => { let handle = &imports.instances[*instance]; let export_index = &handle.module().exports[*export]; let item = Extern::from_wasmtime_export( handle.lookup_by_declaration(export_index), - handle.clone(), + unsafe { store.existing_instance_handle(handle.clone()) }, ); imports.push_extern(&item); } @@ -100,14 +110,16 @@ fn instantiate<'a>( // to be a DAG. Additionally the recursion should also be bounded // due to validation. We may one day need to make this an iterative // loop, however. + // + // Also note that there's some unsafety here around cloning + // `InstanceHandle` because the handle may not live long enough, but + // 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 module_to_instantiate = imports.modules[*module]; let mut args = args.iter(); let handle = instantiate( store, - module_to_instantiate, - all_modules, - types, + &imports.modules[*module], &imports.modules, &mut |_, builder| { match *args.next().unwrap() { @@ -124,16 +136,18 @@ fn instantiate<'a>( builder.memories.push(imports.memories[i]); } EntityIndex::Module(i) => { - builder.modules.push(imports.modules[i]); + builder.modules.push(imports.modules[i].clone()); } EntityIndex::Instance(i) => { - builder.instances.push(imports.instances[i].clone()); + builder + .instances + .push(unsafe { imports.instances[i].clone() }); } } Ok(()) }, )?; - imports.instances.push(handle); + imports.instances.push(unsafe { (*handle).clone() }); } } } @@ -146,16 +160,16 @@ fn instantiate<'a>( // 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`. - store.register_module(compiled_module, types); + store.register_module(module); let config = store.engine().config(); let instance = unsafe { let instance = compiled_module.instantiate( imports, - &store.lookup_shared_signature(types), + &store.lookup_shared_signature(module.types()), config.memory_creator.as_ref().map(|a| a as _), store.interrupts(), - Box::new(()), + Box::new(module.types().clone()), store.externref_activations_table() as *const VMExternRefActivationsTable as *mut _, store.stack_map_registry() as *const StackMapRegistry as *mut _, )?; @@ -230,7 +244,6 @@ fn instantiate<'a>( #[derive(Clone)] pub struct Instance { pub(crate) handle: StoreInstanceHandle, - module: Module, } impl Instance { @@ -294,16 +307,7 @@ impl Instance { // Perform some pre-flight checks before we get into the meat of // instantiation. - let expected = module - .compiled_module() - .module() - .initializers - .iter() - .filter(|e| match e { - Initializer::Import { .. } => true, - _ => false, - }) - .count(); + let expected = module.compiled_module().module().imports().count(); if expected != imports.len() { bail!("expected {} imports, found {}", expected, imports.len()); } @@ -314,22 +318,34 @@ impl Instance { } let mut imports = imports.iter(); - let handle = instantiate( - store, - module.compiled_module(), - module.all_compiled_modules(), - module.types(), - &PrimaryMap::new(), - &mut |idx, builder| { - let import = imports.next().expect("already checked the length"); - builder.define_extern(idx, import) - }, - )?; + let handle = instantiate(store, module, &PrimaryMap::new(), &mut |idx, builder| { + let import = imports.next().expect("already checked the length"); + builder.define_extern(idx, import) + })?; - Ok(Instance { - handle, - module: module.clone(), - }) + Ok(Instance { handle }) + } + + pub(crate) fn from_wasmtime(handle: StoreInstanceHandle) -> Instance { + Instance { handle } + } + + /// 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)), + ); + } + return ty; } /// Returns the associated [`Store`] that this `Instance` is compiled into. @@ -400,24 +416,20 @@ struct ImportsBuilder<'a> { tables: PrimaryMap<TableIndex, VMTableImport>, memories: PrimaryMap<MemoryIndex, VMMemoryImport>, globals: PrimaryMap<GlobalIndex, VMGlobalImport>, - instances: PrimaryMap<InstanceIndex, StoreInstanceHandle>, - modules: PrimaryMap<ModuleIndex, &'a CompiledModule>, + instances: PrimaryMap<InstanceIndex, InstanceHandle>, + modules: PrimaryMap<ModuleIndex, Module>, module: &'a wasmtime_environ::Module, - store: &'a Store, - types: &'a TypeTables, + matcher: matching::MatchCx<'a>, } impl<'a> ImportsBuilder<'a> { - fn new( - module: &'a wasmtime_environ::Module, - types: &'a TypeTables, - store: &'a Store, - ) -> ImportsBuilder<'a> { + fn new(store: &'a Store, module: &'a Module) -> ImportsBuilder<'a> { + let types = module.types(); + let module = module.compiled_module().module(); ImportsBuilder { module, - store, - types, + matcher: matching::MatchCx { store, types }, functions: PrimaryMap::with_capacity(module.num_imported_funcs), tables: PrimaryMap::with_capacity(module.num_imported_tables), memories: PrimaryMap::with_capacity(module.num_imported_memories), @@ -428,59 +440,38 @@ impl<'a> ImportsBuilder<'a> { } fn define_extern(&mut self, expected: &EntityIndex, actual: &Extern) -> Result<()> { - match *expected { - EntityIndex::Table(i) => { - self.tables.push(match actual { - Extern::Table(e) if e.matches_expected(&self.module.table_plans[i]) => { - e.vmimport() - } - Extern::Table(_) => bail!("table types incompatible"), - _ => bail!("expected table, but found {}", actual.desc()), - }); - } - EntityIndex::Memory(i) => { - self.memories.push(match actual { - Extern::Memory(e) if e.matches_expected(&self.module.memory_plans[i]) => { - e.vmimport() - } - Extern::Memory(_) => bail!("memory types incompatible"), - _ => bail!("expected memory, but found {}", actual.desc()), - }); - } - EntityIndex::Global(i) => { - self.globals.push(match actual { - Extern::Global(e) if e.matches_expected(&self.module.globals[i]) => { - e.vmimport() - } - Extern::Global(_) => bail!("global types incompatible"), - _ => bail!("expected global, but found {}", actual.desc()), - }); - } - EntityIndex::Function(i) => { - let func = match actual { - Extern::Func(e) => e, - _ => bail!("expected function, but found {}", actual.desc()), - }; - // Look up the `i`th function's type from the module in our - // signature registry. If it's not present then we have no - // functions registered with that type, so `func` is guaranteed - // to not match. - let ty = self - .store - .signatures() - .borrow() - .lookup(&self.types.wasm_signatures[self.module.functions[i]]) - .ok_or_else(|| anyhow::format_err!("function types incompatible"))?; - if !func.matches_expected(ty) { - bail!("function types incompatible"); - } - self.functions.push(func.vmimport()); - } - - // FIXME(#2094) - EntityIndex::Module(_i) => unimplemented!(), - EntityIndex::Instance(_i) => unimplemented!(), + 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.push_extern(actual); Ok(()) } @@ -498,15 +489,27 @@ impl<'a> ImportsBuilder<'a> { Extern::Memory(i) => { self.memories.push(i.vmimport()); } + Extern::Instance(i) => { + debug_assert!(Store::same(i.store(), self.matcher.store)); + self.instances.push(unsafe { (*i.handle).clone() }); + } + Extern::Module(m) => { + self.modules.push(m.clone()); + } } } - fn imports(&self) -> Imports<'_> { + fn imports(&mut self) -> Imports<'_> { Imports { tables: self.tables.values().as_slice(), 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(), } } } diff --git a/crates/wasmtime/src/linker.rs b/crates/wasmtime/src/linker.rs index e04052789898..763d0fa3cb3d 100644 --- a/crates/wasmtime/src/linker.rs +++ b/crates/wasmtime/src/linker.rs @@ -58,6 +58,8 @@ enum ImportKind { Global(GlobalType), Memory, Table, + Module, + Instance, } impl Linker { @@ -516,10 +518,8 @@ impl Linker { ExternType::Global(f) => ImportKind::Global(f), ExternType::Memory(_) => ImportKind::Memory, ExternType::Table(_) => ImportKind::Table, - - // FIXME(#2094) - ExternType::Module(_) => unimplemented!(), - ExternType::Instance(_) => unimplemented!(), + ExternType::Module(_) => ImportKind::Module, + ExternType::Instance(_) => ImportKind::Instance, } } diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index 37f068af6c14..b3d19f5ff496 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -1,5 +1,5 @@ -use crate::types::{EntityType, ExportType, ExternType, ImportType}; -use crate::Engine; +use crate::types::{ExportType, ExternType, ImportType}; +use crate::{Engine, ModuleType}; use anyhow::{bail, Context, Result}; use bincode::Options; use std::hash::Hash; @@ -86,7 +86,7 @@ pub struct Module { } pub(crate) struct ModuleData { - pub(crate) types: TypeTables, + pub(crate) types: Arc<TypeTables>, pub(crate) modules: Vec<CompiledModule>, } @@ -258,6 +258,7 @@ impl Module { &*engine.config().profiler, )?; + let types = Arc::new(types); Ok(Module { engine: engine.clone(), index: 0, @@ -291,6 +292,23 @@ 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)), + ); + } + return sig; + } + /// Serialize compilation artifacts to the buffer. See also `deseriaize`. pub fn serialize(&self) -> Result<Vec<u8>> { let artifacts = ( @@ -300,7 +318,7 @@ impl Module { .iter() .map(|i| i.compilation_artifacts()) .collect::<Vec<_>>(), - &self.data.types, + &*self.data.types, self.index, ); @@ -333,6 +351,7 @@ impl Module { &*engine.config().profiler, )?; + let types = Arc::new(types); Ok(Module { engine: engine.clone(), index, @@ -344,11 +363,16 @@ impl Module { &self.data.modules[self.index] } - pub(crate) fn all_compiled_modules(&self) -> &[CompiledModule] { - &self.data.modules + pub(crate) fn submodule(&self, index: usize) -> Module { + assert!(index < self.data.modules.len()); + Module { + engine: self.engine.clone(), + data: self.data.clone(), + index, + } } - pub(crate) fn types(&self) -> &TypeTables { + pub(crate) fn types(&self) -> &Arc<TypeTables> { &self.data.types } @@ -433,20 +457,10 @@ impl Module { &'module self, ) -> impl ExactSizeIterator<Item = ImportType<'module>> + 'module { let module = self.compiled_module().module(); + let types = self.types(); module - .initializers - .iter() - .filter_map(move |initializer| match initializer { - wasmtime_environ::Initializer::Import { - module, - field, - index, - } => { - let ty = EntityType::new(index, self); - Some(ImportType::new(module, field.as_deref(), ty)) - } - _ => None, - }) + .imports() + .map(move |(module, field, ty)| ImportType::new(module, field, ty, types)) .collect::<Vec<_>>() .into_iter() } @@ -509,9 +523,9 @@ impl Module { &'module self, ) -> impl ExactSizeIterator<Item = ExportType<'module>> + 'module { let module = self.compiled_module().module(); + let types = self.types(); module.exports.iter().map(move |(name, entity_index)| { - let ty = EntityType::new(entity_index, self); - ExportType::new(name, ty) + ExportType::new(name, module.type_of(*entity_index), types) }) } @@ -561,7 +575,10 @@ impl Module { pub fn get_export<'module>(&'module self, name: &'module str) -> Option<ExternType> { let module = self.compiled_module().module(); let entity_index = module.exports.get(name)?; - Some(EntityType::new(entity_index, self).extern_type()) + Some(ExternType::from_wasmtime( + self.types(), + &module.type_of(*entity_index), + )) } /// Returns the [`Engine`] that this [`Module`] was compiled by. diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index 9761b40b9190..6b314f6ac16b 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -1,7 +1,7 @@ use crate::frame_info::StoreFrameInfo; use crate::sig_registry::SignatureRegistry; use crate::trampoline::StoreInstanceHandle; -use crate::Engine; +use crate::{Engine, Module}; use anyhow::{bail, Result}; use std::any::Any; use std::cell::RefCell; @@ -147,7 +147,7 @@ impl Store { } } - pub(crate) fn register_module(&self, module: &CompiledModule, types: &TypeTables) { + pub(crate) fn register_module(&self, module: &Module) { // All modules register their JIT code in a store for two reasons // currently: // @@ -158,18 +158,18 @@ impl Store { // * Second when generating a backtrace we'll use this mapping to // only generate wasm frames for instruction pointers that fall // within jit code. - self.register_jit_code(module); + self.register_jit_code(module.compiled_module()); // We need to know about all the stack maps of all instantiated modules // so when performing a GC we know about all wasm frames that we find // on the stack. - self.register_stack_maps(module); + self.register_stack_maps(module.compiled_module()); // Signatures are loaded into our `SignatureRegistry` here // once-per-module (and once-per-signature). This allows us to create // a `Func` wrapper for any function in the module, which requires that // we know about the signature and trampoline for all instances. - self.register_signatures(module, types); + self.register_signatures(module); // And finally with a module being instantiated into this `Store` we // need to preserve its jit-code. References to this module's code and @@ -178,7 +178,7 @@ impl Store { self.inner .modules .borrow_mut() - .insert(ArcModuleCode(module.code().clone())); + .insert(ArcModuleCode(module.compiled_module().code().clone())); } fn register_jit_code(&self, module: &CompiledModule) { @@ -205,10 +205,10 @@ impl Store { })); } - fn register_signatures(&self, module: &CompiledModule, types: &TypeTables) { - let trampolines = module.trampolines(); + fn register_signatures(&self, module: &Module) { + let trampolines = module.compiled_module().trampolines(); let mut signatures = self.signatures().borrow_mut(); - for (index, wasm) in types.wasm_signatures.iter() { + for (index, wasm) in module.types().wasm_signatures.iter() { signatures.register(wasm, trampolines[index]); } } diff --git a/crates/wasmtime/src/types.rs b/crates/wasmtime/src/types.rs index 9e992aa8e89c..3a7076a764aa 100644 --- a/crates/wasmtime/src/types.rs +++ b/crates/wasmtime/src/types.rs @@ -1,7 +1,9 @@ -use crate::Module; use std::fmt; -use wasmtime_environ::wasm::WasmFuncType; +use wasmtime_environ::wasm::{EntityType, WasmFuncType}; use wasmtime_environ::{ir, wasm}; +use wasmtime_jit::TypeTables; + +pub(crate) mod matching; // Type Representations @@ -196,23 +198,25 @@ impl ExternType { (Instance(InstanceType) instance unwrap_instance) } - fn from_wasmtime(module: &Module, ty: &wasmtime_environ::wasm::EntityType) -> ExternType { - use wasmtime_environ::wasm::EntityType; + pub(crate) fn from_wasmtime( + types: &TypeTables, + ty: &wasmtime_environ::wasm::EntityType, + ) -> ExternType { match ty { EntityType::Function(idx) => { - let sig = &module.types().wasm_signatures[*idx]; + let sig = &types.wasm_signatures[*idx]; FuncType::from_wasm_func_type(sig).into() } 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 = &module.types().module_signatures[*ty]; - ModuleType::from_wasmtime(module, ty).into() + let ty = &types.module_signatures[*ty]; + ModuleType::from_wasmtime(types, ty).into() } EntityType::Instance(ty) => { - let ty = &module.types().instance_signatures[*ty]; - InstanceType::from_wasmtime(module, ty).into() + let ty = &types.instance_signatures[*ty]; + InstanceType::from_wasmtime(types, ty).into() } EntityType::Event(_) => unimplemented!("wasm event support"), } @@ -490,14 +494,14 @@ impl ModuleType { } pub(crate) fn from_wasmtime( - module: &Module, + types: &TypeTables, ty: &wasmtime_environ::ModuleSignature, ) -> ModuleType { - let exports = &module.types().instance_signatures[ty.exports].exports; + let exports = &types.instance_signatures[ty.exports].exports; ModuleType { exports: exports .iter() - .map(|(name, ty)| (name.to_string(), ExternType::from_wasmtime(module, ty))) + .map(|(name, ty)| (name.to_string(), ExternType::from_wasmtime(types, ty))) .collect(), imports: ty .imports @@ -506,7 +510,7 @@ impl ModuleType { ( m.to_string(), name.as_ref().map(|n| n.to_string()), - ExternType::from_wasmtime(module, ty), + ExternType::from_wasmtime(types, ty), ) }) .collect(), @@ -548,83 +552,19 @@ impl InstanceType { } pub(crate) fn from_wasmtime( - module: &Module, + types: &TypeTables, ty: &wasmtime_environ::InstanceSignature, ) -> InstanceType { InstanceType { exports: ty .exports .iter() - .map(|(name, ty)| (name.to_string(), ExternType::from_wasmtime(module, ty))) + .map(|(name, ty)| (name.to_string(), ExternType::from_wasmtime(types, ty))) .collect(), } } } -// Entity Types - -#[derive(Clone)] -pub(crate) enum EntityType<'module> { - Function(&'module wasm::WasmFuncType), - Table(&'module wasm::Table), - Memory(&'module wasm::Memory), - Global(&'module wasm::Global), - Module { - ty: &'module wasmtime_environ::ModuleSignature, - module: &'module Module, - }, - Instance { - ty: &'module wasmtime_environ::InstanceSignature, - module: &'module Module, - }, -} - -impl<'module> EntityType<'module> { - /// Translate from a `EntityIndex` into an `ExternType`. - pub(crate) fn new( - entity_index: &wasm::EntityIndex, - module: &'module Module, - ) -> EntityType<'module> { - let env_module = module.compiled_module().module(); - match entity_index { - wasm::EntityIndex::Function(func_index) => { - let sig_index = env_module.functions[*func_index]; - let sig = &module.types().wasm_signatures[sig_index]; - EntityType::Function(sig) - } - wasm::EntityIndex::Table(table_index) => { - EntityType::Table(&env_module.table_plans[*table_index].table) - } - wasm::EntityIndex::Memory(memory_index) => { - EntityType::Memory(&env_module.memory_plans[*memory_index].memory) - } - wasm::EntityIndex::Global(global_index) => { - EntityType::Global(&env_module.globals[*global_index]) - } - wasm::EntityIndex::Module(idx) => { - let ty = &module.types().module_signatures[env_module.modules[*idx]]; - EntityType::Module { ty, module } - } - wasm::EntityIndex::Instance(idx) => { - let ty = &module.types().instance_signatures[env_module.instances[*idx]]; - EntityType::Instance { ty, module } - } - } - } - - /// Convert this `EntityType` to an `ExternType`. - pub(crate) fn extern_type(&self) -> ExternType { - match self { - EntityType::Function(sig) => FuncType::from_wasm_func_type(sig).into(), - EntityType::Table(table) => TableType::from_wasmtime_table(table).into(), - EntityType::Memory(memory) => MemoryType::from_wasmtime_memory(memory).into(), - EntityType::Global(global) => GlobalType::from_wasmtime_global(global).into(), - EntityType::Instance { module, ty } => InstanceType::from_wasmtime(module, ty).into(), - EntityType::Module { module, ty } => ModuleType::from_wasmtime(module, ty).into(), - } - } -} - // Import Types /// A descriptor for an imported value into a wasm module. @@ -647,7 +587,7 @@ pub struct ImportType<'module> { #[derive(Clone)] enum EntityOrExtern<'a> { - Entity(EntityType<'a>), + Entity(EntityType, &'a TypeTables), Extern(&'a ExternType), } @@ -657,12 +597,13 @@ impl<'module> ImportType<'module> { pub(crate) fn new( module: &'module str, name: Option<&'module str>, - ty: EntityType<'module>, + ty: EntityType, + types: &'module TypeTables, ) -> ImportType<'module> { ImportType { module, name, - ty: EntityOrExtern::Entity(ty), + ty: EntityOrExtern::Entity(ty, types), } } @@ -683,7 +624,7 @@ impl<'module> ImportType<'module> { /// Returns the expected type of this import. pub fn ty(&self) -> ExternType { match &self.ty { - EntityOrExtern::Entity(e) => e.extern_type(), + EntityOrExtern::Entity(e, types) => ExternType::from_wasmtime(types, e), EntityOrExtern::Extern(e) => (*e).clone(), } } @@ -719,10 +660,14 @@ pub struct ExportType<'module> { impl<'module> ExportType<'module> { /// Creates a new export which is exported with the given `name` and has the /// given `ty`. - pub(crate) fn new(name: &'module str, ty: EntityType<'module>) -> ExportType<'module> { + pub(crate) fn new( + name: &'module str, + ty: EntityType, + types: &'module TypeTables, + ) -> ExportType<'module> { ExportType { name, - ty: EntityOrExtern::Entity(ty), + ty: EntityOrExtern::Entity(ty, types), } } @@ -734,7 +679,7 @@ impl<'module> ExportType<'module> { /// Returns the type of this export. pub fn ty(&self) -> ExternType { match &self.ty { - EntityOrExtern::Entity(e) => e.extern_type(), + EntityOrExtern::Entity(e, types) => ExternType::from_wasmtime(types, e), EntityOrExtern::Extern(e) => (*e).clone(), } } diff --git a/crates/wasmtime/src/types/matching.rs b/crates/wasmtime/src/types/matching.rs new file mode 100644 index 000000000000..c9ef03c26c12 --- /dev/null +++ b/crates/wasmtime/src/types/matching.rs @@ -0,0 +1,195 @@ +use crate::Store; +use std::sync::Arc; +use wasmtime_environ::wasm::{ + EntityType, Global, InstanceTypeIndex, Memory, ModuleTypeIndex, SignatureIndex, Table, +}; +use wasmtime_jit::TypeTables; + +pub struct MatchCx<'a> { + pub types: &'a TypeTables, + pub store: &'a Store, +} + +impl MatchCx<'_> { + pub fn global(&self, expected: &Global, actual: &crate::Global) -> bool { + self.global_ty(expected, actual.wasmtime_ty()) + } + + fn global_ty(&self, expected: &Global, actual: &Global) -> bool { + expected.ty == actual.ty + && expected.wasm_ty == actual.wasm_ty + && expected.mutability == actual.mutability + } + + pub fn table(&self, expected: &Table, actual: &crate::Table) -> bool { + self.table_ty(expected, actual.wasmtime_ty()) + } + + fn table_ty(&self, expected: &Table, actual: &Table) -> bool { + expected.wasm_ty == actual.wasm_ty + && expected.ty == actual.ty + && expected.minimum <= actual.minimum + && match expected.maximum { + Some(expected) => match actual.maximum { + Some(actual) => expected >= actual, + None => false, + }, + None => true, + } + } + + pub fn memory(&self, expected: &Memory, actual: &crate::Memory) -> bool { + self.memory_ty(expected, actual.wasmtime_ty()) + } + + fn memory_ty(&self, expected: &Memory, actual: &Memory) -> bool { + expected.shared == actual.shared + && expected.minimum <= actual.minimum + && match expected.maximum { + Some(expected) => match actual.maximum { + Some(actual) => expected >= actual, + None => false, + }, + None => true, + } + } + + pub fn func(&self, expected: SignatureIndex, actual: &crate::Func) -> bool { + match self + .store + .signatures() + .borrow() + .lookup(&self.types.wasm_signatures[expected]) + { + Some(idx) => actual.sig_index() == idx, + // If our expected signature isn't registered, then there's no way + // that `actual` can match it. + None => false, + } + } + + 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)), + ) + } + + /// Validates that the type signature of `actual` matches the `expected` + /// module type signature. + pub fn module(&self, expected: ModuleTypeIndex, actual: &crate::Module) -> bool { + 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)) + }) + } + + /// Validaates 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_types: &TypeTables, + mut actual_imports: impl Iterator<Item = (&'a str, Option<&'a str>, EntityType)>, + ) -> bool { + 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, + }; + if !self.extern_ty_matches(expected, &ty, actual_types) { + return false; + } + } + actual_imports.next().is_none() + } + + /// Validates that all exports in `expected` are defined by `lookup` within + /// `actual_types`. + fn exports_match( + &self, + expected: InstanceTypeIndex, + actual_types: &TypeTables, + lookup: impl Fn(&str) -> Option<EntityType>, + ) -> bool { + // 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, + }, + ) + } + + /// Validates that the `expected` entity matches the `actual_ty` defined + /// within `actual_types`. + fn extern_ty_matches( + &self, + expected: &EntityType, + actual_ty: &EntityType, + actual_types: &TypeTables, + ) -> bool { + match expected { + EntityType::Global(expected) => match actual_ty { + EntityType::Global(actual) => self.global_ty(expected, actual), + _ => false, + }, + EntityType::Table(expected) => match actual_ty { + EntityType::Table(actual) => self.table_ty(expected, actual), + _ => false, + }, + EntityType::Memory(expected) => match actual_ty { + EntityType::Memory(actual) => self.memory_ty(expected, actual), + _ => false, + }, + EntityType::Function(expected) => match *actual_ty { + EntityType::Function(actual) => { + self.types.wasm_signatures[*expected] == actual_types.wasm_signatures[actual] + } + _ => false, + }, + 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() + }) + } + _ => false, + }, + 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_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_instance_sig.exports.get(name).cloned() + }) + } + _ => false, + }, + EntityType::Event(_) => unimplemented!(), + } + } +} diff --git a/tests/all/wast.rs b/tests/all/wast.rs index 9b069c2ebe0f..9227079a0f85 100644 --- a/tests/all/wast.rs +++ b/tests/all/wast.rs @@ -23,8 +23,8 @@ fn run_wast(wast: &str, strategy: Strategy) -> anyhow::Result<()> { let mut cfg = Config::new(); cfg.wasm_simd(simd) .wasm_bulk_memory(bulk_mem) - .wasm_reference_types(reftypes) - .wasm_multi_memory(multi_memory) + .wasm_reference_types(reftypes || module_linking) + .wasm_multi_memory(multi_memory || module_linking) .wasm_module_linking(module_linking) .strategy(strategy)? .cranelift_debug_verifier(true); diff --git a/tests/misc_testsuite/module-linking/alias.wast b/tests/misc_testsuite/module-linking/alias.wast index 73324b251b9f..0c69b4084c7c 100644 --- a/tests/misc_testsuite/module-linking/alias.wast +++ b/tests/misc_testsuite/module-linking/alias.wast @@ -54,7 +54,38 @@ ) (assert_return (invoke "get") (i32.const 4)) -;; TODO instances/modules -- needs import/export of modules/instances to work +;; modules +(module + (module $m + (module $sub (export "module") + (func $f (export "") (result i32) + i32.const 5)) + ) + (instance $a (instantiate $m)) + (instance $b (instantiate $a.$sub)) + (alias $b.$f (instance $b) (func 0)) + + (func (export "get") (result i32) + call $b.$f) +) +(assert_return (invoke "get") (i32.const 5)) + +;; instances +(module + (module $m + (module $sub + (func $f (export "") (result i32) + i32.const 6)) + (instance $i (export "") (instantiate $sub)) + ) + (instance $a (instantiate $m)) + (alias $a.$i (instance $a) (instance 0)) + (alias $a.$i.$f (instance $a.$i) (func 0)) + + (func (export "get") (result i32) + call $a.$i.$f) +) +(assert_return (invoke "get") (i32.const 6)) ;; alias parent -- type (module diff --git a/tests/misc_testsuite/module-linking/import-subtyping.wast b/tests/misc_testsuite/module-linking/import-subtyping.wast new file mode 100644 index 000000000000..4ac16580706c --- /dev/null +++ b/tests/misc_testsuite/module-linking/import-subtyping.wast @@ -0,0 +1,348 @@ +;; subsets of imports +(module $a + (module (export "m") + (func (export "")) + (func (export "a")) + (global (export "b") i32 (i32.const 0)) + ) +) + +(module + (import "a" "m" (module)) + (import "a" "m" (module (export "" (func)))) + (import "a" "m" (module (export "a" (func)))) + (import "a" "m" (module (export "b" (global i32)))) + (import "a" "m" (module + (export "" (func)) + (export "a" (func)) + )) + (import "a" "m" (module + (export "a" (func)) + (export "" (func)) + )) + (import "a" "m" (module + (export "a" (func)) + (export "" (func)) + (export "b" (global i32)) + )) + (import "a" "m" (module + (export "b" (global i32)) + (export "a" (func)) + (export "" (func)) + )) +) + +;; functions +(module $a + (module (export "m") + (func (export "")))) + +(module + (import "a" "m" (module)) + (import "a" "m" (module (export "" (func)))) +) +(assert_unlinkable + (module (import "a" "m" (module (export "" (func (param i32)))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (func (result i32)))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (global i32))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (table 1 funcref))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (memory 1))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (module))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (instance))))) + "module types incompatible") + +(module $a + (module (export "m") + (global (export "") i32 (i32.const 0)))) + +;; globals +(module + (import "a" "m" (module)) + (import "a" "m" (module (export "" (global i32)))) +) +(assert_unlinkable + (module + (import "a" "m" (module (export "" (global (mut i32))))) + ) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (global f32))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (func))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (table 1 funcref))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (memory 1))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (module))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (instance))))) + "module types incompatible") + +;; tables +(module $a + (module (export "m") + (table (export "") 1 funcref) + (table (export "max") 1 10 funcref) + ) +) +(module + (import "a" "m" (module)) + (import "a" "m" (module (export "" (table 1 funcref)))) + (import "a" "m" (module (export "" (table 0 funcref)))) + (import "a" "m" (module (export "max" (table 1 10 funcref)))) + (import "a" "m" (module (export "max" (table 0 10 funcref)))) + (import "a" "m" (module (export "max" (table 0 11 funcref)))) + (import "a" "m" (module (export "max" (table 0 funcref)))) +) +(assert_unlinkable + (module (import "a" "m" (module (export "" (global f32))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (func))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (table 2 funcref))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (table 1 10 funcref))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "max" (table 2 10 funcref))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "max" (table 1 9 funcref))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (memory 1))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (module))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (instance))))) + "module types incompatible") + +;; memories +(module $a + (module (export "m") + (memory (export "") 1) + (memory (export "max") 1 10) + ) +) +(module + (import "a" "m" (module)) + (import "a" "m" (module (export "" (memory 1)))) + (import "a" "m" (module (export "" (memory 0)))) + (import "a" "m" (module (export "max" (memory 1 10)))) + (import "a" "m" (module (export "max" (memory 0 10)))) + (import "a" "m" (module (export "max" (memory 0 11)))) + (import "a" "m" (module (export "max" (memory 0)))) +) +(assert_unlinkable + (module (import "a" "m" (module (export "" (global f32))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (func))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (table 1 funcref))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (memory 2))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (memory 1 10))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "max" (memory 2 10))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "max" (memory 2))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (module))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (instance))))) + "module types incompatible") + +;; modules +(module $a + (module (export "m") + ;; export nothing + (module (export "a")) + ;; export one thing + (module (export "b") + (func (export "")) + ) + ;; export a mixture + (module (export "c") + (func (export "a")) + (func (export "b") (result i32) + i32.const 0) + (global (export "c") i32 (i32.const 0)) + ) + ;; import one thing + (module (export "d") + (import "" (func)) + ) + ;; import a mixture + (module (export "e") + (import "" (func)) + (import "" (func)) + (import "" (global i32)) + ) + ) +) +(module + (import "a" "m" (module)) + (import "a" "m" (module (export "a" (module)))) + (import "a" "m" (module (export "b" (module)))) + (import "a" "m" (module (export "b" (module (export "" (func)))))) + (import "a" "m" (module (export "c" (module)))) + (import "a" "m" (module (export "c" (module + (export "a" (func)) + )))) + (import "a" "m" (module (export "c" (module + (export "a" (func)) + (export "b" (func (result i32))) + )))) + (import "a" "m" (module (export "c" (module + (export "c" (global i32)) + )))) + (import "a" "m" (module (export "c" (module + (export "c" (global i32)) + (export "a" (func)) + )))) + + ;; for now import strings aren't matched at all, imports must simply pairwise + ;; line up + (import "a" "m" (module (export "d" (module (import "" (func)))))) + (import "a" "m" (module (export "d" (module (import "x" (func)))))) + (import "a" "m" (module (export "d" (module (import "x" "y" (func)))))) + + (import "a" "m" (module (export "e" (module + (import "x" "y" (func)) + (import "a" (func)) + (import "z" (global i32)) + )))) +) +(assert_unlinkable + (module (import "a" "m" (module (export "" (module (export "a" (func))))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "d" (module))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "d" (module (import "" (module))))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (global f32))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (func))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (table 1 funcref))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (memory 2))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (module (export "foo" (func))))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (instance))))) + "module types incompatible") + +;; instances +(module $a + ;; export nothing + (module $m1) + (instance (export "a") (instantiate $m1)) + ;; export one thing + (module $m2 + (func (export "")) + ) + (instance (export "b") (instantiate $m2)) + ;; export a mixture + (module $m3 + (func (export "a")) + (func (export "b") (result i32) + i32.const 0) + (global (export "c") i32 (i32.const 0)) + ) + (instance (export "c") (instantiate $m3)) + + (module (export "m") + ;; export one thing + (module $m2 + (func (export "")) + ) + (instance (export "i") (instantiate $m2)) + ) + +) +(module + (import "a" "a" (instance)) + (import "a" "b" (instance)) + (import "a" "b" (instance (export "" (func)))) + (import "a" "c" (instance)) + (import "a" "c" (instance (export "a" (func)))) + (import "a" "c" (instance (export "b" (func (result i32))))) + (import "a" "c" (instance (export "c" (global i32)))) + (import "a" "c" (instance + (export "a" (func)) + (export "b" (func (result i32))) + (export "c" (global i32)) + )) + (import "a" "c" (instance + (export "c" (global i32)) + (export "a" (func)) + )) + + (import "a" "m" (module (export "i" (instance)))) + (import "a" "m" (module (export "i" (instance (export "" (func)))))) +) +(assert_unlinkable + (module (import "a" "a" (instance (export "" (global f32))))) + "instance types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "i" (instance (export "x" (func))))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (func))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (table 1 funcref))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (memory 2))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (memory 1 10))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "max" (memory 2 10))))) + "module types incompatible") +(assert_unlinkable + (module (import "a" "m" (module (export "" (module))))) + "module types incompatible") diff --git a/tests/misc_testsuite/module-linking/instantiate.wast b/tests/misc_testsuite/module-linking/instantiate.wast index a0e24c7a7bac..c04929257f56 100644 --- a/tests/misc_testsuite/module-linking/instantiate.wast +++ b/tests/misc_testsuite/module-linking/instantiate.wast @@ -117,6 +117,24 @@ ) (assert_return (invoke "get") (i32.const 5)) +;; imported modules again +(module + (module $m + (import "" (module $m (export "get" (func (result i32))))) + (instance $i (instantiate $m)) + (alias $f (instance $i) (func 0)) + (export "" (func $f)) + ) + (module $m2 + (func (export "get") (result i32) + i32.const 6)) + (instance $a (instantiate $m (module $m2))) + + (func (export "get") (result i32) + call $a.$f) +) +(assert_return (invoke "get") (i32.const 6)) + ;; all at once (module (import "a" "inc" (func $f)) @@ -195,3 +213,23 @@ (instance (instantiate 0 (func 0))) ) (assert_return (invoke $a "get") (i32.const 1)) + +;; module/instance top-level imports work +(module $b + (module (export "m")) + (instance (export "i") (instantiate 0)) +) +(module + (import "b" "m" (module)) + (import "b" "i" (instance)) +) +(assert_unlinkable + (module + (import "b" "m" (module (import "" (func)))) + ) + "module types incompatible") +(assert_unlinkable + (module + (import "b" "i" (instance (export "" (func)))) + ) + "instance types incompatible")