owasm_vm/
cache.rs

1use std::{
2    borrow::BorrowMut,
3    sync::{Arc, RwLock},
4};
5
6use crate::checksum::Checksum;
7use crate::error::Error;
8
9use clru::CLruCache;
10use wasmer::{Instance, Module, Store};
11
12/// An in-memory module cache
13pub struct InMemoryCache {
14    modules: CLruCache<Checksum, Module>,
15}
16
17impl InMemoryCache {
18    pub fn new(max_entries: u32) -> Self {
19        InMemoryCache { modules: CLruCache::new(max_entries as usize) }
20    }
21
22    pub fn store(&mut self, checksum: &Checksum, module: Module) -> Option<Module> {
23        self.modules.put(*checksum, module)
24    }
25
26    /// Looks up a module in the cache and creates a new module
27    pub fn load(&mut self, checksum: &Checksum) -> Option<Module> {
28        self.modules.get(checksum).cloned()
29    }
30}
31
32#[derive(Clone, Debug)]
33pub struct CacheOptions {
34    pub cache_size: u32,
35}
36
37pub struct Cache {
38    memory_cache: Arc<RwLock<InMemoryCache>>,
39}
40
41impl Cache {
42    pub fn new(options: CacheOptions) -> Self {
43        let CacheOptions { cache_size } = options;
44
45        Self { memory_cache: Arc::new(RwLock::new(InMemoryCache::new(cache_size))) }
46    }
47
48    fn with_in_memory_cache<C, R>(&mut self, callback: C) -> R
49    where
50        C: FnOnce(&mut InMemoryCache) -> R,
51    {
52        let mut guard = self.memory_cache.as_ref().write().unwrap();
53        let in_memory_cache = guard.borrow_mut();
54        callback(in_memory_cache)
55    }
56
57    pub fn get_instance(
58        &mut self,
59        wasm: &[u8],
60        store: &Store,
61        import_object: &wasmer::ImportObject,
62    ) -> Result<(wasmer::Instance, bool), Error> {
63        let checksum = Checksum::generate(wasm);
64        self.with_in_memory_cache(|in_memory_cache| {
65            // lookup cache
66            if let Some(module) = in_memory_cache.load(&checksum) {
67                return Ok((Instance::new(&module, &import_object).unwrap(), true));
68            }
69
70            // recompile
71            let module = Module::new(store, &wasm).map_err(|_| Error::InstantiationError)?;
72            let instance =
73                Instance::new(&module, &import_object).map_err(|_| Error::InstantiationError)?;
74
75            in_memory_cache.store(&checksum, module);
76
77            Ok((instance, false))
78        })
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use std::io::{Read, Write};
86    use std::process::Command;
87    use tempfile::NamedTempFile;
88    use wasmer::{imports, Singlepass, Store, Universal};
89
90    fn wat2wasm(wat: impl AsRef<[u8]>) -> Vec<u8> {
91        let mut input_file = NamedTempFile::new().unwrap();
92        let mut output_file = NamedTempFile::new().unwrap();
93        input_file.write_all(wat.as_ref()).unwrap();
94        Command::new("wat2wasm")
95            .args(&[
96                input_file.path().to_str().unwrap(),
97                "-o",
98                output_file.path().to_str().unwrap(),
99            ])
100            .output()
101            .unwrap();
102        let mut wasm = Vec::new();
103        output_file.read_to_end(&mut wasm).unwrap();
104        wasm
105    }
106
107    fn get_instance_without_err(cache: &mut Cache, wasm: &[u8]) -> (wasmer::Instance, bool) {
108        let compiler = Singlepass::new();
109        let store = Store::new(&Universal::new(compiler).engine());
110        let import_object = imports! {};
111
112        match cache.get_instance(&wasm, &store, &import_object) {
113            Ok((instance, is_hit)) => (instance, is_hit),
114            Err(_) => panic!("Fail to get instance"),
115        }
116    }
117
118    #[test]
119    fn test_cache_catch() {
120        let mut cache = Cache::new(CacheOptions { cache_size: 10000 });
121        let wasm = wat2wasm(
122            r#"(module
123                (func $execute (export "execute"))
124                (func $prepare (export "prepare"))
125              )"#,
126        );
127
128        let wasm2 = wat2wasm(
129            r#"(module
130                (func $execute (export "execute"))
131                (func $prepare (export "prepare"))
132                (func $foo2 (export "foo2"))
133              )"#,
134        );
135
136        let (instance1, is_hit) = get_instance_without_err(&mut cache, &wasm);
137        assert_eq!(false, is_hit);
138
139        let (instance2, is_hit) = get_instance_without_err(&mut cache, &wasm);
140        assert_eq!(true, is_hit);
141
142        let (_, is_hit) = get_instance_without_err(&mut cache, &wasm2);
143        assert_eq!(false, is_hit);
144
145        let (_, is_hit) = get_instance_without_err(&mut cache, &wasm);
146        assert_eq!(true, is_hit);
147
148        let (_, is_hit) = get_instance_without_err(&mut cache, &wasm2);
149        assert_eq!(true, is_hit);
150
151        let ser1 = match instance1.module().serialize() {
152            Ok(r) => r,
153            Err(_) => panic!("Fail to serialize module"),
154        };
155
156        let ser2 = match instance2.module().serialize() {
157            Ok(r) => r,
158            Err(_) => panic!("Fail to serialize module"),
159        };
160
161        assert_eq!(ser1, ser2);
162    }
163
164    #[test]
165    fn test_cache_size() {
166        let mut cache = Cache::new(CacheOptions { cache_size: 2 });
167        let wasm1 = wat2wasm(
168            r#"(module
169                (func $execute (export "execute"))
170                (func $prepare (export "prepare"))
171                (func $foo (export "foo"))
172              )"#,
173        );
174
175        let wasm2 = wat2wasm(
176            r#"(module
177                (func $execute (export "execute"))
178                (func $prepare (export "prepare"))
179                (func $foo2 (export "foo2"))
180              )"#,
181        );
182
183        let wasm3 = wat2wasm(
184            r#"(module
185                (func $execute (export "execute"))
186                (func $prepare (export "prepare"))
187                (func $foo3 (export "foo3"))
188              )"#,
189        );
190
191        // miss [_ _] => [1 _]
192        let (_, is_hit) = get_instance_without_err(&mut cache, &wasm1);
193        assert_eq!(false, is_hit);
194
195        // miss [1 _] => [2 1]
196        let (_, is_hit) = get_instance_without_err(&mut cache, &wasm2);
197        assert_eq!(false, is_hit);
198
199        // miss [2 1] => [3 2]
200        let (_, is_hit) = get_instance_without_err(&mut cache, &wasm3);
201        assert_eq!(false, is_hit);
202
203        // hit [3 2] => [2 3]
204        let (_, is_hit) = get_instance_without_err(&mut cache, &wasm2);
205        assert_eq!(true, is_hit);
206
207        // miss [2 3] => [1 2]
208        let (_, is_hit) = get_instance_without_err(&mut cache, &wasm1);
209        assert_eq!(false, is_hit);
210
211        // hit [1 2] => [2 1]
212        let (_, is_hit) = get_instance_without_err(&mut cache, &wasm2);
213        assert_eq!(true, is_hit);
214
215        // miss [2 1] => [3 2]
216        let (_, is_hit) = get_instance_without_err(&mut cache, &wasm3);
217        assert_eq!(false, is_hit);
218
219        cache = Cache::new(CacheOptions { cache_size: 0 });
220
221        let (_, is_hit) = get_instance_without_err(&mut cache, &wasm1);
222        assert_eq!(false, is_hit);
223
224        let (_, is_hit) = get_instance_without_err(&mut cache, &wasm1);
225        assert_eq!(false, is_hit);
226    }
227}