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/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