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