1use alloy_primitives::B256;
2use parking_lot::Mutex;
3use std::collections::HashMap;
4use wasmer::{Engine, Module, Store};
5
6use crate::config::CompileConfig;
7
8lazy_static::lazy_static! {
9 static ref INIT_CACHE: Mutex<InitCache> = Mutex::new(InitCache::new());
10}
11
12macro_rules! cache {
13 () => {
14 INIT_CACHE.lock()
15 };
16}
17
18#[derive(Debug, Default)]
20pub struct LruCounters {
21 pub hits: u32,
22 pub misses: u32,
23 pub does_not_fit: u32,
24}
25
26#[derive(Debug, Default)]
28pub struct LongTermCounters {
29 pub hits: u32,
30 pub misses: u32,
31}
32
33pub struct InitCache {
35 long_term: HashMap<CacheKey, CacheItem>,
36 long_term_size_bytes: usize,
37 long_term_counters: LongTermCounters,
38
39 lru: HashMap<CacheKey, CacheItem>,
40 lru_capacity: usize,
41 lru_counters: LruCounters,
42}
43
44#[derive(Clone, Copy, Hash, PartialEq, Eq)]
45struct CacheKey {
46 module_hash: B256,
47 version: u16,
48 debug: bool,
49}
50
51impl CacheKey {
52 fn new(module_hash: B256, version: u16, debug: bool) -> Self {
53 Self {
54 module_hash,
55 version,
56 debug,
57 }
58 }
59}
60
61#[derive(Clone)]
62struct CacheItem {
63 module: Module,
64 engine: Engine,
65 entry_size_estimate_bytes: usize,
66}
67
68impl CacheItem {
69 fn new(module: Module, engine: Engine, entry_size_estimate_bytes: usize) -> Self {
70 Self {
71 module,
72 engine,
73 entry_size_estimate_bytes,
74 }
75 }
76
77 fn data(&self) -> (Module, Store) {
78 (self.module.clone(), Store::new(self.engine.clone()))
79 }
80}
81
82#[derive(Debug, Default)]
84pub struct LruCacheMetrics {
85 pub size_bytes: u64,
86 pub count: u32,
87 pub hits: u32,
88 pub misses: u32,
89 pub does_not_fit: u32,
90}
91
92#[derive(Debug, Default)]
94pub struct LongTermCacheMetrics {
95 pub size_bytes: u64,
96 pub count: u32,
97 pub hits: u32,
98 pub misses: u32,
99}
100
101#[derive(Debug, Default)]
103pub struct CacheMetrics {
104 pub lru: LruCacheMetrics,
105 pub long_term: LongTermCacheMetrics,
106}
107
108pub fn deserialize_module(
110 module: &[u8],
111 version: u16,
112 debug: bool,
113) -> eyre::Result<(Module, Engine, usize)> {
114 let compile = CompileConfig::version(version, debug);
115 let engine = compile.engine();
116 let module = unsafe { Module::deserialize_unchecked(&engine, module)? };
117 let asm_size_estimate_bytes = module.serialize()?.len();
118 let entry_size_estimate_bytes = asm_size_estimate_bytes + 128;
119 Ok((module, engine, entry_size_estimate_bytes))
120}
121
122impl CompileConfig {
123 pub fn engine(&self) -> Engine {
125 use std::sync::Arc;
126 use wasmer::{sys::EngineBuilder, CompilerConfig, Cranelift, CraneliftOptLevel};
127
128 use crate::middleware;
129
130 let mut cranelift = Cranelift::new();
131 cranelift.opt_level(CraneliftOptLevel::Speed);
132 cranelift.canonicalize_nans(true);
133
134 if self.pricing.ink_header_cost > 0 {
135 cranelift.push_middleware(Arc::new(middleware::StartMover::new(self.debug.debug_info)));
138 cranelift.push_middleware(Arc::new(middleware::InkMeter::new(
139 self.pricing.ink_header_cost,
140 )));
141 cranelift.push_middleware(Arc::new(middleware::DynamicMeter::new(
142 self.pricing.memory_fill_ink,
143 self.pricing.memory_copy_ink,
144 )));
145 cranelift.push_middleware(Arc::new(middleware::DepthChecker::new(
146 self.bounds.max_frame_size,
147 self.bounds.max_frame_contention,
148 )));
149 cranelift.push_middleware(Arc::new(middleware::HeapBound::new()));
150 }
151
152 EngineBuilder::new(cranelift).into()
153 }
154
155 pub fn store(&self) -> Store {
157 Store::new(self.engine())
158 }
159}
160
161impl InitCache {
162 const ARBOS_TAG: u32 = 1;
163 const DEFAULT_LRU_CAPACITY: usize = 1024;
164
165 fn new() -> Self {
166 Self {
167 long_term: HashMap::new(),
168 long_term_size_bytes: 0,
169 long_term_counters: LongTermCounters::default(),
170 lru: HashMap::new(),
171 lru_capacity: Self::DEFAULT_LRU_CAPACITY,
172 lru_counters: LruCounters::default(),
173 }
174 }
175
176 pub fn set_lru_capacity(capacity: u32) {
178 cache!().lru_capacity = capacity as usize;
179 }
180
181 pub fn get(
183 module_hash: B256,
184 version: u16,
185 long_term_tag: u32,
186 debug: bool,
187 ) -> Option<(Module, Store)> {
188 let key = CacheKey::new(module_hash, version, debug);
189 let mut cache = cache!();
190
191 if let Some(item) = cache.long_term.get(&key) {
192 let data = item.data();
193 cache.long_term_counters.hits += 1;
194 return Some(data);
195 }
196 if long_term_tag == Self::ARBOS_TAG {
197 cache.long_term_counters.misses += 1;
198 }
199
200 if let Some(item) = cache.lru.get(&key).cloned() {
201 cache.lru_counters.hits += 1;
202 if long_term_tag == Self::ARBOS_TAG {
203 cache.long_term_size_bytes += item.entry_size_estimate_bytes;
204 cache.long_term.insert(key, item.clone());
205 }
206 return Some(item.data());
207 }
208 cache.lru_counters.misses += 1;
209
210 None
211 }
212
213 pub fn insert(
215 module_hash: B256,
216 module: &[u8],
217 version: u16,
218 long_term_tag: u32,
219 debug: bool,
220 ) -> eyre::Result<(Module, Store)> {
221 let key = CacheKey::new(module_hash, version, debug);
222 let mut cache = cache!();
223
224 if let Some(item) = cache.long_term.get(&key) {
225 return Ok(item.data());
226 }
227 if let Some(item) = cache.lru.get(&key).cloned() {
228 if long_term_tag == Self::ARBOS_TAG {
229 cache.long_term_size_bytes += item.entry_size_estimate_bytes;
230 cache.long_term.insert(key, item.clone());
231 }
232 return Ok(item.data());
233 }
234 drop(cache);
235
236 let (module, engine, entry_size_estimate_bytes) =
237 deserialize_module(module, version, debug)?;
238 let item = CacheItem::new(module, engine, entry_size_estimate_bytes);
239 let data = item.data();
240
241 let mut cache = cache!();
242 if long_term_tag == Self::ARBOS_TAG {
243 cache.long_term_size_bytes += entry_size_estimate_bytes;
244 cache.long_term.insert(key, item);
245 } else {
246 if cache.lru.len() >= cache.lru_capacity {
248 let first_key = cache.lru.keys().next().copied();
249 if let Some(k) = first_key {
250 cache.lru.remove(&k);
251 }
252 }
253 cache.lru.insert(key, item);
254 }
255 Ok(data)
256 }
257
258 pub fn evict(module_hash: B256, version: u16, long_term_tag: u32, debug: bool) {
260 if long_term_tag != Self::ARBOS_TAG {
261 return;
262 }
263 let key = CacheKey::new(module_hash, version, debug);
264 let mut cache = cache!();
265 if let Some(item) = cache.long_term.remove(&key) {
266 cache.long_term_size_bytes -= item.entry_size_estimate_bytes;
267 cache.lru.insert(key, item);
268 }
269 }
270
271 pub fn clear_long_term(long_term_tag: u32) {
273 if long_term_tag != Self::ARBOS_TAG {
274 return;
275 }
276 let mut cache = cache!();
277 let drained: Vec<_> = cache.long_term.drain().collect();
278 for (key, item) in drained {
279 cache.lru.insert(key, item);
280 }
281 cache.long_term_size_bytes = 0;
282 }
283
284 pub fn get_metrics() -> CacheMetrics {
286 let mut cache = cache!();
287 let metrics = CacheMetrics {
288 lru: LruCacheMetrics {
289 size_bytes: cache.lru.len() as u64,
290 count: cache.lru.len() as u32,
291 hits: cache.lru_counters.hits,
292 misses: cache.lru_counters.misses,
293 does_not_fit: cache.lru_counters.does_not_fit,
294 },
295 long_term: LongTermCacheMetrics {
296 size_bytes: cache.long_term_size_bytes as u64,
297 count: cache.long_term.len() as u32,
298 hits: cache.long_term_counters.hits,
299 misses: cache.long_term_counters.misses,
300 },
301 };
302 cache.lru_counters = LruCounters::default();
303 cache.long_term_counters = LongTermCounters::default();
304 metrics
305 }
306
307 pub fn clear_lru_cache() {
309 let mut cache = cache!();
310 cache.lru.clear();
311 cache.lru_counters = LruCounters::default();
312 }
313}