mirror of
https://github.com/fluencelabs/wasmer
synced 2024-12-12 22:05:33 +00:00
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:
commit
40e67e37aa
@ -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
|
||||||
|
@ -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"]
|
||||||
|
|
||||||
|
@ -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" = []
|
||||||
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)?;
|
||||||
|
|
||||||
|
@ -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,31 +368,49 @@ 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> {
|
||||||
// cache.load will return the Module if it's able to deserialize it properly, and an error if:
|
if let Some(ref prehashed_cache_key) = options.cache_key {
|
||||||
// * The file is not found
|
if let Ok(module) =
|
||||||
// * The file exists, but it's corrupted or can't be converted to a module
|
WasmHash::decode(prehashed_cache_key).and_then(|prehashed_key| {
|
||||||
match cache.load(hash) {
|
cache.load_with_backend(prehashed_key, options.backend)
|
||||||
Ok(module) => {
|
})
|
||||||
// We are able to load the module from cache
|
{
|
||||||
module
|
debug!("using prehashed key: {}", prehashed_cache_key);
|
||||||
|
return Ok(module);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
|
||||||
let module = webassembly::compile_with_config_with(
|
|
||||||
&wasm_binary[..],
|
|
||||||
CompilerConfig {
|
|
||||||
symbol_map: em_symbol_map,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&*compiler,
|
|
||||||
)
|
|
||||||
.map_err(|e| format!("Can't compile module: {:?}", e))?;
|
|
||||||
// We try to save the module into a cache file
|
|
||||||
cache.store(hash, module.clone()).unwrap_or_default();
|
|
||||||
|
|
||||||
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:
|
||||||
|
// * The file is not found
|
||||||
|
// * The file exists, but it's corrupted or can't be converted to a module
|
||||||
|
match cache.load_with_backend(hash, options.backend) {
|
||||||
|
Ok(module) => {
|
||||||
|
// We are able to load the module from cache
|
||||||
|
Ok(module)
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
let module = webassembly::compile_with_config_with(
|
||||||
|
&wasm_binary[..],
|
||||||
|
CompilerConfig {
|
||||||
|
symbol_map: em_symbol_map,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&*compiler,
|
||||||
|
)
|
||||||
|
.map_err(|e| format!("Can't compile module: {:?}", e))?;
|
||||||
|
// We try to save the module into a cache file
|
||||||
|
cache.store(hash, module.clone()).unwrap_or_default();
|
||||||
|
|
||||||
|
Ok(module)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
load_cache_key()?
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(loader) = options.loader {
|
if let Some(loader) = options.loader {
|
||||||
|
Loading…
Reference in New Issue
Block a user