Skip to content

Commit d4f2bfe

Browse files
authored
Merge pull request #140 from confio/cool-cache
Cache wasmer instances and add cache stats
2 parents ada3c7a + de489a9 commit d4f2bfe

File tree

3 files changed

+95
-37
lines changed

3 files changed

+95
-37
lines changed

lib/vm/src/cache.rs

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::fs::create_dir_all;
2+
use std::marker::PhantomData;
23
use std::path::PathBuf;
34

45
use lru::LruCache;
@@ -16,10 +17,21 @@ use crate::wasm_store::{load, save, wasm_hash};
1617
static WASM_DIR: &str = "wasm";
1718
static MODULES_DIR: &str = "modules";
1819

20+
#[derive(Debug, Default, Clone)]
21+
struct Stats {
22+
hits_instance: u32,
23+
hits_module: u32,
24+
misses: u32,
25+
}
26+
1927
pub struct CosmCache<S: Storage + 'static, A: Api + 'static> {
2028
wasm_path: PathBuf,
2129
modules: FileSystemCache,
22-
instances: Option<LruCache<WasmHash, Instance<S, A>>>,
30+
instances: Option<LruCache<WasmHash, wasmer_runtime_core::Instance>>,
31+
stats: Stats,
32+
// Those two don't store data but only fix type information
33+
type_storage: PhantomData<S>,
34+
type_api: PhantomData<A>,
2335
}
2436

2537
impl<S, A> CosmCache<S, A>
@@ -48,6 +60,9 @@ where
4860
modules,
4961
wasm_path,
5062
instances,
63+
stats: Stats::default(),
64+
type_storage: PhantomData::<S> {},
65+
type_api: PhantomData::<A> {},
5166
})
5267
}
5368

@@ -78,30 +93,31 @@ where
7893

7994
// pop from lru cache if present
8095
if let Some(cache) = &mut self.instances {
81-
let val = cache.pop(&hash);
82-
if let Some(inst) = val {
83-
inst.leave_storage(Some(deps.storage));
84-
return Ok(inst);
96+
if let Some(cached_instance) = cache.pop(&hash) {
97+
self.stats.hits_instance += 1;
98+
return Ok(Instance::from_wasmer(cached_instance, deps));
8599
}
86100
}
87101

88102
// try from the module cache
89103
let res = self.modules.load_with_backend(hash, backend());
90104
if let Ok(module) = res {
105+
self.stats.hits_module += 1;
91106
return Instance::from_module(&module, deps);
92107
}
93108

94109
// fall back to wasm cache (and re-compiling) - this is for backends that don't support serialization
95110
let wasm = self.load_wasm(id)?;
111+
self.stats.misses += 1;
96112
Instance::from_code(&wasm, deps)
97113
}
98114

