diff --git a/cranelift/wasm/src/environ/spec.rs b/cranelift/wasm/src/environ/spec.rs index 06e207e4fe59..b01cd43764bc 100644 --- a/cranelift/wasm/src/environ/spec.rs +++ b/cranelift/wasm/src/environ/spec.rs @@ -847,4 +847,31 @@ pub trait ModuleEnvironment<'data>: TargetEnvironment { fn wasm_features(&self) -> WasmFeatures { WasmFeatures::default() } + + /// Indicates that this module will have `amount` submodules. + /// + /// Note that this is just child modules of this module, and each child + /// module may have yet more submodules. + fn reserve_modules(&mut self, amount: u32) { + drop(amount); + } + + /// Called at the beginning of translating a module. + /// + /// The `index` argument is a monotonically increasing index which + /// corresponds to the nth module that's being translated. This is not the + /// 32-bit index in the current module's index space. For example the first + /// call to `module_start` will have index 0. + /// + /// Note that for nested modules this may be called multiple times. + fn module_start(&mut self, index: usize) { + drop(index); + } + + /// Called at the end of translating a module. + /// + /// Note that for nested modules this may be called multiple times. + fn module_end(&mut self, index: usize) { + drop(index); + } } diff --git a/cranelift/wasm/src/module_translator.rs b/cranelift/wasm/src/module_translator.rs index 89e6923fcf86..0b1794158f1c 100644 --- a/cranelift/wasm/src/module_translator.rs +++ b/cranelift/wasm/src/module_translator.rs @@ -8,6 +8,7 @@ use crate::sections_translator::{ }; use crate::state::ModuleTranslationState; use cranelift_codegen::timing; +use std::prelude::v1::*; use wasmparser::{NameSectionReader, Parser, Payload, Validator}; /// Translate a sequence of bytes forming a valid Wasm binary into a list of valid Cranelift IR @@ -20,14 +21,23 @@ pub fn translate_module<'data>( let mut module_translation_state = ModuleTranslationState::new(); let mut validator = Validator::new(); validator.wasm_features(environ.wasm_features()); + let mut stack = Vec::new(); + let mut modules = 1; + let mut cur_module = 0; for payload in Parser::new(0).parse_all(data) { match payload? { Payload::Version { num, range } => { validator.version(num, &range)?; + environ.module_start(cur_module); } Payload::End => { validator.end()?; + environ.module_end(cur_module); + if let Some((other, other_index)) = stack.pop() { + validator = other; + cur_module = other_index; + } } Payload::TypeSection(types) => { @@ -97,7 +107,7 @@ pub fn translate_module<'data>( Payload::ModuleSection(s) => { validator.module_section(&s)?; - unimplemented!("module linking not implemented yet") + environ.reserve_modules(s.get_count()); } Payload::InstanceSection(s) => { validator.instance_section(&s)?; @@ -113,11 +123,14 @@ pub fn translate_module<'data>( size: _, } => { validator.module_code_section_start(count, &range)?; - unimplemented!("module linking not implemented yet") } Payload::ModuleCodeSectionEntry { .. } => { - unimplemented!("module linking not implemented yet") + let subvalidator = validator.module_code_section_entry(); + stack.push((validator, cur_module)); + validator = subvalidator; + cur_module = modules; + modules += 1; } Payload::CustomSection { diff --git a/cranelift/wasm/src/sections_translator.rs b/cranelift/wasm/src/sections_translator.rs index da52b2846802..a2bb649557e6 100644 --- a/cranelift/wasm/src/sections_translator.rs +++ b/cranelift/wasm/src/sections_translator.rs @@ -41,25 +41,43 @@ pub fn parse_type_section( environ.reserve_signatures(count)?; for entry in types { - if let Ok(TypeDef::Func(wasm_func_ty)) = entry { - let mut sig = - Signature::new(ModuleEnvironment::target_config(environ).default_call_conv); - sig.params.extend(wasm_func_ty.params.iter().map(|ty| { - let cret_arg: ir::Type = type_to_type(*ty, environ) - .expect("only numeric types are supported in function signatures"); - AbiParam::new(cret_arg) - })); - sig.returns.extend(wasm_func_ty.returns.iter().map(|ty| { - let cret_arg: ir::Type = type_to_type(*ty, environ) - .expect("only numeric types are supported in function signatures"); - AbiParam::new(cret_arg) - })); - environ.declare_signature(wasm_func_ty.clone().try_into()?, sig)?; - module_translation_state - .wasm_types - .push((wasm_func_ty.params, wasm_func_ty.returns)); - } else { - unimplemented!("module linking not implemented yet") + match entry? { + TypeDef::Func(wasm_func_ty) => { + let mut sig = + Signature::new(ModuleEnvironment::target_config(environ).default_call_conv); + sig.params.extend(wasm_func_ty.params.iter().map(|ty| { + let cret_arg: ir::Type = type_to_type(*ty, environ) + .expect("only numeric types are supported in function signatures"); + AbiParam::new(cret_arg) + })); + sig.returns.extend(wasm_func_ty.returns.iter().map(|ty| { + let cret_arg: ir::Type = type_to_type(*ty, environ) + .expect("only numeric types are supported in function signatures"); + AbiParam::new(cret_arg) + })); + environ.declare_signature(wasm_func_ty.clone().try_into()?, sig)?; + module_translation_state + .wasm_types + .push((wasm_func_ty.params, wasm_func_ty.returns)); + } + + // Not implemented yet for module linking. Push dummy function types + // though to keep the function type index space consistent. We'll + // want an actual implementation here that handles this eventually. + TypeDef::Module(_) | TypeDef::Instance(_) => { + let sig = + Signature::new(ModuleEnvironment::target_config(environ).default_call_conv); + environ.declare_signature( + crate::environ::WasmFuncType { + params: Box::new([]), + returns: Box::new([]), + }, + sig, + )?; + module_translation_state + .wasm_types + .push((Box::new([]), Box::new([]))); + } } } Ok(()) diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs index 30ba228b2af2..f128ab5717c8 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, + TrapInformation, Tunables, }; mod func_environ; @@ -356,9 +356,9 @@ impl Compiler for Cranelift { func_index: DefinedFuncIndex, mut input: FunctionBodyData<'_>, isa: &dyn isa::TargetIsa, + tunables: &Tunables, ) -> Result { let module = &translation.module; - let tunables = &translation.tunables; let func_index = module.func_index(func_index); let mut context = Context::new(); context.func.name = get_func_name(func_index); diff --git a/crates/environ/src/compilation.rs b/crates/environ/src/compilation.rs index c3ecd2e451a4..5008273bc779 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}; +use crate::{FunctionAddressMap, FunctionBodyData, ModuleTranslation, Tunables}; use cranelift_codegen::{binemit, ir, isa, isa::unwind::UnwindInfo}; use cranelift_entity::PrimaryMap; use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmError}; @@ -103,5 +103,6 @@ pub trait Compiler: Send + Sync { index: DefinedFuncIndex, data: FunctionBodyData<'_>, isa: &dyn isa::TargetIsa, + tunables: &Tunables, ) -> Result; } diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index 54a162e4ddda..d2c651578e8e 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -325,6 +325,12 @@ impl Module { } } +impl Default for Module { + fn default() -> Module { + Module::new() + } +} + mod passive_data_serde { use super::{Arc, DataIndex, HashMap}; use serde::{de::MapAccess, de::Visitor, ser::SerializeMap, Deserializer, Serializer}; diff --git a/crates/environ/src/module_environ.rs b/crates/environ/src/module_environ.rs index 60e105924184..85ae46ba81b9 100644 --- a/crates/environ/src/module_environ.rs +++ b/crates/environ/src/module_environ.rs @@ -6,12 +6,13 @@ use cranelift_codegen::isa::TargetFrontendConfig; use cranelift_entity::PrimaryMap; use cranelift_wasm::{ self, translate_module, DataIndex, DefinedFuncIndex, ElemIndex, FuncIndex, Global, GlobalIndex, - Memory, MemoryIndex, ModuleTranslationState, SignatureIndex, Table, TableIndex, - TargetEnvironment, WasmError, WasmFuncType, WasmResult, + Memory, MemoryIndex, SignatureIndex, Table, TableIndex, TargetEnvironment, WasmError, + WasmFuncType, WasmResult, }; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::convert::TryFrom; +use std::mem; use std::path::PathBuf; use std::sync::Arc; use wasmparser::Type as WasmType; @@ -19,19 +20,29 @@ use wasmparser::{FuncValidator, FunctionBody, ValidatorResources, WasmFeatures}; /// Object containing the standalone environment information. pub struct ModuleEnvironment<'data> { - /// The result to be filled in. + /// The current module being translated result: ModuleTranslation<'data>, - code_index: u32, + + /// Modules which have finished translation. This only really applies for + /// 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>, + + // Various bits and pieces of configuration features: WasmFeatures, + target_config: TargetFrontendConfig, + tunables: Tunables, } /// The result of translating via `ModuleEnvironment`. Function bodies are not /// yet translated, and data initializers have not yet been copied out of the /// original buffer. +#[derive(Default)] pub struct ModuleTranslation<'data> { - /// Compilation setting flags. - pub target_config: TargetFrontendConfig, - /// Module information. pub module: Module, @@ -44,14 +55,14 @@ pub struct ModuleTranslation<'data> { /// References to the data initializers. pub data_initializers: Vec>, - /// Tunable parameters. - pub tunables: Tunables, + /// DWARF debug information, if enabled, parsed from the module. + pub debuginfo: DebugInfoData<'data>, - /// The decoded Wasm types for the module. - pub module_translation: Option, + /// Indexes into the returned list of translations that are submodules of + /// this module. + pub submodules: Vec, - /// DWARF debug information, if enabled, parsed from the module. - pub debuginfo: Option>, + code_index: u32, } /// Contains function data: byte code and its offset in the module. @@ -111,36 +122,25 @@ impl<'data> ModuleEnvironment<'data> { features: &WasmFeatures, ) -> Self { Self { - result: ModuleTranslation { - target_config, - module: Module::new(), - native_signatures: PrimaryMap::new(), - function_body_inputs: PrimaryMap::new(), - data_initializers: Vec::new(), - tunables: tunables.clone(), - module_translation: None, - debuginfo: if tunables.debug_info { - Some(DebugInfoData::default()) - } else { - None - }, - }, - code_index: 0, + result: ModuleTranslation::default(), + results: Vec::with_capacity(1), + in_progress: Vec::new(), + target_config, + tunables: tunables.clone(), features: *features, } } fn pointer_type(&self) -> ir::Type { - self.result.target_config.pointer_type() + 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> { - assert!(self.result.module_translation.is_none()); - let module_translation = translate_module(data, &mut self)?; - self.result.module_translation = Some(module_translation); - Ok(self.result) + pub fn translate(mut self, data: &'data [u8]) -> WasmResult>> { + translate_module(data, &mut self)?; + assert!(self.results.len() > 0); + Ok(self.results) } fn declare_export(&mut self, export: EntityIndex, name: &str) -> WasmResult<()> { @@ -152,13 +152,13 @@ impl<'data> ModuleEnvironment<'data> { } fn register_dwarf_section(&mut self, name: &str, data: &'data [u8]) { - let info = match &mut self.result.debuginfo { - Some(info) => info, - None => return, - }; + if !self.tunables.debug_info { + return; + } if !name.starts_with(".debug_") { return; } + let info = &mut self.result.debuginfo; let dwarf = &mut info.dwarf; let endian = gimli::LittleEndian; let slice = gimli::EndianSlice::new(data, endian); @@ -190,7 +190,7 @@ impl<'data> ModuleEnvironment<'data> { impl<'data> TargetEnvironment for ModuleEnvironment<'data> { fn target_config(&self) -> TargetFrontendConfig { - self.result.target_config + self.target_config } fn reference_type(&self, ty: cranelift_wasm::WasmType) -> ir::Type { @@ -242,9 +242,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data EntityIndex::Function(func_index), )); self.result.module.num_imported_funcs += 1; - if let Some(info) = &mut self.result.debuginfo { - info.wasm_file.imported_func_count += 1; - } + self.result.debuginfo.wasm_file.imported_func_count += 1; Ok(()) } @@ -254,7 +252,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data self.result.module.num_imported_tables, "Imported tables must be declared first" ); - let plan = TablePlan::for_table(table, &self.result.tunables); + 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(), @@ -279,7 +277,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data if memory.shared { return Err(WasmError::Unsupported("shared memories".to_owned())); } - let plan = MemoryPlan::for_memory(memory, &self.result.tunables); + 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(), @@ -336,7 +334,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data } fn declare_table(&mut self, table: Table) -> WasmResult<()> { - let plan = TablePlan::for_table(table, &self.result.tunables); + let plan = TablePlan::for_table(table, &self.tunables); self.result.module.table_plans.push(plan); Ok(()) } @@ -353,7 +351,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data if memory.shared { return Err(WasmError::Unsupported("shared memories".to_owned())); } - let plan = MemoryPlan::for_memory(memory, &self.result.tunables); + let plan = MemoryPlan::for_memory(memory, &self.tunables); self.result.module.memory_plans.push(plan); Ok(()) } @@ -444,9 +442,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data } fn reserve_function_bodies(&mut self, _count: u32, offset: u64) { - if let Some(info) = &mut self.result.debuginfo { - info.wasm_file.code_section_offset = offset; - } + self.result.debuginfo.wasm_file.code_section_offset = offset; } fn define_function_body( @@ -454,8 +450,8 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data validator: FuncValidator, body: FunctionBody<'data>, ) -> WasmResult<()> { - if let Some(info) = &mut self.result.debuginfo { - let func_index = self.code_index + self.result.module.num_imported_funcs as u32; + if self.tunables.debug_info { + 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]; @@ -463,15 +459,19 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data for pair in body.get_locals_reader()? { locals.push(pair?); } - info.wasm_file.funcs.push(FunctionMetadata { - locals: locals.into_boxed_slice(), - params: sig.params.iter().cloned().map(|i| i.into()).collect(), - }); + self.result + .debuginfo + .wasm_file + .funcs + .push(FunctionMetadata { + locals: locals.into_boxed_slice(), + params: sig.params.iter().cloned().map(|i| i.into()).collect(), + }); } self.result .function_body_inputs .push(FunctionBodyData { validator, body }); - self.code_index += 1; + self.result.code_index += 1; Ok(()) } @@ -520,8 +520,8 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data fn declare_module_name(&mut self, name: &'data str) { self.result.module.name = Some(name.to_string()); - if let Some(info) = &mut self.result.debuginfo { - info.name_section.module_name = Some(name); + if self.tunables.debug_info { + self.result.debuginfo.name_section.module_name = Some(name); } } @@ -530,16 +530,20 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data .module .func_names .insert(func_index, name.to_string()); - if let Some(info) = &mut self.result.debuginfo { - info.name_section + if self.tunables.debug_info { + self.result + .debuginfo + .name_section .func_names .insert(func_index.as_u32(), name); } } fn declare_local_name(&mut self, func_index: FuncIndex, local: u32, name: &'data str) { - if let Some(info) = &mut self.result.debuginfo { - info.name_section + if self.tunables.debug_info { + self.result + .debuginfo + .name_section .locals_names .entry(func_index.as_u32()) .or_insert(HashMap::new()) @@ -574,6 +578,34 @@ and for re-adding support for interface types you can see this issue: fn wasm_features(&self) -> WasmFeatures { self.features } + + fn reserve_modules(&mut self, amount: u32) { + let extra = self.results.capacity() + (amount as usize) - self.results.len(); + self.results.reserve(extra); + self.result.submodules.reserve(amount as usize); + } + + fn module_start(&mut self, index: usize) { + // skip the first module since `self.result` is already empty and we'll + // be translating into that. + if index > 0 { + let in_progress = mem::replace(&mut self.result, ModuleTranslation::default()); + self.in_progress.push(in_progress); + } + } + + 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); + } } /// Add environment-specific function parameters. diff --git a/crates/jit/src/compiler.rs b/crates/jit/src/compiler.rs index 86adb133d559..48fcb2c688e7 100644 --- a/crates/jit/src/compiler.rs +++ b/crates/jit/src/compiler.rs @@ -3,6 +3,8 @@ use crate::instantiate::SetupError; use crate::object::{build_object, ObjectUnwindInfo}; use object::write::Object; +#[cfg(feature = "parallel-compilation")] +use rayon::prelude::*; use std::hash::{Hash, Hasher}; use std::mem; use wasmparser::WasmFeatures; @@ -127,31 +129,21 @@ impl Compiler { translation: &mut ModuleTranslation, ) -> Result { let functions = mem::take(&mut translation.function_body_inputs); - cfg_if::cfg_if! { - if #[cfg(feature = "parallel-compilation")] { - use rayon::prelude::*; - let iter = functions - .into_iter() - .collect::>() - .into_par_iter(); - } else { - let iter = functions.into_iter(); - } - } - let funcs = iter + 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) + .compile_function(translation, index, func, &*self.isa, &self.tunables) }) .collect::, _>>()? .into_iter() .collect::(); - let dwarf_sections = if translation.debuginfo.is_some() && !funcs.is_empty() { + let dwarf_sections = if self.tunables.debug_info && !funcs.is_empty() { transform_dwarf_data( &*self.isa, &translation.module, - translation.debuginfo.as_ref().unwrap(), + &translation.debuginfo, &funcs, )? } else { @@ -191,6 +183,9 @@ impl Hash for Compiler { isa.frontend_config().hash(hasher); tunables.hash(hasher); + // Catch accidental bugs of reusing across crate versions. + env!("CARGO_PKG_VERSION").hash(hasher); + // TODO: ... and should we hash anything else? There's a lot of stuff in // `TargetIsa`, like registers/encodings/etc. Should we be hashing that // too? It seems like wasmtime doesn't configure it too too much, but diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index e8b224ebe437..7807c80c39c7 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -8,6 +8,8 @@ use crate::compiler::{Compilation, Compiler}; use crate::link::link_module; use crate::object::ObjectUnwindInfo; use object::File as ObjectFile; +#[cfg(feature = "parallel-compilation")] +use rayon::prelude::*; use serde::{Deserialize, Serialize}; use std::any::Any; use std::sync::Arc; @@ -71,70 +73,75 @@ pub struct CompilationArtifacts { debug_info: bool, } -#[derive(Serialize, Deserialize, Clone)] -struct FunctionInfo { - traps: Vec, - address_map: FunctionAddressMap, - stack_maps: Vec, -} - impl CompilationArtifacts { - /// Builds compilation artifacts. - pub fn build(compiler: &Compiler, data: &[u8]) -> Result { - let environ = ModuleEnvironment::new( + /// Creates a `CompilationArtifacts` for a singular translated wasm module. + pub fn build( + compiler: &Compiler, + data: &[u8], + ) -> Result, SetupError> { + let translations = ModuleEnvironment::new( compiler.frontend_config(), compiler.tunables(), compiler.features(), - ); - - let mut translation = environ - .translate(data) - .map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?; - - let Compilation { - obj, - unwind_info, - funcs, - } = compiler.compile(&mut translation)?; - - let ModuleTranslation { - module, - data_initializers, - .. - } = translation; - - let data_initializers = data_initializers - .into_iter() - .map(OwnedDataInitializer::new) - .collect::>() - .into_boxed_slice(); - - let obj = obj.write().map_err(|_| { - SetupError::Instantiate(InstantiationError::Resource( - "failed to create image memory".to_string(), - )) - })?; - - Ok(Self { - module, - obj: obj.into_boxed_slice(), - unwind_info: unwind_info.into_boxed_slice(), - data_initializers, - funcs: funcs - .into_iter() - .map(|(_, func)| FunctionInfo { - stack_maps: func.stack_maps, - traps: func.traps, - address_map: func.address_map, + ) + .translate(data) + .map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?; + + maybe_parallel!(translations.(into_iter | into_par_iter)) + .map(|mut translation| { + let Compilation { + obj, + unwind_info, + funcs, + } = compiler.compile(&mut translation)?; + + let ModuleTranslation { + module, + data_initializers, + .. + } = translation; + + let data_initializers = data_initializers + .into_iter() + .map(OwnedDataInitializer::new) + .collect::>() + .into_boxed_slice(); + + let obj = obj.write().map_err(|_| { + SetupError::Instantiate(InstantiationError::Resource( + "failed to create image memory".to_string(), + )) + })?; + + Ok(CompilationArtifacts { + module, + obj: obj.into_boxed_slice(), + unwind_info: unwind_info.into_boxed_slice(), + data_initializers, + funcs: funcs + .into_iter() + .map(|(_, func)| FunctionInfo { + stack_maps: func.stack_maps, + traps: func.traps, + address_map: func.address_map, + }) + .collect(), + debug_info: compiler.tunables().debug_info, }) - .collect(), - debug_info: compiler.tunables().debug_info, - }) + }) + .collect::, SetupError>>() } } struct FinishedFunctions(PrimaryMap); +#[derive(Serialize, Deserialize, Clone)] +struct FunctionInfo { + traps: Vec, + address_map: FunctionAddressMap, + stack_maps: Vec, +} + unsafe impl Send for FinishedFunctions {} unsafe impl Sync for FinishedFunctions {} @@ -147,25 +154,24 @@ pub struct ModuleCode { /// A compiled wasm module, ready to be instantiated. pub struct CompiledModule { + artifacts: CompilationArtifacts, module: Arc, code: Arc, finished_functions: FinishedFunctions, trampolines: PrimaryMap, - data_initializers: Box<[OwnedDataInitializer]>, - funcs: PrimaryMap, - obj: Box<[u8]>, - unwind_info: Box<[ObjectUnwindInfo]>, } impl CompiledModule { - /// Compile a data buffer into a `CompiledModule`, which may then be instantiated. - pub fn new<'data>( - compiler: &Compiler, - data: &'data [u8], + /// Creates a list of compiled modules from the given list of compilation + /// artifacts. + pub fn from_artifacts_list( + artifacts: Vec, + isa: &dyn TargetIsa, profiler: &dyn ProfilingAgent, - ) -> Result { - let artifacts = CompilationArtifacts::build(compiler, data)?; - Self::from_artifacts(artifacts, compiler.isa(), profiler) + ) -> Result, SetupError> { + maybe_parallel!(artifacts.(into_iter | into_par_iter)) + .map(|a| CompiledModule::from_artifacts(a, isa, profiler)) + .collect() } /// Creates `CompiledModule` directly from `CompilationArtifacts`. @@ -174,67 +180,51 @@ impl CompiledModule { isa: &dyn TargetIsa, profiler: &dyn ProfilingAgent, ) -> Result { - let CompilationArtifacts { - module, - obj, - unwind_info, - data_initializers, - funcs, - debug_info, - } = artifacts; - // Allocate all of the compiled functions into executable memory, // copying over their contents. - let (code_memory, code_range, finished_functions, trampolines) = - build_code_memory(isa, &obj, &module, &unwind_info).map_err(|message| { - SetupError::Instantiate(InstantiationError::Resource(format!( - "failed to build code memory for functions: {}", - message - ))) - })?; + let (code_memory, code_range, finished_functions, trampolines) = build_code_memory( + isa, + &artifacts.obj, + &artifacts.module, + &artifacts.unwind_info, + ) + .map_err(|message| { + SetupError::Instantiate(InstantiationError::Resource(format!( + "failed to build code memory for functions: {}", + message + ))) + })?; // Register GDB JIT images; initialize profiler and load the wasm module. - let dbg_jit_registration = if debug_info { - let bytes = create_dbg_image(obj.to_vec(), code_range, &module, &finished_functions)?; - - profiler.module_load(&module, &finished_functions, Some(&bytes)); - + let dbg_jit_registration = if artifacts.debug_info { + let bytes = create_dbg_image( + artifacts.obj.to_vec(), + code_range, + &artifacts.module, + &finished_functions, + )?; + profiler.module_load(&artifacts.module, &finished_functions, Some(&bytes)); let reg = GdbJitImageRegistration::register(bytes); Some(reg) } else { - profiler.module_load(&module, &finished_functions, None); + profiler.module_load(&artifacts.module, &finished_functions, None); None }; let finished_functions = FinishedFunctions(finished_functions); Ok(Self { - module: Arc::new(module), + module: Arc::new(artifacts.module.clone()), + artifacts, code: Arc::new(ModuleCode { code_memory, dbg_jit_registration, }), finished_functions, trampolines, - data_initializers, - funcs, - obj, - unwind_info, }) } - /// Extracts `CompilationArtifacts` from the compiled module. - pub fn to_compilation_artifacts(&self) -> CompilationArtifacts { - CompilationArtifacts { - module: (*self.module).clone(), - obj: self.obj.clone(), - unwind_info: self.unwind_info.clone(), - data_initializers: self.data_initializers.clone(), - funcs: self.funcs.clone(), - debug_info: self.code.dbg_jit_registration.is_some(), - } - } - /// Crate an `Instance` from this `CompiledModule`. /// /// Note that if only one instance of this module is needed, it may be more @@ -267,10 +257,15 @@ impl CompiledModule { stack_map_registry, ) } + /// Extracts `CompilationArtifacts` from the compiled module. + pub fn compilation_artifacts(&self) -> &CompilationArtifacts { + &self.artifacts + } /// Returns data initializers to pass to `InstanceHandle::initialize` pub fn data_initializers(&self) -> Vec> { - self.data_initializers + self.artifacts + .data_initializers .iter() .map(|init| DataInitializer { location: init.location.clone(), @@ -307,10 +302,12 @@ impl CompiledModule { pub fn stack_maps( &self, ) -> impl Iterator { - self.finished_functions() - .values() - .copied() - .zip(self.funcs.values().map(|f| f.stack_maps.as_slice())) + self.finished_functions().values().copied().zip( + self.artifacts + .funcs + .values() + .map(|f| f.stack_maps.as_slice()), + ) } /// Iterates over all functions in this module, returning information about @@ -327,7 +324,7 @@ impl CompiledModule { > { self.finished_functions() .iter() - .zip(self.funcs.values()) + .zip(self.artifacts.funcs.values()) .map(|((i, alloc), func)| (i, *alloc, func.traps.as_slice(), &func.address_map)) } diff --git a/crates/jit/src/lib.rs b/crates/jit/src/lib.rs index 28c1d701c416..faa3e8150c59 100644 --- a/crates/jit/src/lib.rs +++ b/crates/jit/src/lib.rs @@ -21,6 +21,20 @@ ) )] +#[cfg(feature = "parallel-compilation")] +macro_rules! maybe_parallel { + ($e:ident.($serial:ident | $parallel:ident)) => { + $e.$parallel() + }; +} + +#[cfg(not(feature = "parallel-compilation"))] +macro_rules! maybe_parallel { + ($e:ident.($serial:ident | $parallel:ident)) => { + $e.$serial() + }; +} + mod code_memory; mod compiler; mod instantiate; diff --git a/crates/lightbeam/wasmtime/src/lib.rs b/crates/lightbeam/wasmtime/src/lib.rs index 50309f6c6300..a1e67c62711c 100644 --- a/crates/lightbeam/wasmtime/src/lib.rs +++ b/crates/lightbeam/wasmtime/src/lib.rs @@ -14,7 +14,7 @@ use wasmtime_environ::wasm::{ use wasmtime_environ::{ entity::PrimaryMap, BuiltinFunctionIndex, CompileError, CompiledFunction, Compiler, FunctionBodyData, Module, ModuleTranslation, Relocation, RelocationTarget, TrapInformation, - VMOffsets, + Tunables, VMOffsets, }; /// A compiler that compiles a WebAssembly module with Lightbeam, directly translating the Wasm file. @@ -27,8 +27,9 @@ impl Compiler for Lightbeam { i: DefinedFuncIndex, function_body: FunctionBodyData<'_>, isa: &dyn isa::TargetIsa, + tunables: &Tunables, ) -> Result { - if translation.tunables.debug_info { + if tunables.debug_info { return Err(CompileError::DebugInfoNotSupported); } let func_index = translation.module.func_index(i); diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index 1b4967dfd4ad..9da0181a893e 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -1,8 +1,9 @@ use crate::frame_info::GlobalFrameInfoRegistration; -use crate::runtime::{Config, Engine}; use crate::types::{EntityType, ExportType, ExternType, ImportType}; +use crate::Engine; use anyhow::{bail, Context, Result}; use bincode::Options; +use std::hash::Hash; use std::path::Path; use std::sync::{Arc, Mutex}; use wasmparser::Validator; @@ -81,7 +82,8 @@ use wasmtime_jit::{CompilationArtifacts, CompiledModule}; #[derive(Clone)] pub struct Module { engine: Engine, - compiled: Arc, + compiled: Arc<[CompiledModule]>, + index: usize, frame_info_registration: Arc>>>>, } @@ -164,8 +166,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() + Arc::get_mut(&mut module.compiled).unwrap()[module.index] .module_mut() .expect("mutable module") .name = Some(name.to_string()); @@ -248,7 +249,7 @@ impl Module { #[cfg(not(feature = "cache"))] let artifacts = CompilationArtifacts::build(engine.compiler(), binary)?; - let compiled = CompiledModule::from_artifacts( + let compiled = CompiledModule::from_artifacts_list( artifacts, engine.compiler().isa(), &*engine.config().profiler, @@ -256,7 +257,8 @@ impl Module { Ok(Module { engine: engine.clone(), - compiled: Arc::new(compiled), + index: compiled.len() - 1, + compiled: compiled.into(), frame_info_registration: Arc::new(Mutex::new(None)), }) } @@ -290,8 +292,12 @@ impl Module { /// Serialize compilation artifacts to the buffer. See also `deseriaize`. pub fn serialize(&self) -> Result> { let artifacts = ( - compiler_fingerprint(self.engine.config()), - self.compiled.to_compilation_artifacts(), + compiler_fingerprint(&self.engine), + self.compiled + .iter() + .map(|i| i.compilation_artifacts()) + .collect::>(), + self.index, ); let buffer = bincode_options().serialize(&artifacts)?; @@ -308,16 +314,16 @@ impl Module { /// for modifications or curruptions. All responsibily of signing and its /// verification falls on the embedder. pub fn deserialize(engine: &Engine, serialized: &[u8]) -> Result { - let expected_fingerprint = compiler_fingerprint(engine.config()); + let expected_fingerprint = compiler_fingerprint(engine); - let (fingerprint, artifacts) = bincode_options() - .deserialize::<(u64, CompilationArtifacts)>(serialized) + let (fingerprint, artifacts, index) = bincode_options() + .deserialize::<(u64, _, _)>(serialized) .context("Deserialize compilation artifacts")?; if fingerprint != expected_fingerprint { bail!("Incompatible compilation artifact"); } - let compiled = CompiledModule::from_artifacts( + let compiled = CompiledModule::from_artifacts_list( artifacts, engine.compiler().isa(), &*engine.config().profiler, @@ -325,13 +331,14 @@ impl Module { Ok(Module { engine: engine.clone(), - compiled: Arc::new(compiled), + index, + compiled: compiled.into(), frame_info_registration: Arc::new(Mutex::new(None)), }) } pub(crate) fn compiled_module(&self) -> &CompiledModule { - &self.compiled + &self.compiled[self.index] } /// Returns identifier/name that this [`Module`] has. This name @@ -359,7 +366,7 @@ impl Module { /// # } /// ``` pub fn name(&self) -> Option<&str> { - self.compiled.module().name.as_deref() + self.compiled_module().module().name.as_deref() } /// Returns the list of imports that this [`Module`] has and must be @@ -414,7 +421,7 @@ impl Module { pub fn imports<'module>( &'module self, ) -> impl ExactSizeIterator> + 'module { - let module = self.compiled.module(); + let module = self.compiled_module().module(); module .imports .iter() @@ -481,7 +488,7 @@ impl Module { pub fn exports<'module>( &'module self, ) -> impl ExactSizeIterator> + 'module { - let module = self.compiled.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) @@ -532,7 +539,7 @@ impl Module { /// # } /// ``` pub fn get_export<'module>(&'module self, name: &'module str) -> Option { - let module = self.compiled.module(); + let module = self.compiled_module().module(); let entity_index = module.exports.get(name)?; Some(EntityType::new(entity_index, module).extern_type()) } @@ -550,7 +557,7 @@ impl Module { if let Some(info) = &*info { return info.clone(); } - let ret = super::frame_info::register(&self.compiled).map(Arc::new); + let ret = super::frame_info::register(self.compiled_module()).map(Arc::new); *info = Some(ret.clone()); return ret; } @@ -567,10 +574,10 @@ fn bincode_options() -> impl Options { bincode::DefaultOptions::new().with_varint_encoding() } -fn compiler_fingerprint(config: &Config) -> u64 { +fn compiler_fingerprint(engine: &Engine) -> u64 { use std::hash::Hasher; let mut hasher = std::collections::hash_map::DefaultHasher::new(); - config.compiler_fingerprint(&mut hasher); + engine.compiler().hash(&mut hasher); hasher.finish() } diff --git a/crates/wasmtime/src/runtime.rs b/crates/wasmtime/src/runtime.rs index d17238ebafb7..2697c3db0070 100644 --- a/crates/wasmtime/src/runtime.rs +++ b/crates/wasmtime/src/runtime.rs @@ -12,7 +12,6 @@ use std::hash::{Hash, Hasher}; use std::path::Path; use std::rc::{Rc, Weak}; use std::sync::Arc; -use target_lexicon::Triple; use wasmparser::WasmFeatures; #[cfg(feature = "cache")] use wasmtime_cache::CacheConfig; @@ -257,6 +256,20 @@ impl Config { self } + /// Configures whether the WebAssembly module linking [proposal] will + /// be enabled for compilation. + /// + /// Note that development of this feature is still underway, so enabling + /// this is likely to be full of bugs. + /// + /// This is `false` by default. + /// + /// [proposal]: https://github.com/webassembly/module-linking + pub fn wasm_module_linking(&mut self, enable: bool) -> &mut Self { + self.features.module_linking = enable; + self + } + /// Configures which compilation strategy will be used for wasm modules. /// /// This method can be used to configure which compiler is used for wasm @@ -616,22 +629,6 @@ impl Config { let isa = self.target_isa(); Compiler::new(isa, self.strategy, self.tunables.clone(), self.features) } - - /// Hashes/fingerprints compiler setting to ensure that compatible - /// compilation artifacts are used. - pub(crate) fn compiler_fingerprint(&self, state: &mut H) - where - H: Hasher, - { - self.flags.hash(state); - self.tunables.hash(state); - - let triple = Triple::host(); - triple.hash(state); - - // Catch accidental bugs of reusing across wasmtime versions. - env!("CARGO_PKG_VERSION").hash(state); - } } fn round_up_to_pages(val: u64) -> u64 { diff --git a/src/obj.rs b/src/obj.rs index 48048e904701..8e3ebc6b61a5 100644 --- a/src/obj.rs +++ b/src/obj.rs @@ -67,6 +67,7 @@ pub fn compile_to_obj( let mut translation = environ .translate(wasm) .context("failed to translate module")?; - let compilation = compiler.compile(&mut translation)?; + assert_eq!(translation.len(), 1); + let compilation = compiler.compile(&mut translation[0])?; Ok(compilation.obj) } diff --git a/tests/all/iloop.rs b/tests/all/iloop.rs index 23e6f85aa8d3..dd41eec23ef4 100644 --- a/tests/all/iloop.rs +++ b/tests/all/iloop.rs @@ -82,6 +82,7 @@ fn loop_interrupt_from_afar() -> anyhow::Result<()> { while HITS.load(SeqCst) <= 100_000 { // continue ... } + println!("interrupting"); handle.interrupt(); }); diff --git a/tests/all/main.rs b/tests/all/main.rs index ff4c45229256..414708144f22 100644 --- a/tests/all/main.rs +++ b/tests/all/main.rs @@ -12,6 +12,7 @@ mod instance; mod invoke_func_via_table; mod linker; mod memory_creator; +mod module_linking; mod module_serialize; mod name; mod stack_overflow; diff --git a/tests/all/module_linking.rs b/tests/all/module_linking.rs new file mode 100644 index 000000000000..61949c576d38 --- /dev/null +++ b/tests/all/module_linking.rs @@ -0,0 +1,46 @@ +use anyhow::Result; +use wasmtime::*; + +fn engine() -> Engine { + let mut config = Config::new(); + config.wasm_module_linking(true); + Engine::new(&config) +} + +#[test] +fn compile() -> Result<()> { + let engine = engine(); + Module::new(&engine, "(module (module))")?; + Module::new(&engine, "(module (module) (module))")?; + Module::new(&engine, "(module (module (module)))")?; + Module::new( + &engine, + " + (module + (func) + (module (func)) + (module (func)) + ) + ", + )?; + let m = Module::new( + &engine, + " + (module + (global i32 (i32.const 0)) + (func) + (module (memory 1) (func)) + (module (memory 2) (func)) + (module (table 2 funcref) (func)) + (module (global i64 (i64.const 0)) (func)) + ) + ", + )?; + assert_eq!(m.imports().len(), 0); + assert_eq!(m.exports().len(), 0); + let bytes = m.serialize()?; + Module::deserialize(&engine, &bytes)?; + assert_eq!(m.imports().len(), 0); + assert_eq!(m.exports().len(), 0); + Ok(()) +}