diff --git a/lib/vm/src/cache.rs b/lib/vm/src/cache.rs index 8691c689ec..1b34c35679 100644 --- a/lib/vm/src/cache.rs +++ b/lib/vm/src/cache.rs @@ -1,4 +1,5 @@ use std::fs::create_dir_all; +use std::marker::PhantomData; use std::path::PathBuf; use lru::LruCache; @@ -16,10 +17,21 @@ use crate::wasm_store::{load, save, wasm_hash}; static WASM_DIR: &str = "wasm"; static MODULES_DIR: &str = "modules"; +#[derive(Debug, Default, Clone)] +struct Stats { + hits_instance: u32, + hits_module: u32, + misses: u32, +} + pub struct CosmCache { wasm_path: PathBuf, modules: FileSystemCache, - instances: Option>>, + instances: Option>, + stats: Stats, + // Those two don't store data but only fix type information + type_storage: PhantomData, + type_api: PhantomData, } impl CosmCache @@ -48,6 +60,9 @@ where modules, wasm_path, instances, + stats: Stats::default(), + type_storage: PhantomData:: {}, + type_api: PhantomData:: {}, }) } @@ -78,21 +93,22 @@ where // pop from lru cache if present if let Some(cache) = &mut self.instances { - let val = cache.pop(&hash); - if let Some(inst) = val { - inst.leave_storage(Some(deps.storage)); - return Ok(inst); + if let Some(cached_instance) = cache.pop(&hash) { + self.stats.hits_instance += 1; + return Ok(Instance::from_wasmer(cached_instance, deps)); } } // try from the module cache let res = self.modules.load_with_backend(hash, backend()); if let Ok(module) = res { + self.stats.hits_module += 1; return Instance::from_module(&module, deps); } // fall back to wasm cache (and re-compiling) - this is for backends that don't support serialization let wasm = self.load_wasm(id)?; + self.stats.misses += 1; Instance::from_code(&wasm, deps) } @@ -100,8 +116,8 @@ where if let Some(cache) = &mut self.instances { let hash = WasmHash::generate(&id); let storage = instance.take_storage(); - let api = instance.api; // copy it - cache.put(hash, instance); + let (wasmer_instance, api) = Instance::recycle(instance); + cache.put(hash, wasmer_instance); if let Some(storage) = storage { return Some(Extern { storage, api }); } @@ -148,6 +164,37 @@ mod test { } } + #[test] + fn finds_cached_module() { + let tmp_dir = TempDir::new().unwrap(); + let mut cache = unsafe { CosmCache::new(tmp_dir.path(), 10).unwrap() }; + let id = cache.save_wasm(CONTRACT_0_7).unwrap(); + let deps = dependencies(20); + let _instance = cache.get_instance(&id, deps).unwrap(); + assert_eq!(cache.stats.hits_instance, 0); + assert_eq!(cache.stats.hits_module, 1); + assert_eq!(cache.stats.misses, 0); + } + + #[test] + fn finds_cached_instance() { + let tmp_dir = TempDir::new().unwrap(); + let mut cache = unsafe { CosmCache::new(tmp_dir.path(), 10).unwrap() }; + let id = cache.save_wasm(CONTRACT_0_7).unwrap(); + let deps1 = dependencies(20); + let deps2 = dependencies(20); + let deps3 = dependencies(20); + let instance1 = cache.get_instance(&id, deps1).unwrap(); + cache.store_instance(&id, instance1); + let instance2 = cache.get_instance(&id, deps2).unwrap(); + cache.store_instance(&id, instance2); + let instance3 = cache.get_instance(&id, deps3).unwrap(); + cache.store_instance(&id, instance3); + assert_eq!(cache.stats.hits_instance, 2); + assert_eq!(cache.stats.hits_module, 1); + assert_eq!(cache.stats.misses, 0); + } + #[test] fn init_cached_contract() { let tmp_dir = TempDir::new().unwrap(); diff --git a/lib/vm/src/context.rs b/lib/vm/src/context.rs index 50b82dd19f..92c49b9030 100644 --- a/lib/vm/src/context.rs +++ b/lib/vm/src/context.rs @@ -74,50 +74,50 @@ pub fn do_human_address(api: A, ctx: &mut Ctx, canonical_ptr: u32, human /** context data **/ -struct ContextData { - data: Option, +struct ContextData { + data: Option, } -pub fn setup_context() -> (*mut c_void, fn(*mut c_void)) { +pub fn setup_context() -> (*mut c_void, fn(*mut c_void)) { ( - create_unmanaged_storage::(), - destroy_unmanaged_storage::, + create_unmanaged_storage::(), + destroy_unmanaged_storage::, ) } -fn create_unmanaged_storage() -> *mut c_void { - let data = ContextData:: { data: None }; +fn create_unmanaged_storage() -> *mut c_void { + let data = ContextData:: { data: None }; let state = Box::new(data); Box::into_raw(state) as *mut c_void } -unsafe fn get_data(ptr: *mut c_void) -> Box> { - Box::from_raw(ptr as *mut ContextData) +unsafe fn get_data(ptr: *mut c_void) -> Box> { + Box::from_raw(ptr as *mut ContextData) } -fn destroy_unmanaged_storage(ptr: *mut c_void) { +fn destroy_unmanaged_storage(ptr: *mut c_void) { if !ptr.is_null() { // auto-dropped with scope - let _ = unsafe { get_data::(ptr) }; + let _ = unsafe { get_data::(ptr) }; } } -pub fn with_storage_from_context(ctx: &Ctx, mut func: F) { - let mut storage: Option = take_storage(ctx); +pub fn with_storage_from_context(ctx: &Ctx, mut func: F) { + let mut storage: Option = take_storage(ctx); if let Some(data) = &mut storage { func(data); } leave_storage(ctx, storage); } -pub fn take_storage(ctx: &Ctx) -> Option { +pub fn take_storage(ctx: &Ctx) -> Option { let mut b = unsafe { get_data(ctx.data) }; let res = b.data.take(); mem::forget(b); // we do this to avoid cleanup res } -pub fn leave_storage(ctx: &Ctx, storage: Option) { +pub fn leave_storage(ctx: &Ctx, storage: Option) { let mut b = unsafe { get_data(ctx.data) }; // clean-up if needed let _ = b.data.take(); diff --git a/lib/vm/src/instance.rs b/lib/vm/src/instance.rs index d99b6f5fab..f64356dfe3 100644 --- a/lib/vm/src/instance.rs +++ b/lib/vm/src/instance.rs @@ -20,9 +20,10 @@ use crate::errors::{ResolveErr, Result, RuntimeErr, WasmerErr}; use crate::memory::{read_region, write_region}; pub struct Instance { - instance: wasmer_runtime_core::instance::Instance, + wasmer_instance: wasmer_runtime_core::instance::Instance, pub api: A, - storage: PhantomData, + // This does not store data but only fixes type information + type_storage: PhantomData, } impl Instance @@ -70,38 +71,48 @@ where }), }, }; - let instance = module.instantiate(&import_obj).context(WasmerErr {})?; + let wasmer_instance = module.instantiate(&import_obj).context(WasmerErr {})?; + Ok(Instance::from_wasmer(wasmer_instance, deps)) + } + + pub fn from_wasmer(wasmer_instance: wasmer_runtime_core::Instance, deps: Extern) -> Self { let res = Instance { - instance, - api, - storage: PhantomData:: {}, + wasmer_instance: wasmer_instance, + api: deps.api, + type_storage: PhantomData:: {}, }; res.leave_storage(Some(deps.storage)); - Ok(res) + res + } + + /// Takes ownership of instance and decomposes it into its components. + /// The components we want to preserve are returned, the rest is dropped. + pub fn recycle(instance: Self) -> (wasmer_runtime_core::Instance, A) { + (instance.wasmer_instance, instance.api) } pub fn get_gas(&self) -> u64 { - get_gas(&self.instance) + get_gas(&self.wasmer_instance) } pub fn set_gas(&mut self, gas: u64) { - set_gas(&mut self.instance, gas) + set_gas(&mut self.wasmer_instance, gas) } pub fn with_storage(&self, func: F) { - with_storage_from_context(self.instance.context(), func) + with_storage_from_context(self.wasmer_instance.context(), func) } pub fn take_storage(&self) -> Option { - take_storage(self.instance.context()) + take_storage(self.wasmer_instance.context()) } pub fn leave_storage(&self, storage: Option) { - leave_storage(self.instance.context(), storage); + leave_storage(self.wasmer_instance.context(), storage); } pub fn memory(&self, ptr: u32) -> Vec { - read_region(self.instance.context(), ptr) + read_region(self.wasmer_instance.context(), ptr) } // allocate memory in the instance and copies the given data in @@ -109,7 +120,7 @@ where pub fn allocate(&mut self, data: &[u8]) -> Result { let alloc: Func = self.func("allocate")?; let ptr = alloc.call(data.len() as u32).context(RuntimeErr {})?; - write_region(self.instance.context(), ptr, data)?; + write_region(self.wasmer_instance.context(), ptr, data)?; Ok(ptr) } @@ -127,7 +138,7 @@ where Args: WasmTypeList, Rets: WasmTypeList, { - self.instance.func(name).context(ResolveErr {}) + self.wasmer_instance.func(name).context(ResolveErr {}) } }