99115
pub fn store_instance(&mut self, id: &[u8], instance: Instance<S, A>) -> Option<Extern<S, A>> {
100116
if let Some(cache) = &mut self.instances {
101117
let hash = WasmHash::generate(&id);
102118
let storage = instance.take_storage();
103-
let api = instance.api; // copy it
104-
cache.put(hash, instance);
119+
let (wasmer_instance, api) = Instance::recycle(instance);
120+
cache.put(hash, wasmer_instance);
105121
if let Some(storage) = storage {
106122
return Some(Extern { storage, api });
107123
}
@@ -148,6 +164,37 @@ mod test {
148164
}
149165
}
150166

167+
#[test]
168+
fn finds_cached_module() {
169+
let tmp_dir = TempDir::new().unwrap();
170+
let mut cache = unsafe { CosmCache::new(tmp_dir.path(), 10).unwrap() };
171+
let id = cache.save_wasm(CONTRACT_0_7).unwrap();
172+
let deps = dependencies(20);
173+
let _instance = cache.get_instance(&id, deps).unwrap();
174+
assert_eq!(cache.stats.hits_instance, 0);
175+
assert_eq!(cache.stats.hits_module, 1);
176+
assert_eq!(cache.stats.misses, 0);
177+
}
178+
179+
#[test]
180+
fn finds_cached_instance() {
181+
let tmp_dir = TempDir::new().unwrap();
182+
let mut cache = unsafe { CosmCache::new(tmp_dir.path(), 10).unwrap() };
183+
let id = cache.save_wasm(CONTRACT_0_7).unwrap();
184+
let deps1 = dependencies(20);
185+
let deps2 = dependencies(20);
186+
let deps3 = dependencies(20);
187+
let instance1 = cache.get_instance(&id, deps1).unwrap();
188+
cache.store_instance(&id, instance1);
189+
let instance2 = cache.get_instance(&id, deps2).unwrap();
190+
cache.store_instance(&id, instance2);
191+
let instance3 = cache.get_instance(&id, deps3).unwrap();
192+
cache.store_instance(&id, instance3);
193+
assert_eq!(cache.stats.hits_instance, 2);
194+
assert_eq!(cache.stats.hits_module, 1);
195+
assert_eq!(cache.stats.misses, 0);
196+
}
197+
151198
#[test]
152199
fn init_cached_contract() {
153200
let tmp_dir = TempDir::new().unwrap();

lib/vm/src/context.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -74,50 +74,50 @@ pub fn do_human_address<A: Api>(api: A, ctx: &mut Ctx, canonical_ptr: u32, human
7474

7575
/** context data **/
7676

77-
struct ContextData<T: Storage> {
78-
data: Option<T>,
77+
struct ContextData<S: Storage> {
78+
data: Option<S>,
7979
}
8080

81-
pub fn setup_context<T: Storage>() -> (*mut c_void, fn(*mut c_void)) {
81+
pub fn setup_context<S: Storage>() -> (*mut c_void, fn(*mut c_void)) {
8282
(
83-
create_unmanaged_storage::<T>(),
84-
destroy_unmanaged_storage::<T>,
83+
create_unmanaged_storage::<S>(),
84+
destroy_unmanaged_storage::<S>,
8585
)
8686
}
8787

88-
fn create_unmanaged_storage<T: Storage>() -> *mut c_void {
89-
let data = ContextData::<T> { data: None };
88+
fn create_unmanaged_storage<S: Storage>() -> *mut c_void {
89+
let data = ContextData::<S> { data: None };
9090
let state = Box::new(data);
9191
Box::into_raw(state) as *mut c_void
9292
}
9393

94-
unsafe fn get_data<T: Storage>(ptr: *mut c_void) -> Box<ContextData<T>> {
95-
Box::from_raw(ptr as *mut ContextData<T>)
94+
unsafe fn get_data<S: Storage>(ptr: *mut c_void) -> Box<ContextData<S>> {
95+
Box::from_raw(ptr as *mut ContextData<S>)
9696
}
9797

98-
fn destroy_unmanaged_storage<T: Storage>(ptr: *mut c_void) {
98+
fn destroy_unmanaged_storage<S: Storage>(ptr: *mut c_void) {
9999
if !ptr.is_null() {
100100
// auto-dropped with scope
101-
let _ = unsafe { get_data::<T>(ptr) };
101+
let _ = unsafe { get_data::<S>(ptr) };
102102
}
103103
}
104104

105-
pub fn with_storage_from_context<T: Storage, F: FnMut(&mut T)>(ctx: &Ctx, mut func: F) {
106-
let mut storage: Option<T> = take_storage(ctx);
105+
pub fn with_storage_from_context<S: Storage, F: FnMut(&mut S)>(ctx: &Ctx, mut func: F) {
106+
let mut storage: Option<S> = take_storage(ctx);
107107
if let Some(data) = &mut storage {
108108
func(data);
109109
}
110110
leave_storage(ctx, storage);
111111
}
112112

113-
pub fn take_storage<T: Storage>(ctx: &Ctx) -> Option<T> {
113+
pub fn take_storage<S: Storage>(ctx: &Ctx) -> Option<S> {
114114
let mut b = unsafe { get_data(ctx.data) };
115115
let res = b.data.take();
116116
mem::forget(b); // we do this to avoid cleanup
117117
res
118118
}
119119

120-
pub fn leave_storage<T: Storage>(ctx: &Ctx, storage: Option<T>) {
120+
pub fn leave_storage<S: Storage>(ctx: &Ctx, storage: Option<S>) {
121121
let mut b = unsafe { get_data(ctx.data) };
122122
// clean-up if needed
123123
let _ = b.data.take();

lib/vm/src/instance.rs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ use crate::errors::{ResolveErr, Result, RuntimeErr, WasmerErr};
2020
use crate::memory::{read_region, write_region};
2121

2222
pub struct Instance<S: Storage + 'static, A: Api + 'static> {
23-
instance: wasmer_runtime_core::instance::Instance,
23+
wasmer_instance: wasmer_runtime_core::instance::Instance,
2424
pub api: A,
25-
storage: PhantomData<S>,
25+
// This does not store data but only fixes type information
26+
type_storage: PhantomData<S>,
2627
}
2728

2829
impl<S, A> Instance<S, A>
@@ -70,46 +71,56 @@ where
7071
}),
7172
},
7273
};
73-
let instance = module.instantiate(&import_obj).context(WasmerErr {})?;
74+
let wasmer_instance = module.instantiate(&import_obj).context(WasmerErr {})?;
75+
Ok(Instance::from_wasmer(wasmer_instance, deps))
76+
}
77+
78+
pub fn from_wasmer(wasmer_instance: wasmer_runtime_core::Instance, deps: Extern<S, A>) -> Self {
7479
let res = Instance {
75-
instance,
76-
api,
77-
storage: PhantomData::<S> {},
80+
wasmer_instance: wasmer_instance,
81+
api: deps.api,
82+
type_storage: PhantomData::<S> {},
7883
};
7984
res.leave_storage(Some(deps.storage));
80-
Ok(res)
85+
res
86+
}
87+
88+
/// Takes ownership of instance and decomposes it into its components.
89+
/// The components we want to preserve are returned, the rest is dropped.
90+
pub fn recycle(instance: Self) -> (wasmer_runtime_core::Instance, A) {
91+
(instance.wasmer_instance, instance.api)
8192
}
8293

8394
pub fn get_gas(&self) -> u64 {
84-
get_gas(&self.instance)
95+
get_gas(&self.wasmer_instance)
8596
}
8697

8798
pub fn set_gas(&mut self, gas: u64) {
88-
set_gas(&mut self.instance, gas)
99+
set_gas(&mut self.wasmer_instance, gas)
89100
}
90101

91102
pub fn with_storage<F: FnMut(&mut S)>(&self, func: F) {
92-
with_storage_from_context(self.instance.context(), func)
103+
with_storage_from_context(self.wasmer_instance.context(), func)
93104
}
94105

95106
pub fn take_storage(&self) -> Option<S> {
96-
take_storage(self.instance.context())
107+
take_storage(self.wasmer_instance.context())
97108
}
98109

99110
pub fn leave_storage(&self, storage: Option<S>) {
100-
leave_storage(self.instance.context(), storage);
111+
leave_storage(self.wasmer_instance.context(), storage);
101112
}
102113

103114
pub fn memory(&self, ptr: u32) -> Vec<u8> {
104-
read_region(self.instance.context(), ptr)
115+
read_region(self.wasmer_instance.context(), ptr)
105116
}
106117

107118
// allocate memory in the instance and copies the given data in
108119
// returns the memory offset, to be later passed as an argument
109120
pub fn allocate(&mut self, data: &[u8]) -> Result<u32> {
110121
let alloc: Func<u32, u32> = self.func("allocate")?;
111122
let ptr = alloc.call(data.len() as u32).context(RuntimeErr {})?;
112-
write_region(self.instance.context(), ptr, data)?;
123+
write_region(self.wasmer_instance.context(), ptr, data)?;
113124
Ok(ptr)
114125
}
115126

@@ -127,7 +138,7 @@ where
127138
Args: WasmTypeList,
128139
Rets: WasmTypeList,
129140
{
130-
self.instance.func(name).context(ResolveErr {})
141+
self.wasmer_instance.func(name).context(ResolveErr {})
131142
}
132143
}
133144

0 commit comments

Comments
 (0)