diff --git a/Cargo.lock b/Cargo.lock index f829cc5bff..acfc8bf474 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2166,7 +2166,6 @@ dependencies = [ "log", "wasmtime", "wasmtime-fuzzing", - "wasmtime-jit", ] [[package]] @@ -2181,8 +2180,6 @@ dependencies = [ "wasmparser 0.47.0", "wasmprinter", "wasmtime", - "wasmtime-environ", - "wasmtime-jit", "wat", ] diff --git a/crates/api/src/instance.rs b/crates/api/src/instance.rs index 3299b16382..64f5024dfa 100644 --- a/crates/api/src/instance.rs +++ b/crates/api/src/instance.rs @@ -20,36 +20,22 @@ impl Resolver for SimpleResolver<'_> { } } -fn instantiate_in_context( - store: &Store, - data: &[u8], +fn instantiate( + compiled_module: &CompiledModule, imports: &[Extern], - module_name: Option<&str>, -) -> Result { +) -> Result { let mut resolver = SimpleResolver { imports }; - let mut compiled_module = CompiledModule::new( - &mut store.compiler_mut(), - data, - module_name, - &mut resolver, - store.global_exports().clone(), - store.engine().config().debug_info, - )?; - - // Register all module signatures - for signature in compiled_module.module().signatures.values() { - store.register_wasmtime_signature(signature); - } - - let instance = compiled_module.instantiate().map_err(|e| -> Error { - if let Some(trap) = take_api_trap() { - trap.into() - } else if let InstantiationError::StartTrap(trap) = e { - Trap::from_jit(trap).into() - } else { - e.into() - } - })?; + let instance = compiled_module + .instantiate(&mut resolver) + .map_err(|e| -> Error { + if let Some(trap) = take_api_trap() { + trap.into() + } else if let InstantiationError::StartTrap(trap) = e { + Trap::from_jit(trap).into() + } else { + e.into() + } + })?; Ok(instance) } @@ -63,12 +49,8 @@ pub struct Instance { impl Instance { pub fn new(module: &Module, externs: &[Extern]) -> Result { let store = module.store(); - let mut instance_handle = instantiate_in_context( - store, - module.binary().expect("binary"), - externs, - module.name(), - )?; + let mut instance_handle = + instantiate(module.compiled_module().expect("compiled_module"), externs)?; let exports = { let mut exports = Vec::with_capacity(module.exports().len()); diff --git a/crates/api/src/module.rs b/crates/api/src/module.rs index d6fd7f0579..f901a83762 100644 --- a/crates/api/src/module.rs +++ b/crates/api/src/module.rs @@ -9,6 +9,7 @@ use wasmparser::{ validate, CustomSectionKind, ExternalKind, ImportSectionEntryType, ModuleReader, Name, OperatorValidatorConfig, SectionCode, ValidatingParserConfig, }; +use wasmtime_jit::CompiledModule; fn into_memory_type(mt: wasmparser::MemoryType) -> MemoryType { assert!(!mt.shared); @@ -56,12 +57,6 @@ fn into_table_type(tt: wasmparser::TableType) -> TableType { TableType::new(ty, limits) } -#[derive(Clone)] -pub(crate) enum ModuleCodeSource { - Binary(Box<[u8]>), - Unknown, -} - /// A compiled WebAssembly module, ready to be instantiated. /// /// A `Module` is a compiled in-memory representation of an input WebAssembly @@ -84,10 +79,10 @@ pub struct Module { struct ModuleInner { store: Store, - source: ModuleCodeSource, imports: Box<[ImportType]>, exports: Box<[ExportType]>, name: Option, + compiled: Option, } impl Module { @@ -131,7 +126,7 @@ impl Module { // Note that the call to `unsafe` here should be ok because we // previously validated the binary, meaning we're guaranteed to pass a // valid binary for `store`. - unsafe { Module::new_unchecked(store, binary) } + unsafe { Module::new_internal(store, binary, None) } } /// Creates a new WebAssembly `Module` from the given in-memory `binary` @@ -139,9 +134,11 @@ impl Module { /// /// See [`Module::new`] for other details. pub fn new_with_name(store: &Store, binary: &[u8], name: &str) -> Result { - let mut ret = Module::new(store, binary)?; - Rc::get_mut(&mut ret.inner).unwrap().name = Some(name.to_string()); - Ok(ret) + Module::validate(store, binary)?; + // Note that the call to `unsafe` here should be ok because we + // previously validated the binary, meaning we're guaranteed to pass a + // valid binary for `store`. + unsafe { Module::new_internal(store, binary, Some(name)) } } /// Creates a new WebAssembly `Module` from the given in-memory `binary` @@ -171,8 +168,20 @@ impl Module { /// be somewhat valid for decoding purposes, and the basics of decoding can /// still fail. pub unsafe fn new_unchecked(store: &Store, binary: &[u8]) -> Result { + Module::new_internal(store, binary, None) + } + + /// Creates a new `Module` and compiles it without doing any validation. + unsafe fn new_internal(store: &Store, binary: &[u8], name: Option<&str>) -> Result { let mut ret = Module::empty(store); ret.read_imports_and_exports(binary)?; + + let inner = Rc::get_mut(&mut ret.inner).unwrap(); + if let Some(name) = name { + // Assign or override the module's name if supplied. + inner.name = Some(name.to_string()); + } + inner.compiled = Some(compile(store, binary, inner.name.as_deref())?); Ok(ret) } @@ -219,19 +228,16 @@ impl Module { Module { inner: Rc::new(ModuleInner { store: store.clone(), - source: ModuleCodeSource::Unknown, imports: Box::new([]), exports: Box::new([]), name: None, + compiled: None, }), } } - pub(crate) fn binary(&self) -> Option<&[u8]> { - match &self.inner.source { - ModuleCodeSource::Binary(b) => Some(b), - _ => None, - } + pub(crate) fn compiled_module(&self) -> Option<&CompiledModule> { + self.inner.compiled.as_ref() } /// Returns identifier/name that this [`Module`] has. This name @@ -259,7 +265,6 @@ impl Module { fn read_imports_and_exports(&mut self, binary: &[u8]) -> Result<()> { let inner = Rc::get_mut(&mut self.inner).unwrap(); - inner.source = ModuleCodeSource::Binary(binary.into()); let mut reader = ModuleReader::new(binary)?; let mut imports = Vec::new(); let mut exports = Vec::new(); @@ -387,3 +392,21 @@ impl Module { Ok(()) } } + +fn compile(store: &Store, binary: &[u8], module_name: Option<&str>) -> Result { + let exports = store.global_exports().clone(); + let compiled_module = CompiledModule::new( + &mut store.compiler_mut(), + binary, + module_name, + exports, + store.engine().config().debug_info, + )?; + + // Register all module signatures + for signature in compiled_module.module().signatures.values() { + store.register_wasmtime_signature(signature); + } + + Ok(compiled_module) +} diff --git a/crates/fuzzing/Cargo.toml b/crates/fuzzing/Cargo.toml index 81b1d471b3..98fe89affe 100644 --- a/crates/fuzzing/Cargo.toml +++ b/crates/fuzzing/Cargo.toml @@ -17,8 +17,6 @@ log = "0.4.8" wasmparser = "0.47.0" wasmprinter = "0.2.0" wasmtime = { path = "../api", version = "0.9.0" } -wasmtime-environ = { path = "../environ", version = "0.9.0" } -wasmtime-jit = { path = "../jit", version = "0.9.0" } [dev-dependencies] wat = "1.0" diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs index bb5860c9a1..09d7afca8c 100644 --- a/crates/fuzzing/src/oracles.rs +++ b/crates/fuzzing/src/oracles.rs @@ -13,25 +13,15 @@ pub mod dummy; use dummy::{dummy_imports, dummy_value}; -use std::cell::RefCell; use std::collections::HashMap; -use std::rc::Rc; use wasmtime::*; -use wasmtime_environ::{isa, settings}; -use wasmtime_jit::{native, CompilationStrategy, CompiledModule, Compiler, NullResolver}; - -fn host_isa() -> Box { - let flag_builder = settings::builder(); - let isa_builder = native::builder(); - isa_builder.finish(settings::Flags::new(flag_builder)) -} /// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected /// panic or segfault or anything else that can be detected "passively". /// /// Performs initial validation, and returns early if the Wasm is invalid. /// -/// You can control which compiler is used via passing a `CompilationStrategy`. +/// You can control which compiler is used via passing a `Strategy`. pub fn instantiate(wasm: &[u8], strategy: Strategy) { if wasmparser::validate(wasm, None).is_err() { return; @@ -68,24 +58,13 @@ pub fn instantiate(wasm: &[u8], strategy: Strategy) { /// /// Performs initial validation, and returns early if the Wasm is invalid. /// -/// You can control which compiler is used via passing a `CompilationStrategy`. -pub fn compile(wasm: &[u8], compilation_strategy: CompilationStrategy) { - if wasmparser::validate(wasm, None).is_err() { - return; - } - - let isa = host_isa(); - let mut compiler = Compiler::new(isa, compilation_strategy); - let mut resolver = NullResolver {}; - let global_exports = Rc::new(RefCell::new(HashMap::new())); - let _ = CompiledModule::new( - &mut compiler, - wasm, - None, - &mut resolver, - global_exports, - false, - ); +/// You can control which compiler is used via passing a `Strategy`. +pub fn compile(wasm: &[u8], strategy: Strategy) { + let mut config = Config::new(); + config.strategy(strategy).unwrap(); + let engine = Engine::new(&config); + let store = Store::new(&engine); + let _ = Module::new(&store, wasm); } /// Invoke the given API calls. diff --git a/crates/jit/src/context.rs b/crates/jit/src/context.rs index 8632b0db8e..98f5d17682 100644 --- a/crates/jit/src/context.rs +++ b/crates/jit/src/context.rs @@ -156,7 +156,6 @@ impl Context { &mut *self.compiler, data, None, - &mut self.namespace, Rc::clone(&self.global_exports), debug_info, ) diff --git a/crates/jit/src/imports.rs b/crates/jit/src/imports.rs new file mode 100644 index 0000000000..a8f7daa24f --- /dev/null +++ b/crates/jit/src/imports.rs @@ -0,0 +1,289 @@ +//! Module imports resolving logic. + +use crate::resolver::Resolver; +use more_asserts::assert_ge; +use std::collections::HashSet; +use wasmtime_environ::entity::PrimaryMap; +use wasmtime_environ::wasm::{Global, GlobalInit, Memory, Table, TableElementType}; +use wasmtime_environ::{MemoryPlan, MemoryStyle, Module, TablePlan}; +use wasmtime_runtime::{ + Export, Imports, InstanceHandle, LinkError, VMFunctionImport, VMGlobalImport, VMMemoryImport, + VMTableImport, +}; + +/// This function allows to match all imports of a `Module` with concrete definitions provided by +/// a `Resolver`. +/// +/// If all imports are satisfied returns an `Imports` instance required for a module instantiation. +pub fn resolve_imports(module: &Module, resolver: &mut dyn Resolver) -> Result { + let mut dependencies = HashSet::new(); + + let mut function_imports = PrimaryMap::with_capacity(module.imported_funcs.len()); + for (index, (module_name, field, import_idx)) in module.imported_funcs.iter() { + match resolver.resolve(*import_idx, module_name, field) { + Some(export_value) => match export_value { + Export::Function { + address, + signature, + vmctx, + } => { + let import_signature = &module.signatures[module.functions[index]]; + if signature != *import_signature { + // TODO: If the difference is in the calling convention, + // we could emit a wrapper function to fix it up. + return Err(LinkError(format!( + "{}/{}: incompatible import type: exported function with signature {} \ + incompatible with function import with signature {}", + module_name, field, signature, import_signature + ))); + } + dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) }); + function_imports.push(VMFunctionImport { + body: address, + vmctx, + }); + } + Export::Table { .. } | Export::Memory { .. } | Export::Global { .. } => { + return Err(LinkError(format!( + "{}/{}: incompatible import type: export incompatible with function import", + module_name, field + ))); + } + }, + None => { + return Err(LinkError(format!( + "{}/{}: unknown import function: function not provided", + module_name, field + ))); + } + } + } + + let mut table_imports = PrimaryMap::with_capacity(module.imported_tables.len()); + for (index, (module_name, field, import_idx)) in module.imported_tables.iter() { + match resolver.resolve(*import_idx, module_name, field) { + Some(export_value) => match export_value { + Export::Table { + definition, + vmctx, + table, + } => { + let import_table = &module.table_plans[index]; + if !is_table_compatible(&table, import_table) { + return Err(LinkError(format!( + "{}/{}: incompatible import type: exported table incompatible with \ + table import", + module_name, field, + ))); + } + dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) }); + table_imports.push(VMTableImport { + from: definition, + vmctx, + }); + } + Export::Global { .. } | Export::Memory { .. } | Export::Function { .. } => { + return Err(LinkError(format!( + "{}/{}: incompatible import type: export incompatible with table import", + module_name, field + ))); + } + }, + None => { + return Err(LinkError(format!( + "unknown import: no provided import table for {}/{}", + module_name, field + ))); + } + } + } + + let mut memory_imports = PrimaryMap::with_capacity(module.imported_memories.len()); + for (index, (module_name, field, import_idx)) in module.imported_memories.iter() { + match resolver.resolve(*import_idx, module_name, field) { + Some(export_value) => match export_value { + Export::Memory { + definition, + vmctx, + memory, + } => { + let import_memory = &module.memory_plans[index]; + if !is_memory_compatible(&memory, import_memory) { + return Err(LinkError(format!( + "{}/{}: incompatible import type: exported memory incompatible with \ + memory import", + module_name, field + ))); + } + + // Sanity-check: Ensure that the imported memory has at least + // guard-page protections the importing module expects it to have. + if let ( + MemoryStyle::Static { bound }, + MemoryStyle::Static { + bound: import_bound, + }, + ) = (memory.style, &import_memory.style) + { + assert_ge!(bound, *import_bound); + } + assert_ge!(memory.offset_guard_size, import_memory.offset_guard_size); + + dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) }); + memory_imports.push(VMMemoryImport { + from: definition, + vmctx, + }); + } + Export::Table { .. } | Export::Global { .. } | Export::Function { .. } => { + return Err(LinkError(format!( + "{}/{}: incompatible import type: export incompatible with memory import", + module_name, field + ))); + } + }, + None => { + return Err(LinkError(format!( + "unknown import: no provided import memory for {}/{}", + module_name, field + ))); + } + } + } + + let mut global_imports = PrimaryMap::with_capacity(module.imported_globals.len()); + for (index, (module_name, field, import_idx)) in module.imported_globals.iter() { + match resolver.resolve(*import_idx, module_name, field) { + Some(export_value) => match export_value { + Export::Table { .. } | Export::Memory { .. } | Export::Function { .. } => { + return Err(LinkError(format!( + "{}/{}: incompatible import type: exported global incompatible with \ + global import", + module_name, field + ))); + } + Export::Global { + definition, + vmctx, + global, + } => { + let imported_global = module.globals[index]; + if !is_global_compatible(&global, &imported_global) { + return Err(LinkError(format!( + "{}/{}: incompatible import type: exported global incompatible with \ + global import", + module_name, field + ))); + } + dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) }); + global_imports.push(VMGlobalImport { from: definition }); + } + }, + None => { + return Err(LinkError(format!( + "unknown import: no provided import global for {}/{}", + module_name, field + ))); + } + } + } + + Ok(Imports::new( + dependencies, + function_imports, + table_imports, + memory_imports, + global_imports, + )) +} + +fn is_global_compatible(exported: &Global, imported: &Global) -> bool { + match imported.initializer { + GlobalInit::Import => (), + _ => panic!("imported Global should have an Imported initializer"), + } + + let Global { + ty: exported_ty, + mutability: exported_mutability, + initializer: _exported_initializer, + } = exported; + let Global { + ty: imported_ty, + mutability: imported_mutability, + initializer: _imported_initializer, + } = imported; + exported_ty == imported_ty && imported_mutability == exported_mutability +} + +fn is_table_element_type_compatible( + exported_type: TableElementType, + imported_type: TableElementType, +) -> bool { + match exported_type { + TableElementType::Func => match imported_type { + TableElementType::Func => true, + _ => false, + }, + TableElementType::Val(exported_val_ty) => match imported_type { + TableElementType::Val(imported_val_ty) => exported_val_ty == imported_val_ty, + _ => false, + }, + } +} + +fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool { + let TablePlan { + table: + Table { + ty: exported_ty, + minimum: exported_minimum, + maximum: exported_maximum, + }, + style: _exported_style, + } = exported; + let TablePlan { + table: + Table { + ty: imported_ty, + minimum: imported_minimum, + maximum: imported_maximum, + }, + style: _imported_style, + } = imported; + + is_table_element_type_compatible(*exported_ty, *imported_ty) + && imported_minimum <= exported_minimum + && (imported_maximum.is_none() + || (!exported_maximum.is_none() + && imported_maximum.unwrap() >= exported_maximum.unwrap())) +} + +fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool { + let MemoryPlan { + memory: + Memory { + minimum: exported_minimum, + maximum: exported_maximum, + shared: exported_shared, + }, + style: _exported_style, + offset_guard_size: _exported_offset_guard_size, + } = exported; + let MemoryPlan { + memory: + Memory { + minimum: imported_minimum, + maximum: imported_maximum, + shared: imported_shared, + }, + style: _imported_style, + offset_guard_size: _imported_offset_guard_size, + } = imported; + + imported_minimum <= exported_minimum + && (imported_maximum.is_none() + || (!exported_maximum.is_none() + && imported_maximum.unwrap() >= exported_maximum.unwrap())) + && exported_shared == imported_shared +} diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index dd927b2ed7..c303266c43 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -4,6 +4,7 @@ //! steps. use crate::compiler::Compiler; +use crate::imports::resolve_imports; use crate::link::link_module; use crate::resolver::Resolver; use std::cell::RefCell; @@ -19,7 +20,7 @@ use wasmtime_environ::{ ModuleSyncString, }; use wasmtime_runtime::{ - Export, GdbJitImageRegistration, Imports, InstanceHandle, InstantiationError, VMFunctionBody, + Export, GdbJitImageRegistration, InstanceHandle, InstantiationError, VMFunctionBody, VMSharedSignatureIndex, }; @@ -50,7 +51,6 @@ pub enum SetupError { struct RawCompiledModule<'data> { module: Module, finished_functions: BoxedSlice, - imports: Imports, data_initializers: Box<[DataInitializer<'data>]>, signatures: BoxedSlice, dbg_jit_registration: Option, @@ -62,7 +62,6 @@ impl<'data> RawCompiledModule<'data> { compiler: &mut Compiler, data: &'data [u8], module_name: Option<&str>, - resolver: &mut dyn Resolver, debug_info: bool, ) -> Result { let environ = ModuleEnvironment::new(compiler.frontend_config(), compiler.tunables()); @@ -86,14 +85,12 @@ impl<'data> RawCompiledModule<'data> { debug_data, )?; - let imports = link_module( + link_module( &translation.module, &allocated_functions, &jt_offsets, relocations, - resolver, - ) - .map_err(|err| SetupError::Instantiate(InstantiationError::Link(err)))?; + ); // Gather up the pointers to the compiled functions. let finished_functions: BoxedSlice = @@ -132,7 +129,6 @@ impl<'data> RawCompiledModule<'data> { Ok(Self { module: translation.module, finished_functions, - imports, data_initializers: translation.data_initializers.into_boxed_slice(), signatures: signatures.into_boxed_slice(), dbg_jit_registration, @@ -144,7 +140,6 @@ impl<'data> RawCompiledModule<'data> { pub struct CompiledModule { module: Rc, finished_functions: BoxedSlice, - imports: Imports, data_initializers: Box<[OwnedDataInitializer]>, signatures: BoxedSlice, global_exports: Rc>>>, @@ -157,18 +152,15 @@ impl CompiledModule { compiler: &mut Compiler, data: &'data [u8], module_name: Option<&str>, - resolver: &mut dyn Resolver, global_exports: Rc>>>, debug_info: bool, ) -> Result { - let raw = - RawCompiledModule::<'data>::new(compiler, data, module_name, resolver, debug_info)?; + let raw = RawCompiledModule::<'data>::new(compiler, data, module_name, debug_info)?; Ok(Self::from_parts( raw.module, global_exports, raw.finished_functions, - raw.imports, raw.data_initializers .iter() .map(OwnedDataInitializer::new) @@ -184,7 +176,6 @@ impl CompiledModule { module: Module, global_exports: Rc>>>, finished_functions: BoxedSlice, - imports: Imports, data_initializers: Box<[OwnedDataInitializer]>, signatures: BoxedSlice, dbg_jit_registration: Option, @@ -193,7 +184,6 @@ impl CompiledModule { module: Rc::new(module), global_exports: Rc::clone(&global_exports), finished_functions, - imports, data_initializers, signatures, dbg_jit_registration: dbg_jit_registration.map(Rc::new), @@ -205,7 +195,10 @@ impl CompiledModule { /// Note that if only one instance of this module is needed, it may be more /// efficient to call the top-level `instantiate`, since that avoids copying /// the data initializers. - pub fn instantiate(&mut self) -> Result { + pub fn instantiate( + &self, + resolver: &mut dyn Resolver, + ) -> Result { let data_initializers = self .data_initializers .iter() @@ -214,11 +207,12 @@ impl CompiledModule { data: &*init.data, }) .collect::>(); + let imports = resolve_imports(&self.module, resolver)?; InstanceHandle::new( Rc::clone(&self.module), Rc::clone(&self.global_exports), self.finished_functions.clone(), - self.imports.clone(), + imports, &data_initializers, self.signatures.clone(), self.dbg_jit_registration.as_ref().map(|r| Rc::clone(&r)), @@ -269,13 +263,14 @@ pub fn instantiate( global_exports: Rc>>>, debug_info: bool, ) -> Result { - let raw = RawCompiledModule::new(compiler, data, module_name, resolver, debug_info)?; - + let raw = RawCompiledModule::new(compiler, data, module_name, debug_info)?; + let imports = resolve_imports(&raw.module, resolver) + .map_err(|err| SetupError::Instantiate(InstantiationError::Link(err)))?; InstanceHandle::new( Rc::new(raw.module), global_exports, raw.finished_functions, - raw.imports, + imports, &*raw.data_initializers, raw.signatures, raw.dbg_jit_registration.map(Rc::new), diff --git a/crates/jit/src/lib.rs b/crates/jit/src/lib.rs index bc5fc3a2fe..30cf311991 100644 --- a/crates/jit/src/lib.rs +++ b/crates/jit/src/lib.rs @@ -26,6 +26,7 @@ mod code_memory; mod compiler; mod context; mod function_table; +mod imports; mod instantiate; mod link; mod namespace; diff --git a/crates/jit/src/link.rs b/crates/jit/src/link.rs index f8c9c62589..fd2b9ba9f0 100644 --- a/crates/jit/src/link.rs +++ b/crates/jit/src/link.rs @@ -1,313 +1,22 @@ //! Linking for JIT-compiled code. -use crate::resolver::Resolver; use cranelift_codegen::binemit::Reloc; use cranelift_codegen::ir::JumpTableOffsets; -use more_asserts::assert_ge; -use std::collections::HashSet; use std::ptr::write_unaligned; use wasmtime_environ::entity::PrimaryMap; -use wasmtime_environ::wasm::{ - DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType, -}; -use wasmtime_environ::{ - MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan, -}; +use wasmtime_environ::wasm::DefinedFuncIndex; +use wasmtime_environ::{Module, RelocationTarget, Relocations}; use wasmtime_runtime::libcalls; -use wasmtime_runtime::{ - Export, Imports, InstanceHandle, LinkError, VMFunctionBody, VMFunctionImport, VMGlobalImport, - VMMemoryImport, VMTableImport, -}; +use wasmtime_runtime::VMFunctionBody; /// Links a module that has been compiled with `compiled_module` in `wasmtime-environ`. +/// +/// Performs all required relocations inside the function code, provided the necessary metadata. pub fn link_module( module: &Module, allocated_functions: &PrimaryMap, jt_offsets: &PrimaryMap, relocations: Relocations, - resolver: &mut dyn Resolver, -) -> Result { - let mut dependencies = HashSet::new(); - - let mut function_imports = PrimaryMap::with_capacity(module.imported_funcs.len()); - for (index, (module_name, field, import_idx)) in module.imported_funcs.iter() { - match resolver.resolve(*import_idx, module_name, field) { - Some(export_value) => match export_value { - Export::Function { - address, - signature, - vmctx, - } => { - let import_signature = &module.signatures[module.functions[index]]; - if signature != *import_signature { - // TODO: If the difference is in the calling convention, - // we could emit a wrapper function to fix it up. - return Err(LinkError(format!( - "{}/{}: incompatible import type: exported function with signature {} \ - incompatible with function import with signature {}", - module_name, field, signature, import_signature - ))); - } - dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) }); - function_imports.push(VMFunctionImport { - body: address, - vmctx, - }); - } - Export::Table { .. } | Export::Memory { .. } | Export::Global { .. } => { - return Err(LinkError(format!( - "{}/{}: incompatible import type: export incompatible with function import", - module_name, field - ))); - } - }, - None => { - return Err(LinkError(format!( - "{}/{}: unknown import function: function not provided", - module_name, field - ))); - } - } - } - - let mut table_imports = PrimaryMap::with_capacity(module.imported_tables.len()); - for (index, (module_name, field, import_idx)) in module.imported_tables.iter() { - match resolver.resolve(*import_idx, module_name, field) { - Some(export_value) => match export_value { - Export::Table { - definition, - vmctx, - table, - } => { - let import_table = &module.table_plans[index]; - if !is_table_compatible(&table, import_table) { - return Err(LinkError(format!( - "{}/{}: incompatible import type: exported table incompatible with \ - table import", - module_name, field, - ))); - } - dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) }); - table_imports.push(VMTableImport { - from: definition, - vmctx, - }); - } - Export::Global { .. } | Export::Memory { .. } | Export::Function { .. } => { - return Err(LinkError(format!( - "{}/{}: incompatible import type: export incompatible with table import", - module_name, field - ))); - } - }, - None => { - return Err(LinkError(format!( - "unknown import: no provided import table for {}/{}", - module_name, field - ))); - } - } - } - - let mut memory_imports = PrimaryMap::with_capacity(module.imported_memories.len()); - for (index, (module_name, field, import_idx)) in module.imported_memories.iter() { - match resolver.resolve(*import_idx, module_name, field) { - Some(export_value) => match export_value { - Export::Memory { - definition, - vmctx, - memory, - } => { - let import_memory = &module.memory_plans[index]; - if !is_memory_compatible(&memory, import_memory) { - return Err(LinkError(format!( - "{}/{}: incompatible import type: exported memory incompatible with \ - memory import", - module_name, field - ))); - } - - // Sanity-check: Ensure that the imported memory has at least - // guard-page protections the importing module expects it to have. - if let ( - MemoryStyle::Static { bound }, - MemoryStyle::Static { - bound: import_bound, - }, - ) = (memory.style, &import_memory.style) - { - assert_ge!(bound, *import_bound); - } - assert_ge!(memory.offset_guard_size, import_memory.offset_guard_size); - - dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) }); - memory_imports.push(VMMemoryImport { - from: definition, - vmctx, - }); - } - Export::Table { .. } | Export::Global { .. } | Export::Function { .. } => { - return Err(LinkError(format!( - "{}/{}: incompatible import type: export incompatible with memory import", - module_name, field - ))); - } - }, - None => { - return Err(LinkError(format!( - "unknown import: no provided import memory for {}/{}", - module_name, field - ))); - } - } - } - - let mut global_imports = PrimaryMap::with_capacity(module.imported_globals.len()); - for (index, (module_name, field, import_idx)) in module.imported_globals.iter() { - match resolver.resolve(*import_idx, module_name, field) { - Some(export_value) => match export_value { - Export::Table { .. } | Export::Memory { .. } | Export::Function { .. } => { - return Err(LinkError(format!( - "{}/{}: incompatible import type: exported global incompatible with \ - global import", - module_name, field - ))); - } - Export::Global { - definition, - vmctx, - global, - } => { - let imported_global = module.globals[index]; - if !is_global_compatible(&global, &imported_global) { - return Err(LinkError(format!( - "{}/{}: incompatible import type: exported global incompatible with \ - global import", - module_name, field - ))); - } - dependencies.insert(unsafe { InstanceHandle::from_vmctx(vmctx) }); - global_imports.push(VMGlobalImport { from: definition }); - } - }, - None => { - return Err(LinkError(format!( - "unknown import: no provided import global for {}/{}", - module_name, field - ))); - } - } - } - - // Apply relocations, now that we have virtual addresses for everything. - relocate(allocated_functions, jt_offsets, relocations, module); - - Ok(Imports::new( - dependencies, - function_imports, - table_imports, - memory_imports, - global_imports, - )) -} - -fn is_global_compatible(exported: &Global, imported: &Global) -> bool { - match imported.initializer { - GlobalInit::Import => (), - _ => panic!("imported Global should have an Imported initializer"), - } - - let Global { - ty: exported_ty, - mutability: exported_mutability, - initializer: _exported_initializer, - } = exported; - let Global { - ty: imported_ty, - mutability: imported_mutability, - initializer: _imported_initializer, - } = imported; - exported_ty == imported_ty && imported_mutability == exported_mutability -} - -fn is_table_element_type_compatible( - exported_type: TableElementType, - imported_type: TableElementType, -) -> bool { - match exported_type { - TableElementType::Func => match imported_type { - TableElementType::Func => true, - _ => false, - }, - TableElementType::Val(exported_val_ty) => match imported_type { - TableElementType::Val(imported_val_ty) => exported_val_ty == imported_val_ty, - _ => false, - }, - } -} - -fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool { - let TablePlan { - table: - Table { - ty: exported_ty, - minimum: exported_minimum, - maximum: exported_maximum, - }, - style: _exported_style, - } = exported; - let TablePlan { - table: - Table { - ty: imported_ty, - minimum: imported_minimum, - maximum: imported_maximum, - }, - style: _imported_style, - } = imported; - - is_table_element_type_compatible(*exported_ty, *imported_ty) - && imported_minimum <= exported_minimum - && (imported_maximum.is_none() - || (!exported_maximum.is_none() - && imported_maximum.unwrap() >= exported_maximum.unwrap())) -} - -fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool { - let MemoryPlan { - memory: - Memory { - minimum: exported_minimum, - maximum: exported_maximum, - shared: exported_shared, - }, - style: _exported_style, - offset_guard_size: _exported_offset_guard_size, - } = exported; - let MemoryPlan { - memory: - Memory { - minimum: imported_minimum, - maximum: imported_maximum, - shared: imported_shared, - }, - style: _imported_style, - offset_guard_size: _imported_offset_guard_size, - } = imported; - - imported_minimum <= exported_minimum - && (imported_maximum.is_none() - || (!exported_maximum.is_none() - && imported_maximum.unwrap() >= exported_maximum.unwrap())) - && exported_shared == imported_shared -} - -/// Performs the relocations inside the function bytecode, provided the necessary metadata. -fn relocate( - allocated_functions: &PrimaryMap, - jt_offsets: &PrimaryMap, - relocations: PrimaryMap>, - module: &Module, ) { for (i, function_relocs) in relocations.into_iter() { for r in function_relocs { diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 50dfe42fae..0d28aae9a1 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -13,7 +13,6 @@ arbitrary = "0.2.0" env_logger = "0.7.1" log = "0.4.8" wasmtime-fuzzing = { path = "../crates/fuzzing", features = ["env_logger"] } -wasmtime-jit = { path = "../crates/jit" } wasmtime = { path = "../crates/api" } libfuzzer-sys = "0.2.0" diff --git a/fuzz/fuzz_targets/compile.rs b/fuzz/fuzz_targets/compile.rs index 2b495f63dc..371ba11947 100644 --- a/fuzz/fuzz_targets/compile.rs +++ b/fuzz/fuzz_targets/compile.rs @@ -1,20 +1,14 @@ #![no_main] use libfuzzer_sys::fuzz_target; +use wasmtime::Strategy; use wasmtime_fuzzing::{oracles, with_log_wasm_test_case}; -use wasmtime_jit::CompilationStrategy; fuzz_target!(|data: &[u8]| { - with_log_wasm_test_case!(data, |data| oracles::compile( - data, - CompilationStrategy::Cranelift - )); + with_log_wasm_test_case!(data, |data| oracles::compile(data, Strategy::Cranelift)); }); #[cfg(feature = "lightbeam")] fuzz_target!(|data: &[u8]| { - with_log_wasm_test_case!(data, |data| oracles::compile( - data, - CompilationStrategy::Lightbeam - )); + with_log_wasm_test_case!(data, |data| oracles::compile(data, Strategy::Lightbeam)); });