From 0597fbaa47d4c227079ed98de4b03f86ad1a2a47 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 8 Jan 2021 10:40:51 +0100 Subject: [PATCH 01/21] Add PinnedMemoryCache Based on InMemoryCache --- packages/vm/src/modules/mod.rs | 2 + .../vm/src/modules/pinned_memory_cache.rs | 99 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 packages/vm/src/modules/pinned_memory_cache.rs diff --git a/packages/vm/src/modules/mod.rs b/packages/vm/src/modules/mod.rs index 343057c08e..bb2ea7cda4 100644 --- a/packages/vm/src/modules/mod.rs +++ b/packages/vm/src/modules/mod.rs @@ -1,5 +1,7 @@ mod file_system_cache; mod in_memory_cache; +mod pinned_memory_cache; pub use file_system_cache::FileSystemCache; pub use in_memory_cache::InMemoryCache; +pub use pinned_memory_cache::PinnedMemoryCache; diff --git a/packages/vm/src/modules/pinned_memory_cache.rs b/packages/vm/src/modules/pinned_memory_cache.rs new file mode 100644 index 0000000000..cade1b692a --- /dev/null +++ b/packages/vm/src/modules/pinned_memory_cache.rs @@ -0,0 +1,99 @@ +use std::collections::HashMap; +use wasmer::{Module, Store}; + +use crate::{Checksum, VmResult}; + +/// An pinned in memory module cache +pub struct PinnedMemoryCache { + artifacts: HashMap>, +} + +impl PinnedMemoryCache { + /// Creates a new cache + pub fn new() -> Self { + PinnedMemoryCache { + artifacts: HashMap::new(), + } + } + + pub fn store(&mut self, checksum: &Checksum, module: Module) -> VmResult<()> { + let serialized_artifact = module.serialize()?; + self.artifacts.insert(*checksum, serialized_artifact); + Ok(()) + } + + /// Looks up a module in the cache and takes its artifact and + /// creates a new module from store and artifact. + pub fn load(&mut self, checksum: &Checksum, store: &Store) -> VmResult> { + match self.artifacts.get(checksum) { + Some(serialized_artifact) => { + let new_module = unsafe { Module::deserialize(store, &serialized_artifact) }?; + Ok(Some(new_module)) + } + None => Ok(None), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::size::Size; + use crate::wasm_backend::{compile_only, make_runtime_store}; + use wasmer::{imports, Instance as WasmerInstance}; + use wasmer_middlewares::metering::set_remaining_points; + + const TESTING_MEMORY_LIMIT: Size = Size::mebi(16); + const TESTING_GAS_LIMIT: u64 = 5_000; + + #[test] + fn pinned_memory_cache_run() { + let mut cache = PinnedMemoryCache::new(); + + // Create module + let wasm = wat::parse_str( + r#"(module + (type $t0 (func (param i32) (result i32))) + (func $add_one (export "add_one") (type $t0) (param $p0 i32) (result i32) + get_local $p0 + i32.const 1 + i32.add) + )"#, + ) + .unwrap(); + let checksum = Checksum::generate(&wasm); + + // Module does not exist + let store = make_runtime_store(TESTING_MEMORY_LIMIT); + let cache_entry = cache.load(&checksum, &store).unwrap(); + assert!(cache_entry.is_none()); + + // Compile module + let original = compile_only(&wasm).unwrap(); + + // Ensure original module can be executed + { + let instance = WasmerInstance::new(&original, &imports! {}).unwrap(); + set_remaining_points(&instance, TESTING_GAS_LIMIT); + let add_one = instance.exports.get_function("add_one").unwrap(); + let result = add_one.call(&[42.into()]).unwrap(); + assert_eq!(result[0].unwrap_i32(), 43); + } + + // Store module + cache.store(&checksum, original).unwrap(); + + // Load module + let store = make_runtime_store(TESTING_MEMORY_LIMIT); + let cached = cache.load(&checksum, &store).unwrap().unwrap(); + + // Ensure cached module can be executed + { + let instance = WasmerInstance::new(&cached, &imports! {}).unwrap(); + set_remaining_points(&instance, TESTING_GAS_LIMIT); + let add_one = instance.exports.get_function("add_one").unwrap(); + let result = add_one.call(&[42.into()]).unwrap(); + assert_eq!(result[0].unwrap_i32(), 43); + } + } +} From 13932027f9274a906445a97d89db6373548455c0 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 8 Jan 2021 10:59:53 +0100 Subject: [PATCH 02/21] Integrate PinnedMemoryCache to Cache --- packages/vm/src/cache.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 25efb5ac7d..f216f78448 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -9,7 +9,7 @@ use crate::checksum::Checksum; use crate::compatibility::check_wasm; use crate::errors::{VmError, VmResult}; use crate::instance::{Instance, InstanceOptions}; -use crate::modules::{FileSystemCache, InMemoryCache}; +use crate::modules::{FileSystemCache, InMemoryCache, PinnedMemoryCache}; use crate::size::Size; use crate::wasm_backend::{compile_and_use, compile_only, make_runtime_store}; @@ -18,6 +18,7 @@ const MODULES_DIR: &str = "modules"; #[derive(Debug, Default, Clone, Copy)] pub struct Stats { + pub hits_pinned_memory_cache: u32, pub hits_memory_cache: u32, pub hits_fs_cache: u32, pub misses: u32, @@ -39,6 +40,7 @@ pub struct Cache { /// Instances memory limit in bytes. Use a value that is divisible by the Wasm page size 65536, /// e.g. full MiBs. instance_memory_limit: Size, + pinned_memory_cache: PinnedMemoryCache, memory_cache: InMemoryCache, fs_cache: FileSystemCache, stats: Stats, @@ -80,6 +82,7 @@ where wasm_path, supported_features, instance_memory_limit, + pinned_memory_cache: PinnedMemoryCache::new(), memory_cache: InMemoryCache::new(memory_cache_size), fs_cache, stats: Stats::default(), @@ -124,6 +127,15 @@ where backend: Backend, options: InstanceOptions, ) -> VmResult> { + let store = make_runtime_store(self.instance_memory_limit); + // Try to get module from the pinned memory cache + if let Some(module) = self.pinned_memory_cache.load(checksum, &store)? { + self.stats.hits_pinned_memory_cache += 1; + let instance = + Instance::from_module(&module, backend, options.gas_limit, options.print_debug)?; + return Ok(instance); + } + // Get module from memory cache if let Some(module) = self.memory_cache.load(checksum)? { self.stats.hits_memory_cache += 1; @@ -133,7 +145,6 @@ where } // Get module from file system cache - let store = make_runtime_store(self.instance_memory_limit); if let Some(module) = self.fs_cache.load(checksum, &store)? { self.stats.hits_fs_cache += 1; let instance = From a77622f015eacd62ad7c78f395069d435fb2a42b Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 8 Jan 2021 16:15:02 +0100 Subject: [PATCH 03/21] pin() / unpin() impl (unoptimized) --- packages/vm/src/cache.rs | 67 +++++++++++++++++++ .../vm/src/modules/pinned_memory_cache.rs | 8 +++ 2 files changed, 75 insertions(+) diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index f216f78448..0ff7a29931 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -119,6 +119,30 @@ where } } + /// Pins a Wasm that was previously stored via save_wasm. + /// This stores an already saved wasm into the pinned memory cache. + /// + /// If the given ID is not found, or the content does not match the hash (=ID), an error is returned. + pub fn pin_wasm(&mut self, checksum: &Checksum) -> VmResult<()> { + let code = load_wasm_from_disk(&self.wasm_path, checksum)?; + // verify hash matches (integrity check) + if Checksum::generate(&code) != *checksum { + Err(VmError::integrity_err()) + } else { + // store into the pinned cache + let module = compile_only(code.as_slice())?; + self.pinned_memory_cache.store(checksum, module) + } + } + + /// Unpins a Wasm, i.e. removes it from the pinned memory cache. + /// + /// Not found IDs are silently ignored, and no integrity check (checksum validation) is done + /// on the removed value. + pub fn unpin_wasm(&mut self, checksum: &Checksum) -> VmResult<()> { + self.pinned_memory_cache.remove(checksum) + } + /// Returns an Instance tied to a previously saved Wasm. /// Depending on availability, this is either generated from a cached instance, a cached module or Wasm code. pub fn get_instance( @@ -679,4 +703,47 @@ mod tests { let loaded = load_wasm_from_disk(&path, &id).unwrap(); assert_eq!(code, loaded); } + + #[test] + fn pin_unpin_works() { + let mut cache = unsafe { Cache::new(make_testing_options()).unwrap() }; + let id = cache.save_wasm(CONTRACT).unwrap(); + + // check not pinned + let backend = mock_backend(&[]); + let _instance = cache.get_instance(&id, backend, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); + assert_eq!(cache.stats().hits_memory_cache, 0); + assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().misses, 0); + + // pin + let _ = cache.pin_wasm(&id); + + // check pinned + let backend = mock_backend(&[]); + let _instance = cache.get_instance(&id, backend, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 1); + assert_eq!(cache.stats().hits_memory_cache, 0); + assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().misses, 0); + + // unpin + let _ = cache.unpin_wasm(&id); + + // verify unpinned + let backend = mock_backend(&[]); + let _instance = cache.get_instance(&id, backend, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 1); + assert_eq!(cache.stats().hits_memory_cache, 1); + assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().misses, 0); + + // unpin again has no effect + let _ = cache.unpin_wasm(&id); + + // unpin non existent id has no effect + let non_id = Checksum::generate(b"non_existent"); + let _ = cache.unpin_wasm(&non_id); + } } diff --git a/packages/vm/src/modules/pinned_memory_cache.rs b/packages/vm/src/modules/pinned_memory_cache.rs index cade1b692a..88c151ba70 100644 --- a/packages/vm/src/modules/pinned_memory_cache.rs +++ b/packages/vm/src/modules/pinned_memory_cache.rs @@ -22,6 +22,14 @@ impl PinnedMemoryCache { Ok(()) } + /// Removes a module from the cache. + /// Not found modules are silently ignored. + /// Potential integrity errors (wrong checksum) are not checked / enforced. + pub fn remove(&mut self, checksum: &Checksum) -> VmResult<()> { + self.artifacts.remove(checksum); + Ok(()) + } + /// Looks up a module in the cache and takes its artifact and /// creates a new module from store and artifact. pub fn load(&mut self, checksum: &Checksum, store: &Store) -> VmResult> { From 42d624f9d015759f6bb07f09a180e3ff65177800 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 8 Jan 2021 16:32:40 +0100 Subject: [PATCH 04/21] Improve unpin() checks --- packages/vm/src/cache.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 0ff7a29931..bf3a7885d0 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -740,10 +740,10 @@ mod tests { assert_eq!(cache.stats().misses, 0); // unpin again has no effect - let _ = cache.unpin_wasm(&id); + let _ = cache.unpin_wasm(&id).unwrap(); // unpin non existent id has no effect let non_id = Checksum::generate(b"non_existent"); - let _ = cache.unpin_wasm(&non_id); + let _ = cache.unpin_wasm(&non_id).unwrap(); } } From 357cfef931dcb764f78b139da7b31b8f6e07aa75 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Sun, 10 Jan 2021 09:11:15 +0100 Subject: [PATCH 05/21] Add pinned stats checks to tests --- packages/vm/src/cache.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index bf3a7885d0..81de3445b8 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -316,6 +316,7 @@ mod tests { let _ = cache .get_instance(&checksum, backend, TESTING_OPTIONS) .unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 0); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); @@ -412,6 +413,7 @@ mod tests { let id = cache.save_wasm(CONTRACT).unwrap(); let backend = mock_backend(&[]); let _instance = cache.get_instance(&id, backend, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 0); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); @@ -427,18 +429,21 @@ mod tests { // from file system let _instance1 = cache.get_instance(&id, backend1, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 0); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); // from memory let _instance2 = cache.get_instance(&id, backend2, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 1); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); // from memory again let _instance3 = cache.get_instance(&id, backend3, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 2); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); @@ -454,6 +459,7 @@ mod tests { let mut instance = cache .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS) .unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 0); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); @@ -471,6 +477,7 @@ mod tests { let mut instance = cache .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS) .unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 1); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); @@ -494,6 +501,7 @@ mod tests { let mut instance = cache .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS) .unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 0); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); @@ -520,6 +528,7 @@ mod tests { let mut instance = cache .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS) .unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 1); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); @@ -596,6 +605,7 @@ mod tests { // Init from module cache let mut instance1 = cache.get_instance(&id, backend1, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 0); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); @@ -611,6 +621,7 @@ mod tests { // Init from memory cache let instance2 = cache.get_instance(&id, backend2, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 1); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); @@ -649,6 +660,7 @@ mod tests { print_debug: false, }; let mut instance2 = cache.get_instance(&id, backend2, options).unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 1); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); From 7fd8a1a2a50da7de710f8d789007227bed1a19aa Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Sun, 10 Jan 2021 09:29:46 +0100 Subject: [PATCH 06/21] Simplify pin_wasm() --- packages/vm/src/cache.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 81de3445b8..31a1419f65 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -124,15 +124,10 @@ where /// /// If the given ID is not found, or the content does not match the hash (=ID), an error is returned. pub fn pin_wasm(&mut self, checksum: &Checksum) -> VmResult<()> { - let code = load_wasm_from_disk(&self.wasm_path, checksum)?; - // verify hash matches (integrity check) - if Checksum::generate(&code) != *checksum { - Err(VmError::integrity_err()) - } else { - // store into the pinned cache - let module = compile_only(code.as_slice())?; - self.pinned_memory_cache.store(checksum, module) - } + let code = self.load_wasm(checksum)?; + // compile and store into the pinned cache + let module = compile_only(code.as_slice())?; + self.pinned_memory_cache.store(checksum, module) } /// Unpins a Wasm, i.e. removes it from the pinned memory cache. From b21c2a6a7dbd8c230c52667117296dc679f6c269 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Sun, 10 Jan 2021 09:32:16 +0100 Subject: [PATCH 07/21] Optimize pin_wasm() Try to load module from the fs cache --- packages/vm/src/cache.rs | 11 +++++++++-- packages/vm/src/wasm_backend/store.rs | 9 +++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 31a1419f65..ea7749316e 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -124,6 +124,13 @@ where /// /// If the given ID is not found, or the content does not match the hash (=ID), an error is returned. pub fn pin_wasm(&mut self, checksum: &Checksum) -> VmResult<()> { + // Create an unlimited store (just for reading) + let store = make_runtime_store(Size(0)); + // Try to get module from file system cache first + if let Some(module) = self.fs_cache.load(checksum, &store)? { + self.stats.hits_fs_cache += 1; + return self.pinned_memory_cache.store(checksum, module); + } let code = self.load_wasm(checksum)?; // compile and store into the pinned cache let module = compile_only(code.as_slice())?; @@ -732,7 +739,7 @@ mod tests { let _instance = cache.get_instance(&id, backend, TESTING_OPTIONS).unwrap(); assert_eq!(cache.stats().hits_pinned_memory_cache, 1); assert_eq!(cache.stats().hits_memory_cache, 0); - assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().hits_fs_cache, 2); assert_eq!(cache.stats().misses, 0); // unpin @@ -743,7 +750,7 @@ mod tests { let _instance = cache.get_instance(&id, backend, TESTING_OPTIONS).unwrap(); assert_eq!(cache.stats().hits_pinned_memory_cache, 1); assert_eq!(cache.stats().hits_memory_cache, 1); - assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().hits_fs_cache, 2); assert_eq!(cache.stats().misses, 0); // unpin again has no effect diff --git a/packages/vm/src/wasm_backend/store.rs b/packages/vm/src/wasm_backend/store.rs index fdb092f56f..f26f41be38 100644 --- a/packages/vm/src/wasm_backend/store.rs +++ b/packages/vm/src/wasm_backend/store.rs @@ -55,10 +55,15 @@ pub fn make_compile_time_store(memory_limit: Option) -> Store { } /// Created a store with no compiler and the given memory limit (in bytes) -/// If memory_limit is None, no limit is applied. +/// If memory_limit is 0, no limit is applied. pub fn make_runtime_store(memory_limit: Size) -> Store { let engine = JIT::headless().engine(); - make_store_with_engine(&engine, Some(memory_limit)) + let limit = if memory_limit.0 > 0 { + Some(memory_limit) + } else { + None + }; + make_store_with_engine(&engine, limit) } /// Creates a store from an engine and an optional memory limit. From 9ce768b4579b9f866b0c6706b1c3a74ca4524e72 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Sun, 10 Jan 2021 09:45:16 +0100 Subject: [PATCH 08/21] Optimize pin_wasm() Try to load module from the memory cache first --- packages/vm/src/cache.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index ea7749316e..7866836dbf 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -122,15 +122,27 @@ where /// Pins a Wasm that was previously stored via save_wasm. /// This stores an already saved wasm into the pinned memory cache. /// + /// The module is lookup first in the memory cache, and then in the file system cache. + /// If not found, the code is loaded from the file system, compiled, and stored into the + /// pinned cache. /// If the given ID is not found, or the content does not match the hash (=ID), an error is returned. pub fn pin_wasm(&mut self, checksum: &Checksum) -> VmResult<()> { // Create an unlimited store (just for reading) let store = make_runtime_store(Size(0)); - // Try to get module from file system cache first + + // Try to get module from the memory cache + if let Some(module) = self.memory_cache.load(checksum, &store)? { + self.stats.hits_memory_cache += 1; + return self.pinned_memory_cache.store(checksum, module); + } + + // Try to get module from file system cache if let Some(module) = self.fs_cache.load(checksum, &store)? { self.stats.hits_fs_cache += 1; return self.pinned_memory_cache.store(checksum, module); } + + // Load code from the file system let code = self.load_wasm(checksum)?; // compile and store into the pinned cache let module = compile_only(code.as_slice())?; @@ -738,8 +750,8 @@ mod tests { let backend = mock_backend(&[]); let _instance = cache.get_instance(&id, backend, TESTING_OPTIONS).unwrap(); assert_eq!(cache.stats().hits_pinned_memory_cache, 1); - assert_eq!(cache.stats().hits_memory_cache, 0); - assert_eq!(cache.stats().hits_fs_cache, 2); + assert_eq!(cache.stats().hits_memory_cache, 1); + assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); // unpin @@ -749,8 +761,8 @@ mod tests { let backend = mock_backend(&[]); let _instance = cache.get_instance(&id, backend, TESTING_OPTIONS).unwrap(); assert_eq!(cache.stats().hits_pinned_memory_cache, 1); - assert_eq!(cache.stats().hits_memory_cache, 1); - assert_eq!(cache.stats().hits_fs_cache, 2); + assert_eq!(cache.stats().hits_memory_cache, 2); + assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); // unpin again has no effect From df755afbb56a9bc2a0f81a67322056a85968c5e1 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Sun, 10 Jan 2021 10:50:10 +0100 Subject: [PATCH 09/21] Add extra pinned cache tests --- packages/vm/src/cache.rs | 72 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 7866836dbf..0ddb48042d 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -440,6 +440,8 @@ mod tests { let backend1 = mock_backend(&[]); let backend2 = mock_backend(&[]); let backend3 = mock_backend(&[]); + let backend4 = mock_backend(&[]); + let backend5 = mock_backend(&[]); // from file system let _instance1 = cache.get_instance(&id, backend1, TESTING_OPTIONS).unwrap(); @@ -461,6 +463,27 @@ mod tests { assert_eq!(cache.stats().hits_memory_cache, 2); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); + + // pinning hits the memory cache + cache.pin_wasm(&id).unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); + assert_eq!(cache.stats().hits_memory_cache, 3); + assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().misses, 0); + + // from pinned memory cache + let _instance4 = cache.get_instance(&id, backend4, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 1); + assert_eq!(cache.stats().hits_memory_cache, 3); + assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().misses, 0); + + // from pinned memory cache again + let _instance5 = cache.get_instance(&id, backend5, TESTING_OPTIONS).unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 2); + assert_eq!(cache.stats().hits_memory_cache, 3); + assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().misses, 0); } #[test] @@ -503,6 +526,26 @@ mod tests { let msgs = res.unwrap().messages; assert_eq!(msgs.len(), 0); } + + // from pinned memory + { + cache.pin_wasm(&checksum).unwrap(); + + let mut instance = cache + .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS) + .unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 1); + assert_eq!(cache.stats().hits_memory_cache, 2); + assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().misses, 0); + + // init + let info = mock_info("creator", &coins(1000, "earth")); + let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes(); + let res = call_init::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg).unwrap(); + let msgs = res.unwrap().messages; + assert_eq!(msgs.len(), 0); + } } #[test] @@ -563,6 +606,35 @@ mod tests { .unwrap(); assert_eq!(response.messages.len(), 1); } + + // from pinned memory + { + cache.pin_wasm(&checksum).unwrap(); + + let mut instance = cache + .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS) + .unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 1); + assert_eq!(cache.stats().hits_memory_cache, 2); + assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().misses, 0); + + // init + let info = mock_info("creator", &coins(1000, "earth")); + let msg = r#"{"verifier": "verifies", "beneficiary": "benefits"}"#.as_bytes(); + let response = call_init::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg) + .unwrap() + .unwrap(); + assert_eq!(response.messages.len(), 0); + + // handle + let info = mock_info("verifies", &coins(15, "earth")); + let msg = br#"{"release":{}}"#; + let response = call_handle::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg) + .unwrap() + .unwrap(); + assert_eq!(response.messages.len(), 1); + } } #[test] From d2f497be13fad4425f989144db7861cc407a4bc7 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Sun, 10 Jan 2021 11:59:05 +0100 Subject: [PATCH 10/21] Add no memory limits runtime store test --- packages/vm/src/wasm_backend/store.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/vm/src/wasm_backend/store.rs b/packages/vm/src/wasm_backend/store.rs index f26f41be38..f968230909 100644 --- a/packages/vm/src/wasm_backend/store.rs +++ b/packages/vm/src/wasm_backend/store.rs @@ -162,6 +162,23 @@ mod tests { module.serialize().unwrap() }; + // No limit + let store = make_runtime_store(Size(0)); + let module = unsafe { Module::deserialize(&store, &serialized) }.unwrap(); + let module_memory = module.info().memories.last().unwrap(); + assert_eq!(module_memory.minimum, Pages(4)); + assert_eq!(module_memory.maximum, None); + let instance = Instance::new(&module, &ImportObject::new()).unwrap(); + let instance_memory: Memory = instance + .exports + .iter() + .memories() + .map(|pair| pair.1.clone()) + .next() + .unwrap(); + assert_eq!(instance_memory.ty().minimum, Pages(4)); + assert_eq!(instance_memory.ty().maximum, None); + // Instantiate with limit let store = make_runtime_store(Size::kibi(23 * 64)); let module = unsafe { Module::deserialize(&store, &serialized) }.unwrap(); From 0d71757aa55be1dc9d5d984d5e0e7a5293df898e Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 12 Jan 2021 14:37:21 +0100 Subject: [PATCH 11/21] Avoid PinnedMemoryCache store/load serialization/deserialization --- packages/vm/src/cache.rs | 10 ++--- packages/vm/src/modules/in_memory_cache.rs | 3 +- .../vm/src/modules/pinned_memory_cache.rs | 38 +++++++------------ 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 0ddb48042d..a5a7df6269 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -127,16 +127,14 @@ where /// pinned cache. /// If the given ID is not found, or the content does not match the hash (=ID), an error is returned. pub fn pin_wasm(&mut self, checksum: &Checksum) -> VmResult<()> { - // Create an unlimited store (just for reading) - let store = make_runtime_store(Size(0)); - // Try to get module from the memory cache - if let Some(module) = self.memory_cache.load(checksum, &store)? { + if let Some(module) = self.memory_cache.load(checksum)? { self.stats.hits_memory_cache += 1; return self.pinned_memory_cache.store(checksum, module); } // Try to get module from file system cache + let store = make_runtime_store(self.instance_memory_limit); if let Some(module) = self.fs_cache.load(checksum, &store)? { self.stats.hits_fs_cache += 1; return self.pinned_memory_cache.store(checksum, module); @@ -165,9 +163,8 @@ where backend: Backend, options: InstanceOptions, ) -> VmResult> { - let store = make_runtime_store(self.instance_memory_limit); // Try to get module from the pinned memory cache - if let Some(module) = self.pinned_memory_cache.load(checksum, &store)? { + if let Some(module) = self.pinned_memory_cache.load(checksum)? { self.stats.hits_pinned_memory_cache += 1; let instance = Instance::from_module(&module, backend, options.gas_limit, options.print_debug)?; @@ -183,6 +180,7 @@ where } // Get module from file system cache + let store = make_runtime_store(self.instance_memory_limit); if let Some(module) = self.fs_cache.load(checksum, &store)? { self.stats.hits_fs_cache += 1; let instance = diff --git a/packages/vm/src/modules/in_memory_cache.rs b/packages/vm/src/modules/in_memory_cache.rs index 3a3526703d..687ea2596a 100644 --- a/packages/vm/src/modules/in_memory_cache.rs +++ b/packages/vm/src/modules/in_memory_cache.rs @@ -24,8 +24,7 @@ impl InMemoryCache { Ok(()) } - /// Looks up a module in the cache and takes its artifact and - /// creates a new module from store and artifact. + /// Looks up a module in the cache and creates a new module pub fn load(&mut self, checksum: &Checksum) -> VmResult> { match self.modules.get(checksum) { Some(module) => Ok(Some(module.clone())), diff --git a/packages/vm/src/modules/pinned_memory_cache.rs b/packages/vm/src/modules/pinned_memory_cache.rs index 88c151ba70..e1bbf36876 100644 --- a/packages/vm/src/modules/pinned_memory_cache.rs +++ b/packages/vm/src/modules/pinned_memory_cache.rs @@ -1,43 +1,37 @@ use std::collections::HashMap; -use wasmer::{Module, Store}; +use wasmer::Module; use crate::{Checksum, VmResult}; /// An pinned in memory module cache pub struct PinnedMemoryCache { - artifacts: HashMap>, + modules: HashMap, } impl PinnedMemoryCache { /// Creates a new cache pub fn new() -> Self { PinnedMemoryCache { - artifacts: HashMap::new(), + modules: HashMap::new(), } } pub fn store(&mut self, checksum: &Checksum, module: Module) -> VmResult<()> { - let serialized_artifact = module.serialize()?; - self.artifacts.insert(*checksum, serialized_artifact); + self.modules.insert(*checksum, module); Ok(()) } - /// Removes a module from the cache. - /// Not found modules are silently ignored. - /// Potential integrity errors (wrong checksum) are not checked / enforced. + /// Removes a module from the cache + /// Not found modules are silently ignored. Potential integrity errors (wrong checksum) are not checked / enforced pub fn remove(&mut self, checksum: &Checksum) -> VmResult<()> { - self.artifacts.remove(checksum); + self.modules.remove(checksum); Ok(()) } - /// Looks up a module in the cache and takes its artifact and - /// creates a new module from store and artifact. - pub fn load(&mut self, checksum: &Checksum, store: &Store) -> VmResult> { - match self.artifacts.get(checksum) { - Some(serialized_artifact) => { - let new_module = unsafe { Module::deserialize(store, &serialized_artifact) }?; - Ok(Some(new_module)) - } + /// Looks up a module in the cache and creates a new module + pub fn load(&mut self, checksum: &Checksum) -> VmResult> { + match self.modules.get(checksum) { + Some(module) => Ok(Some(module.clone())), None => Ok(None), } } @@ -46,12 +40,10 @@ impl PinnedMemoryCache { #[cfg(test)] mod tests { use super::*; - use crate::size::Size; - use crate::wasm_backend::{compile_only, make_runtime_store}; + use crate::wasm_backend::compile_only; use wasmer::{imports, Instance as WasmerInstance}; use wasmer_middlewares::metering::set_remaining_points; - const TESTING_MEMORY_LIMIT: Size = Size::mebi(16); const TESTING_GAS_LIMIT: u64 = 5_000; #[test] @@ -72,8 +64,7 @@ mod tests { let checksum = Checksum::generate(&wasm); // Module does not exist - let store = make_runtime_store(TESTING_MEMORY_LIMIT); - let cache_entry = cache.load(&checksum, &store).unwrap(); + let cache_entry = cache.load(&checksum).unwrap(); assert!(cache_entry.is_none()); // Compile module @@ -92,8 +83,7 @@ mod tests { cache.store(&checksum, original).unwrap(); // Load module - let store = make_runtime_store(TESTING_MEMORY_LIMIT); - let cached = cache.load(&checksum, &store).unwrap().unwrap(); + let cached = cache.load(&checksum).unwrap().unwrap(); // Ensure cached module can be executed { From 8ea461614b690a34886a7836500854906c85f883 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 12 Jan 2021 14:39:43 +0100 Subject: [PATCH 12/21] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fafdae8da9..7d3d65b841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ **cosmwasm-vm** +- Add PinnedMemoryCache. (#696) + - Avoid serialization of Modules in `InMemoryCache`, for performance. (#697) Also, remove `memory_limit` from `InstanceOptions`, and define it instead at From f8a481854212ab63280a58de3b222b8d691eba85 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 12 Jan 2021 15:23:34 +0100 Subject: [PATCH 13/21] Rename pin/unpin_wasm to pin/unpin --- packages/vm/src/cache.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index a5a7df6269..42c9c9abb3 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -119,14 +119,13 @@ where } } - /// Pins a Wasm that was previously stored via save_wasm. - /// This stores an already saved wasm into the pinned memory cache. + /// Pins a Module that was previously stored via save_wasm. /// /// The module is lookup first in the memory cache, and then in the file system cache. /// If not found, the code is loaded from the file system, compiled, and stored into the /// pinned cache. /// If the given ID is not found, or the content does not match the hash (=ID), an error is returned. - pub fn pin_wasm(&mut self, checksum: &Checksum) -> VmResult<()> { + pub fn pin(&mut self, checksum: &Checksum) -> VmResult<()> { // Try to get module from the memory cache if let Some(module) = self.memory_cache.load(checksum)? { self.stats.hits_memory_cache += 1; @@ -142,16 +141,16 @@ where // Load code from the file system let code = self.load_wasm(checksum)?; - // compile and store into the pinned cache + // Compile and store into the pinned cache let module = compile_only(code.as_slice())?; self.pinned_memory_cache.store(checksum, module) } - /// Unpins a Wasm, i.e. removes it from the pinned memory cache. + /// Unpins a Module, i.e. removes it from the pinned memory cache. /// /// Not found IDs are silently ignored, and no integrity check (checksum validation) is done /// on the removed value. - pub fn unpin_wasm(&mut self, checksum: &Checksum) -> VmResult<()> { + pub fn unpin(&mut self, checksum: &Checksum) -> VmResult<()> { self.pinned_memory_cache.remove(checksum) } @@ -463,7 +462,7 @@ mod tests { assert_eq!(cache.stats().misses, 0); // pinning hits the memory cache - cache.pin_wasm(&id).unwrap(); + cache.pin(&id).unwrap(); assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 3); assert_eq!(cache.stats().hits_fs_cache, 1); @@ -527,7 +526,7 @@ mod tests { // from pinned memory { - cache.pin_wasm(&checksum).unwrap(); + cache.pin(&checksum).unwrap(); let mut instance = cache .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS) @@ -607,7 +606,7 @@ mod tests { // from pinned memory { - cache.pin_wasm(&checksum).unwrap(); + cache.pin(&checksum).unwrap(); let mut instance = cache .get_instance(&checksum, mock_backend(&[]), TESTING_OPTIONS) @@ -814,7 +813,7 @@ mod tests { assert_eq!(cache.stats().misses, 0); // pin - let _ = cache.pin_wasm(&id); + let _ = cache.pin(&id); // check pinned let backend = mock_backend(&[]); @@ -825,7 +824,7 @@ mod tests { assert_eq!(cache.stats().misses, 0); // unpin - let _ = cache.unpin_wasm(&id); + let _ = cache.unpin(&id); // verify unpinned let backend = mock_backend(&[]); @@ -836,10 +835,10 @@ mod tests { assert_eq!(cache.stats().misses, 0); // unpin again has no effect - let _ = cache.unpin_wasm(&id).unwrap(); + let _ = cache.unpin(&id).unwrap(); // unpin non existent id has no effect let non_id = Checksum::generate(b"non_existent"); - let _ = cache.unpin_wasm(&non_id).unwrap(); + let _ = cache.unpin(&non_id).unwrap(); } } From e26868a098eb01f753a8a8b3c7ffa800ae20e5fa Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 12 Jan 2021 15:53:18 +0100 Subject: [PATCH 14/21] Make Instances memory_limit Optional --- packages/vm/benches/main.rs | 2 +- packages/vm/src/cache.rs | 6 +++--- packages/vm/src/environment.rs | 2 +- packages/vm/src/imports.rs | 2 +- packages/vm/src/instance.rs | 4 ++-- packages/vm/src/modules/file_system_cache.rs | 2 +- packages/vm/src/testing/instance.rs | 6 +++--- packages/vm/src/wasm_backend/compile.rs | 4 ++-- packages/vm/src/wasm_backend/store.rs | 15 +++++---------- 9 files changed, 19 insertions(+), 24 deletions(-) diff --git a/packages/vm/benches/main.rs b/packages/vm/benches/main.rs index c55ecc2e57..179fd518d5 100644 --- a/packages/vm/benches/main.rs +++ b/packages/vm/benches/main.rs @@ -12,7 +12,7 @@ use cosmwasm_vm::{ }; // Instance -const DEFAULT_MEMORY_LIMIT: Size = Size::mebi(64); +const DEFAULT_MEMORY_LIMIT: Option = Some(Size::mebi(64)); const DEFAULT_GAS_LIMIT: u64 = 400_000; const DEFAULT_INSTANCE_OPTIONS: InstanceOptions = InstanceOptions { gas_limit: DEFAULT_GAS_LIMIT, diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 42c9c9abb3..0adda6b5a6 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -31,7 +31,7 @@ pub struct CacheOptions { pub memory_cache_size: Size, /// Memory limit for instances, in bytes. Use a value that is divisible by the Wasm page size 65536, /// e.g. full MiBs. - pub instance_memory_limit: Size, + pub instance_memory_limit: Option, } pub struct Cache { @@ -39,7 +39,7 @@ pub struct Cache { supported_features: HashSet, /// Instances memory limit in bytes. Use a value that is divisible by the Wasm page size 65536, /// e.g. full MiBs. - instance_memory_limit: Size, + instance_memory_limit: Option, pinned_memory_cache: PinnedMemoryCache, memory_cache: InMemoryCache, fs_cache: FileSystemCache, @@ -252,7 +252,7 @@ mod tests { use tempfile::TempDir; const TESTING_GAS_LIMIT: u64 = 4_000_000; - const TESTING_MEMORY_LIMIT: Size = Size::mebi(16); + const TESTING_MEMORY_LIMIT: Option = Some(Size::mebi(16)); const TESTING_OPTIONS: InstanceOptions = InstanceOptions { gas_limit: TESTING_GAS_LIMIT, print_debug: false, diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs index 6143288a8b..5b8fa252b8 100644 --- a/packages/vm/src/environment.rs +++ b/packages/vm/src/environment.rs @@ -344,7 +344,7 @@ mod tests { const TESTING_GAS_LIMIT: u64 = 500_000; const DEFAULT_QUERY_GAS_LIMIT: u64 = 300_000; - const TESTING_MEMORY_LIMIT: Size = Size::mebi(16); + const TESTING_MEMORY_LIMIT: Option = Some(Size::mebi(16)); fn make_instance(gas_limit: u64) -> (Environment, Box) { let env = Environment::new(MockApi::default(), gas_limit, false); diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index 7cf0dc505f..77395fb3d9 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -342,7 +342,7 @@ mod tests { const INIT_DENOM: &str = "TOKEN"; const TESTING_GAS_LIMIT: u64 = 500_000; - const TESTING_MEMORY_LIMIT: Size = Size::mebi(16); + const TESTING_MEMORY_LIMIT: Option = Some(Size::mebi(16)); fn make_instance(api: MA) -> (Environment, Box) { let gas_limit = TESTING_GAS_LIMIT; diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index ded842a6e8..5f435b7051 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -58,7 +58,7 @@ where code: &[u8], backend: Backend, options: InstanceOptions, - memory_limit: Size, + memory_limit: Option, ) -> VmResult { let module = compile_and_use(code, memory_limit)?; Instance::from_module(&module, backend, options.gas_limit, options.print_debug) @@ -378,7 +378,7 @@ mod tests { let mut instance = mock_instance_with_options( &CONTRACT, MockInstanceOptions { - memory_limit: Size::mebi(500), + memory_limit: Some(Size::mebi(500)), ..Default::default() }, ); diff --git a/packages/vm/src/modules/file_system_cache.rs b/packages/vm/src/modules/file_system_cache.rs index f0e95439e1..53faefdcf4 100644 --- a/packages/vm/src/modules/file_system_cache.rs +++ b/packages/vm/src/modules/file_system_cache.rs @@ -111,7 +111,7 @@ mod tests { use wasmer::{imports, Instance as WasmerInstance}; use wasmer_middlewares::metering::set_remaining_points; - const TESTING_MEMORY_LIMIT: Size = Size::mebi(16); + const TESTING_MEMORY_LIMIT: Option = Some(Size::mebi(16)); const TESTING_GAS_LIMIT: u64 = 5_000; #[test] diff --git a/packages/vm/src/testing/instance.rs b/packages/vm/src/testing/instance.rs index 0f9a3ad600..8d6ac75238 100644 --- a/packages/vm/src/testing/instance.rs +++ b/packages/vm/src/testing/instance.rs @@ -18,7 +18,7 @@ use super::storage::MockStorage; /// number of contract executions and queries on one instance. For this reason it is significatly /// higher than the limit for a single execution that we have in the production setup. const DEFAULT_GAS_LIMIT: u64 = 5_000_000; -const DEFAULT_MEMORY_LIMIT: Size = Size::mebi(16); +const DEFAULT_MEMORY_LIMIT: Option = Some(Size::mebi(16)); const DEFAULT_PRINT_DEBUG: bool = true; pub fn mock_instance( @@ -89,7 +89,7 @@ pub struct MockInstanceOptions<'a> { pub gas_limit: u64, pub print_debug: bool, /// Memory limit in bytes. Use a value that is divisible by the Wasm page size 65536, e.g. full MiBs. - pub memory_limit: Size, + pub memory_limit: Option, } impl Default for MockInstanceOptions<'_> { @@ -146,7 +146,7 @@ pub fn mock_instance_with_options( } /// Creates InstanceOptions for testing -pub fn mock_instance_options() -> (InstanceOptions, Size) { +pub fn mock_instance_options() -> (InstanceOptions, Option) { ( InstanceOptions { gas_limit: DEFAULT_GAS_LIMIT, diff --git a/packages/vm/src/wasm_backend/compile.rs b/packages/vm/src/wasm_backend/compile.rs index f12ba1755e..74f2d59d17 100644 --- a/packages/vm/src/wasm_backend/compile.rs +++ b/packages/vm/src/wasm_backend/compile.rs @@ -18,8 +18,8 @@ pub fn compile_only(code: &[u8]) -> VmResult { /// Compiles a given Wasm bytecode into a module. /// The given memory limit (in bytes) is used when memories are created. -pub fn compile_and_use(code: &[u8], memory_limit: Size) -> VmResult { - let store = make_compile_time_store(Some(memory_limit)); +pub fn compile_and_use(code: &[u8], memory_limit: Option) -> VmResult { + let store = make_compile_time_store(memory_limit); let module = Module::new(&store, code)?; Ok(module) } diff --git a/packages/vm/src/wasm_backend/store.rs b/packages/vm/src/wasm_backend/store.rs index f968230909..041e3c7ef2 100644 --- a/packages/vm/src/wasm_backend/store.rs +++ b/packages/vm/src/wasm_backend/store.rs @@ -55,15 +55,10 @@ pub fn make_compile_time_store(memory_limit: Option) -> Store { } /// Created a store with no compiler and the given memory limit (in bytes) -/// If memory_limit is 0, no limit is applied. -pub fn make_runtime_store(memory_limit: Size) -> Store { +/// If memory_limit is None, no limit is applied. +pub fn make_runtime_store(memory_limit: Option) -> Store { let engine = JIT::headless().engine(); - let limit = if memory_limit.0 > 0 { - Some(memory_limit) - } else { - None - }; - make_store_with_engine(&engine, limit) + make_store_with_engine(&engine, memory_limit) } /// Creates a store from an engine and an optional memory limit. @@ -163,7 +158,7 @@ mod tests { }; // No limit - let store = make_runtime_store(Size(0)); + let store = make_runtime_store(None); let module = unsafe { Module::deserialize(&store, &serialized) }.unwrap(); let module_memory = module.info().memories.last().unwrap(); assert_eq!(module_memory.minimum, Pages(4)); @@ -180,7 +175,7 @@ mod tests { assert_eq!(instance_memory.ty().maximum, None); // Instantiate with limit - let store = make_runtime_store(Size::kibi(23 * 64)); + let store = make_runtime_store(Some(Size::kibi(23 * 64))); let module = unsafe { Module::deserialize(&store, &serialized) }.unwrap(); let module_memory = module.info().memories.last().unwrap(); assert_eq!(module_memory.minimum, Pages(4)); From f8f6785eb786e945132d37398330808eb7c717f1 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 12 Jan 2021 16:13:50 +0100 Subject: [PATCH 15/21] Add pinned memory cache benchmark --- packages/vm/benches/main.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/vm/benches/main.rs b/packages/vm/benches/main.rs index 179fd518d5..0f0b703982 100644 --- a/packages/vm/benches/main.rs +++ b/packages/vm/benches/main.rs @@ -115,6 +115,7 @@ fn bench_cache(c: &mut Criterion) { let _ = cache .get_instance(&checksum, mock_backend(&[]), DEFAULT_INSTANCE_OPTIONS) .unwrap(); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert_eq!(cache.stats().hits_memory_cache, 0); assert!(cache.stats().hits_fs_cache >= 1); assert_eq!(cache.stats().misses, 0); @@ -135,8 +136,33 @@ fn bench_cache(c: &mut Criterion) { let _ = cache .get_instance(&checksum, backend, DEFAULT_INSTANCE_OPTIONS) .unwrap(); - assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().hits_pinned_memory_cache, 0); assert!(cache.stats().hits_memory_cache >= 1); + assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().misses, 0); + }); + }); + + group.bench_function("instantiate from pinned memory", |b| { + let checksum = Checksum::generate(CONTRACT); + let mut cache: Cache = + unsafe { Cache::new(options.clone()).unwrap() }; + // Load into memory + cache + .get_instance(&checksum, mock_backend(&[]), DEFAULT_INSTANCE_OPTIONS) + .unwrap(); + + // Pin + cache.pin(&checksum).unwrap(); + + b.iter(|| { + let backend = mock_backend(&[]); + let _ = cache + .get_instance(&checksum, backend, DEFAULT_INSTANCE_OPTIONS) + .unwrap(); + assert_eq!(cache.stats().hits_memory_cache, 1); + assert!(cache.stats().hits_pinned_memory_cache >= 1); + assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); }); }); From 665e9bba734c92c15e5539de77d41f8d07e40430 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 12 Jan 2021 16:22:45 +0100 Subject: [PATCH 16/21] Add pinned-memory-cache branch to CI benchmarking --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2b9f83afd9..5e57638cf5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,6 +27,7 @@ workflows: - master - /^[0-9]+\.[0-9]+$/ # 👇 Add your branch here if benchmarking matters to your work + - pinned-memory-cache - benchmarking - update-wasmer - metering-restart From 911b73495f3cd08ec10bdc40b0f186940a22f96a Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 12 Jan 2021 17:14:29 +0100 Subject: [PATCH 17/21] Simplify pinned memory cache bench --- packages/vm/benches/main.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/vm/benches/main.rs b/packages/vm/benches/main.rs index 0f0b703982..c62f516667 100644 --- a/packages/vm/benches/main.rs +++ b/packages/vm/benches/main.rs @@ -147,12 +147,7 @@ fn bench_cache(c: &mut Criterion) { let checksum = Checksum::generate(CONTRACT); let mut cache: Cache = unsafe { Cache::new(options.clone()).unwrap() }; - // Load into memory - cache - .get_instance(&checksum, mock_backend(&[]), DEFAULT_INSTANCE_OPTIONS) - .unwrap(); - - // Pin + // Load into pinned memory cache.pin(&checksum).unwrap(); b.iter(|| { @@ -160,7 +155,7 @@ fn bench_cache(c: &mut Criterion) { let _ = cache .get_instance(&checksum, backend, DEFAULT_INSTANCE_OPTIONS) .unwrap(); - assert_eq!(cache.stats().hits_memory_cache, 1); + assert_eq!(cache.stats().hits_memory_cache, 0); assert!(cache.stats().hits_pinned_memory_cache >= 1); assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); From 18576af52f42cf4a90a254366f55afe4a5c28731 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 12 Jan 2021 17:15:03 +0100 Subject: [PATCH 18/21] Store into the fs cache too when pinning --- packages/vm/src/cache.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 0adda6b5a6..f9bb400832 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -143,6 +143,8 @@ where let code = self.load_wasm(checksum)?; // Compile and store into the pinned cache let module = compile_only(code.as_slice())?; + // Store into the fs cache too + self.fs_cache.store(checksum, &module)?; self.pinned_memory_cache.store(checksum, module) } From 84bc38fa7c4ca7e9cdd989976c30221cec4e93a8 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 12 Jan 2021 17:15:34 +0100 Subject: [PATCH 19/21] Remove newline --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d3d65b841..c9b4c67151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,6 @@ **cosmwasm-vm** - Add PinnedMemoryCache. (#696) - - Avoid serialization of Modules in `InMemoryCache`, for performance. (#697) Also, remove `memory_limit` from `InstanceOptions`, and define it instead at From c7b5b7c8a846df854f848eacba1e393942529f4f Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 12 Jan 2021 17:26:35 +0100 Subject: [PATCH 20/21] Make Cache instance_memory_limit non-Optional --- packages/vm/benches/main.rs | 6 +++--- packages/vm/src/cache.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/vm/benches/main.rs b/packages/vm/benches/main.rs index c62f516667..d16ae546da 100644 --- a/packages/vm/benches/main.rs +++ b/packages/vm/benches/main.rs @@ -12,7 +12,7 @@ use cosmwasm_vm::{ }; // Instance -const DEFAULT_MEMORY_LIMIT: Option = Some(Size::mebi(64)); +const DEFAULT_MEMORY_LIMIT: Size = Size::mebi(64); const DEFAULT_GAS_LIMIT: u64 = 400_000; const DEFAULT_INSTANCE_OPTIONS: InstanceOptions = InstanceOptions { gas_limit: DEFAULT_GAS_LIMIT, @@ -42,7 +42,7 @@ fn bench_instance(c: &mut Criterion) { ..DEFAULT_INSTANCE_OPTIONS }; let mut instance = - Instance::from_code(CONTRACT, backend, much_gas, DEFAULT_MEMORY_LIMIT).unwrap(); + Instance::from_code(CONTRACT, backend, much_gas, Some(DEFAULT_MEMORY_LIMIT)).unwrap(); b.iter(|| { let info = mock_info("creator", &coins(1000, "earth")); @@ -60,7 +60,7 @@ fn bench_instance(c: &mut Criterion) { ..DEFAULT_INSTANCE_OPTIONS }; let mut instance = - Instance::from_code(CONTRACT, backend, much_gas, DEFAULT_MEMORY_LIMIT).unwrap(); + Instance::from_code(CONTRACT, backend, much_gas, Some(DEFAULT_MEMORY_LIMIT)).unwrap(); let info = mock_info("creator", &coins(1000, "earth")); let msg = br#"{"verifier": "verifies", "beneficiary": "benefits"}"#; diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index f9bb400832..d768c4d388 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -31,7 +31,7 @@ pub struct CacheOptions { pub memory_cache_size: Size, /// Memory limit for instances, in bytes. Use a value that is divisible by the Wasm page size 65536, /// e.g. full MiBs. - pub instance_memory_limit: Option, + pub instance_memory_limit: Size, } pub struct Cache { @@ -39,7 +39,7 @@ pub struct Cache { supported_features: HashSet, /// Instances memory limit in bytes. Use a value that is divisible by the Wasm page size 65536, /// e.g. full MiBs. - instance_memory_limit: Option, + instance_memory_limit: Size, pinned_memory_cache: PinnedMemoryCache, memory_cache: InMemoryCache, fs_cache: FileSystemCache, @@ -133,7 +133,7 @@ where } // Try to get module from file system cache - let store = make_runtime_store(self.instance_memory_limit); + let store = make_runtime_store(Some(self.instance_memory_limit)); if let Some(module) = self.fs_cache.load(checksum, &store)? { self.stats.hits_fs_cache += 1; return self.pinned_memory_cache.store(checksum, module); @@ -181,7 +181,7 @@ where } // Get module from file system cache - let store = make_runtime_store(self.instance_memory_limit); + let store = make_runtime_store(Some(self.instance_memory_limit)); if let Some(module) = self.fs_cache.load(checksum, &store)? { self.stats.hits_fs_cache += 1; let instance = @@ -197,7 +197,7 @@ where // stored the old module format. let wasm = self.load_wasm(checksum)?; self.stats.misses += 1; - let module = compile_and_use(&wasm, self.instance_memory_limit)?; + let module = compile_and_use(&wasm, Some(self.instance_memory_limit))?; let instance = Instance::from_module(&module, backend, options.gas_limit, options.print_debug)?; self.fs_cache.store(checksum, &module)?; @@ -254,7 +254,7 @@ mod tests { use tempfile::TempDir; const TESTING_GAS_LIMIT: u64 = 4_000_000; - const TESTING_MEMORY_LIMIT: Option = Some(Size::mebi(16)); + const TESTING_MEMORY_LIMIT: Size = Size::mebi(16); const TESTING_OPTIONS: InstanceOptions = InstanceOptions { gas_limit: TESTING_GAS_LIMIT, print_debug: false, From daf3dacbd92ae4da2add5a5df4f863dd109990a7 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 12 Jan 2021 17:41:42 +0100 Subject: [PATCH 21/21] Revert "Add pinned-memory-cache branch to CI benchmarking" This reverts commit 665e9bba734c92c15e5539de77d41f8d07e40430. --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5e57638cf5..2b9f83afd9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,7 +27,6 @@ workflows: - master - /^[0-9]+\.[0-9]+$/ # 👇 Add your branch here if benchmarking matters to your work - - pinned-memory-cache - benchmarking - update-wasmer - metering-restart