From ea0e33bb54babef91a9c4957211b5bf917bfcf75 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 24 Nov 2020 08:37:26 -0800 Subject: [PATCH] Implement the module linking alias section This commit is intended to do almost everything necessary for processing the alias section of module linking. Most of this is internal refactoring, the highlights being: * Type contents are now stored separately from a `wasmtime_env::Module`. Given that modules can freely alias types and have them used all over the place, it seemed best to have one canonical location to type storage which everywhere else points to (with indices). A new `TypeTables` structure is produced during compilation which is shared amongst all member modules in a wasm blob. * Instantiation is heavily refactored to account for module linking. The main gotcha here is that imports are now listed as "initializers". We have a sort of pseudo-bytecode-interpreter which interprets the initialization of a module. This is more complicated than just matching imports at this point because in the module linking proposal the module, alias, import, and instance sections may all be interleaved. This means that imports aren't guaranteed to show up at the beginning of the address space for modules/instances. Otherwise most of the changes here largely fell out from these two design points. Aliases are recorded as initializers in this scheme. Copying around type information and/or just knowing type information during compilation is also pretty easy since everything is just a pointer into a `TypeTables` and we don't have to actually copy any types themselves. Lots of various refactorings were necessary to accomodate these changes. Tests are hoped to cover a breadth of functionality here, but not necessarily a depth. There's still one more piece of the module linking proposal missing which is exporting instances/modules, which will come in a future PR. It's also worth nothing that there's one large TODO which isn't implemented in this change that I plan on opening an issue for. With module linking when a set of modules comes back from compilation each modules has all the trampolines for the entire set of modules. This is quite a lot of duplicate trampolines across module-linking modules. We'll want to refactor this at some point to instead have only one set of trampolines per set of module linking modules and have them shared from there. I figured it was best to separate out this change, however, since it's purely related to resource usage, and doesn't impact non-module-linking modules at all. cc #2094 --- cranelift/wasm/src/environ/mod.rs | 4 +- cranelift/wasm/src/environ/spec.rs | 80 +++- cranelift/wasm/src/lib.rs | 2 +- cranelift/wasm/src/module_translator.rs | 12 +- cranelift/wasm/src/sections_translator.rs | 105 ++++- cranelift/wasm/src/translation_utils.rs | 18 +- crates/cranelift/src/func_environ.rs | 3 +- crates/cranelift/src/lib.rs | 7 +- crates/environ/src/compilation.rs | 3 +- crates/environ/src/module.rs | 170 ++++---- crates/environ/src/module_environ.rs | 367 ++++++++++++++---- crates/environ/src/vmoffsets.rs | 6 +- crates/jit/src/compiler.rs | 16 +- crates/jit/src/instantiate.rs | 52 +-- crates/jit/src/lib.rs | 4 +- crates/jit/src/object.rs | 13 +- crates/lightbeam/wasmtime/src/lib.rs | 17 +- crates/obj/src/context.rs | 23 +- crates/runtime/src/instance.rs | 17 +- crates/wasmtime/src/externals.rs | 9 + crates/wasmtime/src/instance.rs | 366 ++++++++++------- crates/wasmtime/src/module.rs | 66 +++- crates/wasmtime/src/store.rs | 15 +- .../wasmtime/src/trampoline/create_handle.rs | 6 +- crates/wasmtime/src/trampoline/func.rs | 23 +- crates/wasmtime/src/trampoline/global.rs | 25 +- crates/wasmtime/src/trampoline/memory.rs | 2 +- crates/wasmtime/src/trampoline/table.rs | 2 +- crates/wasmtime/src/types.rs | 102 ++--- crates/wast/src/wast.rs | 26 +- src/obj.rs | 4 +- tests/all/module_linking.rs | 123 ++++++ .../misc_testsuite/module-linking/alias.wast | 114 ++++++ .../module-linking/instantiate.wast | 39 +- 34 files changed, 1321 insertions(+), 520 deletions(-) create mode 100644 tests/misc_testsuite/module-linking/alias.wast diff --git a/cranelift/wasm/src/environ/mod.rs b/cranelift/wasm/src/environ/mod.rs index bb4b7cc34ee0..cf4672beb3ce 100644 --- a/cranelift/wasm/src/environ/mod.rs +++ b/cranelift/wasm/src/environ/mod.rs @@ -6,6 +6,6 @@ mod spec; pub use crate::environ::dummy::DummyEnvironment; pub use crate::environ::spec::{ - FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode, TargetEnvironment, WasmError, - WasmFuncType, WasmResult, WasmType, + Alias, FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode, TargetEnvironment, + WasmError, WasmFuncType, WasmResult, WasmType, }; diff --git a/cranelift/wasm/src/environ/spec.rs b/cranelift/wasm/src/environ/spec.rs index 66d0971dce1d..a2ce3a17d440 100644 --- a/cranelift/wasm/src/environ/spec.rs +++ b/cranelift/wasm/src/environ/spec.rs @@ -9,7 +9,8 @@ use crate::state::FuncTranslationState; use crate::translation_utils::{ DataIndex, ElemIndex, EntityIndex, EntityType, Event, EventIndex, FuncIndex, Global, - GlobalIndex, Memory, MemoryIndex, ModuleIndex, Table, TableIndex, TypeIndex, + GlobalIndex, InstanceIndex, InstanceTypeIndex, Memory, MemoryIndex, ModuleIndex, + ModuleTypeIndex, SignatureIndex, Table, TableIndex, TypeIndex, }; use core::convert::From; use core::convert::TryFrom; @@ -202,6 +203,30 @@ pub enum ReturnMode { FallthroughReturn, } +/// An entry in the alias section of a wasm module (from the module linking +/// proposal) +pub enum Alias { + /// A parent's module is being aliased into our own index space. + /// + /// Note that the index here is in the parent's index space, not our own. + ParentModule(ModuleIndex), + + /// A parent's type is being aliased into our own index space + /// + /// Note that the index here is in the parent's index space, not our own. + ParentType(TypeIndex), + + /// A previously created instance is having one of its exports aliased into + /// our index space. + Child { + /// The index we're aliasing. + instance: InstanceIndex, + /// The nth export that we're inserting into our own index space + /// locally. + export: usize, + }, +} + /// Environment affecting the translation of a WebAssembly. pub trait TargetEnvironment { /// Get the information needed to produce Cranelift IR for the given target. @@ -664,6 +689,27 @@ pub trait ModuleEnvironment<'data>: TargetEnvironment { Err(WasmError::Unsupported("module linking".to_string())) } + /// Translates a type index to its signature index, only called for type + /// indices which point to functions. + fn type_to_signature(&self, index: TypeIndex) -> WasmResult { + drop(index); + Err(WasmError::Unsupported("module linking".to_string())) + } + + /// Translates a type index to its module type index, only called for type + /// indices which point to modules. + fn type_to_module_type(&self, index: TypeIndex) -> WasmResult { + drop(index); + Err(WasmError::Unsupported("module linking".to_string())) + } + + /// Translates a type index to its instance type index, only called for type + /// indices which point to instances. + fn type_to_instance_type(&self, index: TypeIndex) -> WasmResult { + drop(index); + Err(WasmError::Unsupported("module linking".to_string())) + } + /// Provides the number of imports up front. By default this does nothing, but /// implementations can use this to preallocate memory if desired. fn reserve_imports(&mut self, _num: u32) -> WasmResult<()> { @@ -825,6 +871,22 @@ pub trait ModuleEnvironment<'data>: TargetEnvironment { name: &'data str, ) -> WasmResult<()>; + /// Declares an instance export to the environment. + fn declare_instance_export( + &mut self, + index: InstanceIndex, + name: &'data str, + ) -> WasmResult<()> { + drop((index, name)); + Err(WasmError::Unsupported("module linking".to_string())) + } + + /// Declares an instance export to the environment. + fn declare_module_export(&mut self, index: ModuleIndex, name: &'data str) -> WasmResult<()> { + drop((index, name)); + Err(WasmError::Unsupported("module linking".to_string())) + } + /// Notifies the implementation that all exports have been declared. fn finish_exports(&mut self) -> WasmResult<()> { Ok(()) @@ -932,6 +994,12 @@ pub trait ModuleEnvironment<'data>: TargetEnvironment { drop(amount); } + /// Declares that a module will come later with the type signature provided. + fn declare_module(&mut self, ty: TypeIndex) -> WasmResult<()> { + drop(ty); + Err(WasmError::Unsupported("module linking".to_string())) + } + /// Called at the beginning of translating a module. /// /// The `index` argument is a monotonically increasing index which @@ -962,4 +1030,14 @@ pub trait ModuleEnvironment<'data>: TargetEnvironment { drop((module, args)); Err(WasmError::Unsupported("wasm instance".to_string())) } + + /// Declares a new alias being added to this module. + /// + /// The alias comes from the `instance` specified (or the parent if `None` + /// is supplied) and the index is either in the module's own index spaces + /// for the parent or an index into the exports for nested instances. + fn declare_alias(&mut self, alias: Alias) -> WasmResult<()> { + drop(alias); + Err(WasmError::Unsupported("wasm alias".to_string())) + } } diff --git a/cranelift/wasm/src/lib.rs b/cranelift/wasm/src/lib.rs index 3e6d4401a1f9..4e066803857b 100644 --- a/cranelift/wasm/src/lib.rs +++ b/cranelift/wasm/src/lib.rs @@ -57,7 +57,7 @@ mod state; mod translation_utils; pub use crate::environ::{ - DummyEnvironment, FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode, + Alias, DummyEnvironment, FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode, TargetEnvironment, WasmError, WasmFuncType, WasmResult, WasmType, }; pub use crate::func_translator::FuncTranslator; diff --git a/cranelift/wasm/src/module_translator.rs b/cranelift/wasm/src/module_translator.rs index 5fc60ccbdda5..c3e27dd82041 100644 --- a/cranelift/wasm/src/module_translator.rs +++ b/cranelift/wasm/src/module_translator.rs @@ -2,10 +2,10 @@ //! to deal with each part of it. use crate::environ::{ModuleEnvironment, WasmResult}; use crate::sections_translator::{ - parse_data_section, parse_element_section, parse_event_section, parse_export_section, - parse_function_section, parse_global_section, parse_import_section, parse_instance_section, - parse_memory_section, parse_name_section, parse_start_section, parse_table_section, - parse_type_section, + parse_alias_section, parse_data_section, parse_element_section, parse_event_section, + parse_export_section, parse_function_section, parse_global_section, parse_import_section, + parse_instance_section, parse_memory_section, parse_module_section, parse_name_section, + parse_start_section, parse_table_section, parse_type_section, }; use crate::state::ModuleTranslationState; use cranelift_codegen::timing; @@ -113,7 +113,7 @@ pub fn translate_module<'data>( Payload::ModuleSection(s) => { validator.module_section(&s)?; - environ.reserve_modules(s.get_count()); + parse_module_section(s, environ)?; } Payload::InstanceSection(s) => { validator.instance_section(&s)?; @@ -121,7 +121,7 @@ pub fn translate_module<'data>( } Payload::AliasSection(s) => { validator.alias_section(&s)?; - unimplemented!("module linking not implemented yet") + parse_alias_section(s, environ)?; } Payload::ModuleCodeSectionStart { count, diff --git a/cranelift/wasm/src/sections_translator.rs b/cranelift/wasm/src/sections_translator.rs index e9fc525542a4..0e791c1a4301 100644 --- a/cranelift/wasm/src/sections_translator.rs +++ b/cranelift/wasm/src/sections_translator.rs @@ -7,7 +7,7 @@ //! The special case of the initialize expressions for table elements offsets or global variables //! is handled, according to the semantics of WebAssembly, to only specific expressions that are //! interpreted on the fly. -use crate::environ::{ModuleEnvironment, WasmError, WasmResult}; +use crate::environ::{Alias, ModuleEnvironment, WasmError, WasmResult}; use crate::state::ModuleTranslationState; use crate::translation_utils::{ tabletype_to_type, type_to_type, DataIndex, ElemIndex, EntityIndex, EntityType, Event, @@ -36,9 +36,15 @@ fn entity_type( environ: &mut dyn ModuleEnvironment<'_>, ) -> WasmResult { Ok(match ty { - ImportSectionEntryType::Function(sig) => EntityType::Function(TypeIndex::from_u32(sig)), - ImportSectionEntryType::Module(sig) => EntityType::Module(TypeIndex::from_u32(sig)), - ImportSectionEntryType::Instance(sig) => EntityType::Instance(TypeIndex::from_u32(sig)), + ImportSectionEntryType::Function(sig) => { + EntityType::Function(environ.type_to_signature(TypeIndex::from_u32(sig))?) + } + ImportSectionEntryType::Module(sig) => { + EntityType::Module(environ.type_to_module_type(TypeIndex::from_u32(sig))?) + } + ImportSectionEntryType::Instance(sig) => { + EntityType::Instance(environ.type_to_instance_type(TypeIndex::from_u32(sig))?) + } ImportSectionEntryType::Memory(ty) => EntityType::Memory(memory(ty)), ImportSectionEntryType::Event(evt) => EntityType::Event(event(evt)), ImportSectionEntryType::Global(ty) => { @@ -156,24 +162,40 @@ pub fn parse_import_section<'data>( for entry in imports { let import = entry?; - match entity_type(import.ty, environ)? { - EntityType::Function(idx) => { - environ.declare_func_import(idx, import.module, import.field)?; + match import.ty { + ImportSectionEntryType::Function(sig) => { + environ.declare_func_import( + TypeIndex::from_u32(sig), + import.module, + import.field, + )?; } - EntityType::Module(idx) => { - environ.declare_module_import(idx, import.module, import.field)?; + ImportSectionEntryType::Module(sig) => { + environ.declare_module_import( + TypeIndex::from_u32(sig), + import.module, + import.field, + )?; + } + ImportSectionEntryType::Instance(sig) => { + environ.declare_instance_import( + TypeIndex::from_u32(sig), + import.module, + import.field, + )?; } - EntityType::Instance(idx) => { - environ.declare_instance_import(idx, import.module, import.field)?; + ImportSectionEntryType::Memory(ty) => { + environ.declare_memory_import(memory(ty), import.module, import.field)?; } - EntityType::Memory(ty) => { - environ.declare_memory_import(ty, import.module, import.field)?; + ImportSectionEntryType::Event(e) => { + environ.declare_event_import(event(e), import.module, import.field)?; } - EntityType::Event(e) => environ.declare_event_import(e, import.module, import.field)?, - EntityType::Global(ty) => { + ImportSectionEntryType::Global(ty) => { + let ty = global(ty, environ, GlobalInit::Import)?; environ.declare_global_import(ty, import.module, import.field)?; } - EntityType::Table(ty) => { + ImportSectionEntryType::Table(ty) => { + let ty = table(ty, environ)?; environ.declare_table_import(ty, import.module, import.field)?; } } @@ -316,9 +338,15 @@ pub fn parse_export_section<'data>( ExternalKind::Global => { environ.declare_global_export(GlobalIndex::new(index), field)? } - ExternalKind::Type | ExternalKind::Module | ExternalKind::Instance => { - unimplemented!("module linking not implemented yet") + ExternalKind::Module => { + environ.declare_module_export(ModuleIndex::new(index), field)? } + ExternalKind::Instance => { + environ.declare_instance_export(InstanceIndex::new(index), field)? + } + + // this never gets past validation + ExternalKind::Type => unreachable!(), } } @@ -476,12 +504,25 @@ pub fn parse_name_section<'data>( Ok(()) } +/// Parses the Module section of the wasm module. +pub fn parse_module_section<'data>( + section: wasmparser::ModuleSectionReader<'data>, + environ: &mut dyn ModuleEnvironment<'data>, +) -> WasmResult<()> { + environ.reserve_modules(section.get_count()); + + for module_ty in section { + environ.declare_module(TypeIndex::from_u32(module_ty?))?; + } + Ok(()) +} + /// Parses the Instance section of the wasm module. pub fn parse_instance_section<'data>( section: wasmparser::InstanceSectionReader<'data>, environ: &mut dyn ModuleEnvironment<'data>, ) -> WasmResult<()> { - environ.reserve_types(section.get_count())?; + environ.reserve_instances(section.get_count()); for instance in section { let instance = instance?; @@ -509,3 +550,29 @@ pub fn parse_instance_section<'data>( } Ok(()) } + +/// Parses the Alias section of the wasm module. +pub fn parse_alias_section<'data>( + section: wasmparser::AliasSectionReader<'data>, + environ: &mut dyn ModuleEnvironment<'data>, +) -> WasmResult<()> { + for alias in section { + let alias = alias?; + let alias = match alias.instance { + wasmparser::AliasedInstance::Parent => { + match alias.kind { + ExternalKind::Module => Alias::ParentModule(ModuleIndex::from_u32(alias.index)), + ExternalKind::Type => Alias::ParentType(TypeIndex::from_u32(alias.index)), + // shouldn't get past validation + _ => unreachable!(), + } + } + wasmparser::AliasedInstance::Child(i) => Alias::Child { + instance: InstanceIndex::from_u32(i), + export: alias.index as usize, + }, + }; + environ.declare_alias(alias)?; + } + Ok(()) +} diff --git a/cranelift/wasm/src/translation_utils.rs b/cranelift/wasm/src/translation_utils.rs index 613e50ac7326..e16103323884 100644 --- a/cranelift/wasm/src/translation_utils.rs +++ b/cranelift/wasm/src/translation_utils.rs @@ -97,6 +97,18 @@ entity_impl!(InstanceIndex); pub struct EventIndex(u32); entity_impl!(EventIndex); +/// Specialized index for just module types. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct ModuleTypeIndex(u32); +entity_impl!(ModuleTypeIndex); + +/// Specialized index for just instance types. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct InstanceTypeIndex(u32); +entity_impl!(InstanceTypeIndex); + /// An index of an entity. #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] @@ -131,13 +143,13 @@ pub enum EntityType { Table(Table), /// A function type where the index points to the type section and records a /// function signature. - Function(TypeIndex), + Function(SignatureIndex), /// An instance where the index points to the type section and records a /// instance's exports. - Instance(TypeIndex), + Instance(InstanceTypeIndex), /// A module where the index points to the type section and records a /// module's imports and exports. - Module(TypeIndex), + Module(ModuleTypeIndex), } /// A WebAssembly global. diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index ad95c0395a3b..aa89f2cfc47a 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -1039,7 +1039,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m callee: ir::Value, call_args: &[ir::Value], ) -> WasmResult { - let sig_index = self.module.types[ty_index].unwrap_function(); let pointer_type = self.pointer_type(); let table_entry_addr = pos.ins().table_addr(pointer_type, table, callee, 0); @@ -1071,7 +1070,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let vmctx = self.vmctx(pos.func); let base = pos.ins().global_value(pointer_type, vmctx); let offset = - i32::try_from(self.offsets.vmctx_vmshared_signature_id(sig_index)).unwrap(); + i32::try_from(self.offsets.vmctx_vmshared_signature_id(ty_index)).unwrap(); // Load the caller ID. let mut mem_flags = ir::MemFlags::trusted(); diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs index a0e241572e46..428f3ad6a8ee 100644 --- a/crates/cranelift/src/lib.rs +++ b/crates/cranelift/src/lib.rs @@ -99,7 +99,7 @@ use std::sync::Mutex; use wasmtime_environ::{ CompileError, CompiledFunction, Compiler, FunctionAddressMap, FunctionBodyData, InstructionAddressMap, ModuleTranslation, Relocation, RelocationTarget, StackMapInformation, - TrapInformation, Tunables, + TrapInformation, Tunables, TypeTables, }; mod func_environ; @@ -348,13 +348,14 @@ impl Compiler for Cranelift { mut input: FunctionBodyData<'_>, isa: &dyn isa::TargetIsa, tunables: &Tunables, + types: &TypeTables, ) -> Result { let module = &translation.module; let func_index = module.func_index(func_index); let mut context = Context::new(); context.func.name = get_func_name(func_index); let sig_index = module.functions[func_index]; - context.func.signature = translation.native_signatures[sig_index].clone(); + context.func.signature = types.native_signatures[sig_index].clone(); if tunables.debug_info { context.func.collect_debug_info(); } @@ -362,7 +363,7 @@ impl Compiler for Cranelift { let mut func_env = FuncEnvironment::new( isa.frontend_config(), module, - &translation.native_signatures, + &types.native_signatures, tunables, ); diff --git a/crates/environ/src/compilation.rs b/crates/environ/src/compilation.rs index 5008273bc779..cd2f8b31a9c6 100644 --- a/crates/environ/src/compilation.rs +++ b/crates/environ/src/compilation.rs @@ -1,7 +1,7 @@ //! A `Compilation` contains the compiled function bodies for a WebAssembly //! module. -use crate::{FunctionAddressMap, FunctionBodyData, ModuleTranslation, Tunables}; +use crate::{FunctionAddressMap, FunctionBodyData, ModuleTranslation, Tunables, TypeTables}; use cranelift_codegen::{binemit, ir, isa, isa::unwind::UnwindInfo}; use cranelift_entity::PrimaryMap; use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmError}; @@ -104,5 +104,6 @@ pub trait Compiler: Send + Sync { data: FunctionBodyData<'_>, isa: &dyn isa::TargetIsa, tunables: &Tunables, + types: &TypeTables, ) -> Result; } diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index 8cd04647f679..d78efadf0f33 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -2,20 +2,14 @@ use crate::tunables::Tunables; use crate::WASM_MAX_PAGES; +use cranelift_codegen::ir; use cranelift_entity::{EntityRef, PrimaryMap}; -use cranelift_wasm::{ - DataIndex, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, - ElemIndex, EntityIndex, EntityType, FuncIndex, Global, GlobalIndex, InstanceIndex, Memory, - MemoryIndex, ModuleIndex, SignatureIndex, Table, TableIndex, TypeIndex, WasmFuncType, -}; +use cranelift_wasm::*; use indexmap::IndexMap; use more_asserts::assert_ge; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::sync::{ - atomic::{AtomicUsize, Ordering::SeqCst}, - Arc, -}; +use std::sync::Arc; /// A WebAssembly table initializer. #[derive(Clone, Debug, Hash, Serialize, Deserialize)] @@ -121,23 +115,16 @@ impl TablePlan { } } -/// Different types that can appear in a module -#[derive(Debug, Clone, Serialize, Deserialize)] +/// Different types that can appear in a module. +/// +/// Note that each of these variants are intended to index further into a +/// separate table. +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[allow(missing_docs)] pub enum ModuleType { - /// A function type, indexed further into the `signatures` table. Function(SignatureIndex), - /// A module type - Module { - /// The module's imports - imports: Vec<(String, Option, EntityType)>, - /// The module's exports - exports: Vec<(String, EntityType)>, - }, - /// An instance type - Instance { - /// the instance's exports - exports: Vec<(String, EntityType)>, - }, + Module(ModuleTypeIndex), + Instance(InstanceTypeIndex), } impl ModuleType { @@ -153,17 +140,19 @@ impl ModuleType { /// A translated WebAssembly module, excluding the function bodies and /// memory initializers. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct Module { - /// A unique identifier (within this process) for this module. - #[serde(skip_serializing, skip_deserializing, default = "Module::next_id")] - pub id: usize, + /// The parent index of this module, used for the module linking proposal. + /// + /// This index is into the list of modules returned from compilation of a + /// single wasm file with nested modules. + pub parent: Option, /// The name of this wasm module, often found in the wasm file. pub name: Option, /// All import records, in the order they are declared in the module. - pub imports: Vec<(String, Option, EntityIndex)>, + pub initializers: Vec, /// Exported entities. pub exports: IndexMap, @@ -184,22 +173,19 @@ pub struct Module { /// WebAssembly table initializers. pub func_names: HashMap, - /// Unprocessed signatures exactly as provided by `declare_signature()`. - pub signatures: PrimaryMap, - /// Types declared in the wasm module. pub types: PrimaryMap, - /// Number of imported functions in the module. + /// Number of imported or aliased functions in the module. pub num_imported_funcs: usize, - /// Number of imported tables in the module. + /// Number of imported or aliased tables in the module. pub num_imported_tables: usize, - /// Number of imported memories in the module. + /// Number of imported or aliased memories in the module. pub num_imported_memories: usize, - /// Number of imported globals in the module. + /// Number of imported or aliased globals in the module. pub num_imported_globals: usize, /// Types of functions, imported and local. @@ -214,54 +200,58 @@ pub struct Module { /// WebAssembly global variables. pub globals: PrimaryMap, - /// WebAssembly instances. - pub instances: PrimaryMap, + /// The type of each wasm instance this module defines. + pub instances: PrimaryMap, - /// WebAssembly modules. - pub modules: PrimaryMap, + /// The type of each nested wasm module this module contains. + pub modules: PrimaryMap, } -/// Different forms an instance can take in a wasm module +/// Initialization routines for creating an instance, encompassing imports, +/// modules, instances, aliases, etc. #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum Instance { - /// This is an imported instance with the specified type - Import(TypeIndex), - /// This is a locally created instance which instantiates the specified - /// module with the given list of entities. +pub enum Initializer { + /// An imported item is required to be provided. + Import { + /// Module name of this import + module: String, + /// Optional field name of this import + field: Option, + /// Where this import will be placed, which also has type information + /// about the import. + index: EntityIndex, + }, + + /// A module from the parent's declared modules is inserted into our own + /// index space. + AliasParentModule(ModuleIndex), + + /// A module from the parent's declared modules is inserted into our own + /// index space. + #[allow(missing_docs)] + AliasInstanceExport { + instance: InstanceIndex, + export: usize, + }, + + /// A module is being instantiated with previously configured intializers + /// as arguments. Instantiate { /// The module that this instance is instantiating. module: ModuleIndex, /// The arguments provided to instantiation. args: Vec, }, + + /// A module is defined into the module index space, and which module is + /// being defined is specified by the index payload. + DefineModule(usize), } impl Module { /// Allocates the module data structures. pub fn new() -> Self { - Self { - id: Self::next_id(), - name: None, - imports: Vec::new(), - exports: IndexMap::new(), - start_func: None, - table_elements: Vec::new(), - passive_elements: HashMap::new(), - passive_data: HashMap::new(), - func_names: HashMap::new(), - num_imported_funcs: 0, - num_imported_tables: 0, - num_imported_memories: 0, - num_imported_globals: 0, - signatures: PrimaryMap::new(), - functions: PrimaryMap::new(), - table_plans: PrimaryMap::new(), - memory_plans: PrimaryMap::new(), - globals: PrimaryMap::new(), - instances: PrimaryMap::new(), - modules: PrimaryMap::new(), - types: PrimaryMap::new(), - } + Module::default() } /// Get the given passive element, if it exists. @@ -269,11 +259,6 @@ impl Module { self.passive_elements.get(&index).map(|es| &**es) } - fn next_id() -> usize { - static NEXT_ID: AtomicUsize = AtomicUsize::new(0); - NEXT_ID.fetch_add(1, SeqCst) - } - /// Convert a `DefinedFuncIndex` into a `FuncIndex`. pub fn func_index(&self, defined_func: DefinedFuncIndex) -> FuncIndex { FuncIndex::new(self.num_imported_funcs + defined_func.index()) @@ -361,18 +346,37 @@ impl Module { pub fn is_imported_global(&self, index: GlobalIndex) -> bool { index.index() < self.num_imported_globals } +} - /// Convenience method for looking up the original Wasm signature of a - /// function. - pub fn wasm_func_type(&self, func_index: FuncIndex) -> &WasmFuncType { - &self.signatures[self.functions[func_index]] - } +/// All types which are recorded for the entirety of a translation. +/// +/// Note that this is shared amongst all modules coming out of a translation +/// in the case of nested modules and the module linking proposal. +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[allow(missing_docs)] +pub struct TypeTables { + pub wasm_signatures: PrimaryMap, + pub native_signatures: PrimaryMap, + pub module_signatures: PrimaryMap, + pub instance_signatures: PrimaryMap, } -impl Default for Module { - fn default() -> Module { - Module::new() - } +/// The type signature of known modules. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ModuleSignature { + /// All imports in this module, listed in order with their module/name and + /// what type they're importing. + pub imports: Vec<(String, Option, EntityType)>, + /// Exports are what an instance type conveys, so we go through an + /// indirection over there. + pub exports: InstanceTypeIndex, +} + +/// The type signature of known instances. +#[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)>, } mod passive_data_serde { diff --git a/crates/environ/src/module_environ.rs b/crates/environ/src/module_environ.rs index e085751d77e7..3cadd11eb494 100644 --- a/crates/environ/src/module_environ.rs +++ b/crates/environ/src/module_environ.rs @@ -1,13 +1,17 @@ -use crate::module::{Instance, MemoryPlan, Module, ModuleType, TableElements, TablePlan}; +use crate::module::{ + Initializer, InstanceSignature, MemoryPlan, Module, ModuleSignature, ModuleType, TableElements, + TablePlan, TypeTables, +}; use crate::tunables::Tunables; use cranelift_codegen::ir; use cranelift_codegen::ir::{AbiParam, ArgumentPurpose}; use cranelift_codegen::isa::TargetFrontendConfig; use cranelift_entity::PrimaryMap; use cranelift_wasm::{ - self, translate_module, DataIndex, DefinedFuncIndex, ElemIndex, EntityIndex, EntityType, - FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, ModuleIndex, SignatureIndex, Table, - TableIndex, TargetEnvironment, TypeIndex, WasmError, WasmFuncType, WasmResult, + self, translate_module, Alias, DataIndex, DefinedFuncIndex, ElemIndex, EntityIndex, EntityType, + FuncIndex, Global, GlobalIndex, InstanceIndex, InstanceTypeIndex, Memory, MemoryIndex, + ModuleIndex, ModuleTypeIndex, SignatureIndex, Table, TableIndex, TargetEnvironment, TypeIndex, + WasmError, WasmFuncType, WasmResult, }; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -27,10 +31,11 @@ pub struct ModuleEnvironment<'data> { /// the module linking proposal. results: Vec>, - /// Modules which are in-progress for being translated (our parents) and - /// we'll resume once we finish the current module. This is only applicable - /// with the module linking proposal. - in_progress: Vec>, + /// Intern'd types for this entire translation, shared by all modules. + types: TypeTables, + + /// Where our module will get pushed into `results` after it's finished. + cur: usize, // Various bits and pieces of configuration features: WasmFeatures, @@ -46,9 +51,6 @@ pub struct ModuleTranslation<'data> { /// Module information. pub module: Module, - /// Map of native signatures - pub native_signatures: PrimaryMap, - /// References to the function bodies. pub function_body_inputs: PrimaryMap>, @@ -58,11 +60,19 @@ pub struct ModuleTranslation<'data> { /// DWARF debug information, if enabled, parsed from the module. pub debuginfo: DebugInfoData<'data>, - /// Indexes into the returned list of translations that are submodules of - /// this module. - pub submodules: PrimaryMap, - + /// When we're parsing the code section this will be incremented so we know + /// which function is currently being defined. code_index: u32, + + /// When local modules are declared an entry is pushed onto this list which + /// indicates that the initializer at the specified position needs to be + /// rewritten with the module's final index in the global list of compiled + /// modules. + module_initializer_indexes: Vec, + + /// Used as a pointer into the above list as the module code section is + /// parsed. + num_modules_defined: usize, } /// Contains function data: byte code and its offset in the module. @@ -124,7 +134,8 @@ impl<'data> ModuleEnvironment<'data> { Self { result: ModuleTranslation::default(), results: Vec::with_capacity(1), - in_progress: Vec::new(), + cur: 0, + types: Default::default(), target_config, tunables: tunables.clone(), features: *features, @@ -135,12 +146,28 @@ impl<'data> ModuleEnvironment<'data> { self.target_config.pointer_type() } - /// Translate a wasm module using this environment. This consumes the - /// `ModuleEnvironment` and produces a `ModuleTranslation`. - pub fn translate(mut self, data: &'data [u8]) -> WasmResult>> { + /// Translate a wasm module using this environment. + /// + /// This consumes the `ModuleEnvironment` and produces a list of + /// `ModuleTranslation`s as well as a `TypeTables`. The list of module + /// translations corresponds to all wasm modules found in the input `data`. + /// Note that for MVP modules this will always be a list with one element, + /// but with the module linking proposal this may have many elements. + /// + /// For the module linking proposal the top-level module is at index 0. + /// + /// The `TypeTables` structure returned contains intern'd versions of types + /// referenced from each module translation. This primarily serves as the + /// source of truth for module-linking use cases where modules can refer to + /// other module's types. All `SignatureIndex`, `ModuleTypeIndex`, and + /// `InstanceTypeIndex` values are resolved through the returned tables. + pub fn translate( + mut self, + data: &'data [u8], + ) -> WasmResult<(Vec>, TypeTables)> { translate_module(data, &mut self)?; assert!(self.results.len() > 0); - Ok(self.results) + Ok((self.results, self.types)) } fn declare_export(&mut self, export: EntityIndex, name: &str) -> WasmResult<()> { @@ -203,16 +230,22 @@ impl<'data> TargetEnvironment for ModuleEnvironment<'data> { impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data> { fn reserve_types(&mut self, num: u32) -> WasmResult<()> { let num = usize::try_from(num).unwrap(); - self.result.module.types.reserve_exact(num); - self.result.native_signatures.reserve_exact(num); + self.result.module.types.reserve(num); + self.types.native_signatures.reserve(num); + self.types.wasm_signatures.reserve(num); Ok(()) } fn declare_type_func(&mut self, wasm: WasmFuncType, sig: ir::Signature) -> WasmResult<()> { let sig = translate_signature(sig, self.pointer_type()); - // TODO: Deduplicate signatures. - self.result.native_signatures.push(sig); - let sig_index = self.result.module.signatures.push(wasm); + + // TODO: Signatures should be deduplicated in these two tables since + // `SignatureIndex` is already a index space separate from the module's + // index space. Note that this may get more urgent with module-linking + // modules where types are more likely to get repeated (across modules). + let sig_index = self.types.native_signatures.push(sig); + let sig_index2 = self.types.wasm_signatures.push(wasm); + debug_assert_eq!(sig_index, sig_index2); self.result .module .types @@ -233,10 +266,19 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data .iter() .map(|e| (e.0.to_string(), e.1.clone())) .collect(); - self.result - .module + + // TODO: Like signatures above we should probably deduplicate the + // listings of module types since with module linking it's possible + // you'll need to write down the module type in multiple locations. + let exports = self + .types + .instance_signatures + .push(InstanceSignature { exports }); + let idx = self .types - .push(ModuleType::Module { imports, exports }); + .module_signatures + .push(ModuleSignature { imports, exports }); + self.result.module.types.push(ModuleType::Module(idx)); Ok(()) } @@ -245,19 +287,45 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data .iter() .map(|e| (e.0.to_string(), e.1.clone())) .collect(); - self.result - .module + + // TODO: Like signatures above we should probably deduplicate the + // listings of instance types since with module linking it's possible + // you'll need to write down the module type in multiple locations. + let idx = self .types - .push(ModuleType::Instance { exports }); + .instance_signatures + .push(InstanceSignature { exports }); + self.result.module.types.push(ModuleType::Instance(idx)); Ok(()) } + fn type_to_signature(&self, index: TypeIndex) -> WasmResult { + match self.result.module.types[index] { + ModuleType::Function(sig) => Ok(sig), + _ => unreachable!(), + } + } + + fn type_to_module_type(&self, index: TypeIndex) -> WasmResult { + match self.result.module.types[index] { + ModuleType::Module(sig) => Ok(sig), + _ => unreachable!(), + } + } + + fn type_to_instance_type(&self, index: TypeIndex) -> WasmResult { + match self.result.module.types[index] { + ModuleType::Instance(sig) => Ok(sig), + _ => unreachable!(), + } + } + fn reserve_imports(&mut self, num: u32) -> WasmResult<()> { Ok(self .result .module - .imports - .reserve_exact(usize::try_from(num).unwrap())) + .initializers + .reserve(usize::try_from(num).unwrap())) } fn declare_func_import( @@ -273,11 +341,11 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data ); let sig_index = self.result.module.types[index].unwrap_function(); let func_index = self.result.module.functions.push(sig_index); - self.result.module.imports.push(( - module.to_owned(), - field.map(|s| s.to_owned()), - EntityIndex::Function(func_index), - )); + self.result.module.initializers.push(Initializer::Import { + module: module.to_owned(), + field: field.map(|s| s.to_owned()), + index: EntityIndex::Function(func_index), + }); self.result.module.num_imported_funcs += 1; self.result.debuginfo.wasm_file.imported_func_count += 1; Ok(()) @@ -296,11 +364,11 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data ); let plan = TablePlan::for_table(table, &self.tunables); let table_index = self.result.module.table_plans.push(plan); - self.result.module.imports.push(( - module.to_owned(), - field.map(|s| s.to_owned()), - EntityIndex::Table(table_index), - )); + self.result.module.initializers.push(Initializer::Import { + module: module.to_owned(), + field: field.map(|s| s.to_owned()), + index: EntityIndex::Table(table_index), + }); self.result.module.num_imported_tables += 1; Ok(()) } @@ -321,11 +389,11 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data } let plan = MemoryPlan::for_memory(memory, &self.tunables); let memory_index = self.result.module.memory_plans.push(plan); - self.result.module.imports.push(( - module.to_owned(), - field.map(|s| s.to_owned()), - EntityIndex::Memory(memory_index), - )); + self.result.module.initializers.push(Initializer::Import { + module: module.to_owned(), + field: field.map(|s| s.to_owned()), + index: EntityIndex::Memory(memory_index), + }); self.result.module.num_imported_memories += 1; Ok(()) } @@ -342,15 +410,47 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data "Imported globals must be declared first" ); let global_index = self.result.module.globals.push(global); - self.result.module.imports.push(( - module.to_owned(), - field.map(|s| s.to_owned()), - EntityIndex::Global(global_index), - )); + self.result.module.initializers.push(Initializer::Import { + module: module.to_owned(), + field: field.map(|s| s.to_owned()), + index: EntityIndex::Global(global_index), + }); self.result.module.num_imported_globals += 1; Ok(()) } + fn declare_module_import( + &mut self, + ty_index: TypeIndex, + module: &'data str, + field: Option<&'data str>, + ) -> WasmResult<()> { + let signature = self.type_to_module_type(ty_index)?; + let module_index = self.result.module.modules.push(signature); + self.result.module.initializers.push(Initializer::Import { + module: module.to_owned(), + field: field.map(|s| s.to_owned()), + index: EntityIndex::Module(module_index), + }); + Ok(()) + } + + fn declare_instance_import( + &mut self, + ty_index: TypeIndex, + module: &'data str, + field: Option<&'data str>, + ) -> WasmResult<()> { + let signature = self.type_to_instance_type(ty_index)?; + let instance_index = self.result.module.instances.push(signature); + self.result.module.initializers.push(Initializer::Import { + module: module.to_owned(), + field: field.map(|s| s.to_owned()), + index: EntityIndex::Instance(instance_index), + }); + Ok(()) + } + fn reserve_func_types(&mut self, num: u32) -> WasmResult<()> { self.result .module @@ -436,6 +536,14 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data self.declare_export(EntityIndex::Global(global_index), name) } + fn declare_module_export(&mut self, index: ModuleIndex, name: &str) -> WasmResult<()> { + self.declare_export(EntityIndex::Module(index), name) + } + + fn declare_instance_export(&mut self, index: InstanceIndex, name: &str) -> WasmResult<()> { + self.declare_export(EntityIndex::Instance(index), name) + } + fn declare_start_func(&mut self, func_index: FuncIndex) -> WasmResult<()> { debug_assert!(self.result.module.start_func.is_none()); self.result.module.start_func = Some(func_index); @@ -497,7 +605,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data let func_index = self.result.code_index + self.result.module.num_imported_funcs as u32; let func_index = FuncIndex::from_u32(func_index); let sig_index = self.result.module.functions[func_index]; - let sig = &self.result.module.signatures[sig_index]; + let sig = &self.types.wasm_signatures[sig_index]; let mut locals = Vec::new(); for pair in body.get_locals_reader()? { locals.push(pair?); @@ -623,42 +731,159 @@ and for re-adding support for interface types you can see this issue: } fn reserve_modules(&mut self, amount: u32) { + // Go ahead and reserve space in the final `results` array for `amount` + // more modules. let extra = self.results.capacity() + (amount as usize) - self.results.len(); self.results.reserve(extra); - self.result.submodules.reserve(amount as usize); + + // Then also reserve space in our own local module's metadata fields + // we'll be adding to. + self.result.module.modules.reserve(amount as usize); + self.result.module.initializers.reserve(amount as usize); + } + + fn declare_module(&mut self, ty: TypeIndex) -> WasmResult<()> { + // Record the type signature of this module ... + let signature = self.type_to_module_type(ty)?; + self.result.module.modules.push(signature); + + // ... and then record that in the initialization steps of this module + // we're inserting this module into the module index space. At this + // point we don't know the final index of the module we're defining, so + // we leave a placeholder to get rewritten later. + let loc = self.result.module.initializers.len(); + self.result + .module + .initializers + .push(Initializer::DefineModule(usize::max_value())); + self.result.module_initializer_indexes.push(loc); + Ok(()) } fn module_start(&mut self, index: usize) { - // skip the first module since `self.result` is already empty and we'll - // be translating into that. + // Reset the contents of `self.result` for a new module that's getting + // translataed. + let mut prev = mem::replace(&mut self.result, ModuleTranslation::default()); + + // If this is a nested submodule then we record the final destination of + // the child in parent (we store `index` into `prev`) in the appropriate + // initialization slot as dicated by `num_modules_defined` (our index of + // iteration through the code section). + // Record that the `num_modules_defined`-th module is defined at index + // by updating the initializer entry. if index > 0 { - let in_progress = mem::replace(&mut self.result, ModuleTranslation::default()); - self.in_progress.push(in_progress); + let initializer_idx = prev.module_initializer_indexes[prev.num_modules_defined]; + prev.num_modules_defined += 1; + debug_assert!(match &prev.module.initializers[initializer_idx] { + Initializer::DefineModule(usize::MAX) => true, + _ => false, + }); + prev.module.initializers[initializer_idx] = Initializer::DefineModule(index); + self.result.module.parent = Some(self.cur); } + + // Update our current index counter and save our parent's translation + // where this current translation will end up, which we'll swap back as + // part of `module_end`. + self.cur = index; + assert_eq!(index, self.results.len()); + self.results.push(prev); } fn module_end(&mut self, index: usize) { - let to_continue = match self.in_progress.pop() { - Some(m) => m, - None => { - assert_eq!(index, 0); - ModuleTranslation::default() - } - }; - let finished = mem::replace(&mut self.result, to_continue); - self.result.submodules.push(self.results.len()); - self.results.push(finished); + assert!(self.result.num_modules_defined == self.result.module_initializer_indexes.len()); + + // Move our finished module into its final location, swapping it with + // what was this module's parent. + self.cur = self.result.module.parent.unwrap_or(0); + mem::swap(&mut self.result, &mut self.results[index]); } fn reserve_instances(&mut self, amt: u32) { self.result.module.instances.reserve(amt as usize); + self.result.module.initializers.reserve(amt as usize); } fn declare_instance(&mut self, module: ModuleIndex, args: Vec) -> WasmResult<()> { + // Record the type of this instance with the type signature of the + // module we're instantiating and then also add an initializer which + // records that we'll be adding to the instance index space here. + let module_ty = self.result.module.modules[module]; + let instance_ty = self.types.module_signatures[module_ty].exports; + self.result.module.instances.push(instance_ty); self.result .module - .instances - .push(Instance::Instantiate { module, args }); + .initializers + .push(Initializer::Instantiate { module, args }); + Ok(()) + } + + fn declare_alias(&mut self, alias: Alias) -> WasmResult<()> { + match alias { + // Types are easy, we statically know everything so we're just + // copying some pointers from our parent module to our own module. + // + // Note that we don't add an initializer for this alias because + // we statically know where all types point to. + Alias::ParentType(parent_idx) => { + let ty = self.results[self.cur].module.types[parent_idx]; + self.result.module.types.push(ty); + } + + // This is similar to types in that it's easy for us to record the + // type of the module that's being aliased, but we also need to add + // an initializer so during instantiation we can prepare the index + // space appropriately. + Alias::ParentModule(parent_idx) => { + let module_idx = self.results[self.cur].module.modules[parent_idx]; + self.result.module.modules.push(module_idx); + self.result + .module + .initializers + .push(Initializer::AliasParentModule(parent_idx)); + } + + // This case is slightly more involved, we'll be recording all the + // type information for each kind of entity, and then we also need + // to record an initialization step to get the export from the + // instance. + Alias::Child { instance, export } => { + let ty = self.result.module.instances[instance]; + match &self.types.instance_signatures[ty].exports[export].1 { + EntityType::Global(g) => { + self.result.module.globals.push(g.clone()); + self.result.module.num_imported_globals += 1; + } + EntityType::Memory(mem) => { + let plan = MemoryPlan::for_memory(*mem, &self.tunables); + self.result.module.memory_plans.push(plan); + self.result.module.num_imported_memories += 1; + } + EntityType::Table(t) => { + let plan = TablePlan::for_table(*t, &self.tunables); + self.result.module.table_plans.push(plan); + self.result.module.num_imported_tables += 1; + } + EntityType::Function(sig) => { + self.result.module.functions.push(*sig); + self.result.module.num_imported_funcs += 1; + self.result.debuginfo.wasm_file.imported_func_count += 1; + } + EntityType::Instance(sig) => { + self.result.module.instances.push(*sig); + } + EntityType::Module(sig) => { + self.result.module.modules.push(*sig); + } + EntityType::Event(_) => unimplemented!(), + } + self.result + .module + .initializers + .push(Initializer::AliasInstanceExport { instance, export }) + } + } + Ok(()) } } diff --git a/crates/environ/src/vmoffsets.rs b/crates/environ/src/vmoffsets.rs index aa5e00d0ac32..8673a38d3cc2 100644 --- a/crates/environ/src/vmoffsets.rs +++ b/crates/environ/src/vmoffsets.rs @@ -24,7 +24,7 @@ use crate::BuiltinFunctionIndex; use cranelift_codegen::ir; use cranelift_wasm::{ DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, GlobalIndex, MemoryIndex, - SignatureIndex, TableIndex, + TableIndex, TypeIndex, }; use more_asserts::assert_lt; use std::convert::TryFrom; @@ -78,7 +78,7 @@ impl VMOffsets { pub fn new(pointer_size: u8, module: &Module) -> Self { Self { pointer_size, - num_signature_ids: cast_to_u32(module.signatures.len()), + num_signature_ids: cast_to_u32(module.types.len()), num_imported_functions: cast_to_u32(module.num_imported_funcs), num_imported_tables: cast_to_u32(module.num_imported_tables), num_imported_memories: cast_to_u32(module.num_imported_memories), @@ -430,7 +430,7 @@ impl VMOffsets { } /// Return the offset to `VMSharedSignatureId` index `index`. - pub fn vmctx_vmshared_signature_id(&self, index: SignatureIndex) -> u32 { + pub fn vmctx_vmshared_signature_id(&self, index: TypeIndex) -> u32 { assert_lt!(index.as_u32(), self.num_signature_ids); self.vmctx_signature_ids_begin() .checked_add( diff --git a/crates/jit/src/compiler.rs b/crates/jit/src/compiler.rs index 48fcb2c688e7..6e2ea687d84f 100644 --- a/crates/jit/src/compiler.rs +++ b/crates/jit/src/compiler.rs @@ -14,7 +14,7 @@ use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa}; use wasmtime_environ::wasm::{DefinedMemoryIndex, MemoryIndex}; use wasmtime_environ::{ CompiledFunctions, Compiler as EnvCompiler, DebugInfoData, Module, ModuleMemoryOffset, - ModuleTranslation, Tunables, VMOffsets, + ModuleTranslation, Tunables, TypeTables, VMOffsets, }; /// Select which kind of compilation to use. @@ -127,13 +127,20 @@ impl Compiler { pub fn compile<'data>( &self, translation: &mut ModuleTranslation, + types: &TypeTables, ) -> Result { let functions = mem::take(&mut translation.function_body_inputs); let functions = functions.into_iter().collect::>(); let funcs = maybe_parallel!(functions.(into_iter | into_par_iter)) .map(|(index, func)| { - self.compiler - .compile_function(translation, index, func, &*self.isa, &self.tunables) + self.compiler.compile_function( + translation, + index, + func, + &*self.isa, + &self.tunables, + types, + ) }) .collect::, _>>()? .into_iter() @@ -150,7 +157,8 @@ impl Compiler { vec![] }; - let (obj, unwind_info) = build_object(&*self.isa, &translation, &funcs, dwarf_sections)?; + let (obj, unwind_info) = + build_object(&*self.isa, &translation, types, &funcs, dwarf_sections)?; Ok(Compilation { obj, diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index 3ec6f04bf1db..2c1211e29470 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -17,10 +17,13 @@ use thiserror::Error; use wasmtime_debug::create_gdbjit_image; use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::isa::TargetIsa; -use wasmtime_environ::wasm::{DefinedFuncIndex, ModuleIndex, SignatureIndex}; +use wasmtime_environ::wasm::{ + DefinedFuncIndex, InstanceTypeIndex, ModuleTypeIndex, SignatureIndex, WasmFuncType, +}; use wasmtime_environ::{ - CompileError, DataInitializer, DataInitializerLocation, FunctionAddressMap, Module, - ModuleEnvironment, ModuleTranslation, StackMapInformation, TrapInformation, + CompileError, DataInitializer, DataInitializerLocation, FunctionAddressMap, InstanceSignature, + Module, ModuleEnvironment, ModuleSignature, ModuleTranslation, StackMapInformation, + TrapInformation, }; use wasmtime_profiling::ProfilingAgent; use wasmtime_runtime::{ @@ -71,10 +74,6 @@ pub struct CompilationArtifacts { /// Debug info presence flags. debug_info: bool, - - /// Where to find this module's submodule code in the top-level list of - /// modules. - submodules: PrimaryMap, } impl CompilationArtifacts { @@ -82,8 +81,8 @@ impl CompilationArtifacts { pub fn build( compiler: &Compiler, data: &[u8], - ) -> Result, SetupError> { - let translations = ModuleEnvironment::new( + ) -> Result<(Vec, TypeTables), SetupError> { + let (translations, types) = ModuleEnvironment::new( compiler.frontend_config(), compiler.tunables(), compiler.features(), @@ -91,18 +90,17 @@ impl CompilationArtifacts { .translate(data) .map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?; - maybe_parallel!(translations.(into_iter | into_par_iter)) + let list = maybe_parallel!(translations.(into_iter | into_par_iter)) .map(|mut translation| { let Compilation { obj, unwind_info, funcs, - } = compiler.compile(&mut translation)?; + } = compiler.compile(&mut translation, &types)?; let ModuleTranslation { module, data_initializers, - submodules, .. } = translation; @@ -123,7 +121,6 @@ impl CompilationArtifacts { obj: obj.into_boxed_slice(), unwind_info: unwind_info.into_boxed_slice(), data_initializers, - submodules, funcs: funcs .into_iter() .map(|(_, func)| FunctionInfo { @@ -135,11 +132,21 @@ impl CompilationArtifacts { debug_info: compiler.tunables().debug_info, }) }) - .collect::, SetupError>>() + .collect::, SetupError>>()?; + Ok(( + list, + TypeTables { + wasm_signatures: types.wasm_signatures, + module_signatures: types.module_signatures, + instance_signatures: types.instance_signatures, + }, + )) } } struct FinishedFunctions(PrimaryMap); +unsafe impl Send for FinishedFunctions {} +unsafe impl Sync for FinishedFunctions {} #[derive(Serialize, Deserialize, Clone)] struct FunctionInfo { @@ -148,8 +155,15 @@ struct FunctionInfo { stack_maps: Vec, } -unsafe impl Send for FinishedFunctions {} -unsafe impl Sync for FinishedFunctions {} +/// This is intended to mirror the type tables in `wasmtime_environ`, except that +/// it doesn't store the native signatures which are no longer needed past compilation. +#[derive(Serialize, Deserialize)] +#[allow(missing_docs)] +pub struct TypeTables { + pub wasm_signatures: PrimaryMap, + pub module_signatures: PrimaryMap, + pub instance_signatures: PrimaryMap, +} /// Container for data needed for an Instance function to exist. pub struct ModuleCode { @@ -342,12 +356,6 @@ impl CompiledModule { pub fn code(&self) -> &Arc { &self.code } - - /// Returns where the specified submodule lives in this module's - /// array-of-modules (store at the top-level) - pub fn submodule_idx(&self, idx: ModuleIndex) -> usize { - self.artifacts.submodules[idx] - } } /// Similar to `DataInitializer`, but owns its own copy of the data rather diff --git a/crates/jit/src/lib.rs b/crates/jit/src/lib.rs index 94f268de8258..3e3cd9378cc4 100644 --- a/crates/jit/src/lib.rs +++ b/crates/jit/src/lib.rs @@ -46,7 +46,9 @@ pub mod trampoline; pub use crate::code_memory::CodeMemory; pub use crate::compiler::{Compilation, CompilationStrategy, Compiler}; -pub use crate::instantiate::{CompilationArtifacts, CompiledModule, ModuleCode, SetupError}; +pub use crate::instantiate::{ + CompilationArtifacts, CompiledModule, ModuleCode, SetupError, TypeTables, +}; pub use crate::link::link_module; /// Version number of this crate. diff --git a/crates/jit/src/object.rs b/crates/jit/src/object.rs index f492d645fadf..24b431e59783 100644 --- a/crates/jit/src/object.rs +++ b/crates/jit/src/object.rs @@ -8,7 +8,7 @@ use wasmtime_debug::DwarfSection; use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::isa::{unwind::UnwindInfo, TargetIsa}; use wasmtime_environ::wasm::{FuncIndex, SignatureIndex}; -use wasmtime_environ::{CompiledFunctions, ModuleTranslation}; +use wasmtime_environ::{CompiledFunctions, ModuleTranslation, TypeTables}; use wasmtime_obj::{ObjectBuilder, ObjectBuilderTarget}; pub use wasmtime_obj::utils; @@ -24,6 +24,7 @@ pub enum ObjectUnwindInfo { pub(crate) fn build_object( isa: &dyn TargetIsa, translation: &ModuleTranslation, + types: &TypeTables, funcs: &CompiledFunctions, dwarf_sections: Vec, ) -> Result<(Object, Vec), anyhow::Error> { @@ -38,10 +39,16 @@ pub(crate) fn build_object( .map(|info| ObjectUnwindInfo::Func(translation.module.func_index(index), info.clone())) })); - let mut trampolines = PrimaryMap::with_capacity(translation.module.signatures.len()); + let mut trampolines = PrimaryMap::with_capacity(types.native_signatures.len()); let mut cx = FunctionBuilderContext::new(); // Build trampolines for every signature. - for (i, native_sig) in translation.native_signatures.iter() { + // + // TODO: for the module linking proposal this builds too many native + // signatures. This builds trampolines for all signatures for all modules + // for each module. That's a lot of trampolines! We should instead figure + // out a way to share trampolines amongst all modules when compiling + // module-linking modules. + for (i, native_sig) in types.native_signatures.iter() { let func = build_trampoline(isa, &mut cx, native_sig, std::mem::size_of::())?; // Preserve trampoline function unwind info. if let Some(info) = &func.unwind_info { diff --git a/crates/lightbeam/wasmtime/src/lib.rs b/crates/lightbeam/wasmtime/src/lib.rs index ed09e04550c2..1f27f8697567 100644 --- a/crates/lightbeam/wasmtime/src/lib.rs +++ b/crates/lightbeam/wasmtime/src/lib.rs @@ -9,12 +9,12 @@ use cranelift_codegen::isa; use lightbeam::{CodeGenSession, NullOffsetSink, Sinks}; use wasmtime_environ::wasm::{ DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, - GlobalIndex, MemoryIndex, SignatureIndex, TableIndex, + GlobalIndex, MemoryIndex, SignatureIndex, TableIndex, TypeIndex, }; use wasmtime_environ::{ entity::PrimaryMap, BuiltinFunctionIndex, CompileError, CompiledFunction, Compiler, FunctionBodyData, Module, ModuleTranslation, Relocation, RelocationTarget, TrapInformation, - Tunables, VMOffsets, + Tunables, TypeTables, VMOffsets, }; /// A compiler that compiles a WebAssembly module with Lightbeam, directly translating the Wasm file. @@ -28,13 +28,14 @@ impl Compiler for Lightbeam { function_body: FunctionBodyData<'_>, isa: &dyn isa::TargetIsa, tunables: &Tunables, + types: &TypeTables, ) -> Result { if tunables.debug_info { return Err(CompileError::DebugInfoNotSupported); } let func_index = translation.module.func_index(i); - let env = FuncEnvironment::new(isa.frontend_config().pointer_bytes(), translation); + let env = FuncEnvironment::new(isa.frontend_config().pointer_bytes(), translation, types); let mut codegen_session: CodeGenSession<_> = CodeGenSession::new( translation.function_body_inputs.len() as u32, &env, @@ -180,11 +181,15 @@ struct FuncEnvironment<'module_environment> { } impl<'module_environment> FuncEnvironment<'module_environment> { - fn new(pointer_bytes: u8, translation: &'module_environment ModuleTranslation<'_>) -> Self { + fn new( + pointer_bytes: u8, + translation: &'module_environment ModuleTranslation<'_>, + types: &'module_environment TypeTables, + ) -> Self { Self { module: &translation.module, offsets: VMOffsets::new(pointer_bytes, &translation.module), - native_signatures: &translation.native_signatures, + native_signatures: &types.native_signatures, } } } @@ -322,7 +327,7 @@ impl lightbeam::ModuleContext for FuncEnvironment<'_> { } fn vmctx_vmshared_signature_id(&self, signature_idx: u32) -> u32 { self.offsets - .vmctx_vmshared_signature_id(SignatureIndex::from_u32(signature_idx)) + .vmctx_vmshared_signature_id(TypeIndex::from_u32(signature_idx)) } // TODO: type of a global diff --git a/crates/obj/src/context.rs b/crates/obj/src/context.rs index e306f6f36b77..2f737dac8af0 100644 --- a/crates/obj/src/context.rs +++ b/crates/obj/src/context.rs @@ -7,7 +7,7 @@ use std::ptr; use wasmtime_environ::entity::EntityRef; use wasmtime_environ::isa::TargetFrontendConfig; use wasmtime_environ::wasm::GlobalInit; -use wasmtime_environ::{Module, TargetSharedSignatureIndex, VMOffsets}; +use wasmtime_environ::{Module, ModuleType, TargetSharedSignatureIndex, VMOffsets}; pub struct TableRelocation { pub index: usize, @@ -25,16 +25,19 @@ pub fn layout_vmcontext( // Assign unique indices to unique signatures. let mut signature_registry = HashMap::new(); let mut signature_registry_len = signature_registry.len(); - for (index, sig) in module.signatures.iter() { + for (index, sig) in module.types.iter() { let offset = ofs.vmctx_vmshared_signature_id(index) as usize; - let target_index = match signature_registry.entry(sig) { - Entry::Occupied(o) => *o.get(), - Entry::Vacant(v) => { - assert_le!(signature_registry_len, std::u32::MAX as usize); - let id = TargetSharedSignatureIndex::new(signature_registry_len as u32); - signature_registry_len += 1; - *v.insert(id) - } + let target_index = match sig { + ModuleType::Function(sig) => match signature_registry.entry(sig) { + Entry::Occupied(o) => *o.get(), + Entry::Vacant(v) => { + assert_le!(signature_registry_len, std::u32::MAX as usize); + let id = TargetSharedSignatureIndex::new(signature_registry_len as u32); + signature_registry_len += 1; + *v.insert(id) + } + }, + _ => TargetSharedSignatureIndex::new(u32::max_value()), }; unsafe { let to = out.as_mut_ptr().add(offset) as *mut TargetSharedSignatureIndex; diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index a95a4b594bfd..1f39b171a032 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -31,7 +31,7 @@ use wasmtime_environ::wasm::{ ElemIndex, EntityIndex, FuncIndex, GlobalIndex, GlobalInit, MemoryIndex, SignatureIndex, TableElementType, TableIndex, WasmType, }; -use wasmtime_environ::{ir, DataInitializer, Module, TableElements, VMOffsets}; +use wasmtime_environ::{ir, DataInitializer, Module, ModuleType, TableElements, VMOffsets}; /// A WebAssembly instance. /// @@ -78,12 +78,6 @@ impl Instance { .cast() } - /// Return the indexed `VMSharedSignatureIndex`. - fn signature_id(&self, index: SignatureIndex) -> VMSharedSignatureIndex { - let index = usize::try_from(index.as_u32()).unwrap(); - unsafe { *self.signature_ids_ptr().add(index) } - } - pub(crate) fn module(&self) -> &Module { &self.module } @@ -868,8 +862,11 @@ impl InstanceHandle { let instance = handle.instance(); let mut ptr = instance.signature_ids_ptr(); - for (signature, _) in handle.module().signatures.iter() { - *ptr = lookup_shared_signature(signature); + for sig in handle.module().types.values() { + *ptr = match sig { + ModuleType::Function(sig) => lookup_shared_signature(*sig), + _ => VMSharedSignatureIndex::new(u32::max_value()), + }; ptr = ptr.add(1); } @@ -924,7 +921,7 @@ impl InstanceHandle { *instance.stack_map_registry() = stack_map_registry; for (index, sig) in instance.module.functions.iter() { - let type_index = instance.signature_id(*sig); + let type_index = lookup_shared_signature(*sig); let (func_ptr, vmctx) = if let Some(def_index) = instance.module.defined_func_index(index) { diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs index bc8d2daffcbd..63f2e2678f4d 100644 --- a/crates/wasmtime/src/externals.rs +++ b/crates/wasmtime/src/externals.rs @@ -115,6 +115,15 @@ impl Extern { }; Store::same(my_store, store) } + + pub(crate) fn desc(&self) -> &'static str { + match self { + Extern::Func(_) => "function", + Extern::Table(_) => "table", + Extern::Memory(_) => "memory", + Extern::Global(_) => "global", + } + } } impl From for Extern { diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs index b375b4884889..6c94c202e144 100644 --- a/crates/wasmtime/src/instance.rs +++ b/crates/wasmtime/src/instance.rs @@ -1,74 +1,158 @@ use crate::trampoline::StoreInstanceHandle; use crate::{Engine, Export, Extern, Func, Global, Memory, Module, Store, Table, Trap}; -use anyhow::{bail, Error, Result}; +use anyhow::{bail, Context, Error, Result}; use std::mem; use wasmtime_environ::entity::PrimaryMap; -use wasmtime_environ::wasm::{EntityIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex}; -use wasmtime_jit::CompiledModule; +use wasmtime_environ::wasm::{ + EntityIndex, FuncIndex, GlobalIndex, InstanceIndex, MemoryIndex, ModuleIndex, TableIndex, +}; +use wasmtime_environ::Initializer; +use wasmtime_jit::{CompiledModule, TypeTables}; use wasmtime_runtime::{ Imports, InstantiationError, StackMapRegistry, VMContext, VMExternRefActivationsTable, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport, }; -fn instantiate( - store: &Store, - compiled_module: &CompiledModule, - all_modules: &[CompiledModule], - imports: &mut ImportsBuilder<'_>, +/// Performs all low-level steps necessary for instantiation. +/// +/// This function will take all the arguments and attempt to do everything +/// necessary to instantiate the referenced instance. The trickiness of this +/// function stems from the implementation of the module-linking proposal where +/// we're handling nested instances, interleaved imports/aliases, etc. That's +/// all an internal implementation here ideally though! +/// +/// * `store` - the store we're instantiating into +/// * `compiled_module` - the module that we're instantiating +/// * `all_modules` - the list of all modules that were part of the compilation +/// of `compiled_module`. This is only applicable in the module linking +/// proposal, otherwise this will just be a list containing `compiled_module` +/// 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 +/// 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, + define_import: &mut dyn FnMut(&EntityIndex, &mut ImportsBuilder<'a>) -> Result<()>, ) -> Result { let env_module = compiled_module.module(); - // The first part of instantiating any module is to first follow any - // `instantiate` instructions it has as part of the module linking - // proposal. Here we iterate overall those instructions and create the - // instances as necessary. - for instance in env_module.instances.values() { - let (module_idx, args) = match instance { - wasmtime_environ::Instance::Instantiate { module, args } => (*module, args), - wasmtime_environ::Instance::Import(_) => continue, - }; - // Translate the `module_idx` to a top-level module `usize` and then - // use that to extract the child `&CompiledModule` itself. Then we can - // iterate over each of the arguments provided to satisfy its imports. - // - // Note that we directly reach into `imports` below based on indexes - // and push raw value into how to instantiate our submodule. This should - // be safe due to wasm validation ensuring that all our indices are - // in-bounds and all the expected types and such line up. - let module_idx = compiled_module.submodule_idx(module_idx); - let compiled_module = &all_modules[module_idx]; - let mut builder = ImportsBuilder::new(compiled_module.module(), store); - for arg in args { - match *arg { - EntityIndex::Global(i) => { - builder.globals.push(imports.globals[i]); - } - EntityIndex::Table(i) => { - builder.tables.push(imports.tables[i]); - } - EntityIndex::Function(i) => { - builder.functions.push(imports.functions[i]); - } - EntityIndex::Memory(i) => { - builder.memories.push(imports.memories[i]); - } - EntityIndex::Module(_) => unimplemented!(), - EntityIndex::Instance(_) => unimplemented!(), + let mut imports = ImportsBuilder::new(env_module, types, store); + for initializer in env_module.initializers.iter() { + match initializer { + // Definition of an import depends on how our parent is providing + // imports, so we delegate to our custom closure. This will resolve + // 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]); + } + + // 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]); + } + + // 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. + 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(), + ); + imports.push_extern(&item); + } + + // Oh boy a recursive instantiation! The recursive arguments here + // are pretty simply, and the only slightly-meaty one is how + // arguments are pulled from `args` and pushed directly into the + // builder specified, which should be an easy enough + // copy-the-pointer operation in all cases. + // + // Note that this recursive call shouldn't result in an infinite + // loop because of wasm module validation which requires everything + // 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. + 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, + &mut |_, builder| { + match *args.next().unwrap() { + EntityIndex::Global(i) => { + builder.globals.push(imports.globals[i]); + } + EntityIndex::Function(i) => { + builder.functions.push(imports.functions[i]); + } + EntityIndex::Table(i) => { + builder.tables.push(imports.tables[i]); + } + EntityIndex::Memory(i) => { + builder.memories.push(imports.memories[i]); + } + EntityIndex::Module(i) => { + builder.modules.push(imports.modules[i]); + } + EntityIndex::Instance(i) => { + builder.instances.push(imports.instances[i].clone()); + } + } + Ok(()) + }, + )?; + imports.instances.push(handle); } } - instantiate(store, compiled_module, all_modules, &mut builder)?; } + // 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.imports(); + // 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); + store.register_module(compiled_module, types); let config = store.engine().config(); let instance = unsafe { let instance = compiled_module.instantiate( - imports.imports(), - &store.lookup_shared_signature(compiled_module.module()), + imports, + &store.lookup_shared_signature(types), config.memory_creator.as_ref().map(|a| a as _), store.interrupts(), Box::new(()), @@ -208,26 +292,38 @@ impl Instance { bail!("cross-`Engine` instantiation is not currently supported"); } - let mut builder = ImportsBuilder::new(module.compiled_module().module(), store); + // 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(); + if expected != imports.len() { + bail!("expected {} imports, found {}", expected, imports.len()); + } for import in imports { - // For now we have a restriction that the `Store` that we're working - // with is the same for everything involved here. if !import.comes_from_same_store(store) { bail!("cross-`Store` instantiation is not currently supported"); } - match import { - Extern::Global(e) => builder.global(e)?, - Extern::Func(e) => builder.func(e)?, - Extern::Table(e) => builder.table(e)?, - Extern::Memory(e) => builder.memory(e)?, - } } - builder.validate_all_imports_provided()?; + + let mut imports = imports.iter(); let handle = instantiate( store, module.compiled_module(), - &module.compiled, - &mut builder, + 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) + }, )?; Ok(Instance { @@ -304,113 +400,105 @@ struct ImportsBuilder<'a> { tables: PrimaryMap, memories: PrimaryMap, globals: PrimaryMap, + instances: PrimaryMap, + modules: PrimaryMap, + module: &'a wasmtime_environ::Module, - imports: std::slice::Iter<'a, (String, Option, EntityIndex)>, store: &'a Store, + types: &'a TypeTables, } impl<'a> ImportsBuilder<'a> { - fn new(module: &'a wasmtime_environ::Module, store: &'a Store) -> ImportsBuilder<'a> { + fn new( + module: &'a wasmtime_environ::Module, + types: &'a TypeTables, + store: &'a Store, + ) -> ImportsBuilder<'a> { ImportsBuilder { - imports: module.imports.iter(), module, 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), globals: PrimaryMap::with_capacity(module.num_imported_globals), + instances: PrimaryMap::with_capacity(module.instances.len()), + modules: PrimaryMap::with_capacity(module.modules.len()), } } - fn next_import( - &mut self, - found: &str, - get: impl FnOnce(&wasmtime_environ::Module, &EntityIndex) -> Option, - ) -> Result<()> { - match self.imports.next() { - Some((module, field, idx)) => { - let error = match get(self.module, idx) { - Some(true) => return Ok(()), - Some(false) => { - anyhow::anyhow!("{} types incompatible", found) + 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() } - None => { - let desc = match idx { - EntityIndex::Table(_) => "table", - EntityIndex::Function(_) => "func", - EntityIndex::Memory(_) => "memory", - EntityIndex::Global(_) => "global", - EntityIndex::Instance(_) => "instance", - EntityIndex::Module(_) => "module", - }; - anyhow::anyhow!("expected {}, but found {}", desc, found) + 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() } - }; - let import_name = match field { - Some(name) => format!("{}/{}", module, name), - None => module.to_string(), - }; - Err(error.context(format!("incompatible import type for {}", import_name))) + Extern::Memory(_) => bail!("memory types incompatible"), + _ => bail!("expected memory, but found {}", actual.desc()), + }); } - None => bail!("too many imports provided"), - } - } - - fn global(&mut self, global: &Global) -> Result<()> { - self.next_import("global", |m, e| match e { - EntityIndex::Global(i) => Some(global.matches_expected(&m.globals[*i])), - _ => None, - })?; - self.globals.push(global.vmimport()); - Ok(()) - } - - fn memory(&mut self, mem: &Memory) -> Result<()> { - self.next_import("memory", |m, e| match e { - EntityIndex::Memory(i) => Some(mem.matches_expected(&m.memory_plans[*i])), - _ => None, - })?; - self.memories.push(mem.vmimport()); - Ok(()) - } - - fn table(&mut self, table: &Table) -> Result<()> { - self.next_import("table", |m, e| match e { - EntityIndex::Table(i) => Some(table.matches_expected(&m.table_plans[*i])), - _ => None, - })?; - self.tables.push(table.vmimport()); - Ok(()) - } - - fn func(&mut self, func: &Func) -> Result<()> { - let store = self.store; - self.next_import("func", |m, e| match e { - EntityIndex::Function(i) => Some( + 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. - match store + let ty = self + .store .signatures() .borrow() - .lookup(&m.signatures[m.functions[*i]]) - { - Some(ty) => func.matches_expected(ty), - None => false, - }, - ), - _ => None, - })?; - self.functions.push(func.vmimport()); + .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!(), + } Ok(()) } - fn validate_all_imports_provided(&mut self) -> Result<()> { - if self.imports.next().is_some() { - bail!("not enough imports provided"); + fn push_extern(&mut self, item: &Extern) { + match item { + Extern::Func(i) => { + self.functions.push(i.vmimport()); + } + Extern::Global(i) => { + self.globals.push(i.vmimport()); + } + Extern::Table(i) => { + self.tables.push(i.vmimport()); + } + Extern::Memory(i) => { + self.memories.push(i.vmimport()); + } } - Ok(()) } fn imports(&self) -> Imports<'_> { diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index 875a1896d326..37f068af6c14 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use wasmparser::Validator; #[cfg(feature = "cache")] use wasmtime_cache::ModuleCacheEntry; -use wasmtime_jit::{CompilationArtifacts, CompiledModule}; +use wasmtime_jit::{CompilationArtifacts, CompiledModule, TypeTables}; /// A compiled WebAssembly module, ready to be instantiated. /// @@ -81,10 +81,15 @@ use wasmtime_jit::{CompilationArtifacts, CompiledModule}; #[derive(Clone)] pub struct Module { engine: Engine, - pub(crate) compiled: Arc<[CompiledModule]>, + data: Arc, index: usize, } +pub(crate) struct ModuleData { + pub(crate) types: TypeTables, + pub(crate) modules: Vec, +} + impl Module { /// Creates a new WebAssembly `Module` from the given in-memory `bytes`. /// @@ -164,7 +169,7 @@ impl Module { /// See [`Module::new`] for other details. pub fn new_with_name(engine: &Engine, bytes: impl AsRef<[u8]>, name: &str) -> Result { let mut module = Module::new(engine, bytes.as_ref())?; - Arc::get_mut(&mut module.compiled).unwrap()[module.index] + Arc::get_mut(&mut module.data).unwrap().modules[module.index] .module_mut() .expect("mutable module") .name = Some(name.to_string()); @@ -240,14 +245,14 @@ impl Module { /// ``` pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result { #[cfg(feature = "cache")] - let artifacts = ModuleCacheEntry::new("wasmtime", engine.cache_config()) + let (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 = CompilationArtifacts::build(engine.compiler(), binary)?; + let (artifacts, types) = CompilationArtifacts::build(engine.compiler(), binary)?; - let compiled = CompiledModule::from_artifacts_list( + let modules = CompiledModule::from_artifacts_list( artifacts, engine.compiler().isa(), &*engine.config().profiler, @@ -255,8 +260,8 @@ impl Module { Ok(Module { engine: engine.clone(), - index: compiled.len() - 1, - compiled: compiled.into(), + index: 0, + data: Arc::new(ModuleData { types, modules }), }) } @@ -290,10 +295,12 @@ impl Module { pub fn serialize(&self) -> Result> { let artifacts = ( compiler_fingerprint(&self.engine), - self.compiled + self.data + .modules .iter() .map(|i| i.compilation_artifacts()) .collect::>(), + &self.data.types, self.index, ); @@ -313,14 +320,14 @@ impl Module { pub fn deserialize(engine: &Engine, serialized: &[u8]) -> Result { let expected_fingerprint = compiler_fingerprint(engine); - let (fingerprint, artifacts, index) = bincode_options() - .deserialize::<(u64, _, _)>(serialized) + let (fingerprint, artifacts, types, index) = bincode_options() + .deserialize::<(u64, _, _, _)>(serialized) .context("Deserialize compilation artifacts")?; if fingerprint != expected_fingerprint { bail!("Incompatible compilation artifact"); } - let compiled = CompiledModule::from_artifacts_list( + let modules = CompiledModule::from_artifacts_list( artifacts, engine.compiler().isa(), &*engine.config().profiler, @@ -329,12 +336,20 @@ impl Module { Ok(Module { engine: engine.clone(), index, - compiled: compiled.into(), + data: Arc::new(ModuleData { modules, types }), }) } pub(crate) fn compiled_module(&self) -> &CompiledModule { - &self.compiled[self.index] + &self.data.modules[self.index] + } + + pub(crate) fn all_compiled_modules(&self) -> &[CompiledModule] { + &self.data.modules + } + + pub(crate) fn types(&self) -> &TypeTables { + &self.data.types } /// Returns identifier/name that this [`Module`] has. This name @@ -419,12 +434,21 @@ impl Module { ) -> impl ExactSizeIterator> + 'module { let module = self.compiled_module().module(); module - .imports + .initializers .iter() - .map(move |(module_name, name, entity_index)| { - let r#type = EntityType::new(entity_index, module); - ImportType::new(module_name, name.as_deref(), r#type) + .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, }) + .collect::>() + .into_iter() } /// Returns the list of exports that this [`Module`] has and will be @@ -486,8 +510,8 @@ impl Module { ) -> impl ExactSizeIterator> + 'module { let module = self.compiled_module().module(); module.exports.iter().map(move |(name, entity_index)| { - let r#type = EntityType::new(entity_index, module); - ExportType::new(name, r#type) + let ty = EntityType::new(entity_index, self); + ExportType::new(name, ty) }) } @@ -537,7 +561,7 @@ impl Module { pub fn get_export<'module>(&'module self, name: &'module str) -> Option { let module = self.compiled_module().module(); let entity_index = module.exports.get(name)?; - Some(EntityType::new(entity_index, module).extern_type()) + Some(EntityType::new(entity_index, self).extern_type()) } /// Returns the [`Engine`] that this [`Module`] was compiled by. diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index 5317631cc4f7..9761b40b9190 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -11,7 +11,7 @@ use std::hash::{Hash, Hasher}; use std::rc::{Rc, Weak}; use std::sync::Arc; use wasmtime_environ::wasm; -use wasmtime_jit::{CompiledModule, ModuleCode}; +use wasmtime_jit::{CompiledModule, ModuleCode, TypeTables}; use wasmtime_runtime::{ InstanceHandle, RuntimeMemoryCreator, SignalHandler, StackMapRegistry, TrapInfo, VMExternRef, VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex, @@ -137,17 +137,17 @@ impl Store { pub(crate) fn lookup_shared_signature<'a>( &'a self, - module: &'a wasmtime_environ::Module, + types: &'a TypeTables, ) -> impl Fn(wasm::SignatureIndex) -> VMSharedSignatureIndex + 'a { move |index| { self.signatures() .borrow() - .lookup(&module.signatures[index]) + .lookup(&types.wasm_signatures[index]) .expect("signature not previously registered") } } - pub(crate) fn register_module(&self, module: &CompiledModule) { + pub(crate) fn register_module(&self, module: &CompiledModule, types: &TypeTables) { // All modules register their JIT code in a store for two reasons // currently: // @@ -169,7 +169,7 @@ impl Store { // 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); + self.register_signatures(module, types); // And finally with a module being instantiated into this `Store` we // need to preserve its jit-code. References to this module's code and @@ -205,11 +205,10 @@ impl Store { })); } - fn register_signatures(&self, module: &CompiledModule) { + fn register_signatures(&self, module: &CompiledModule, types: &TypeTables) { let trampolines = module.trampolines(); - let module = module.module(); let mut signatures = self.signatures().borrow_mut(); - for (index, wasm) in module.signatures.iter() { + for (index, wasm) in types.wasm_signatures.iter() { signatures.register(wasm, trampolines[index]); } } diff --git a/crates/wasmtime/src/trampoline/create_handle.rs b/crates/wasmtime/src/trampoline/create_handle.rs index 874431861cca..f597987e1b7b 100644 --- a/crates/wasmtime/src/trampoline/create_handle.rs +++ b/crates/wasmtime/src/trampoline/create_handle.rs @@ -10,7 +10,7 @@ use wasmtime_environ::wasm::DefinedFuncIndex; use wasmtime_environ::Module; use wasmtime_runtime::{ Imports, InstanceHandle, StackMapRegistry, VMExternRefActivationsTable, VMFunctionBody, - VMFunctionImport, + VMFunctionImport, VMSharedSignatureIndex, }; pub(crate) fn create_handle( @@ -19,11 +19,11 @@ pub(crate) fn create_handle( finished_functions: PrimaryMap, state: Box, func_imports: &[VMFunctionImport], + shared_signature_id: Option, ) -> Result { let mut imports = Imports::default(); imports.functions = func_imports; let module = Arc::new(module); - let module2 = module.clone(); unsafe { let handle = InstanceHandle::new( @@ -31,7 +31,7 @@ pub(crate) fn create_handle( &finished_functions, imports, store.memory_creator(), - &store.lookup_shared_signature(&module2), + &|_| shared_signature_id.unwrap(), state, store.interrupts(), store.externref_activations_table() as *const VMExternRefActivationsTable as *mut _, diff --git a/crates/wasmtime/src/trampoline/func.rs b/crates/wasmtime/src/trampoline/func.rs index 0050f7333046..1855a178eb8d 100644 --- a/crates/wasmtime/src/trampoline/func.rs +++ b/crates/wasmtime/src/trampoline/func.rs @@ -10,7 +10,8 @@ use std::mem; use std::panic::{self, AssertUnwindSafe}; use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::isa::TargetIsa; -use wasmtime_environ::{ir, wasm, CompiledFunction, Module}; +use wasmtime_environ::wasm::SignatureIndex; +use wasmtime_environ::{ir, wasm, CompiledFunction, Module, ModuleType}; use wasmtime_jit::trampoline::ir::{ ExternalName, Function, InstBuilder, MemFlags, StackSlotData, StackSlotKind, }; @@ -223,7 +224,8 @@ pub fn create_handle_with_function( // First up we manufacture a trampoline which has the ABI specified by `ft` // and calls into `stub_fn`... - let sig_id = module.signatures.push(wft.clone()); + let sig_id = SignatureIndex::from_u32(u32::max_value() - 1); + module.types.push(ModuleType::Function(sig_id)); let func_id = module.functions.push(sig_id); module .exports @@ -241,7 +243,7 @@ pub fn create_handle_with_function( &sig, mem::size_of::(), )?; - store.signatures().borrow_mut().register(wft, trampoline); + let shared_signature_id = store.signatures().borrow_mut().register(wft, trampoline); // Next up we wrap everything up into an `InstanceHandle` by publishing our // code memory (makes it executable) and ensuring all our various bits of @@ -254,6 +256,7 @@ pub fn create_handle_with_function( finished_functions, Box::new(trampoline_state), &[], + Some(shared_signature_id), ) .map(|instance| (instance, trampoline)) } @@ -270,13 +273,21 @@ pub unsafe fn create_handle_with_raw_function( let mut module = Module::new(); let mut finished_functions = PrimaryMap::new(); - let sig_id = module.signatures.push(wft.clone()); + let sig_id = SignatureIndex::from_u32(u32::max_value() - 1); + module.types.push(ModuleType::Function(sig_id)); let func_id = module.functions.push(sig_id); module .exports .insert(String::new(), wasm::EntityIndex::Function(func_id)); finished_functions.push(func); - store.signatures().borrow_mut().register(wft, trampoline); + let shared_signature_id = store.signatures().borrow_mut().register(wft, trampoline); - create_handle(module, store, finished_functions, state, &[]) + create_handle( + module, + store, + finished_functions, + state, + &[], + Some(shared_signature_id), + ) } diff --git a/crates/wasmtime/src/trampoline/global.rs b/crates/wasmtime/src/trampoline/global.rs index dbaf7b34198f..00a91bdb917a 100644 --- a/crates/wasmtime/src/trampoline/global.rs +++ b/crates/wasmtime/src/trampoline/global.rs @@ -3,13 +3,17 @@ use crate::trampoline::StoreInstanceHandle; use crate::{GlobalType, Mutability, Store, Val}; use anyhow::Result; use wasmtime_environ::entity::PrimaryMap; -use wasmtime_environ::{wasm, Module}; +use wasmtime_environ::{ + wasm::{self, SignatureIndex}, + Module, ModuleType, +}; use wasmtime_runtime::VMFunctionImport; pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result { let mut module = Module::new(); let mut func_imports = Vec::new(); let mut externref_init = None; + let mut shared_signature_id = None; let global = wasm::Global { wasm_ty: gt.content().to_wasm_type(), @@ -35,17 +39,19 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result { // Add a function import to the stub module, and then initialize // our global with a `ref.func` to grab that imported function. - let signatures = store.signatures().borrow(); let shared_sig_index = f.sig_index(); - let (wasm, _) = signatures - .lookup_shared(shared_sig_index) - .expect("signature not registered"); - let local_sig_index = module.signatures.push(wasm.clone()); - let func_index = module.functions.push(local_sig_index); + shared_signature_id = Some(shared_sig_index); + let sig_id = SignatureIndex::from_u32(u32::max_value() - 1); + module.types.push(ModuleType::Function(sig_id)); + let func_index = module.functions.push(sig_id); module.num_imported_funcs = 1; module - .imports - .push(("".into(), None, wasm::EntityIndex::Function(func_index))); + .initializers + .push(wasmtime_environ::Initializer::Import { + module: "".into(), + field: None, + index: wasm::EntityIndex::Function(func_index), + }); let f = f.caller_checked_anyfunc(); let f = unsafe { f.as_ref() }; @@ -70,6 +76,7 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result Result ExternType { + fn from_wasmtime(module: &Module, ty: &wasmtime_environ::wasm::EntityType) -> ExternType { use wasmtime_environ::wasm::EntityType; match ty { EntityType::Function(idx) => { - let sig = module.types[*idx].unwrap_function(); - let sig = &module.signatures[sig]; + let sig = &module.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 (imports, exports) = match &module.types[*ty] { - wasmtime_environ::ModuleType::Module { imports, exports } => (imports, exports), - _ => unreachable!("not possible in valid wasm modules"), - }; - ModuleType::from_wasmtime(module, imports, exports).into() + let ty = &module.types().module_signatures[*ty]; + ModuleType::from_wasmtime(module, ty).into() } EntityType::Instance(ty) => { - let exports = match &module.types[*ty] { - wasmtime_environ::ModuleType::Instance { exports } => exports, - _ => unreachable!("not possible in valid wasm modules"), - }; - InstanceType::from_wasmtime(module, exports).into() + let ty = &module.types().instance_signatures[*ty]; + InstanceType::from_wasmtime(module, ty).into() } EntityType::Event(_) => unimplemented!("wasm event support"), } @@ -499,16 +490,17 @@ impl ModuleType { } pub(crate) fn from_wasmtime( - module: &wasmtime_environ::Module, - imports: &[(String, Option, wasmtime_environ::wasm::EntityType)], - exports: &[(String, wasmtime_environ::wasm::EntityType)], + module: &Module, + ty: &wasmtime_environ::ModuleSignature, ) -> ModuleType { + let exports = &module.types().instance_signatures[ty.exports].exports; ModuleType { exports: exports .iter() .map(|(name, ty)| (name.to_string(), ExternType::from_wasmtime(module, ty))) .collect(), - imports: imports + imports: ty + .imports .iter() .map(|(m, name, ty)| { ( @@ -556,11 +548,12 @@ impl InstanceType { } pub(crate) fn from_wasmtime( - module: &wasmtime_environ::Module, - exports: &[(String, wasmtime_environ::wasm::EntityType)], + module: &Module, + ty: &wasmtime_environ::InstanceSignature, ) -> InstanceType { InstanceType { - exports: exports + exports: ty + .exports .iter() .map(|(name, ty)| (name.to_string(), ExternType::from_wasmtime(module, ty))) .collect(), @@ -577,13 +570,12 @@ pub(crate) enum EntityType<'module> { Memory(&'module wasm::Memory), Global(&'module wasm::Global), Module { - imports: &'module [(String, Option, wasmtime_environ::wasm::EntityType)], - exports: &'module [(String, wasmtime_environ::wasm::EntityType)], - module: &'module wasmtime_environ::Module, + ty: &'module wasmtime_environ::ModuleSignature, + module: &'module Module, }, Instance { - exports: &'module [(String, wasmtime_environ::wasm::EntityType)], - module: &'module wasmtime_environ::Module, + ty: &'module wasmtime_environ::InstanceSignature, + module: &'module Module, }, } @@ -591,51 +583,31 @@ impl<'module> EntityType<'module> { /// Translate from a `EntityIndex` into an `ExternType`. pub(crate) fn new( entity_index: &wasm::EntityIndex, - module: &'module wasmtime_environ::Module, + module: &'module Module, ) -> EntityType<'module> { + let env_module = module.compiled_module().module(); match entity_index { wasm::EntityIndex::Function(func_index) => { - let sig = module.wasm_func_type(*func_index); - EntityType::Function(&sig) + 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(&module.table_plans[*table_index].table) + EntityType::Table(&env_module.table_plans[*table_index].table) } wasm::EntityIndex::Memory(memory_index) => { - EntityType::Memory(&module.memory_plans[*memory_index].memory) + EntityType::Memory(&env_module.memory_plans[*memory_index].memory) } wasm::EntityIndex::Global(global_index) => { - EntityType::Global(&module.globals[*global_index]) + EntityType::Global(&env_module.globals[*global_index]) } wasm::EntityIndex::Module(idx) => { - let (imports, exports) = match &module.types[module.modules[*idx]] { - wasmtime_environ::ModuleType::Module { imports, exports } => (imports, exports), - _ => unreachable!("valid modules should never hit this"), - }; - EntityType::Module { - imports, - exports, - module, - } + let ty = &module.types().module_signatures[env_module.modules[*idx]]; + EntityType::Module { ty, module } } wasm::EntityIndex::Instance(idx) => { - // Get the type, either a pointer to an instance for an import - // or a module for an instantiation. - let ty = match module.instances[*idx] { - wasmtime_environ::Instance::Import(ty) => ty, - wasmtime_environ::Instance::Instantiate { module: idx, .. } => { - module.modules[idx] - } - }; - // Get the exports of whatever our type specifies, ignoring - // imports in the module case since we're instantiating the - // module. - let exports = match &module.types[ty] { - wasmtime_environ::ModuleType::Instance { exports } => exports, - wasmtime_environ::ModuleType::Module { exports, .. } => exports, - _ => unreachable!("valid modules should never hit this"), - }; - EntityType::Instance { exports, module } + let ty = &module.types().instance_signatures[env_module.instances[*idx]]; + EntityType::Instance { ty, module } } } } @@ -647,14 +619,8 @@ impl<'module> EntityType<'module> { 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 { exports, module } => { - InstanceType::from_wasmtime(module, exports).into() - } - EntityType::Module { - imports, - exports, - module, - } => ModuleType::from_wasmtime(module, imports, exports).into(), + EntityType::Instance { module, ty } => InstanceType::from_wasmtime(module, ty).into(), + EntityType::Module { module, ty } => ModuleType::from_wasmtime(module, ty).into(), } } } diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs index 1009a32537a6..692e2f583a64 100644 --- a/crates/wast/src/wast.rs +++ b/crates/wast/src/wast.rs @@ -226,20 +226,25 @@ impl WastContext { for directive in ast.directives { let sp = directive.span(); - self.run_directive(directive).with_context(|| { - let (line, col) = sp.linecol_in(wast); - format!("failed directive on {}:{}:{}", filename, line + 1, col) - })?; + self.run_directive(directive, &adjust_wast) + .with_context(|| { + let (line, col) = sp.linecol_in(wast); + format!("failed directive on {}:{}:{}", filename, line + 1, col) + })?; } Ok(()) } - fn run_directive(&mut self, directive: wast::WastDirective) -> Result<()> { + fn run_directive( + &mut self, + directive: wast::WastDirective, + adjust: impl Fn(wast::Error) -> wast::Error, + ) -> Result<()> { use wast::WastDirective::*; match directive { Module(mut module) => { - let binary = module.encode()?; + let binary = module.encode().map_err(adjust)?; self.module(module.id.map(|s| s.name()), &binary)?; } QuoteModule { span: _, source } => { @@ -249,7 +254,10 @@ impl WastContext { module.push_str(" "); } let buf = ParseBuffer::new(&module)?; - let mut wat = parser::parse::(&buf)?; + let mut wat = parser::parse::(&buf).map_err(|mut e| { + e.set_text(&module); + e + })?; let binary = wat.module.encode()?; self.module(wat.module.id.map(|s| s.name()), &binary)?; } @@ -317,7 +325,7 @@ impl WastContext { // interested in. wast::QuoteModule::Quote(_) => return Ok(()), }; - let bytes = module.encode()?; + let bytes = module.encode().map_err(adjust)?; if let Ok(_) = self.module(None, &bytes) { bail!("expected malformed module to fail to instantiate"); } @@ -327,7 +335,7 @@ impl WastContext { mut module, message, } => { - let bytes = module.encode()?; + let bytes = module.encode().map_err(adjust)?; let err = match self.module(None, &bytes) { Ok(()) => bail!("expected module to fail to link"), Err(e) => e, diff --git a/src/obj.rs b/src/obj.rs index 8e3ebc6b61a5..524ba1a0e26a 100644 --- a/src/obj.rs +++ b/src/obj.rs @@ -64,10 +64,10 @@ pub fn compile_to_obj( ); let environ = ModuleEnvironment::new(compiler.isa().frontend_config(), &tunables, &features); - let mut translation = environ + let (mut translation, types) = environ .translate(wasm) .context("failed to translate module")?; assert_eq!(translation.len(), 1); - let compilation = compiler.compile(&mut translation[0])?; + let compilation = compiler.compile(&mut translation[0], &types)?; Ok(compilation.obj) } diff --git a/tests/all/module_linking.rs b/tests/all/module_linking.rs index 30a4b2708814..f352eb7f545d 100644 --- a/tests/all/module_linking.rs +++ b/tests/all/module_linking.rs @@ -52,3 +52,126 @@ fn types() -> Result<()> { Module::new(&engine, "(module (type (instance)))")?; Ok(()) } + +#[test] +fn imports_exports() -> Result<()> { + let engine = engine(); + + // empty module type + let module = Module::new(&engine, "(module (module (export \"\")))")?; + let mut e = module.exports(); + assert_eq!(e.len(), 1); + let export = e.next().unwrap(); + assert_eq!(export.name(), ""); + let module_ty = match export.ty() { + ExternType::Module(m) => m, + _ => panic!("unexpected type"), + }; + assert_eq!(module_ty.imports().len(), 0); + assert_eq!(module_ty.exports().len(), 0); + + // empty instance type + let module = Module::new( + &engine, + " + (module + (module) + (instance (export \"\") (instantiate 0))) + ", + )?; + let mut e = module.exports(); + assert_eq!(e.len(), 1); + let export = e.next().unwrap(); + assert_eq!(export.name(), ""); + let instance_ty = match export.ty() { + ExternType::Instance(i) => i, + _ => panic!("unexpected type"), + }; + assert_eq!(instance_ty.exports().len(), 0); + + // full module type + let module = Module::new( + &engine, + " + (module + (import \"\" \"a\" (module + (import \"a\" (func)) + (export \"\" (global i32)) + )) + ) + ", + )?; + let mut i = module.imports(); + assert_eq!(i.len(), 1); + let import = i.next().unwrap(); + assert_eq!(import.module(), ""); + assert_eq!(import.name(), Some("a")); + let module_ty = match import.ty() { + ExternType::Module(m) => m, + _ => panic!("unexpected type"), + }; + assert_eq!(module_ty.imports().len(), 1); + assert_eq!(module_ty.exports().len(), 1); + let import = module_ty.imports().next().unwrap(); + assert_eq!(import.module(), "a"); + assert_eq!(import.name(), None); + match import.ty() { + ExternType::Func(f) => { + assert_eq!(f.results().len(), 0); + assert_eq!(f.params().len(), 0); + } + _ => panic!("unexpected type"), + } + let export = module_ty.exports().next().unwrap(); + assert_eq!(export.name(), ""); + match export.ty() { + ExternType::Global(g) => { + assert_eq!(*g.content(), ValType::I32); + assert_eq!(g.mutability(), Mutability::Const); + } + _ => panic!("unexpected type"), + } + + // full instance type + let module = Module::new( + &engine, + " + (module + (import \"\" \"b\" (instance + (export \"m\" (memory 1)) + (export \"t\" (table 1 funcref)) + )) + ) + ", + )?; + let mut i = module.imports(); + assert_eq!(i.len(), 1); + let import = i.next().unwrap(); + assert_eq!(import.module(), ""); + assert_eq!(import.name(), Some("b")); + let instance_ty = match import.ty() { + ExternType::Instance(m) => m, + _ => panic!("unexpected type"), + }; + assert_eq!(instance_ty.exports().len(), 2); + let mem_export = instance_ty.exports().nth(0).unwrap(); + assert_eq!(mem_export.name(), "m"); + match mem_export.ty() { + ExternType::Memory(m) => { + assert_eq!(m.limits().min(), 1); + assert_eq!(m.limits().max(), None); + } + _ => panic!("unexpected type"), + } + let table_export = instance_ty.exports().nth(1).unwrap(); + assert_eq!(table_export.name(), "t"); + match table_export.ty() { + ExternType::Table(t) => { + assert_eq!(t.limits().min(), 1); + assert_eq!(t.limits().max(), None); + assert_eq!(*t.element(), ValType::FuncRef); + } + _ => panic!("unexpected type"), + } + Ok(()) +} diff --git a/tests/misc_testsuite/module-linking/alias.wast b/tests/misc_testsuite/module-linking/alias.wast new file mode 100644 index 000000000000..f21d7f1d7f6c --- /dev/null +++ b/tests/misc_testsuite/module-linking/alias.wast @@ -0,0 +1,114 @@ +;; functions +(module + (module $m + (func $foo (export "foo") (result i32) + i32.const 1) + ) + (instance $a (instantiate $m)) + + (func (export "get") (result i32) + call $a.$foo) +) +(assert_return (invoke "get") (i32.const 1)) + +;; globals +(module + (module $m + (global $g (export "g") (mut i32) (i32.const 2)) + ) + (instance $a (instantiate $m)) + + (func (export "get") (result i32) + global.get $a.$g) +) +(assert_return (invoke "get") (i32.const 2)) + +;; memories +(module + (module $m + (memory $m (export "m") 1) + (data (i32.const 0) "\03\00\00\00") + ) + (instance $a (instantiate $m)) + (alias (instance $a) (memory $m)) + + (func (export "get") (result i32) + i32.const 0 + i32.load) +) +(assert_return (invoke "get") (i32.const 3)) + +;; tables +(module + (module $m + (table $t (export "t") 1 funcref) + (func (result i32) + i32.const 4) + (elem (i32.const 0) 0) + ) + (instance $a (instantiate $m)) + + (func (export "get") (result i32) + i32.const 0 + call_indirect $a.$t (result i32)) +) +(assert_return (invoke "get") (i32.const 4)) + +;; TODO instances/modules -- needs import/export of modules/instances to work + +;; alias parent -- type +(module + (type $t (func)) + (module $m + (func $f (type $t)) + ) + (instance $a (instantiate $m)) +) + +;; alias parent -- module +(module + (module $a) + (module $m + (instance (instantiate $a)) + ) + (instance (instantiate $m)) +) + +;; The alias, import, type, module, and instance sections can all be interleaved +(module + (module $a) + (type $t (func)) + (module $m + ;; alias + (alias $thunk parent (type $t)) + ;; import + (import "" "" (func (type $thunk))) + ;; module (referencing previous alias) + (module + (func (type $thunk)) + ) + ;; type + (type $thunk2 (func)) + ;; module (referencing previous alias) + (module $m2 + (func (export "") (type $thunk2)) + ) + ;; instance + (instance $i (instantiate $m2)) + ;; alias that instance + (alias $my_f (instance $i) (func 0)) + ;; module + (module $m3 + (import "" (func))) + ;; use our aliased function to create the module + (instance $i2 (instantiate $m3 (func $my_f))) + ;; module + (module $m4 + (import "" (func))) + ) + + ;; instantiate the above module + (module $smol (func $f (export ""))) + (instance $smol (instantiate $smol)) + (instance (instantiate $m (func $smol.$f))) +) diff --git a/tests/misc_testsuite/module-linking/instantiate.wast b/tests/misc_testsuite/module-linking/instantiate.wast index 9afe725f5200..a0e24c7a7bac 100644 --- a/tests/misc_testsuite/module-linking/instantiate.wast +++ b/tests/misc_testsuite/module-linking/instantiate.wast @@ -74,13 +74,48 @@ (import "" (memory 1)) (func i32.const 0 - i32.const 4 + i32.const 100 i32.store) (start 0)) (instance $a (instantiate 0 (memory $m))) ) -(assert_return (invoke $a "load") (i32.const 4)) +(assert_return (invoke $a "load") (i32.const 100)) + +;; Imported instances work +(module + (import "a" "inc" (func $set)) + + (module $m1 + (import "" (instance (export "" (func)))) + (alias (instance 0) (func 0)) + (start 0)) + + (module $m2 + (func (export "") (import ""))) + (instance $i (instantiate $m2 (func $set))) + (instance (instantiate $m1 (instance $i))) +) +(assert_return (invoke $a "get") (i32.const 4)) + +;; Imported modules work +(module + (import "a" "inc" (func $set)) + + (module $m1 + (import "" (module $m (export "" (func $f (result i32))))) + (instance $i (instantiate $m)) + (func $get (export "") (result i32) + call $i.$f)) + + (module $m2 + (func (export "") (result i32) + i32.const 5)) + (instance $i (instantiate $m1 (module $m2))) + (func (export "get") (result i32) + call $i.$get) +) +(assert_return (invoke "get") (i32.const 5)) ;; all at once (module