Merge pull request #537 from wasmerio/feature/prehashed-keys

Add hidden flag `--cache-key` to use prehashed modules for speed
This commit is contained in:
Syrus Akbary 2019-07-09 15:15:59 -07:00 committed by GitHub
commit 40e67e37aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 101 additions and 48 deletions

View File

@ -5,8 +5,10 @@ All PRs to the Wasmer repository must add to this file.
Blocks of changes will separated by version increments. Blocks of changes will separated by version increments.
## **[Unreleased]** ## **[Unreleased]**
- [#537](https://github.com/wasmerio/wasmer/pull/537) Add hidden flag (`--cache-key`) to use prehashed key into the compiled wasm cache and change compiler backend-specific caching to use directories
- [#536](https://github.com/wasmerio/wasmer/pull/536) ~Update cache to use compiler backend name in cache key~
## 0.5.4 ## 0.5.4
- [#536](https://github.com/wasmerio/wasmer/pull/536) Update cache to use compiler backend name in cache key
- [#529](https://github.com/wasmerio/wasmer/pull/529) Updates the Wasm Interface library, which is used by wapm, with bug fixes and error message improvements - [#529](https://github.com/wasmerio/wasmer/pull/529) Updates the Wasm Interface library, which is used by wapm, with bug fixes and error message improvements
## 0.5.3 ## 0.5.3

View File

@ -69,8 +69,8 @@ trace = ["wasmer-runtime-core/trace"]
extra-debug = ["wasmer-clif-backend/debug", "wasmer-runtime-core/debug"] extra-debug = ["wasmer-clif-backend/debug", "wasmer-runtime-core/debug"]
# This feature will allow cargo test to run much faster # This feature will allow cargo test to run much faster
fast-tests = [] fast-tests = []
"backend:llvm" = ["wasmer-llvm-backend"] "backend:llvm" = ["wasmer-llvm-backend", "wasmer-runtime-core/backend:llvm"]
"backend:singlepass" = ["wasmer-singlepass-backend"] "backend:singlepass" = ["wasmer-singlepass-backend", "wasmer-runtime-core/backend:singlepass"]
wasi = ["wasmer-wasi"] wasi = ["wasmer-wasi"]
# vfs = ["wasmer-runtime-abi"] # vfs = ["wasmer-runtime-abi"]

View File

@ -54,3 +54,6 @@ cc = "1.0"
[features] [features]
debug = [] debug = []
trace = ["debug"] trace = ["debug"]
# backend flags used in conditional compilation of Backend::variants
"backend:singlepass" = []
"backend:llvm" = []

View File

@ -37,31 +37,17 @@ impl From<io::Error> for Error {
pub struct WasmHash([u8; 32], [u8; 32]); pub struct WasmHash([u8; 32], [u8; 32]);
impl WasmHash { impl WasmHash {
/// Hash a wasm module for the default compiler backend. /// Hash a wasm module.
///
/// See also: `WasmHash::generate_for_backend`.
/// ///
/// # Note: /// # Note:
/// This does no verification that the supplied data /// This does no verification that the supplied data
/// is, in fact, a wasm module. /// is, in fact, a wasm module.
pub fn generate(wasm: &[u8]) -> Self { pub fn generate(wasm: &[u8]) -> Self {
WasmHash::generate_for_backend(wasm, Backend::default())
}
/// Hash a wasm module for a specific compiler backend.
/// This allows multiple cache entries containing the same compiled
/// module with different compiler backends.
///
/// # Note:
/// This function also does no verification that the supplied data
/// is a wasm module
pub fn generate_for_backend(wasm: &[u8], backend: Backend) -> Self {
let mut first_part = [0u8; 32]; let mut first_part = [0u8; 32];
let mut second_part = [0u8; 32]; let mut second_part = [0u8; 32];
let mut state = blake2bp::State::new(); let mut state = blake2bp::State::new();
state.update(wasm); state.update(wasm);
state.update(backend.to_string().as_bytes());
let hasher = state.finalize(); let hasher = state.finalize();
let generic_array = hasher.as_bytes(); let generic_array = hasher.as_bytes();
@ -77,6 +63,30 @@ impl WasmHash {
hex::encode(&self.into_array() as &[u8]) hex::encode(&self.into_array() as &[u8])
} }
/// Create hash from hexadecimal representation
pub fn decode(hex_str: &str) -> Result<Self, Error> {
let bytes = hex::decode(hex_str).map_err(|e| {
Error::DeserializeError(format!(
"Could not decode prehashed key as hexadecimal: {}",
e
))
})?;
if bytes.len() != 64 {
return Err(Error::DeserializeError(
"Prehashed keys must deserialze into exactly 64 bytes".to_string(),
));
}
use std::convert::TryInto;
Ok(WasmHash(
bytes[0..32].try_into().map_err(|e| {
Error::DeserializeError(format!("Could not get first 32 bytes: {}", e))
})?,
bytes[32..64].try_into().map_err(|e| {
Error::DeserializeError(format!("Could not get last 32 bytes: {}", e))
})?,
))
}
pub(crate) fn into_array(self) -> [u8; 64] { pub(crate) fn into_array(self) -> [u8; 64] {
let mut total = [0u8; 64]; let mut total = [0u8; 64];
total[0..32].copy_from_slice(&self.0); total[0..32].copy_from_slice(&self.0);
@ -219,7 +229,11 @@ pub trait Cache {
type LoadError: fmt::Debug; type LoadError: fmt::Debug;
type StoreError: fmt::Debug; type StoreError: fmt::Debug;
/// loads a module using the default `Backend`
fn load(&self, key: WasmHash) -> Result<Module, Self::LoadError>; fn load(&self, key: WasmHash) -> Result<Module, Self::LoadError>;
/// loads a cached module using a specific `Backend`
fn load_with_backend(&self, key: WasmHash, backend: Backend)
-> Result<Module, Self::LoadError>;
fn store(&mut self, key: WasmHash, module: Module) -> Result<(), Self::StoreError>; fn store(&mut self, key: WasmHash, module: Module) -> Result<(), Self::StoreError>;
} }

View File

@ -7,7 +7,10 @@ use std::{
}; };
use wasmer_runtime_core::cache::Error as CacheError; use wasmer_runtime_core::cache::Error as CacheError;
pub use wasmer_runtime_core::cache::{Artifact, Cache, WasmHash, WASMER_VERSION_HASH}; pub use wasmer_runtime_core::{
backend::Backend,
cache::{Artifact, Cache, WasmHash, WASMER_VERSION_HASH},
};
/// Representation of a directory that contains compiled wasm artifacts. /// Representation of a directory that contains compiled wasm artifacts.
/// ///
@ -20,7 +23,6 @@ pub use wasmer_runtime_core::cache::{Artifact, Cache, WasmHash, WASMER_VERSION_H
/// ///
/// ```rust /// ```rust
/// use wasmer_runtime::cache::{Cache, FileSystemCache, WasmHash}; /// use wasmer_runtime::cache::{Cache, FileSystemCache, WasmHash};
/// use wasmer_runtime_core::backend::Backend;
/// ///
/// # use wasmer_runtime::{Module, error::CacheError}; /// # use wasmer_runtime::{Module, error::CacheError};
/// fn store_module(module: Module) -> Result<Module, CacheError> { /// fn store_module(module: Module) -> Result<Module, CacheError> {
@ -29,7 +31,7 @@ pub use wasmer_runtime_core::cache::{Artifact, Cache, WasmHash, WASMER_VERSION_H
/// // corrupted or tampered with. /// // corrupted or tampered with.
/// let mut fs_cache = unsafe { FileSystemCache::new("some/directory/goes/here")? }; /// let mut fs_cache = unsafe { FileSystemCache::new("some/directory/goes/here")? };
/// // Compute a key for a given WebAssembly binary /// // Compute a key for a given WebAssembly binary
/// let key = WasmHash::generate_for_backend(&[], Backend::Cranelift); /// let key = WasmHash::generate(&[]);
/// // Store a module into the cache given a key /// // Store a module into the cache given a key
/// fs_cache.store(key, module.clone())?; /// fs_cache.store(key, module.clone())?;
/// Ok(module) /// Ok(module)
@ -88,8 +90,13 @@ impl Cache for FileSystemCache {
type StoreError = CacheError; type StoreError = CacheError;
fn load(&self, key: WasmHash) -> Result<Module, CacheError> { fn load(&self, key: WasmHash) -> Result<Module, CacheError> {
self.load_with_backend(key, Backend::default())
}
fn load_with_backend(&self, key: WasmHash, backend: Backend) -> Result<Module, CacheError> {
let filename = key.encode(); let filename = key.encode();
let mut new_path_buf = self.path.clone(); let mut new_path_buf = self.path.clone();
new_path_buf.push(backend.to_string());
new_path_buf.push(filename); new_path_buf.push(filename);
let file = File::open(new_path_buf)?; let file = File::open(new_path_buf)?;
let mmap = unsafe { Mmap::map(&file)? }; let mmap = unsafe { Mmap::map(&file)? };
@ -102,12 +109,15 @@ impl Cache for FileSystemCache {
fn store(&mut self, key: WasmHash, module: Module) -> Result<(), CacheError> { fn store(&mut self, key: WasmHash, module: Module) -> Result<(), CacheError> {
let filename = key.encode(); let filename = key.encode();
let backend_str = module.info().backend.to_string();
let mut new_path_buf = self.path.clone(); let mut new_path_buf = self.path.clone();
new_path_buf.push(filename); new_path_buf.push(backend_str);
let serialized_cache = module.cache()?; let serialized_cache = module.cache()?;
let buffer = serialized_cache.serialize()?; let buffer = serialized_cache.serialize()?;
std::fs::create_dir_all(&new_path_buf)?;
new_path_buf.push(filename);
let mut file = File::create(new_path_buf)?; let mut file = File::create(new_path_buf)?;
file.write_all(&buffer)?; file.write_all(&buffer)?;

View File

@ -24,6 +24,7 @@ use wasmer_runtime::{
use wasmer_runtime_core::{ use wasmer_runtime_core::{
self, self,
backend::{Backend, Compiler, CompilerConfig, MemoryBoundCheckMode}, backend::{Backend, Compiler, CompilerConfig, MemoryBoundCheckMode},
debug,
loader::{Instance as LoadedInstance, LocalLoader}, loader::{Instance as LoadedInstance, LocalLoader},
}; };
#[cfg(feature = "backend:singlepass")] #[cfg(feature = "backend:singlepass")]
@ -115,9 +116,18 @@ struct Run {
#[structopt(long = "resume")] #[structopt(long = "resume")]
resume: Option<String>, resume: Option<String>,
/// The command name is a string that will override the first argument passed
/// to the wasm program. This is used in wapm to provide nicer output in
/// help commands and error messages of the running wasm program
#[structopt(long = "command-name", hidden = true)] #[structopt(long = "command-name", hidden = true)]
command_name: Option<String>, command_name: Option<String>,
/// A prehashed string, used to speed up start times by avoiding hashing the
/// wasm module. If the specified hash is not found, Wasmer will hash the module
/// as if no `cache-key` argument was passed.
#[structopt(long = "cache-key", hidden = true)]
cache_key: Option<String>,
/// Application arguments /// Application arguments
#[structopt(name = "--", raw(multiple = "true"))] #[structopt(name = "--", raw(multiple = "true"))]
args: Vec<String>, args: Vec<String>,
@ -350,10 +360,6 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
} else { } else {
// If we have cache enabled // If we have cache enabled
// We generate a hash for the given binary, so we can use it as key
// for the Filesystem cache
let hash = WasmHash::generate_for_backend(&wasm_binary, options.backend);
let wasmer_cache_dir = get_cache_dir(); let wasmer_cache_dir = get_cache_dir();
// We create a new cache instance. // We create a new cache instance.
@ -362,14 +368,29 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
let mut cache = unsafe { let mut cache = unsafe {
FileSystemCache::new(wasmer_cache_dir).map_err(|e| format!("Cache error: {:?}", e))? FileSystemCache::new(wasmer_cache_dir).map_err(|e| format!("Cache error: {:?}", e))?
}; };
let load_cache_key = || -> Result<_, String> {
if let Some(ref prehashed_cache_key) = options.cache_key {
if let Ok(module) =
WasmHash::decode(prehashed_cache_key).and_then(|prehashed_key| {
cache.load_with_backend(prehashed_key, options.backend)
})
{
debug!("using prehashed key: {}", prehashed_cache_key);
return Ok(module);
}
}
// We generate a hash for the given binary, so we can use it as key
// for the Filesystem cache
let hash = WasmHash::generate(&wasm_binary);
// cache.load will return the Module if it's able to deserialize it properly, and an error if: // cache.load will return the Module if it's able to deserialize it properly, and an error if:
// * The file is not found // * The file is not found
// * The file exists, but it's corrupted or can't be converted to a module // * The file exists, but it's corrupted or can't be converted to a module
match cache.load(hash) { match cache.load_with_backend(hash, options.backend) {
Ok(module) => { Ok(module) => {
// We are able to load the module from cache // We are able to load the module from cache
module Ok(module)
} }
Err(_) => { Err(_) => {
let module = webassembly::compile_with_config_with( let module = webassembly::compile_with_config_with(
@ -384,11 +405,14 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
// We try to save the module into a cache file // We try to save the module into a cache file
cache.store(hash, module.clone()).unwrap_or_default(); cache.store(hash, module.clone()).unwrap_or_default();
module Ok(module)
} }
} }
}; };
load_cache_key()?
};
if let Some(loader) = options.loader { if let Some(loader) = options.loader {
let mut import_object = wasmer_runtime_core::import::ImportObject::new(); let mut import_object = wasmer_runtime_core::import::ImportObject::new();
import_object.allow_missing_functions = true; // Import initialization might be left to the loader. import_object.allow_missing_functions = true; // Import initialization might be left to the loader.