diff --git a/Cargo.lock b/Cargo.lock index 28e309176..aa14e70be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -375,6 +375,11 @@ dependencies = [ "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "humantime" version = "1.2.0" @@ -1096,6 +1101,7 @@ name = "wasmer-runtime" version = "0.1.4" dependencies = [ "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-clif-backend 0.1.2", "wasmer-runtime-core 0.1.2", ] @@ -1117,10 +1123,10 @@ dependencies = [ "errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "field-offset 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", - "memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "page_size 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1264,6 +1270,7 @@ dependencies = [ "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" diff --git a/lib/clif-backend/Cargo.toml b/lib/clif-backend/Cargo.toml index becb71a04..701f821fc 100644 --- a/lib/clif-backend/Cargo.toml +++ b/lib/clif-backend/Cargo.toml @@ -23,24 +23,16 @@ libc = "0.2.48" # Dependencies for caching. [dependencies.serde] version = "1.0" -optional = true [dependencies.serde_derive] version = "1.0" -optional = true [dependencies.serde_bytes] version = "0.10" -optional = true -# [dependencies.bincode] -# version = "1.0.1" -# optional = true [dependencies.serde-bench] version = "0.0.7" -optional = true [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["errhandlingapi", "minwindef", "minwinbase", "winnt"] } wasmer-win-exception-handler = { path = "../win-exception-handler", version = "0.0.1" } [features] -cache = ["serde", "serde_derive", "serde_bytes", "serde-bench", "wasmer-runtime-core/cache"] debug = ["wasmer-runtime-core/debug"] diff --git a/lib/clif-backend/src/cache.rs b/lib/clif-backend/src/cache.rs index aef6adf4c..5af708703 100644 --- a/lib/clif-backend/src/cache.rs +++ b/lib/clif-backend/src/cache.rs @@ -1,16 +1,50 @@ use crate::relocation::{ExternalRelocation, TrapSink}; use hashbrown::HashMap; +use std::sync::Arc; use wasmer_runtime_core::{ - backend::sys::Memory, - cache::{Cache, Error}, - module::ModuleInfo, + backend::{sys::Memory, CacheGen}, + cache::{Artifact, Error}, + module::{ModuleInfo, ModuleInner}, structures::Map, types::{LocalFuncIndex, SigIndex}, }; use serde_bench::{deserialize, serialize}; +pub struct CacheGenerator { + backend_cache: BackendCache, + memory: Arc, +} + +impl CacheGenerator { + pub fn new(backend_cache: BackendCache, memory: Arc) -> Self { + Self { + backend_cache, + memory, + } + } +} + +impl CacheGen for CacheGenerator { + fn generate_cache( + &self, + module: &ModuleInner, + ) -> Result<(Box, Box<[u8]>, Memory), Error> { + let info = Box::new(module.info.clone()); + + // Clone the memory to a new location. This could take a long time, + // depending on the throughput of your memcpy implementation. + let compiled_code = (*self.memory).clone(); + + Ok(( + info, + self.backend_cache.into_backend_data()?.into_boxed_slice(), + compiled_code, + )) + } +} + #[derive(Serialize, Deserialize)] pub struct TrampolineCache { #[serde(with = "serde_bytes")] @@ -22,24 +56,24 @@ pub struct TrampolineCache { pub struct BackendCache { pub external_relocs: Map>, pub offsets: Map, - pub trap_sink: TrapSink, + pub trap_sink: Arc, pub trampolines: TrampolineCache, } impl BackendCache { - pub fn from_cache(cache: Cache) -> Result<(ModuleInfo, Memory, Self), Error> { + pub fn from_cache(cache: Artifact) -> Result<(ModuleInfo, Memory, Self), Error> { let (info, backend_data, compiled_code) = cache.consume(); - let backend_cache = deserialize(backend_data.as_slice()) - .map_err(|e| Error::DeserializeError(e.to_string()))?; + let backend_cache = + deserialize(&backend_data).map_err(|e| Error::DeserializeError(e.to_string()))?; Ok((info, compiled_code, backend_cache)) } - pub fn into_backend_data(self) -> Result, Error> { + pub fn into_backend_data(&self) -> Result, Error> { let mut buffer = Vec::new(); - serialize(&mut buffer, &self).map_err(|e| Error::SerializeError(e.to_string()))?; + serialize(&mut buffer, self).map_err(|e| Error::SerializeError(e.to_string()))?; Ok(buffer) } diff --git a/lib/clif-backend/src/func_env.rs b/lib/clif-backend/src/func_env.rs index 8670e0000..b08f56dd2 100644 --- a/lib/clif-backend/src/func_env.rs +++ b/lib/clif-backend/src/func_env.rs @@ -75,7 +75,7 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> { let vmctx = func.create_global_value(ir::GlobalValueData::VMContext); let ptr_type = self.pointer_type(); - let local_global_addr = match global_index.local_or_import(self.env.module) { + let local_global_addr = match global_index.local_or_import(&self.env.module.info) { LocalOrImport::Local(local_global_index) => { let globals_base_addr = func.create_global_value(ir::GlobalValueData::Load { base: vmctx, @@ -145,48 +145,49 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> { let vmctx = func.create_global_value(ir::GlobalValueData::VMContext); let ptr_type = self.pointer_type(); - let (local_memory_ptr_ptr, description) = match mem_index.local_or_import(self.env.module) { - LocalOrImport::Local(local_mem_index) => { - let memories_base_addr = func.create_global_value(ir::GlobalValueData::Load { - base: vmctx, - offset: (vm::Ctx::offset_memories() as i32).into(), - global_type: ptr_type, - readonly: true, - }); - - let local_memory_ptr_offset = - local_mem_index.index() * mem::size_of::<*mut vm::LocalMemory>(); - - ( - func.create_global_value(ir::GlobalValueData::IAddImm { - base: memories_base_addr, - offset: (local_memory_ptr_offset as i64).into(), + let (local_memory_ptr_ptr, description) = + match mem_index.local_or_import(&self.env.module.info) { + LocalOrImport::Local(local_mem_index) => { + let memories_base_addr = func.create_global_value(ir::GlobalValueData::Load { + base: vmctx, + offset: (vm::Ctx::offset_memories() as i32).into(), global_type: ptr_type, - }), - self.env.module.info.memories[local_mem_index], - ) - } - LocalOrImport::Import(import_mem_index) => { - let memories_base_addr = func.create_global_value(ir::GlobalValueData::Load { - base: vmctx, - offset: (vm::Ctx::offset_imported_memories() as i32).into(), - global_type: ptr_type, - readonly: true, - }); + readonly: true, + }); - let local_memory_ptr_offset = - import_mem_index.index() * mem::size_of::<*mut vm::LocalMemory>(); + let local_memory_ptr_offset = + local_mem_index.index() * mem::size_of::<*mut vm::LocalMemory>(); - ( - func.create_global_value(ir::GlobalValueData::IAddImm { - base: memories_base_addr, - offset: (local_memory_ptr_offset as i64).into(), + ( + func.create_global_value(ir::GlobalValueData::IAddImm { + base: memories_base_addr, + offset: (local_memory_ptr_offset as i64).into(), + global_type: ptr_type, + }), + self.env.module.info.memories[local_mem_index], + ) + } + LocalOrImport::Import(import_mem_index) => { + let memories_base_addr = func.create_global_value(ir::GlobalValueData::Load { + base: vmctx, + offset: (vm::Ctx::offset_imported_memories() as i32).into(), global_type: ptr_type, - }), - self.env.module.info.imported_memories[import_mem_index].1, - ) - } - }; + readonly: true, + }); + + let local_memory_ptr_offset = + import_mem_index.index() * mem::size_of::<*mut vm::LocalMemory>(); + + ( + func.create_global_value(ir::GlobalValueData::IAddImm { + base: memories_base_addr, + offset: (local_memory_ptr_offset as i64).into(), + global_type: ptr_type, + }), + self.env.module.info.imported_memories[import_mem_index].1, + ) + } + }; let (local_memory_ptr, local_memory_base) = { let local_memory_ptr = func.create_global_value(ir::GlobalValueData::Load { @@ -253,7 +254,8 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> { let vmctx = func.create_global_value(ir::GlobalValueData::VMContext); let ptr_type = self.pointer_type(); - let (table_struct_ptr_ptr, description) = match table_index.local_or_import(self.env.module) + let (table_struct_ptr_ptr, description) = match table_index + .local_or_import(&self.env.module.info) { LocalOrImport::Local(local_table_index) => { let tables_base = func.create_global_value(ir::GlobalValueData::Load { @@ -476,7 +478,7 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> { ) -> cranelift_wasm::WasmResult { let callee_index: FuncIndex = Converter(clif_callee_index).into(); - match callee_index.local_or_import(self.env.module) { + match callee_index.local_or_import(&self.env.module.info) { LocalOrImport::Local(_) => { // this is an internal function let vmctx = pos @@ -568,18 +570,19 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> { let mem_index: MemoryIndex = Converter(clif_mem_index).into(); - let (namespace, mem_index, description) = match mem_index.local_or_import(self.env.module) { - LocalOrImport::Local(local_mem_index) => ( - call_names::LOCAL_NAMESPACE, - local_mem_index.index(), - self.env.module.info.memories[local_mem_index], - ), - LocalOrImport::Import(import_mem_index) => ( - call_names::IMPORT_NAMESPACE, - import_mem_index.index(), - self.env.module.info.imported_memories[import_mem_index].1, - ), - }; + let (namespace, mem_index, description) = + match mem_index.local_or_import(&self.env.module.info) { + LocalOrImport::Local(local_mem_index) => ( + call_names::LOCAL_NAMESPACE, + local_mem_index.index(), + self.env.module.info.memories[local_mem_index], + ), + LocalOrImport::Import(import_mem_index) => ( + call_names::IMPORT_NAMESPACE, + import_mem_index.index(), + self.env.module.info.imported_memories[import_mem_index].1, + ), + }; let name_index = match description.memory_type() { MemoryType::Dynamic => call_names::DYNAMIC_MEM_GROW, @@ -631,18 +634,19 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> { let mem_index: MemoryIndex = Converter(clif_mem_index).into(); - let (namespace, mem_index, description) = match mem_index.local_or_import(self.env.module) { - LocalOrImport::Local(local_mem_index) => ( - call_names::LOCAL_NAMESPACE, - local_mem_index.index(), - self.env.module.info.memories[local_mem_index], - ), - LocalOrImport::Import(import_mem_index) => ( - call_names::IMPORT_NAMESPACE, - import_mem_index.index(), - self.env.module.info.imported_memories[import_mem_index].1, - ), - }; + let (namespace, mem_index, description) = + match mem_index.local_or_import(&self.env.module.info) { + LocalOrImport::Local(local_mem_index) => ( + call_names::LOCAL_NAMESPACE, + local_mem_index.index(), + self.env.module.info.memories[local_mem_index], + ), + LocalOrImport::Import(import_mem_index) => ( + call_names::IMPORT_NAMESPACE, + import_mem_index.index(), + self.env.module.info.imported_memories[import_mem_index].1, + ), + }; let name_index = match description.memory_type() { MemoryType::Dynamic => call_names::DYNAMIC_MEM_SIZE, diff --git a/lib/clif-backend/src/lib.rs b/lib/clif-backend/src/lib.rs index 07bae97c6..df2e4e208 100644 --- a/lib/clif-backend/src/lib.rs +++ b/lib/clif-backend/src/lib.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "cache")] mod cache; mod func_env; mod libcalls; @@ -14,21 +13,17 @@ use cranelift_codegen::{ settings::{self, Configurable}, }; use target_lexicon::Triple; -#[cfg(feature = "cache")] -use wasmer_runtime_core::{ - backend::sys::Memory, - cache::{Cache, Error as CacheError}, - module::ModuleInfo, -}; + +use wasmer_runtime_core::cache::{Artifact, Error as CacheError}; use wasmer_runtime_core::{ backend::{Compiler, Token}, error::{CompileError, CompileResult}, module::ModuleInner, }; -#[cfg(feature = "cache")] + #[macro_use] extern crate serde_derive; -#[cfg(feature = "cache")] + extern crate serde; use wasmparser::{self, WasmDecoder}; @@ -48,7 +43,7 @@ impl Compiler for CraneliftCompiler { let isa = get_isa(); - let mut module = module::Module::empty(); + let mut module = module::Module::new(wasm); let module_env = module_env::ModuleEnv::new(&mut module, &*isa); let func_bodies = module_env.translate(wasm)?; @@ -57,41 +52,41 @@ impl Compiler for CraneliftCompiler { } /// Create a wasmer Module from an already-compiled cache. - #[cfg(feature = "cache")] - unsafe fn from_cache(&self, cache: Cache, _: Token) -> Result { + + unsafe fn from_cache(&self, cache: Artifact, _: Token) -> Result { module::Module::from_cache(cache) } - #[cfg(feature = "cache")] - fn compile_to_backend_cache_data( - &self, - wasm: &[u8], - _: Token, - ) -> CompileResult<(Box, Vec, Memory)> { - validate(wasm)?; + // + // fn compile_to_backend_cache_data( + // &self, + // wasm: &[u8], + // _: Token, + // ) -> CompileResult<(Box, Vec, Memory)> { + // validate(wasm)?; - let isa = get_isa(); + // let isa = get_isa(); - let mut module = module::Module::empty(); - let module_env = module_env::ModuleEnv::new(&mut module, &*isa); + // let mut module = module::Module::new(wasm); + // let module_env = module_env::ModuleEnv::new(&mut module, &*isa); - let func_bodies = module_env.translate(wasm)?; + // let func_bodies = module_env.translate(wasm)?; - let (info, backend_cache, compiled_code) = module - .compile_to_backend_cache(&*isa, func_bodies) - .map_err(|e| CompileError::InternalError { - msg: format!("{:?}", e), - })?; + // let (info, backend_cache, compiled_code) = module + // .compile_to_backend_cache(&*isa, func_bodies) + // .map_err(|e| CompileError::InternalError { + // msg: format!("{:?}", e), + // })?; - let buffer = - backend_cache - .into_backend_data() - .map_err(|e| CompileError::InternalError { - msg: format!("{:?}", e), - })?; + // let buffer = + // backend_cache + // .into_backend_data() + // .map_err(|e| CompileError::InternalError { + // msg: format!("{:?}", e), + // })?; - Ok((Box::new(info), buffer, compiled_code)) - } + // Ok((Box::new(info), buffer, compiled_code)) + // } } fn get_isa() -> Box { diff --git a/lib/clif-backend/src/module.rs b/lib/clif-backend/src/module.rs index c48072ac8..6f95ea828 100644 --- a/lib/clif-backend/src/module.rs +++ b/lib/clif-backend/src/module.rs @@ -1,178 +1,124 @@ -#[cfg(feature = "cache")] -use crate::cache::BackendCache; +use crate::cache::{BackendCache, CacheGenerator}; use crate::{resolver::FuncResolverBuilder, signal::Caller, trampoline::Trampolines}; use cranelift_codegen::{ir, isa}; use cranelift_entity::EntityRef; use cranelift_wasm; use hashbrown::HashMap; -use std::{ - ops::{Deref, DerefMut}, - ptr::NonNull, -}; -#[cfg(feature = "cache")] +use std::sync::Arc; + +use wasmer_runtime_core::cache::{Artifact, Error as CacheError, WasmHash}; + use wasmer_runtime_core::{ - backend::sys::Memory, - cache::{Cache, Error as CacheError}, -}; -use wasmer_runtime_core::{ - backend::{Backend, FuncResolver, ProtectedCaller, Token, UserTrapper}, - error::{CompileResult, RuntimeResult}, + backend::Backend, + error::CompileResult, module::{ModuleInfo, ModuleInner, StringTable}, structures::{Map, TypedIndex}, types::{ FuncIndex, FuncSig, GlobalIndex, LocalFuncIndex, MemoryIndex, SigIndex, TableIndex, Type, - Value, }, - vm::{self, ImportBacking}, }; -struct Placeholder; - -impl FuncResolver for Placeholder { - fn get( - &self, - _module: &ModuleInner, - _local_func_index: LocalFuncIndex, - ) -> Option> { - None - } -} - -impl ProtectedCaller for Placeholder { - fn call( - &self, - _module: &ModuleInner, - _func_index: FuncIndex, - _params: &[Value], - _import_backing: &ImportBacking, - _vmctx: *mut vm::Ctx, - _: Token, - ) -> RuntimeResult> { - Ok(vec![]) - } - - fn get_early_trapper(&self) -> Box { - unimplemented!() - } -} - /// This contains all of the items in a `ModuleInner` except the `func_resolver`. pub struct Module { - pub module: ModuleInner, + pub info: ModuleInfo, } impl Module { - pub fn empty() -> Self { + pub fn new(wasm: &[u8]) -> Self { Self { - module: ModuleInner { - // this is a placeholder - func_resolver: Box::new(Placeholder), - protected_caller: Box::new(Placeholder), + info: ModuleInfo { + memories: Map::new(), + globals: Map::new(), + tables: Map::new(), - info: ModuleInfo { - memories: Map::new(), - globals: Map::new(), - tables: Map::new(), + imported_functions: Map::new(), + imported_memories: Map::new(), + imported_tables: Map::new(), + imported_globals: Map::new(), - imported_functions: Map::new(), - imported_memories: Map::new(), - imported_tables: Map::new(), - imported_globals: Map::new(), + exports: HashMap::new(), - exports: HashMap::new(), + data_initializers: Vec::new(), + elem_initializers: Vec::new(), - data_initializers: Vec::new(), - elem_initializers: Vec::new(), + start_func: None, - start_func: None, + func_assoc: Map::new(), + signatures: Map::new(), + backend: Backend::Cranelift, - func_assoc: Map::new(), - signatures: Map::new(), - backend: Backend::Cranelift, + namespace_table: StringTable::new(), + name_table: StringTable::new(), - namespace_table: StringTable::new(), - name_table: StringTable::new(), - }, + wasm_hash: WasmHash::generate(wasm), }, } } pub fn compile( - mut self, + self, isa: &isa::TargetIsa, functions: Map, ) -> CompileResult { let (func_resolver_builder, handler_data) = - FuncResolverBuilder::new(isa, functions, &self.module.info)?; + FuncResolverBuilder::new(isa, functions, &self.info)?; - self.module.func_resolver = - Box::new(func_resolver_builder.finalize(&self.module.info.signatures)?); + let trampolines = Arc::new(Trampolines::new(isa, &self.info)); - let trampolines = Trampolines::new(isa, &self.module.info); + let (func_resolver, backend_cache) = func_resolver_builder.finalize( + &self.info.signatures, + Arc::clone(&trampolines), + handler_data.clone(), + )?; - self.module.protected_caller = - Box::new(Caller::new(&self.module.info, handler_data, trampolines)); + let protected_caller = Caller::new(&self.info, handler_data, trampolines); - Ok(self.module) + let cache_gen = Box::new(CacheGenerator::new( + backend_cache, + Arc::clone(&func_resolver.memory), + )); + + Ok(ModuleInner { + func_resolver: Box::new(func_resolver), + protected_caller: Box::new(protected_caller), + cache_gen, + + info: self.info, + }) } - #[cfg(feature = "cache")] - pub fn compile_to_backend_cache( - self, - isa: &isa::TargetIsa, - functions: Map, - ) -> CompileResult<(ModuleInfo, BackendCache, Memory)> { - let (func_resolver_builder, handler_data) = - FuncResolverBuilder::new(isa, functions, &self.module.info)?; - - let trampolines = Trampolines::new(isa, &self.module.info); - - let trampoline_cache = trampolines.to_trampoline_cache(); - - let (backend_cache, compiled_code) = - func_resolver_builder.to_backend_cache(trampoline_cache, handler_data); - - Ok((self.module.info, backend_cache, compiled_code)) - } - - #[cfg(feature = "cache")] - pub fn from_cache(cache: Cache) -> Result { + pub fn from_cache(cache: Artifact) -> Result { let (info, compiled_code, backend_cache) = BackendCache::from_cache(cache)?; let (func_resolver_builder, trampolines, handler_data) = FuncResolverBuilder::new_from_backend_cache(backend_cache, compiled_code, &info)?; - let func_resolver = Box::new( - func_resolver_builder - .finalize(&info.signatures) - .map_err(|e| CacheError::Unknown(format!("{:?}", e)))?, - ); + let (func_resolver, backend_cache) = func_resolver_builder + .finalize( + &info.signatures, + Arc::clone(&trampolines), + handler_data.clone(), + ) + .map_err(|e| CacheError::Unknown(format!("{:?}", e)))?; - let protected_caller = Box::new(Caller::new(&info, handler_data, trampolines)); + let protected_caller = Caller::new(&info, handler_data, trampolines); + + let cache_gen = Box::new(CacheGenerator::new( + backend_cache, + Arc::clone(&func_resolver.memory), + )); Ok(ModuleInner { - func_resolver, - protected_caller, + func_resolver: Box::new(func_resolver), + protected_caller: Box::new(protected_caller), + cache_gen, + info, }) } } -impl Deref for Module { - type Target = ModuleInner; - - fn deref(&self) -> &ModuleInner { - &self.module - } -} - -impl DerefMut for Module { - fn deref_mut(&mut self) -> &mut ModuleInner { - &mut self.module - } -} - pub struct Converter(pub T); macro_rules! convert_clif_to_runtime_index { diff --git a/lib/clif-backend/src/module_env.rs b/lib/clif-backend/src/module_env.rs index 65bd5f839..b80e8826d 100644 --- a/lib/clif-backend/src/module_env.rs +++ b/lib/clif-backend/src/module_env.rs @@ -139,7 +139,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa> // assert!(!desc.mutable); let global_index: GlobalIndex = Converter(global_index).into(); let imported_global_index = global_index - .local_or_import(self.module) + .local_or_import(&self.module.info) .import() .expect("invalid global initializer when declaring an imported global"); Initializer::GetGlobal(imported_global_index) @@ -249,7 +249,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa> let base = match base { Some(global_index) => { let global_index: GlobalIndex = Converter(global_index).into(); - Initializer::GetGlobal(match global_index.local_or_import(self.module) { + Initializer::GetGlobal(match global_index.local_or_import(&self.module.info) { LocalOrImport::Import(imported_global_index) => imported_global_index, LocalOrImport::Local(_) => { panic!("invalid global initializer when declaring an imported global") @@ -319,7 +319,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa> let base = match base { Some(global_index) => { let global_index: GlobalIndex = Converter(global_index).into(); - Initializer::GetGlobal(match global_index.local_or_import(self.module) { + Initializer::GetGlobal(match global_index.local_or_import(&self.module.info) { LocalOrImport::Import(imported_global_index) => imported_global_index, LocalOrImport::Local(_) => { panic!("invalid global initializer when declaring an imported global") @@ -389,7 +389,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa> let name = ir::ExternalName::user(0, func_index.index() as u32); let sig = func_env.generate_signature( - self.get_func_type(Converter(func_index.convert_up(self.module)).into()), + self.get_func_type(Converter(func_index.convert_up(&self.module.info)).into()), ); let mut func = ir::Function::with_name_signature(name, sig); diff --git a/lib/clif-backend/src/relocation.rs b/lib/clif-backend/src/relocation.rs index 840255e61..92ef0485a 100644 --- a/lib/clif-backend/src/relocation.rs +++ b/lib/clif-backend/src/relocation.rs @@ -22,16 +22,14 @@ pub mod call_names { pub const DYNAMIC_MEM_SIZE: u32 = 5; } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] pub enum Reloc { Abs8, X86PCRel4, X86CallPCRel4, } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Copy, Clone)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub enum LibCall { Probestack, CeilF32, @@ -44,8 +42,7 @@ pub enum LibCall { NearestF64, } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct ExternalRelocation { /// The relocation code. pub reloc: Reloc, @@ -66,8 +63,7 @@ pub struct LocalRelocation { pub target: FuncIndex, } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub enum VmCallKind { StaticMemoryGrow, StaticMemorySize, @@ -79,16 +75,14 @@ pub enum VmCallKind { DynamicMemorySize, } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub enum VmCall { Local(VmCallKind), Import(VmCallKind), } /// Specify the type of relocation -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum RelocationType { Intrinsic(String), LibCall(LibCall), @@ -218,8 +212,7 @@ impl binemit::RelocSink for RelocSink { } } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub enum TrapCode { StackOverflow, HeapOutOfBounds, @@ -244,8 +237,7 @@ impl RelocSink { } } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub struct TrapData { pub trapcode: TrapCode, pub srcloc: u32, @@ -253,7 +245,7 @@ pub struct TrapData { /// Simple implementation of a TrapSink /// that saves the info for later. -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] +#[derive(Serialize, Deserialize)] pub struct TrapSink { trap_datas: Vec<(usize, TrapData)>, } diff --git a/lib/clif-backend/src/resolver.rs b/lib/clif-backend/src/resolver.rs index 48ea5c7e5..604778f01 100644 --- a/lib/clif-backend/src/resolver.rs +++ b/lib/clif-backend/src/resolver.rs @@ -1,8 +1,4 @@ -#[cfg(feature = "cache")] -use crate::{ - cache::{BackendCache, TrampolineCache}, - trampoline::Trampolines, -}; +use crate::{cache::BackendCache, trampoline::Trampolines}; use crate::{ libcalls, relocation::{ @@ -19,7 +15,7 @@ use std::{ ptr::{write_unaligned, NonNull}, sync::Arc, }; -#[cfg(feature = "cache")] + use wasmer_runtime_core::cache::Error as CacheError; use wasmer_runtime_core::{ self, @@ -42,21 +38,32 @@ extern "C" { pub fn __chkstk(); } +fn lookup_func( + map: &SliceMap, + memory: &Memory, + local_func_index: LocalFuncIndex, +) -> Option> { + let offset = *map.get(local_func_index)?; + let ptr = unsafe { memory.as_ptr().add(offset) }; + + NonNull::new(ptr).map(|nonnull| nonnull.cast()) +} + #[allow(dead_code)] pub struct FuncResolverBuilder { - resolver: FuncResolver, + map: Map, + memory: Memory, local_relocs: Map>, external_relocs: Map>, import_len: usize, } impl FuncResolverBuilder { - #[cfg(feature = "cache")] pub fn new_from_backend_cache( backend_cache: BackendCache, mut code: Memory, info: &ModuleInfo, - ) -> Result<(Self, Trampolines, HandlerData), CacheError> { + ) -> Result<(Self, Arc, HandlerData), CacheError> { unsafe { code.protect(.., Protect::ReadWrite) .map_err(|e| CacheError::Unknown(e.to_string()))?; @@ -67,37 +74,19 @@ impl FuncResolverBuilder { Ok(( Self { - resolver: FuncResolver { - map: backend_cache.offsets, - memory: code, - }, + map: backend_cache.offsets, + memory: code, local_relocs: Map::new(), external_relocs: backend_cache.external_relocs, import_len: info.imported_functions.len(), }, - Trampolines::from_trampoline_cache(backend_cache.trampolines), + Arc::new(Trampolines::from_trampoline_cache( + backend_cache.trampolines, + )), handler_data, )) } - #[cfg(feature = "cache")] - pub fn to_backend_cache( - mut self, - trampolines: TrampolineCache, - handler_data: HandlerData, - ) -> (BackendCache, Memory) { - self.relocate_locals(); - ( - BackendCache { - external_relocs: self.external_relocs, - offsets: self.resolver.map, - trap_sink: handler_data.trap_data, - trampolines, - }, - self.resolver.memory, - ) - } - pub fn new( isa: &isa::TargetIsa, function_bodies: Map, @@ -169,10 +158,12 @@ impl FuncResolverBuilder { previous_end = new_end; } - let handler_data = HandlerData::new(trap_sink, memory.as_ptr() as _, memory.size()); + let handler_data = + HandlerData::new(Arc::new(trap_sink), memory.as_ptr() as _, memory.size()); let mut func_resolver_builder = Self { - resolver: FuncResolver { map, memory }, + map, + memory, local_relocs, external_relocs, import_len: info.imported_functions.len(), @@ -187,12 +178,15 @@ impl FuncResolverBuilder { for (index, relocs) in self.local_relocs.iter() { for ref reloc in relocs.iter() { let local_func_index = LocalFuncIndex::new(reloc.target.index() - self.import_len); - let target_func_address = - self.resolver.lookup(local_func_index).unwrap().as_ptr() as usize; + let target_func_address = lookup_func(&self.map, &self.memory, local_func_index) + .unwrap() + .as_ptr() as usize; // We need the address of the current function // because these calls are relative. - let func_addr = self.resolver.lookup(index).unwrap().as_ptr() as usize; + let func_addr = lookup_func(&self.map, &self.memory, index) + .unwrap() + .as_ptr() as usize; unsafe { let reloc_address = func_addr + reloc.offset as usize; @@ -209,7 +203,9 @@ impl FuncResolverBuilder { pub fn finalize( mut self, signatures: &SliceMap>, - ) -> CompileResult { + trampolines: Arc, + handler_data: HandlerData, + ) -> CompileResult<(FuncResolver, BackendCache)> { for (index, relocs) in self.external_relocs.iter() { for ref reloc in relocs.iter() { let target_func_address: isize = match reloc.target { @@ -281,7 +277,9 @@ impl FuncResolverBuilder { // We need the address of the current function // because some of these calls are relative. - let func_addr = self.resolver.lookup(index).unwrap().as_ptr(); + let func_addr = lookup_func(&self.map, &self.memory, index) + .unwrap() + .as_ptr() as usize; // Determine relocation type and apply relocation. match reloc.reloc { @@ -289,9 +287,9 @@ impl FuncResolverBuilder { let ptr_to_write = (target_func_address as u64) .checked_add(reloc.addend as u64) .unwrap(); - let empty_space_offset = self.resolver.map[index] + reloc.offset as usize; + let empty_space_offset = self.map[index] + reloc.offset as usize; let ptr_slice = unsafe { - &mut self.resolver.memory.as_slice_mut() + &mut self.memory.as_slice_mut() [empty_space_offset..empty_space_offset + 8] }; LittleEndian::write_u64(ptr_slice, ptr_to_write); @@ -309,29 +307,35 @@ impl FuncResolverBuilder { } unsafe { - self.resolver - .memory + self.memory .protect(.., Protect::ReadExec) .map_err(|e| CompileError::InternalError { msg: e.to_string() })?; } - Ok(self.resolver) + let backend_cache = BackendCache { + external_relocs: self.external_relocs.clone(), + offsets: self.map.clone(), + trap_sink: handler_data.trap_data, + trampolines: trampolines.to_trampoline_cache(), + }; + + Ok(( + FuncResolver { + map: self.map, + memory: Arc::new(self.memory), + }, + backend_cache, + )) } } +unsafe impl Sync for FuncResolver {} +unsafe impl Send for FuncResolver {} + /// Resolves a function index to a function address. pub struct FuncResolver { map: Map, - memory: Memory, -} - -impl FuncResolver { - fn lookup(&self, local_func_index: LocalFuncIndex) -> Option> { - let offset = *self.map.get(local_func_index)?; - let ptr = unsafe { self.memory.as_ptr().add(offset) }; - - NonNull::new(ptr).map(|nonnull| nonnull.cast()) - } + pub(crate) memory: Arc, } // Implements FuncResolver trait. @@ -341,7 +345,7 @@ impl backend::FuncResolver for FuncResolver { _module: &wasmer_runtime_core::module::ModuleInner, index: LocalFuncIndex, ) -> Option> { - self.lookup(index) + lookup_func(&self.map, &self.memory, index) } } diff --git a/lib/clif-backend/src/signal/mod.rs b/lib/clif-backend/src/signal/mod.rs index 31dbb44dd..9ccbf1822 100644 --- a/lib/clif-backend/src/signal/mod.rs +++ b/lib/clif-backend/src/signal/mod.rs @@ -40,11 +40,15 @@ impl UserTrapper for Trapper { pub struct Caller { func_export_set: HashSet, handler_data: HandlerData, - trampolines: Trampolines, + trampolines: Arc, } impl Caller { - pub fn new(module: &ModuleInfo, handler_data: HandlerData, trampolines: Trampolines) -> Self { + pub fn new( + module: &ModuleInfo, + handler_data: HandlerData, + trampolines: Arc, + ) -> Self { let mut func_export_set = HashSet::new(); for export_index in module.exports.values() { if let ExportIndex::Func(func_index) = export_index { @@ -160,7 +164,7 @@ fn get_func_from_index( .get(func_index) .expect("broken invariant, incorrect func index"); - let (func_ptr, ctx) = match func_index.local_or_import(module) { + let (func_ptr, ctx) = match func_index.local_or_import(&module.info) { LocalOrImport::Local(local_func_index) => ( module .func_resolver @@ -187,15 +191,16 @@ fn get_func_from_index( unsafe impl Send for HandlerData {} unsafe impl Sync for HandlerData {} +#[derive(Clone)] pub struct HandlerData { - pub trap_data: TrapSink, + pub trap_data: Arc, exec_buffer_ptr: *const c_void, exec_buffer_size: usize, } impl HandlerData { pub fn new( - trap_data: TrapSink, + trap_data: Arc, exec_buffer_ptr: *const c_void, exec_buffer_size: usize, ) -> Self { diff --git a/lib/clif-backend/src/trampoline.rs b/lib/clif-backend/src/trampoline.rs index 76fb521d6..b20109ef0 100644 --- a/lib/clif-backend/src/trampoline.rs +++ b/lib/clif-backend/src/trampoline.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "cache")] use crate::cache::TrampolineCache; use cranelift_codegen::{ binemit::{NullTrapSink, Reloc, RelocSink}, @@ -33,7 +32,6 @@ pub struct Trampolines { } impl Trampolines { - #[cfg(feature = "cache")] pub fn from_trampoline_cache(cache: TrampolineCache) -> Self { // pub struct TrampolineCache { // #[serde(with = "serde_bytes")] @@ -57,8 +55,7 @@ impl Trampolines { } } - #[cfg(feature = "cache")] - pub fn to_trampoline_cache(self) -> TrampolineCache { + pub fn to_trampoline_cache(&self) -> TrampolineCache { let mut code = vec![0; self.memory.size()]; unsafe { @@ -67,7 +64,7 @@ impl Trampolines { TrampolineCache { code, - offsets: self.offsets, + offsets: self.offsets.clone(), } } diff --git a/lib/emscripten/src/lib.rs b/lib/emscripten/src/lib.rs index a58b3b118..8911b823d 100644 --- a/lib/emscripten/src/lib.rs +++ b/lib/emscripten/src/lib.rs @@ -249,13 +249,13 @@ impl EmscriptenGlobals { namespace_index, name_index, }, - ) in &module.0.info.imported_functions + ) in &module.info().imported_functions { - let namespace = module.0.info.namespace_table.get(*namespace_index); - let name = module.0.info.name_table.get(*name_index); + let namespace = module.info().namespace_table.get(*namespace_index); + let name = module.info().name_table.get(*name_index); if name == "abortOnCannotGrowMemory" && namespace == "env" { - let sig_index = module.0.info.func_assoc[index.convert_up(&module.0)]; - let expected_sig = &module.0.info.signatures[sig_index]; + let sig_index = module.info().func_assoc[index.convert_up(module.info())]; + let expected_sig = &module.info().signatures[sig_index]; if **expected_sig == *OLD_ABORT_ON_CANNOT_GROW_MEMORY_SIG { use_old_abort_on_cannot_grow_memory = true; } diff --git a/lib/emscripten/src/utils.rs b/lib/emscripten/src/utils.rs index 82ab0f251..678032374 100644 --- a/lib/emscripten/src/utils.rs +++ b/lib/emscripten/src/utils.rs @@ -16,13 +16,12 @@ use wasmer_runtime_core::{ /// We check if a provided module is an Emscripten generated one pub fn is_emscripten_module(module: &Module) -> bool { - for (_, import_name) in &module.0.info.imported_functions { + for (_, import_name) in &module.info().imported_functions { let namespace = module - .0 - .info + .info() .namespace_table .get(import_name.namespace_index); - let field = module.0.info.name_table.get(import_name.name_index); + let field = module.info().name_table.get(import_name.name_index); if field == "_emscripten_memcpy_big" && namespace == "env" { return true; } @@ -31,12 +30,12 @@ pub fn is_emscripten_module(module: &Module) -> bool { } pub fn get_emscripten_table_size(module: &Module) -> (u32, Option) { - let (_, table) = &module.0.info.imported_tables[ImportedTableIndex::new(0)]; + let (_, table) = &module.info().imported_tables[ImportedTableIndex::new(0)]; (table.minimum, table.maximum) } pub fn get_emscripten_memory_size(module: &Module) -> (Pages, Option) { - let (_, memory) = &module.0.info.imported_memories[ImportedMemoryIndex::new(0)]; + let (_, memory) = &module.info().imported_memories[ImportedMemoryIndex::new(0)]; (memory.minimum, memory.maximum) } diff --git a/lib/runtime-core/Cargo.toml b/lib/runtime-core/Cargo.toml index 9a748eb62..cb5a6b4b1 100644 --- a/lib/runtime-core/Cargo.toml +++ b/lib/runtime-core/Cargo.toml @@ -8,7 +8,6 @@ repository = "https://github.com/wasmerio/wasmer" edition = "2018" [dependencies] -hashbrown = "0.1" nix = "0.12.0" page_size = "0.4.1" wasmparser = "0.23.0" @@ -17,26 +16,24 @@ lazy_static = "1.2.0" indexmap = "1.0.2" errno = "0.2.4" libc = "0.2.48" +hex = "0.3.2" # Dependencies for caching. [dependencies.serde] version = "1.0" -optional = true +# This feature is required for serde to support serializing/deserializing reference counted pointers (e.g. Rc and Arc). +features = ["rc"] [dependencies.serde_derive] version = "1.0" -optional = true [dependencies.serde_bytes] version = "0.10" -optional = true [dependencies.serde-bench] version = "0.0.7" -optional = true -[dependencies.memmap] -version = "0.7.0" -optional = true [dependencies.sha2] version = "0.8.0" -optional = true +[dependencies.hashbrown] +version = "0.1" +features = ["serde"] [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["memoryapi"] } @@ -47,5 +44,4 @@ field-offset = "0.1.1" [features] debug = [] -cache = ["serde/rc", "serde_derive", "serde_bytes", "hashbrown/serde", "serde-bench", "memmap", "sha2"] diff --git a/lib/runtime-core/src/backend.rs b/lib/runtime-core/src/backend.rs index 9b5ec049e..e88383800 100644 --- a/lib/runtime-core/src/backend.rs +++ b/lib/runtime-core/src/backend.rs @@ -6,9 +6,9 @@ use crate::{ types::{FuncIndex, LocalFuncIndex, Value}, vm, }; -#[cfg(feature = "cache")] + use crate::{ - cache::{Cache, Error as CacheError}, + cache::{Artifact, Error as CacheError}, module::ModuleInfo, sys::Memory, }; @@ -19,8 +19,7 @@ pub mod sys { } pub use crate::sig_registry::SigRegistry; -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] pub enum Backend { Cranelift, } @@ -43,15 +42,7 @@ pub trait Compiler { /// be called from inside the runtime. fn compile(&self, wasm: &[u8], _: Token) -> CompileResult; - #[cfg(feature = "cache")] - unsafe fn from_cache(&self, cache: Cache, _: Token) -> Result; - - #[cfg(feature = "cache")] - fn compile_to_backend_cache_data( - &self, - wasm: &[u8], - _: Token, - ) -> CompileResult<(Box, Vec, Memory)>; + unsafe fn from_cache(&self, cache: Artifact, _: Token) -> Result; } /// The functionality exposed by this trait is expected to be used @@ -99,3 +90,10 @@ pub trait FuncResolver: Send + Sync { local_func_index: LocalFuncIndex, ) -> Option>; } + +pub trait CacheGen: Send + Sync { + fn generate_cache( + &self, + module: &ModuleInner, + ) -> Result<(Box, Box<[u8]>, Memory), CacheError>; +} diff --git a/lib/runtime-core/src/backing.rs b/lib/runtime-core/src/backing.rs index d81e33065..ef2556895 100644 --- a/lib/runtime-core/src/backing.rs +++ b/lib/runtime-core/src/backing.rs @@ -91,7 +91,7 @@ impl LocalBacking { } } as usize; - match init.memory_index.local_or_import(module) { + match init.memory_index.local_or_import(&module.info) { LocalOrImport::Local(local_memory_index) => { let memory_desc = module.info.memories[local_memory_index]; let data_top = init_base + init.data.len(); @@ -159,7 +159,7 @@ impl LocalBacking { } } as usize; - match init.table_index.local_or_import(module) { + match init.table_index.local_or_import(&module.info) { LocalOrImport::Local(local_table_index) => { let table = &tables[local_table_index]; @@ -177,7 +177,7 @@ impl LocalBacking { SigRegistry.lookup_sig_index(Arc::clone(&signature)).index() as u32, ); - let (func, ctx) = match func_index.local_or_import(module) { + let (func, ctx) = match func_index.local_or_import(&module.info) { LocalOrImport::Local(local_func_index) => ( module .func_resolver @@ -215,7 +215,7 @@ impl LocalBacking { SigRegistry.lookup_sig_index(Arc::clone(&signature)).index() as u32, ); - let (func, ctx) = match func_index.local_or_import(module) { + let (func, ctx) = match func_index.local_or_import(&module.info) { LocalOrImport::Local(local_func_index) => ( module .func_resolver @@ -364,7 +364,7 @@ fn import_functions( }, ) in &module.info.imported_functions { - let sig_index = module.info.func_assoc[index.convert_up(module)]; + let sig_index = module.info.func_assoc[index.convert_up(&module.info)]; let expected_sig = &module.info.signatures[sig_index]; let namespace = module.info.namespace_table.get(*namespace_index); diff --git a/lib/runtime-core/src/cache.rs b/lib/runtime-core/src/cache.rs index e166599e8..3d894bb09 100644 --- a/lib/runtime-core/src/cache.rs +++ b/lib/runtime-core/src/cache.rs @@ -1,14 +1,9 @@ -use crate::{module::ModuleInfo, sys::Memory}; -use memmap::Mmap; -use serde_bench::{deserialize, serialize}; -use sha2::{Digest, Sha256}; -use std::{ - fs::File, - io::{self, Seek, SeekFrom, Write}, - mem, - path::Path, - slice, +use crate::{ + module::{Module, ModuleInfo}, + sys::Memory, }; +use sha2::{Digest, Sha256}; +use std::{io, mem, slice}; #[derive(Debug)] pub enum InvalidFileType { @@ -26,23 +21,81 @@ pub enum Error { InvalidatedCache, } +impl From for Error { + fn from(io_err: io::Error) -> Self { + Error::IoError(io_err) + } +} + +/// The hash of a wasm module. +/// +/// Used as a key when loading and storing modules in a [`Cache`]. +/// +/// [`Cache`]: trait.Cache.html +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct WasmHash([u8; 32]); + +impl WasmHash { + /// Hash a wasm module. + /// + /// # Note: + /// This does no verification that the supplied data + /// is, in fact, a wasm module. + pub fn generate(wasm: &[u8]) -> Self { + let mut array = [0u8; 32]; + array.copy_from_slice(Sha256::digest(wasm).as_slice()); + WasmHash(array) + } + + /// Create the hexadecimal representation of the + /// stored hash. + pub fn encode(self) -> String { + hex::encode(self.0) + } + + pub(crate) fn into_array(self) -> [u8; 32] { + self.0 + } +} + const CURRENT_CACHE_VERSION: u64 = 0; +static WASMER_CACHE_MAGIC: [u8; 8] = *b"WASMER\0\0"; /// The header of a cache file. #[repr(C, packed)] -struct CacheHeader { +struct ArtifactHeader { magic: [u8; 8], // [W, A, S, M, E, R, \0, \0] version: u64, data_len: u64, wasm_hash: [u8; 32], // Sha256 of the wasm in binary format. } -impl CacheHeader { - pub fn read_from_slice(buffer: &[u8]) -> Result<(&CacheHeader, &[u8]), Error> { - if buffer.len() >= mem::size_of::() { - if &buffer[..8] == "WASMER\0\0".as_bytes() { - let (header_slice, body_slice) = buffer.split_at(mem::size_of::()); - let header = unsafe { &*(header_slice.as_ptr() as *const CacheHeader) }; +impl ArtifactHeader { + pub fn read_from_slice(buffer: &[u8]) -> Result<(&Self, &[u8]), Error> { + if buffer.len() >= mem::size_of::() { + if &buffer[..8] == &WASMER_CACHE_MAGIC { + let (header_slice, body_slice) = buffer.split_at(mem::size_of::()); + let header = unsafe { &*(header_slice.as_ptr() as *const ArtifactHeader) }; + + if header.version == CURRENT_CACHE_VERSION { + Ok((header, body_slice)) + } else { + Err(Error::InvalidatedCache) + } + } else { + Err(Error::InvalidFile(InvalidFileType::InvalidMagic)) + } + } else { + Err(Error::InvalidFile(InvalidFileType::InvalidSize)) + } + } + + pub fn read_from_slice_mut(buffer: &mut [u8]) -> Result<(&mut Self, &mut [u8]), Error> { + if buffer.len() >= mem::size_of::() { + if &buffer[..8] == &WASMER_CACHE_MAGIC { + let (header_slice, body_slice) = + buffer.split_at_mut(mem::size_of::()); + let header = unsafe { &mut *(header_slice.as_ptr() as *mut ArtifactHeader) }; if header.version == CURRENT_CACHE_VERSION { Ok((header, body_slice)) @@ -58,72 +111,53 @@ impl CacheHeader { } pub fn as_slice(&self) -> &[u8] { - let ptr = self as *const CacheHeader as *const u8; - unsafe { slice::from_raw_parts(ptr, mem::size_of::()) } + let ptr = self as *const ArtifactHeader as *const u8; + unsafe { slice::from_raw_parts(ptr, mem::size_of::()) } } } #[derive(Serialize, Deserialize)] -struct CacheInner { +struct ArtifactInner { info: Box, #[serde(with = "serde_bytes")] - backend_metadata: Vec, + backend_metadata: Box<[u8]>, compiled_code: Memory, } -pub struct Cache { - inner: CacheInner, - wasm_hash: Box<[u8; 32]>, +pub struct Artifact { + inner: ArtifactInner, } -impl Cache { - pub(crate) fn new( - wasm: &[u8], +impl Artifact { + pub(crate) fn from_parts( info: Box, - backend_metadata: Vec, + backend_metadata: Box<[u8]>, compiled_code: Memory, ) -> Self { - let wasm_hash = hash_data(wasm); - Self { - inner: CacheInner { + inner: ArtifactInner { info, backend_metadata, compiled_code, }, - wasm_hash: Box::new(wasm_hash), } } - pub fn open

(path: P) -> Result - where - P: AsRef, - { - let file = File::open(path).map_err(|e| Error::IoError(e))?; + pub fn deserialize(bytes: &[u8]) -> Result { + let (_, body_slice) = ArtifactHeader::read_from_slice(bytes)?; - let mmap = unsafe { Mmap::map(&file).map_err(|e| Error::IoError(e))? }; + let inner = serde_bench::deserialize(body_slice) + .map_err(|e| Error::DeserializeError(format!("{:#?}", e)))?; - let (header, body_slice) = CacheHeader::read_from_slice(&mmap[..])?; - - let inner = - deserialize(body_slice).map_err(|e| Error::DeserializeError(format!("{:#?}", e)))?; - - Ok(Cache { - inner, - wasm_hash: Box::new(header.wasm_hash), - }) + Ok(Artifact { inner }) } pub fn info(&self) -> &ModuleInfo { &self.inner.info } - pub fn wasm_hash(&self) -> &[u8; 32] { - &self.wasm_hash - } - #[doc(hidden)] - pub fn consume(self) -> (ModuleInfo, Vec, Memory) { + pub fn consume(self) -> (ModuleInfo, Box<[u8]>, Memory) { ( *self.inner.info, self.inner.backend_metadata, @@ -131,51 +165,35 @@ impl Cache { ) } - pub fn store

(&self, path: P) -> Result<(), Error> - where - P: AsRef, - { - let mut file = File::create(path).map_err(|e| Error::IoError(e))?; - - let mut buffer = Vec::new(); - - serialize(&mut buffer, &self.inner).map_err(|e| Error::SerializeError(e.to_string()))?; - - let data_len = buffer.len() as u64; - - file.seek(SeekFrom::Start(mem::size_of::() as u64)) - .map_err(|e| Error::IoError(e))?; - - file.write(buffer.as_slice()) - .map_err(|e| Error::IoError(e))?; - - file.seek(SeekFrom::Start(0)) - .map_err(|e| Error::Unknown(e.to_string()))?; - - let wasm_hash = { - let mut array = [0u8; 32]; - array.copy_from_slice(&*self.wasm_hash); - array - }; - - let cache_header = CacheHeader { - magic: [ - 'W' as u8, 'A' as u8, 'S' as u8, 'M' as u8, 'E' as u8, 'R' as u8, 0, 0, - ], + pub fn serialize(&self) -> Result, Error> { + let cache_header = ArtifactHeader { + magic: WASMER_CACHE_MAGIC, version: CURRENT_CACHE_VERSION, - data_len, - wasm_hash, + data_len: 0, + wasm_hash: self.inner.info.wasm_hash.into_array(), }; - file.write(cache_header.as_slice()) - .map_err(|e| Error::IoError(e))?; + let mut buffer = cache_header.as_slice().to_vec(); - Ok(()) + serde_bench::serialize(&mut buffer, &self.inner) + .map_err(|e| Error::SerializeError(e.to_string()))?; + + let data_len = (buffer.len() - mem::size_of::()) as u64; + + let (header, _) = ArtifactHeader::read_from_slice_mut(&mut buffer)?; + header.data_len = data_len; + + Ok(buffer) } } -pub fn hash_data(data: &[u8]) -> [u8; 32] { - let mut array = [0u8; 32]; - array.copy_from_slice(Sha256::digest(data).as_slice()); - array +/// A generic cache for storing and loading compiled wasm modules. +/// +/// The `wasmer-runtime` supplies a naive `FileSystemCache` api. +pub trait Cache { + type LoadError; + type StoreError; + + fn load(&self, key: WasmHash) -> Result; + fn store(&mut self, module: Module) -> Result; } diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index a8f36325a..a634b21e5 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -121,14 +121,14 @@ impl Instance { })?; } - let ctx = match func_index.local_or_import(&*self.module) { + let ctx = match func_index.local_or_import(&self.module.info) { LocalOrImport::Local(_) => self.inner.vmctx, LocalOrImport::Import(imported_func_index) => { self.inner.import_backing.vm_functions[imported_func_index].vmctx } }; - let func_ptr = match func_index.local_or_import(&self.module) { + let func_ptr = match func_index.local_or_import(&self.module.info) { LocalOrImport::Local(local_func_index) => self .module .func_resolver @@ -288,7 +288,7 @@ impl Instance { })? } - let vmctx = match func_index.local_or_import(&self.module) { + let vmctx = match func_index.local_or_import(&self.module.info) { LocalOrImport::Local(_) => self.inner.vmctx, LocalOrImport::Import(imported_func_index) => { self.inner.import_backing.vm_functions[imported_func_index].vmctx @@ -355,7 +355,7 @@ impl InstanceInner { .get(func_index) .expect("broken invariant, incorrect func index"); - let (func_ptr, ctx) = match func_index.local_or_import(module) { + let (func_ptr, ctx) = match func_index.local_or_import(&module.info) { LocalOrImport::Local(local_func_index) => ( module .func_resolver @@ -384,7 +384,7 @@ impl InstanceInner { } fn get_memory_from_index(&self, module: &ModuleInner, mem_index: MemoryIndex) -> Memory { - match mem_index.local_or_import(module) { + match mem_index.local_or_import(&module.info) { LocalOrImport::Local(local_mem_index) => self.backing.memories[local_mem_index].clone(), LocalOrImport::Import(imported_mem_index) => { self.import_backing.memories[imported_mem_index].clone() @@ -393,7 +393,7 @@ impl InstanceInner { } fn get_global_from_index(&self, module: &ModuleInner, global_index: GlobalIndex) -> Global { - match global_index.local_or_import(module) { + match global_index.local_or_import(&module.info) { LocalOrImport::Local(local_global_index) => { self.backing.globals[local_global_index].clone() } @@ -404,7 +404,7 @@ impl InstanceInner { } fn get_table_from_index(&self, module: &ModuleInner, table_index: TableIndex) -> Table { - match table_index.local_or_import(module) { + match table_index.local_or_import(&module.info) { LocalOrImport::Local(local_table_index) => { self.backing.tables[local_table_index].clone() } @@ -462,7 +462,7 @@ impl<'a> DynFunc<'a> { })? } - let vmctx = match self.func_index.local_or_import(self.module) { + let vmctx = match self.func_index.local_or_import(&self.module.info) { LocalOrImport::Local(_) => self.instance_inner.vmctx, LocalOrImport::Import(imported_func_index) => { self.instance_inner.import_backing.vm_functions[imported_func_index].vmctx @@ -488,7 +488,7 @@ impl<'a> DynFunc<'a> { } pub fn raw(&self) -> *const vm::Func { - match self.func_index.local_or_import(self.module) { + match self.func_index.local_or_import(&self.module.info) { LocalOrImport::Local(local_func_index) => self .module .func_resolver diff --git a/lib/runtime-core/src/lib.rs b/lib/runtime-core/src/lib.rs index d130b8899..663e17534 100644 --- a/lib/runtime-core/src/lib.rs +++ b/lib/runtime-core/src/lib.rs @@ -2,7 +2,6 @@ #[macro_use] extern crate field_offset; -#[cfg(feature = "cache")] #[macro_use] extern crate serde_derive; @@ -11,7 +10,7 @@ mod macros; #[doc(hidden)] pub mod backend; mod backing; -#[cfg(feature = "cache")] + pub mod cache; pub mod error; pub mod export; @@ -44,8 +43,7 @@ pub use self::module::Module; pub use self::typed_func::Func; use std::sync::Arc; -#[cfg(feature = "cache")] -use self::cache::{Cache, Error as CacheError}; +use self::cache::{Artifact, Error as CacheError}; pub mod prelude { pub use crate::import::{ImportObject, Namespace}; @@ -90,21 +88,8 @@ pub fn validate(wasm: &[u8]) -> bool { } } -#[cfg(feature = "cache")] -pub fn compile_to_cache_with( - wasm: &[u8], - compiler: &dyn backend::Compiler, -) -> CompileResult { - let token = backend::Token::generate(); - let (info, backend_metadata, compiled_code) = - compiler.compile_to_backend_cache_data(wasm, token)?; - - Ok(Cache::new(wasm, info, backend_metadata, compiled_code)) -} - -#[cfg(feature = "cache")] pub unsafe fn load_cache_with( - cache: Cache, + cache: Artifact, compiler: &dyn backend::Compiler, ) -> std::result::Result { let token = backend::Token::generate(); diff --git a/lib/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index 0fdb24018..130082491 100644 --- a/lib/runtime-core/src/module.rs +++ b/lib/runtime-core/src/module.rs @@ -1,6 +1,7 @@ use crate::{ backend::{Backend, FuncResolver, ProtectedCaller}, - error::Result, + cache::{Artifact, Error as CacheError, WasmHash}, + error, import::ImportObject, structures::{Map, TypedIndex}, typed_func::EARLY_TRAPPER, @@ -12,6 +13,8 @@ use crate::{ }, Instance, }; + +use crate::backend::CacheGen; use hashbrown::HashMap; use indexmap::IndexMap; use std::sync::Arc; @@ -22,10 +25,12 @@ pub struct ModuleInner { pub func_resolver: Box, pub protected_caller: Box, + pub cache_gen: Box, + pub info: ModuleInfo, } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] +#[derive(Clone, Serialize, Deserialize)] pub struct ModuleInfo { // This are strictly local and the typsystem ensures that. pub memories: Map, @@ -51,6 +56,8 @@ pub struct ModuleInfo { pub namespace_table: StringTable, pub name_table: StringTable, + + pub wasm_hash: WasmHash, } /// A compiled WebAssembly module. @@ -60,7 +67,9 @@ pub struct ModuleInfo { /// /// [`compile`]: fn.compile.html /// [`compile_with`]: fn.compile_with.html -pub struct Module(#[doc(hidden)] pub Arc); +pub struct Module { + inner: Arc, +} impl Module { pub(crate) fn new(inner: Arc) -> Self { @@ -68,7 +77,7 @@ impl Module { EARLY_TRAPPER .with(|ucell| *ucell.get() = Some(inner.protected_caller.get_early_trapper())); } - Module(inner) + Module { inner } } /// Instantiate a WebAssembly module with the provided [`ImportObject`]. @@ -93,23 +102,30 @@ impl Module { /// # Ok(()) /// # } /// ``` - pub fn instantiate(&self, import_object: &ImportObject) -> Result { - Instance::new(Arc::clone(&self.0), import_object) + pub fn instantiate(&self, import_object: &ImportObject) -> error::Result { + Instance::new(Arc::clone(&self.inner), import_object) + } + + pub fn cache(&self) -> Result { + let (info, backend_metadata, code) = self.inner.cache_gen.generate_cache(&self.inner)?; + Ok(Artifact::from_parts(info, backend_metadata, code)) + } + + pub fn info(&self) -> &ModuleInfo { + &self.inner.info } } impl ModuleInner {} #[doc(hidden)] -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct ImportName { pub namespace_index: NamespaceIndex, pub name_index: NameIndex, } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub enum ExportIndex { Func(FuncIndex), Memory(MemoryIndex), @@ -118,8 +134,7 @@ pub enum ExportIndex { } /// A data initializer for linear memory. -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct DataInitializer { /// The index of the memory to initialize. pub memory_index: MemoryIndex, @@ -131,8 +146,7 @@ pub struct DataInitializer { } /// A WebAssembly table initializer. -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct TableInitializer { /// The index of a table to initialize. pub table_index: TableIndex, @@ -193,8 +207,7 @@ impl StringTableBuilder { } } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct StringTable { table: Map, buffer: String, @@ -217,8 +230,7 @@ impl StringTable { } } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct NamespaceIndex(u32); impl TypedIndex for NamespaceIndex { @@ -233,8 +245,7 @@ impl TypedIndex for NamespaceIndex { } } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct NameIndex(u32); impl TypedIndex for NameIndex { diff --git a/lib/runtime-core/src/structures/map.rs b/lib/runtime-core/src/structures/map.rs index f67000b6d..d7177c427 100644 --- a/lib/runtime-core/src/structures/map.rs +++ b/lib/runtime-core/src/structures/map.rs @@ -8,8 +8,7 @@ use std::{ }; /// Dense item map -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Map where K: TypedIndex, diff --git a/lib/runtime-core/src/sys/mod.rs b/lib/runtime-core/src/sys/mod.rs index 4079a506d..0bfc134c2 100644 --- a/lib/runtime-core/src/sys/mod.rs +++ b/lib/runtime-core/src/sys/mod.rs @@ -10,18 +10,16 @@ pub use self::unix::*; #[cfg(windows)] pub use self::windows::*; -#[cfg(feature = "cache")] use serde::{ de::{self, SeqAccess, Visitor}, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer, }; -#[cfg(feature = "cache")] + use serde_bytes::Bytes; -#[cfg(feature = "cache")] + use std::fmt; -#[cfg(feature = "cache")] impl Serialize for Memory { fn serialize(&self, serializer: S) -> Result where @@ -36,7 +34,6 @@ impl Serialize for Memory { } } -#[cfg(feature = "cache")] impl<'de> Deserialize<'de> for Memory { fn deserialize(deserializer: D) -> Result where diff --git a/lib/runtime-core/src/sys/unix/memory.rs b/lib/runtime-core/src/sys/unix/memory.rs index d1579d958..8c76e6c14 100644 --- a/lib/runtime-core/src/sys/unix/memory.rs +++ b/lib/runtime-core/src/sys/unix/memory.rs @@ -205,8 +205,28 @@ impl Drop for Memory { } } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +impl Clone for Memory { + fn clone(&self) -> Self { + let temp_protection = if self.protection.is_writable() { + self.protection + } else { + Protect::ReadWrite + }; + + let mut new = Memory::with_size_protect(self.size, temp_protection).unwrap(); + unsafe { + new.as_slice_mut().copy_from_slice(self.as_slice()); + + if temp_protection != self.protection { + new.protect(.., self.protection).unwrap(); + } + } + + new + } +} + +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] #[allow(dead_code)] pub enum Protect { None, diff --git a/lib/runtime-core/src/sys/windows/memory.rs b/lib/runtime-core/src/sys/windows/memory.rs index c8fd197eb..771ac6788 100644 --- a/lib/runtime-core/src/sys/windows/memory.rs +++ b/lib/runtime-core/src/sys/windows/memory.rs @@ -156,8 +156,28 @@ impl Drop for Memory { } } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +impl Clone for Memory { + fn clone(&self) -> Self { + let temp_protection = if self.protection.is_writable() { + self.protection + } else { + Protect::ReadWrite + }; + + let mut new = Memory::with_size_protect(self.size, temp_protection).unwrap(); + unsafe { + new.as_slice_mut().copy_from_slice(self.as_slice()); + + if temp_protection != self.protection { + new.protect(.., self.protection).unwrap(); + } + } + + new + } +} + +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] #[allow(dead_code)] pub enum Protect { None, diff --git a/lib/runtime-core/src/types.rs b/lib/runtime-core/src/types.rs index fd57bf12c..9ee5270dd 100644 --- a/lib/runtime-core/src/types.rs +++ b/lib/runtime-core/src/types.rs @@ -1,9 +1,8 @@ -use crate::{memory::MemoryType, module::ModuleInner, structures::TypedIndex, units::Pages}; +use crate::{memory::MemoryType, module::ModuleInfo, structures::TypedIndex, units::Pages}; use std::{borrow::Cow, mem}; /// Represents a WebAssembly type. -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Type { /// The `i32` type. I32, @@ -25,8 +24,7 @@ impl std::fmt::Display for Type { /// /// As the number of types in WebAssembly expand, /// this structure will expand as well. -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum Value { /// The `i32` type. I32(i32), @@ -171,15 +169,13 @@ impl ValueType for f64 { } } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub enum ElementType { /// Any wasm function. Anyfunc, } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub struct TableDescriptor { /// Type of data stored in this table. pub element: ElementType, @@ -203,8 +199,7 @@ impl TableDescriptor { /// A const value initializer. /// Over time, this will be able to represent more and more /// complex expressions. -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum Initializer { /// Corresponds to a `const.*` instruction. Const(Value), @@ -212,24 +207,21 @@ pub enum Initializer { GetGlobal(ImportedGlobalIndex), } -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub struct GlobalDescriptor { pub mutable: bool, pub ty: Type, } /// A wasm global. -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct GlobalInit { pub desc: GlobalDescriptor, pub init: Initializer, } /// A wasm memory. -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub struct MemoryDescriptor { /// The minimum number of allowed pages. pub minimum: Pages, @@ -261,8 +253,7 @@ impl MemoryDescriptor { /// The signature of a function that is either implemented /// in a wasm module or exposed to wasm by the host. -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] pub struct FuncSig { params: Cow<'static, [Type]>, returns: Cow<'static, [Type]>, @@ -324,7 +315,7 @@ pub trait LocalImport { #[rustfmt::skip] macro_rules! define_map_index { ($ty:ident) => { - #[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] + #[derive(Serialize, Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct $ty (u32); impl TypedIndex for $ty { @@ -364,23 +355,23 @@ define_map_index![ macro_rules! define_local_or_import { ($ty:ident, $local_ty:ident, $imported_ty:ident, $imports:ident) => { impl $ty { - pub fn local_or_import(self, module: &ModuleInner) -> LocalOrImport<$ty> { - if self.index() < module.info.$imports.len() { + pub fn local_or_import(self, info: &ModuleInfo) -> LocalOrImport<$ty> { + if self.index() < info.$imports.len() { LocalOrImport::Import(::Import::new(self.index())) } else { - LocalOrImport::Local(::Local::new(self.index() - module.info.$imports.len())) + LocalOrImport::Local(::Local::new(self.index() - info.$imports.len())) } } } impl $local_ty { - pub fn convert_up(self, module: &ModuleInner) -> $ty { - $ty ((self.index() + module.info.$imports.len()) as u32) + pub fn convert_up(self, info: &ModuleInfo) -> $ty { + $ty ((self.index() + info.$imports.len()) as u32) } } impl $imported_ty { - pub fn convert_up(self, _module: &ModuleInner) -> $ty { + pub fn convert_up(self, _info: &ModuleInfo) -> $ty { $ty (self.index() as u32) } } @@ -400,8 +391,7 @@ define_local_or_import![ (GlobalIndex | (LocalGlobalIndex, ImportedGlobalIndex): imported_globals), ]; -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct SigIndex(u32); impl TypedIndex for SigIndex { #[doc(hidden)] diff --git a/lib/runtime-core/src/units.rs b/lib/runtime-core/src/units.rs index 8fa54474b..2486c5738 100644 --- a/lib/runtime-core/src/units.rs +++ b/lib/runtime-core/src/units.rs @@ -7,8 +7,7 @@ const WASM_PAGE_SIZE: usize = 65_536; const WASM_MAX_PAGES: usize = 65_536; /// Units of WebAssembly pages (as specified to be 65,536 bytes). -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Pages(pub u32); impl Pages { @@ -33,8 +32,7 @@ impl fmt::Debug for Pages { } /// Units of WebAssembly memory in terms of 8-bit bytes. -#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))] -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Bytes(pub usize); impl fmt::Debug for Bytes { diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index faee819be..ab386401b 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -116,7 +116,7 @@ impl Ctx { pub fn memory(&self, mem_index: u32) -> &Memory { let module = unsafe { &*self.module }; let mem_index = MemoryIndex::new(mem_index as usize); - match mem_index.local_or_import(module) { + match mem_index.local_or_import(&module.info) { LocalOrImport::Local(local_mem_index) => unsafe { let local_backing = &*self.local_backing; &local_backing.memories[local_mem_index] @@ -494,7 +494,10 @@ mod vm_ctx_tests { fn generate_module() -> ModuleInner { use super::Func; - use crate::backend::{Backend, FuncResolver, ProtectedCaller, Token, UserTrapper}; + use crate::backend::{ + sys::Memory, Backend, CacheGen, FuncResolver, ProtectedCaller, Token, UserTrapper, + }; + use crate::cache::{Error as CacheError, WasmHash}; use crate::error::RuntimeResult; use crate::types::{FuncIndex, LocalFuncIndex, Value}; use hashbrown::HashMap; @@ -525,10 +528,19 @@ mod vm_ctx_tests { unimplemented!() } } + impl CacheGen for Placeholder { + fn generate_cache( + &self, + module: &ModuleInner, + ) -> Result<(Box, Box<[u8]>, Memory), CacheError> { + unimplemented!() + } + } ModuleInner { func_resolver: Box::new(Placeholder), protected_caller: Box::new(Placeholder), + cache_gen: Box::new(Placeholder), info: ModuleInfo { memories: Map::new(), globals: Map::new(), @@ -553,6 +565,8 @@ mod vm_ctx_tests { namespace_table: StringTable::new(), name_table: StringTable::new(), + + wasm_hash: WasmHash::generate(&[]), }, } } diff --git a/lib/runtime/Cargo.toml b/lib/runtime/Cargo.toml index 2a2478193..e4169a652 100644 --- a/lib/runtime/Cargo.toml +++ b/lib/runtime/Cargo.toml @@ -9,12 +9,16 @@ edition = "2018" readme = "README.md" [dependencies] -wasmer-runtime-core = { path = "../runtime-core", version = "0.1.2" } -wasmer-clif-backend = { path = "../clif-backend", version = "0.1.2", optional = true } lazy_static = "1.2.0" +memmap = "0.7.0" + +[dependencies.wasmer-runtime-core] +path = "../runtime-core" +version = "0.1.2" + +[dependencies.wasmer-clif-backend] +path = "../clif-backend" +version = "0.1.2" [features] -default = ["default-compiler", "cache"] -default-compiler = ["wasmer-clif-backend/cache", "wasmer-runtime-core/cache"] -cache = ["default-compiler"] debug = ["wasmer-clif-backend/debug", "wasmer-runtime-core/debug"] diff --git a/lib/runtime/src/cache.rs b/lib/runtime/src/cache.rs index ceb6d171e..09a93356b 100644 --- a/lib/runtime/src/cache.rs +++ b/lib/runtime/src/cache.rs @@ -1,128 +1,109 @@ use crate::Module; -use std::path::Path; -use wasmer_runtime_core::cache::{hash_data, Cache as CoreCache}; +use memmap::Mmap; +use std::{ + fs::{create_dir_all, File}, + io::{self, Write}, + path::PathBuf, +}; -pub use wasmer_runtime_core::cache::Error; +use wasmer_runtime_core::cache::Error as CacheError; +pub use wasmer_runtime_core::cache::{Artifact, Cache, WasmHash}; -/// On-disk storage of compiled WebAssembly. +/// Representation of a directory that contains compiled wasm artifacts. /// -/// A `Cache` can be used to quickly reload already -/// compiled WebAssembly from a previous execution -/// during which the wasm was explicitly compiled -/// as a `Cache`. +/// The `FileSystemCache` type implements the [`Cache`] trait, which allows it to be used +/// generically when some sort of cache is required. +/// +/// [`Cache`]: trait.Cache.html /// /// # Usage: /// +/// ```rust +/// use wasmer_runtime::cache::{Cache, FileSystemCache}; +/// +/// # use wasmer_runtime::{Module, error::CacheError}; +/// fn store_and_load_module(module: Module) -> Result { +/// // Create a new file system cache. +/// // This is unsafe because we can't ensure that the artifact wasn't +/// // corrupted or tampered with. +/// let mut fs_cache = unsafe { FileSystemCache::new("some/directory/goes/here")? }; +/// // Store a module into the cache. +/// // The returned `key` is equivalent to `module.info().wasm_hash`. +/// let key = fs_cache.store(module)?; +/// // Load the module back from the cache with the `key`. +/// fs_cache.load(key) +/// } /// ``` -/// use wasmer_runtime::{compile_cache, Cache}; -/// -/// # use wasmer_runtime::error::{CompileResult, CacheError}; -/// # fn make_cache(wasm: &[u8]) -> CompileResult<()> { -/// // Make a cache. -/// let cache = compile_cache(wasm)?; -/// -/// # Ok(()) -/// # } -/// # fn usage_cache(cache: Cache) -> Result<(), CacheError> { -/// // Store the cache in a file. -/// cache.store("some_cache_file")?; -/// -/// // Load the cache. -/// let cache = Cache::load("some_cache_file")?; -/// let module = unsafe { cache.into_module()? }; -/// # Ok(()) -/// # } -/// ``` -/// -/// # Performance Characteristics: -/// -/// Loading caches from files has been optimized for latency. -/// There is still more work to do that will reduce -/// loading time, especially for very large modules, -/// but it will require signifigant internal work. -/// -/// # Drawbacks: -/// -/// Due to internal shortcomings, you cannot convert -/// a module into a `Cache`. This means that compiling -/// into a `Cache` and then converting into a module -/// has more overhead than directly compiling -/// into a [`Module`]. -/// -/// [`Module`]: struct.Module.html -pub struct Cache(pub(crate) CoreCache); +pub struct FileSystemCache { + path: PathBuf, +} -impl Cache { - /// Load a `Cache` from the file specified by `path`. +impl FileSystemCache { + /// Construct a new `FileSystemCache` around the specified directory. /// - /// # Usage: - /// - /// ``` - /// use wasmer_runtime::Cache; - /// # use wasmer_runtime::error::CacheError; - /// - /// # fn load_cache() -> Result<(), CacheError> { - /// let cache = Cache::load("some_file.cache")?; - /// # Ok(()) - /// # } - /// ``` - pub fn load>(path: P) -> Result { - CoreCache::open(path).map(|core_cache| Cache(core_cache)) - } + /// # Note: + /// This method is unsafe because there's no way to ensure the artifacts + /// stored in this cache haven't been corrupted or tampered with. + pub unsafe fn new>(path: P) -> io::Result { + let path: PathBuf = path.into(); - /// Convert a `Cache` into a [`Module`]. - /// - /// [`Module`]: struct.Module.html - /// - /// # Usage: - /// - /// ``` - /// use wasmer_runtime::Cache; - /// - /// # use wasmer_runtime::error::CacheError; - /// # fn cache2module(cache: Cache) -> Result<(), CacheError> { - /// let module = unsafe { cache.into_module()? }; - /// # Ok(()) - /// # } - /// ``` - /// - /// # Notes: - /// - /// This method is unsafe because the runtime cannot confirm - /// that this cache was not tampered with or corrupted. - pub unsafe fn into_module(self) -> Result { - let default_compiler = super::default_compiler(); - - wasmer_runtime_core::load_cache_with(self.0, default_compiler) - } - - /// Compare the Sha256 hash of the wasm this cache was build - /// from with some other WebAssembly. - /// - /// The main use-case for this is invalidating old caches. - pub fn compare_wasm(&self, wasm: &[u8]) -> bool { - let param_wasm_hash = hash_data(wasm); - self.0.wasm_hash() as &[u8] == ¶m_wasm_hash as &[u8] - } - - /// Store this cache in a file. - /// - /// # Notes: - /// - /// If a file exists at the specified path, it will be overwritten. - /// - /// # Usage: - /// - /// ``` - /// use wasmer_runtime::Cache; - /// - /// # use wasmer_runtime::error::CacheError; - /// # fn store_cache(cache: Cache) -> Result<(), CacheError> { - /// cache.store("some_file.cache")?; - /// # Ok(()) - /// # } - /// ``` - pub fn store>(&self, path: P) -> Result<(), Error> { - self.0.store(path) + if path.exists() { + let metadata = path.metadata()?; + if metadata.is_dir() { + if !metadata.permissions().readonly() { + Ok(Self { path }) + } else { + // This directory is readonly. + Err(io::Error::new( + io::ErrorKind::PermissionDenied, + format!("the supplied path is readonly: {}", path.display()), + )) + } + } else { + // This path points to a file. + Err(io::Error::new( + io::ErrorKind::PermissionDenied, + format!( + "the supplied path already points to a file: {}", + path.display() + ), + )) + } + } else { + // Create the directory and any parent directories if they don't yet exist. + create_dir_all(&path)?; + Ok(Self { path }) + } + } +} + +impl Cache for FileSystemCache { + type LoadError = CacheError; + type StoreError = CacheError; + + fn load(&self, key: WasmHash) -> Result { + let filename = key.encode(); + let mut new_path_buf = self.path.clone(); + new_path_buf.push(filename); + let file = File::open(new_path_buf)?; + let mmap = unsafe { Mmap::map(&file)? }; + + let serialized_cache = Artifact::deserialize(&mmap[..])?; + unsafe { wasmer_runtime_core::load_cache_with(serialized_cache, super::default_compiler()) } + } + + fn store(&mut self, module: Module) -> Result { + let key = module.info().wasm_hash; + let filename = key.encode(); + let mut new_path_buf = self.path.clone(); + new_path_buf.push(filename); + + let serialized_cache = module.cache()?; + let buffer = serialized_cache.serialize()?; + + let mut file = File::create(new_path_buf)?; + file.write_all(&buffer)?; + + Ok(key) } } diff --git a/lib/runtime/src/lib.rs b/lib/runtime/src/lib.rs index f100a5bad..c7e3b3826 100644 --- a/lib/runtime/src/lib.rs +++ b/lib/runtime/src/lib.rs @@ -99,8 +99,7 @@ pub mod wasm { } pub mod error { - #[cfg(feature = "cache")] - pub use super::cache::Error as CacheError; + pub use wasmer_runtime_core::cache::Error as CacheError; pub use wasmer_runtime_core::error::*; } @@ -109,15 +108,10 @@ pub mod units { pub use wasmer_runtime_core::units::{Bytes, Pages}; } -#[cfg(feature = "cache")] -mod cache; +pub mod cache; -#[cfg(feature = "default-compiler")] use wasmer_runtime_core::backend::Compiler; -#[cfg(feature = "cache")] -pub use self::cache::Cache; - /// Compile WebAssembly binary code into a [`Module`]. /// This function is useful if it is necessary to /// compile a module before it can be instantiated @@ -131,7 +125,6 @@ pub use self::cache::Cache; /// binary code of the wasm module you want to compile. /// # Errors: /// If the operation fails, the function returns `Err(error::CompileError::...)`. -#[cfg(feature = "default-compiler")] pub fn compile(wasm: &[u8]) -> error::CompileResult { wasmer_runtime_core::compile_with(&wasm[..], default_compiler()) } @@ -154,38 +147,11 @@ pub fn compile(wasm: &[u8]) -> error::CompileResult { /// `error::CompileError`, `error::LinkError`, or /// `error::RuntimeError` (all combined into an `error::Error`), /// depending on the cause of the failure. -#[cfg(feature = "default-compiler")] pub fn instantiate(wasm: &[u8], import_object: &ImportObject) -> error::Result { let module = compile(wasm)?; module.instantiate(import_object) } -/// Compile wasm into a [`Cache`] that can be stored to a file or -/// converted into [`Module`]. -/// -/// [`Cache`]: struct.Cache.html -/// [`Module`]: struct.Module.html -/// -/// # Usage: -/// -/// ``` -/// # use wasmer_runtime::error::CompileResult; -/// use wasmer_runtime::compile_cache; -/// -/// # fn make_cache(wasm: &[u8]) -> CompileResult<()> { -/// let cache = compile_cache(wasm)?; -/// # Ok(()) -/// # } -/// ``` -#[cfg(feature = "cache")] -pub fn compile_cache(wasm: &[u8]) -> error::CompileResult { - let default_compiler = default_compiler(); - - wasmer_runtime_core::compile_to_cache_with(wasm, default_compiler) - .map(|core_cache| Cache(core_cache)) -} - -#[cfg(feature = "default-compiler")] fn default_compiler() -> &'static dyn Compiler { use lazy_static::lazy_static; use wasmer_clif_backend::CraneliftCompiler; diff --git a/lib/spectests/Cargo.toml b/lib/spectests/Cargo.toml index c02e8269d..f69782b40 100644 --- a/lib/spectests/Cargo.toml +++ b/lib/spectests/Cargo.toml @@ -19,5 +19,5 @@ wasmer-clif-backend = { path = "../clif-backend", version = "0.1.2" } wabt = "0.7.2" [features] -default = ["fast-tests", "wasmer-runtime-core/cache", "wasmer-clif-backend/cache"] +default = ["fast-tests"] fast-tests = [] \ No newline at end of file