1use crate::{
2 LASHLANG_COMPILER_VERSION, LASHLANG_VM_ABI_VERSION, LashlangSurface, LinkError, LinkedModule,
3 ModuleArtifact, ProcessRef, RequiredSurfaceRef,
4};
5
6use super::entry_points::{
7 compile_linked, compile_module_artifact_process, compile_program_internal,
8};
9use super::{CompiledProgram, prewarm};
10use rustc_hash::FxHasher;
11use std::borrow::Borrow;
12use std::collections::VecDeque;
13use std::hash::{Hash, Hasher};
14use std::sync::Arc;
15use thiserror::Error;
16
17const DEFAULT_COMPILED_PROGRAM_CACHE_CAPACITY: usize = 64;
18const DEFAULT_LINKED_PROGRAM_CACHE_CAPACITY: usize = 64;
19const SOURCE_CACHE_VERSION: &str = "lashlang-source-v1";
20
21#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
22pub struct CompiledProgramCacheStats {
23 pub hits: u64,
24 pub misses: u64,
25 pub evictions: u64,
26 pub entries: usize,
27 pub capacity: usize,
28}
29
30const DEFAULT_COMPILED_PROCESS_CACHE_CAPACITY: usize = 64;
31
32#[derive(Clone, Debug, PartialEq, Eq, Hash)]
33pub struct CompiledProcessCacheKey {
34 pub process_ref: ProcessRef,
35 pub required_surface_ref: RequiredSurfaceRef,
36 pub compiler_version: &'static str,
37 pub vm_abi_version: &'static str,
38}
39
40impl CompiledProcessCacheKey {
41 pub fn new(process_ref: ProcessRef, required_surface_ref: RequiredSurfaceRef) -> Self {
42 Self {
43 process_ref,
44 required_surface_ref,
45 compiler_version: LASHLANG_COMPILER_VERSION,
46 vm_abi_version: LASHLANG_VM_ABI_VERSION,
47 }
48 }
49}
50
51pub struct CompiledProcessCache {
52 entries: VecDeque<CachedCompiledProcess>,
53 hits: u64,
54 misses: u64,
55 evictions: u64,
56 capacity: usize,
57}
58
59struct CachedCompiledProcess {
60 key: CompiledProcessCacheKey,
61 compiled: Arc<CompiledProgram>,
62}
63
64impl CompiledProcessCache {
65 pub fn new() -> Self {
66 Self::with_capacity(DEFAULT_COMPILED_PROCESS_CACHE_CAPACITY)
67 }
68
69 pub fn with_capacity(capacity: usize) -> Self {
70 prewarm();
71 Self {
72 entries: VecDeque::with_capacity(capacity),
73 hits: 0,
74 misses: 0,
75 evictions: 0,
76 capacity,
77 }
78 }
79
80 pub fn get_or_compile(
81 &mut self,
82 artifact: &ModuleArtifact,
83 process_ref: &ProcessRef,
84 required_surface_ref: &RequiredSurfaceRef,
85 ) -> Result<Arc<CompiledProgram>, crate::RuntimeError> {
86 let key = CompiledProcessCacheKey::new(process_ref.clone(), required_surface_ref.clone());
87 if let Some(entry) = self.entries.back()
88 && entry.key == key
89 {
90 self.hits += 1;
91 return Ok(entry.compiled.clone());
92 }
93 if let Some(index) = self.entries.iter().position(|entry| entry.key == key) {
94 self.hits += 1;
95 let entry = self
96 .entries
97 .remove(index)
98 .expect("cache index came from existing entry");
99 let compiled = entry.compiled.clone();
100 self.entries.push_back(entry);
101 return Ok(compiled);
102 }
103
104 self.misses += 1;
105 let compiled = Arc::new(compile_module_artifact_process(artifact, process_ref)?);
106 if self.capacity == 0 {
107 return Ok(compiled);
108 }
109 if self.entries.len() == self.capacity {
110 self.entries.pop_front();
111 self.evictions += 1;
112 }
113 self.entries.push_back(CachedCompiledProcess {
114 key,
115 compiled: compiled.clone(),
116 });
117 Ok(compiled)
118 }
119
120 pub fn clear(&mut self) {
121 self.entries.clear();
122 self.hits = 0;
123 self.misses = 0;
124 self.evictions = 0;
125 }
126
127 pub fn stats(&self) -> CompiledProgramCacheStats {
128 CompiledProgramCacheStats {
129 hits: self.hits,
130 misses: self.misses,
131 evictions: self.evictions,
132 entries: self.entries.len(),
133 capacity: self.capacity,
134 }
135 }
136}
137
138impl Default for CompiledProcessCache {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144#[derive(Debug, Error)]
145pub enum LinkedProgramCacheError {
146 #[error(transparent)]
147 Parse(#[from] crate::parser::ParseError),
148 #[error(transparent)]
149 Link(#[from] LinkError),
150}
151
152#[derive(Debug)]
153pub struct CompiledLinkedProgram {
154 linked: LinkedModule,
155 compiled: Arc<CompiledProgram>,
156}
157
158impl CompiledLinkedProgram {
159 pub fn linked_module(&self) -> &LinkedModule {
160 &self.linked
161 }
162
163 pub fn compiled_program(&self) -> &CompiledProgram {
164 self.compiled.as_ref()
165 }
166}
167
168pub struct LinkedProgramCache {
169 entries: VecDeque<CachedLinkedProgram>,
170 hits: u64,
171 misses: u64,
172 evictions: u64,
173 capacity: usize,
174}
175
176struct CachedLinkedProgram {
177 source_hash: u64,
178 source: Arc<str>,
179 program: Arc<CompiledLinkedProgram>,
180}
181
182impl LinkedProgramCache {
183 pub fn new() -> Self {
184 Self::with_capacity(DEFAULT_LINKED_PROGRAM_CACHE_CAPACITY)
185 }
186
187 pub fn with_capacity(capacity: usize) -> Self {
188 prewarm();
189 Self {
190 entries: VecDeque::with_capacity(capacity),
191 hits: 0,
192 misses: 0,
193 evictions: 0,
194 capacity,
195 }
196 }
197
198 pub fn get_or_compile(
199 &mut self,
200 source: &str,
201 surface: impl Borrow<LashlangSurface>,
202 ) -> Result<Arc<CompiledLinkedProgram>, LinkedProgramCacheError> {
203 let source_hash = program_source_hash(source);
204 let surface = surface.borrow();
205 if let Some(entry) = self.entries.back()
206 && linked_program_matches(entry, source_hash, source, surface)
207 {
208 self.hits += 1;
209 return Ok(entry.program.clone());
210 }
211
212 if let Some(index) = self
213 .entries
214 .iter()
215 .position(|entry| linked_program_matches(entry, source_hash, source, surface))
216 {
217 self.hits += 1;
218 let entry = self
219 .entries
220 .remove(index)
221 .expect("cache index came from existing entry");
222 let program = entry.program.clone();
223 self.entries.push_back(entry);
224 return Ok(program);
225 }
226
227 self.misses += 1;
228 let program = crate::parse(source)?;
229 let linked = LinkedModule::link(program, surface)?;
230 let compiled = Arc::new(compile_linked(&linked));
231 let program = Arc::new(CompiledLinkedProgram { linked, compiled });
232 if self.capacity == 0 {
233 return Ok(program);
234 }
235 if self.entries.len() == self.capacity {
236 self.entries.pop_front();
237 self.evictions += 1;
238 }
239 self.entries.push_back(CachedLinkedProgram {
240 source_hash,
241 source: Arc::<str>::from(source),
242 program: program.clone(),
243 });
244 Ok(program)
245 }
246
247 pub fn clear(&mut self) {
248 self.entries.clear();
249 self.hits = 0;
250 self.misses = 0;
251 self.evictions = 0;
252 }
253
254 pub fn stats(&self) -> CompiledProgramCacheStats {
255 CompiledProgramCacheStats {
256 hits: self.hits,
257 misses: self.misses,
258 evictions: self.evictions,
259 entries: self.entries.len(),
260 capacity: self.capacity,
261 }
262 }
263}
264
265impl Default for LinkedProgramCache {
266 fn default() -> Self {
267 Self::new()
268 }
269}
270
271pub struct CompiledProgramCache {
272 entries: VecDeque<CachedCompiledProgram>,
273 hits: u64,
274 misses: u64,
275 evictions: u64,
276 capacity: usize,
277}
278
279struct CachedCompiledProgram {
280 source_hash: u64,
281 source: Arc<str>,
282 compiled: Arc<CompiledProgram>,
283}
284
285impl CompiledProgramCache {
286 pub fn new() -> Self {
287 Self::with_capacity(DEFAULT_COMPILED_PROGRAM_CACHE_CAPACITY)
288 }
289
290 pub fn with_capacity(capacity: usize) -> Self {
291 prewarm();
292 Self {
293 entries: VecDeque::with_capacity(capacity),
294 hits: 0,
295 misses: 0,
296 evictions: 0,
297 capacity,
298 }
299 }
300
301 pub fn get_or_compile(
302 &mut self,
303 source: &str,
304 ) -> Result<Arc<CompiledProgram>, crate::parser::ParseError> {
305 let source_hash = program_source_hash(source);
306 if let Some(entry) = self.entries.back()
307 && program_source_matches(entry, source_hash, source)
308 {
309 self.hits += 1;
310 return Ok(entry.compiled.clone());
311 }
312
313 if let Some(index) = self
314 .entries
315 .iter()
316 .position(|entry| program_source_matches(entry, source_hash, source))
317 {
318 self.hits += 1;
319 let entry = self
320 .entries
321 .remove(index)
322 .expect("cache index came from existing entry");
323 let compiled = entry.compiled.clone();
324 self.entries.push_back(entry);
325 return Ok(compiled);
326 }
327
328 self.misses += 1;
329 let program = crate::parse(source)?;
330 let compiled = Arc::new(compile_program_internal(&program));
331 if self.capacity == 0 {
332 return Ok(compiled);
333 }
334 if self.entries.len() == self.capacity {
335 self.entries.pop_front();
336 self.evictions += 1;
337 }
338 self.entries.push_back(CachedCompiledProgram {
339 source_hash,
340 source: Arc::<str>::from(source),
341 compiled: compiled.clone(),
342 });
343 Ok(compiled)
344 }
345
346 pub fn clear(&mut self) {
347 self.entries.clear();
348 self.hits = 0;
349 self.misses = 0;
350 self.evictions = 0;
351 }
352
353 pub fn stats(&self) -> CompiledProgramCacheStats {
354 CompiledProgramCacheStats {
355 hits: self.hits,
356 misses: self.misses,
357 evictions: self.evictions,
358 entries: self.entries.len(),
359 capacity: self.capacity,
360 }
361 }
362}
363
364impl Default for CompiledProgramCache {
365 fn default() -> Self {
366 Self::new()
367 }
368}
369
370fn program_source_matches(entry: &CachedCompiledProgram, source_hash: u64, source: &str) -> bool {
371 source_matches(
372 entry.source_hash,
373 entry.source.as_ref(),
374 source_hash,
375 source,
376 )
377}
378
379fn linked_program_matches(
380 entry: &CachedLinkedProgram,
381 source_hash: u64,
382 source: &str,
383 surface: &LashlangSurface,
384) -> bool {
385 source_matches(
386 entry.source_hash,
387 entry.source.as_ref(),
388 source_hash,
389 source,
390 ) && surface.satisfies(&entry.program.linked.artifact.required_surface)
391}
392
393fn source_matches(cached_hash: u64, cached_source: &str, source_hash: u64, source: &str) -> bool {
394 cached_hash == source_hash && cached_source == source
395}
396
397fn program_source_hash(source: &str) -> u64 {
398 let mut hasher = FxHasher::default();
399 SOURCE_CACHE_VERSION.hash(&mut hasher);
400 source.hash(&mut hasher);
401 hasher.finish()
402}