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::InkMeter::new(
136 self.pricing.ink_header_cost,
137 )));
138 cranelift.push_middleware(Arc::new(middleware::DynamicMeter::new(
139 self.pricing.memory_fill_ink,
140 self.pricing.memory_copy_ink,
141 )));
142 cranelift.push_middleware(Arc::new(middleware::DepthChecker::new(
143 self.bounds.max_frame_size,
144 )));
145 cranelift.push_middleware(Arc::new(middleware::HeapBound::new()));
146 }
147
148 EngineBuilder::new(cranelift).into()
149 }
150
151 pub fn store(&self) -> Store {
153 Store::new(self.engine())
154 }
155}
156
157impl InitCache {
158 const ARBOS_TAG: u32 = 1;
159 const DEFAULT_LRU_CAPACITY: usize = 1024;
160
161 fn new() -> Self {
162 Self {
163 long_term: HashMap::new(),
164 long_term_size_bytes: 0,
165 long_term_counters: LongTermCounters::default(),
166 lru: HashMap::new(),
167 lru_capacity: Self::DEFAULT_LRU_CAPACITY,
168 lru_counters: LruCounters::default(),
169 }
170 }
171
172 pub fn set_lru_capacity(capacity: u32) {
174 cache!().lru_capacity = capacity as usize;
175 }
176
177 pub fn get(
179 module_hash: B256,
180 version: u16,
181 long_term_tag: u32,
182 debug: bool,
183 ) -> Option<(Module, Store)> {
184 let key = CacheKey::new(module_hash, version, debug);
185 let mut cache = cache!();
186
187 if let Some(item) = cache.long_term.get(&key) {
188 let data = item.data();
189 cache.long_term_counters.hits += 1;
190 return Some(data);
191 }
192 if long_term_tag == Self::ARBOS_TAG {
193 cache.long_term_counters.misses += 1;
194 }
195
196 if let Some(item) = cache.lru.get(&key).cloned() {
197 cache.lru_counters.hits += 1;
198 if long_term_tag == Self::ARBOS_TAG {
199 cache.long_term_size_bytes += item.entry_size_estimate_bytes;
200 cache.long_term.insert(key, item.clone());
201 }
202 return Some(item.data());
203 }
204 cache.lru_counters.misses += 1;
205
206 None
207 }
208
209 pub fn insert(
211 module_hash: B256,
212 module: &[u8],
213 version: u16,
214 long_term_tag: u32,
215 debug: bool,
216 ) -> eyre::Result<(Module, Store)> {
217 let key = CacheKey::new(module_hash, version, debug);
218 let mut cache = cache!();
219
220 if let Some(item) = cache.long_term.get(&key) {
221 return Ok(item.data());
222 }
223 if let Some(item) = cache.lru.get(&key).cloned() {
224 if long_term_tag == Self::ARBOS_TAG {
225 cache.long_term_size_bytes += item.entry_size_estimate_bytes;
226 cache.long_term.insert(key, item.clone());
227 }
228 return Ok(item.data());
229 }
230 drop(cache);
231
232 let (module, engine, entry_size_estimate_bytes) =
233 deserialize_module(module, version, debug)?;
234 let item = CacheItem::new(module, engine, entry_size_estimate_bytes);
235 let data = item.data();
236
237 let mut cache = cache!();
238 if long_term_tag == Self::ARBOS_TAG {
239 cache.long_term_size_bytes += entry_size_estimate_bytes;
240 cache.long_term.insert(key, item);
241 } else {
242 if cache.lru.len() >= cache.lru_capacity {
244 let first_key = cache.lru.keys().next().copied();
245 if let Some(k) = first_key {
246 cache.lru.remove(&k);
247 }
248 }
249 cache.lru.insert(key, item);
250 }
251 Ok(data)
252 }
253
254 pub fn evict(module_hash: B256, version: u16, long_term_tag: u32, debug: bool) {
256 if long_term_tag != Self::ARBOS_TAG {
257 return;
258 }
259 let key = CacheKey::new(module_hash, version, debug);
260 let mut cache = cache!();
261 if let Some(item) = cache.long_term.remove(&key) {
262 cache.long_term_size_bytes -= item.entry_size_estimate_bytes;
263 cache.lru.insert(key, item);
264 }
265 }
266
267 pub fn clear_long_term(long_term_tag: u32) {
269 if long_term_tag != Self::ARBOS_TAG {
270 return;
271 }
272 let mut cache = cache!();
273 let drained: Vec<_> = cache.long_term.drain().collect();
274 for (key, item) in drained {
275 cache.lru.insert(key, item);
276 }
277 cache.long_term_size_bytes = 0;
278 }
279
280 pub fn get_metrics() -> CacheMetrics {
282 let mut cache = cache!();
283 let metrics = CacheMetrics {
284 lru: LruCacheMetrics {
285 size_bytes: cache.lru.len() as u64,
286 count: cache.lru.len() as u32,
287 hits: cache.lru_counters.hits,
288 misses: cache.lru_counters.misses,
289 does_not_fit: cache.lru_counters.does_not_fit,
290 },
291 long_term: LongTermCacheMetrics {
292 size_bytes: cache.long_term_size_bytes as u64,
293 count: cache.long_term.len() as u32,
294 hits: cache.long_term_counters.hits,
295 misses: cache.long_term_counters.misses,
296 },
297 };
298 cache.lru_counters = LruCounters::default();
299 cache.long_term_counters = LongTermCounters::default();
300 metrics
301 }
302
303 pub fn clear_lru_cache() {
305 let mut cache = cache!();
306 cache.lru.clear();
307 cache.lru_counters = LruCounters::default();
308 }
309}