diff --git a/CHANGELOG.md b/CHANGELOG.md index fafdae8da9..c9b4c67151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ **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 diff --git a/packages/vm/benches/main.rs b/packages/vm/benches/main.rs index c55ecc2e57..d16ae546da 100644 --- a/packages/vm/benches/main.rs +++ b/packages/vm/benches/main.rs @@ -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"}"#; @@ -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,28 @@ 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 pinned memory + 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, 0); + assert!(cache.stats().hits_pinned_memory_cache >= 1); + assert_eq!(cache.stats().hits_fs_cache, 1); assert_eq!(cache.stats().misses, 0); }); }); diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 25efb5ac7d..d768c4d388 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(), @@ -116,6 +119,43 @@ where } } + /// 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(&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; + return self.pinned_memory_cache.store(checksum, module); + } + + // Try to get module from file system cache + 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); + } + + // 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())?; + // Store into the fs cache too + self.fs_cache.store(checksum, &module)?; + self.pinned_memory_cache.store(checksum, module) + } + + /// 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(&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( @@ -124,6 +164,14 @@ where backend: Backend, options: InstanceOptions, ) -> VmResult> { + // Try to get module from the pinned memory cache + 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)?; + 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 +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 = @@ -149,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)?; @@ -281,6 +329,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); @@ -377,6 +426,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); @@ -389,24 +439,50 @@ 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(); + 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); + + // pinning hits the memory cache + 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); + 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] @@ -419,6 +495,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); @@ -436,6 +513,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); @@ -447,6 +525,26 @@ mod tests { let msgs = res.unwrap().messages; assert_eq!(msgs.len(), 0); } + + // from pinned memory + { + cache.pin(&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] @@ -459,6 +557,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); @@ -485,6 +584,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); @@ -505,6 +605,35 @@ mod tests { .unwrap(); assert_eq!(response.messages.len(), 1); } + + // from pinned memory + { + cache.pin(&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] @@ -561,6 +690,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); @@ -576,6 +706,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); @@ -614,6 +745,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); @@ -668,4 +800,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(&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, 1); + assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().misses, 0); + + // unpin + let _ = cache.unpin(&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, 2); + assert_eq!(cache.stats().hits_fs_cache, 1); + assert_eq!(cache.stats().misses, 0); + + // unpin again has no effect + let _ = cache.unpin(&id).unwrap(); + + // unpin non existent id has no effect + let non_id = Checksum::generate(b"non_existent"); + let _ = cache.unpin(&non_id).unwrap(); + } } 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/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/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..e1bbf36876 --- /dev/null +++ b/packages/vm/src/modules/pinned_memory_cache.rs @@ -0,0 +1,97 @@ +use std::collections::HashMap; +use wasmer::Module; + +use crate::{Checksum, VmResult}; + +/// An pinned in memory module cache +pub struct PinnedMemoryCache { + modules: HashMap, +} + +impl PinnedMemoryCache { + /// Creates a new cache + pub fn new() -> Self { + PinnedMemoryCache { + modules: HashMap::new(), + } + } + + pub fn store(&mut self, checksum: &Checksum, module: Module) -> VmResult<()> { + 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 + pub fn remove(&mut self, checksum: &Checksum) -> VmResult<()> { + self.modules.remove(checksum); + Ok(()) + } + + /// 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), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::wasm_backend::compile_only; + use wasmer::{imports, Instance as WasmerInstance}; + use wasmer_middlewares::metering::set_remaining_points; + + 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 cache_entry = cache.load(&checksum).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 cached = cache.load(&checksum).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); + } + } +} 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 fdb092f56f..041e3c7ef2 100644 --- a/packages/vm/src/wasm_backend/store.rs +++ b/packages/vm/src/wasm_backend/store.rs @@ -56,9 +56,9 @@ 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. -pub fn make_runtime_store(memory_limit: Size) -> Store { +pub fn make_runtime_store(memory_limit: Option) -> Store { let engine = JIT::headless().engine(); - make_store_with_engine(&engine, Some(memory_limit)) + make_store_with_engine(&engine, memory_limit) } /// Creates a store from an engine and an optional memory limit. @@ -157,8 +157,25 @@ mod tests { module.serialize().unwrap() }; + // No limit + 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)); + 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 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));