1use std::num::NonZeroUsize;
8use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
9
10use bloq_graph::{BlockGraph, ModuleCertificationError, ModuleCertificationLimits, ModuleSummary};
11
12use crate::block::{LoweringTemplate, LoweringTemplateId, LoweringTemplatePool};
13use crate::compile::{Signature, TemplateRef};
14use crate::config::CompileConfig;
15use crate::error::CompileError;
16
17#[derive(Debug, Clone, Default)]
42pub struct SharedCompileCache {
43 shards: Arc<Mutex<crate::FxMap<CompileConfig, Arc<TemplateCacheShard>>>>,
44 summaries: Arc<ModuleSummaryCache>,
45}
46
47impl SharedCompileCache {
48 #[must_use]
50 pub fn new() -> Self {
51 Self::default()
52 }
53
54 pub(crate) fn shard(&self, config: CompileConfig) -> Arc<TemplateCacheShard> {
56 Arc::clone(
57 lock(&self.shards)
58 .entry(config)
59 .or_insert_with(|| Arc::new(TemplateCacheShard::default())),
60 )
61 }
62
63 pub(crate) fn summary_cache(&self) -> Arc<ModuleSummaryCache> {
64 Arc::clone(&self.summaries)
65 }
66
67 pub fn clear(&self) {
77 let mut shards = lock(&self.shards);
78 for shard in shards.values() {
79 shard.clear_objects();
80 }
81 shards.clear();
82 self.summaries.clear();
83 }
84}
85
86#[derive(Debug, Default)]
87pub(crate) struct ModuleSummaryCache {
88 entries: Mutex<crate::FxMap<(String, ModuleCertificationLimits), Arc<ModuleSummary>>>,
90}
91
92impl ModuleSummaryCache {
93 pub(crate) fn get_or_summarize(
94 &self,
95 program: &BlockGraph,
96 limits: ModuleCertificationLimits,
97 jobs: Option<NonZeroUsize>,
98 ) -> Result<Arc<ModuleSummary>, ModuleCertificationError> {
99 if program.has_module_structure() {
100 program.validate_with_limits(limits).map_err(|error| {
101 ModuleCertificationError::Graph {
102 module: program.name.clone(),
103 source: bloq_graph::BlockGraphError::ModuleSource(Arc::new(error)),
104 }
105 })?;
106 } else {
107 program
108 .validate_resource_limits(limits)
109 .and_then(|()| program.validate_source())
110 .map_err(|source| ModuleCertificationError::Graph {
111 module: program.name.clone(),
112 source,
113 })?;
114 }
115 let key = (program.to_blog_text(), limits);
116 if let Some(summary) = lock(&self.entries).get(&key) {
117 return Ok(Arc::clone(summary));
118 }
119 let summary = match jobs {
120 Some(jobs) => program.summarize_root_with_jobs(limits, jobs),
121 None => program.summarize_root(limits),
122 }?;
123 Ok(Arc::clone(
124 lock(&self.entries).entry(key).or_insert(summary),
125 ))
126 }
127
128 fn clear(&self) {
129 lock(&self.entries).clear();
130 }
131}
132
133#[derive(Debug, Default)]
139pub(crate) struct TemplateCacheShard {
140 templates: Mutex<ShardTemplates>,
141 pub(crate) padding: crate::padding::PaddingTemplateCache,
142 module_objects: ObjectCache<(bool, String), crate::CompileArtifacts>,
143 definition_objects: ObjectCache<
144 crate::compile::DefinitionObjectCacheKey,
145 crate::compile::PreparedDefinitionObject,
146 >,
147}
148
149struct ObjectCache<K, V> {
151 entries: Mutex<crate::FxMap<K, Arc<V>>>,
152}
153
154impl<K, V> Default for ObjectCache<K, V> {
156 fn default() -> Self {
157 Self {
158 entries: Mutex::default(),
159 }
160 }
161}
162
163impl<K: std::fmt::Debug, V> std::fmt::Debug for ObjectCache<K, V> {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 f.debug_struct("ObjectCache")
166 .field("entries", &lock(&self.entries).len())
167 .finish()
168 }
169}
170
171impl<K: Eq + std::hash::Hash, V> ObjectCache<K, V> {
172 fn get<Q>(&self, key: &Q) -> Option<Arc<V>>
173 where
174 K: std::borrow::Borrow<Q>,
175 Q: Eq + std::hash::Hash + ?Sized,
176 {
177 lock(&self.entries).get(key).cloned()
178 }
179
180 fn insert(&self, key: K, object: Arc<V>) -> Arc<V> {
181 Arc::clone(lock(&self.entries).entry(key).or_insert(object))
182 }
183
184 fn clear(&self) {
185 lock(&self.entries).clear();
186 }
187}
188
189impl TemplateCacheShard {
190 fn clear_objects(&self) {
191 self.module_objects.clear();
192 self.definition_objects.clear();
193 }
194
195 pub(crate) fn module_object(
196 &self,
197 key: &(bool, String),
198 ) -> Option<Arc<crate::CompileArtifacts>> {
199 self.module_objects.get(key)
200 }
201
202 pub(crate) fn insert_module_object(
203 &self,
204 key: (bool, String),
205 object: Arc<crate::CompileArtifacts>,
206 ) -> Arc<crate::CompileArtifacts> {
207 self.module_objects.insert(key, object)
208 }
209
210 pub(crate) fn insert_definition_object(
211 &self,
212 key: crate::compile::DefinitionObjectCacheKey,
213 object: Arc<crate::compile::PreparedDefinitionObject>,
214 ) -> Arc<crate::compile::PreparedDefinitionObject> {
215 self.definition_objects.insert(key, object)
216 }
217
218 pub(crate) fn definition_object(
219 &self,
220 key: &crate::compile::DefinitionObjectCacheKey,
221 ) -> Option<Arc<crate::compile::PreparedDefinitionObject>> {
222 self.definition_objects.get(key)
223 }
224}
225
226#[derive(Debug, Default)]
227struct ShardTemplates {
228 sig_to_template: crate::FxMap<Signature, TemplateRef>,
229 pool: LoweringTemplatePool,
230}
231
232pub(crate) enum CompiledTemplates {
236 Fixed(Arc<LoweringTemplate>),
237 Selective {
238 when_true: Arc<LoweringTemplate>,
239 when_false: Arc<LoweringTemplate>,
240 },
241 T {
242 cultivation: Arc<LoweringTemplate>,
243 escape: Arc<LoweringTemplate>,
244 },
245}
246
247impl TemplateCacheShard {
248 pub(crate) fn get_or_compile(
253 &self,
254 key: Signature,
255 compile: impl FnOnce() -> Result<CompiledTemplates, CompileError>,
256 ) -> Result<TemplateRef, CompileError> {
257 if let Some(&template) = lock(&self.templates).sig_to_template.get(&key) {
258 return Ok(template);
259 }
260 let compiled = compile()?;
261 let mut templates = lock(&self.templates);
262 if let Some(&template) = templates.sig_to_template.get(&key) {
263 return Ok(template);
264 }
265 let pool = &mut templates.pool;
266 let template = match compiled {
267 CompiledTemplates::Fixed(template) => TemplateRef::Fixed(pool.insert(template)),
268 CompiledTemplates::Selective {
269 when_true,
270 when_false,
271 } => TemplateRef::Selective {
272 when_true: pool.insert(when_true),
273 when_false: pool.insert(when_false),
274 },
275 CompiledTemplates::T {
276 cultivation,
277 escape,
278 } => TemplateRef::T {
279 cultivation: pool.insert(cultivation),
280 escape: pool.insert(escape),
281 },
282 };
283 templates.sig_to_template.insert(key, template);
284 Ok(template)
285 }
286
287 pub(crate) fn get_or_compile_fixed(
291 &self,
292 key: Signature,
293 compile: impl FnOnce() -> Result<Arc<LoweringTemplate>, CompileError>,
294 ) -> Result<LoweringTemplateId, CompileError> {
295 match self.get_or_compile(key, || compile().map(CompiledTemplates::Fixed))? {
296 TemplateRef::Fixed(id) => Ok(id),
297 TemplateRef::Selective { .. } | TemplateRef::T { .. } => {
298 unreachable!("realignment and wall keys are only ever inserted as fixed templates")
299 }
300 }
301 }
302
303 pub(crate) fn pool_snapshot(&self) -> LoweringTemplatePool {
308 lock(&self.templates).pool.clone()
309 }
310
311 #[cfg(test)]
312 pub(crate) fn pool_len(&self) -> usize {
313 lock(&self.templates).pool.len()
314 }
315}
316
317pub(crate) fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
322 mutex.lock().unwrap_or_else(PoisonError::into_inner)
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use crate::CompileContext;
329
330 #[test]
331 fn procedural_graph_summary_infers_open_port_interface() {
332 use bloq_graph::{Block, BlockKind, CubeKind, Direction, Pipe};
333 use glam::IVec3;
334 let mut graph = BlockGraph::new();
335 for (position, kind) in [
336 (-IVec3::Z, BlockKind::Port),
337 (IVec3::ZERO, BlockKind::Cube(CubeKind::ZXZ)),
338 (IVec3::Z, BlockKind::Port),
339 ] {
340 graph.add_block(Block::new(position, kind));
341 }
342 graph.add_pipe(Pipe::new(-IVec3::Z, Direction::ZPLUS));
343 graph.add_pipe(Pipe::new(IVec3::ZERO, Direction::ZPLUS));
344 let inferred = graph.clone().with_inferred_interface().unwrap();
345 let context = CompileContext::new(CompileConfig::default());
346 let limits = ModuleCertificationLimits::DEFAULT;
347 let implicit_summary = context.summarize(&graph, limits).unwrap();
348 assert_eq!(implicit_summary.quantum_ports().len(), 2);
349 let explicit_summary = context.summarize(&inferred, limits).unwrap();
350 assert!(Arc::ptr_eq(&implicit_summary, &explicit_summary));
351 }
352
353 #[test]
354 fn module_summaries_are_shared_across_configs_and_cleared() {
355 let program = BlockGraph::from_text(
356 r#"BLOG 1.0
357module main {
358 in q_in: data = 0
359 out q_out: data = 2
360 0: Port [0, 0, -1] <q_in>
361 1: ZXZ [0, 0, 0]
362 2: Port [0, 0, 1] <q_out>
363 [0, 0, -1] -> +Z
364 [0, 0, 0] -> +Z
365}
366"#,
367 )
368 .unwrap();
369 let cache = SharedCompileCache::new();
370 let first = CompileContext::with_shared_cache(CompileConfig::new(3), &cache);
371 let second = CompileContext::with_shared_cache(CompileConfig::new(5), &cache);
372 let limits = bloq_graph::ModuleCertificationLimits::UNLIMITED;
373 let a = first
374 .summarize_with_jobs(&program, limits, NonZeroUsize::new(2).unwrap())
375 .unwrap();
376 let b = second.summarize(&program, limits).unwrap();
377 assert!(Arc::ptr_eq(&a, &b));
378
379 cache.clear();
380 let c = second.summarize(&program, limits).unwrap();
381 assert!(!Arc::ptr_eq(&a, &c));
382 }
383}