polydat_core/iteration/comprehension/surfaces/compiled.rs
1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! `CompiledComprehension` — the entry point for the three
5//! consumption surfaces.
6//!
7//! Holds an `Arc<Program>` (immutable IR per spec §9.1). Each
8//! factory method on this handle (`coordinate_stream`,
9//! `scoped_kernel_stream`, `scope_once`) returns a fresh
10//! streamer with its own dispense state but shares the
11//! `Arc<Program>` — no recompilation across siblings (spec
12//! §9.5.2's "IR-sharing test" property).
13
14use std::sync::Arc;
15
16use crate::iteration::comprehension::ast::Comprehension;
17use crate::iteration::comprehension::ir::{Program, compile as compile_to_ir};
18
19use super::coord_stream::CoordinateStream;
20use super::instance::{KernelScope, ScopedKernelInstance};
21use super::scope_once::scope_once_with;
22use super::scoped_stream::ScopedKernelStream;
23
24/// A comprehension that has been compiled to immutable IR
25/// and is ready to dispense. The single source of truth for
26/// the underlying program across the consumption surfaces.
27///
28/// Construction is via [`from_ast`](Self::from_ast) (compiles once) or
29/// [`from_program`](Self::from_program) (when the IR was compiled elsewhere).
30/// Cloning a `CompiledComprehension` is cheap — just an
31/// `Arc::clone` on the program.
32#[derive(Debug, Clone)]
33pub struct CompiledComprehension {
34 program: Arc<Program>,
35}
36
37impl CompiledComprehension {
38 /// Compile an AST. Performs the AST → IR pass once.
39 pub fn from_ast(ast: &Comprehension) -> Self {
40 Self {
41 program: Arc::new(compile_to_ir(ast)),
42 }
43 }
44
45 /// Wrap an already-compiled program (the optimizer is the
46 /// canonical caller; tests use this for hand-built IR).
47 pub fn from_program(program: Arc<Program>) -> Self {
48 Self { program }
49 }
50
51 /// Access the underlying compiled program (immutable per
52 /// spec §9.1).
53 pub fn program(&self) -> &Program {
54 &self.program
55 }
56
57 /// Clone the `Arc<Program>` for sharing with other
58 /// handles. Used internally by the streamer factories.
59 pub(crate) fn program_arc(&self) -> Arc<Program> {
60 Arc::clone(&self.program)
61 }
62
63 /// **First-order surface** (spec §9.5).
64 ///
65 /// Return a fresh [`CoordinateStream`]. Each call
66 /// allocates new per-streamer state; siblings share the
67 /// underlying IR but dispense independently per spec
68 /// §9.5.2's independence contract.
69 pub fn coordinate_stream(&self) -> CoordinateStream {
70 CoordinateStream::new(self.program_arc())
71 }
72
73 /// **Second-order surface** (spec §9.5).
74 ///
75 /// Return a fresh [`ScopedKernelStream`] wrapping the
76 /// supplied parent kernel. Each `advance()` pulls one
77 /// coord tuple from the underlying IR and applies
78 /// `parent.scope(&coords)` to produce a
79 /// [`ScopedKernelInstance`].
80 ///
81 /// Independence: pulling from this stream does NOT
82 /// advance any [`CoordinateStream`] obtained from the
83 /// same `CompiledComprehension`.
84 pub fn scoped_kernel_stream<K: KernelScope>(&self, parent: K) -> ScopedKernelStream<K> {
85 ScopedKernelStream::new(self.program_arc(), parent)
86 }
87
88 /// **One-shot surface** (spec §9.5.3).
89 ///
90 /// Apply `parent.scope(coords)` directly, without
91 /// constructing any streamer. Pure function — no
92 /// cursor consulted, no dispense state advanced. Used
93 /// for replay, debugging, and point queries where a
94 /// specific coord tuple is already known.
95 pub fn scope_once<K: KernelScope>(
96 &self,
97 parent: &K,
98 coords: &crate::iteration::comprehension::strategies::Tuple,
99 ) -> ScopedKernelInstance<K::Scoped> {
100 scope_once_with(parent, coords)
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::iteration::comprehension::source::{LiteralValue, Source};
108
109 fn clause(name: &str, vs: &[i64]) -> Comprehension {
110 Comprehension::clause(
111 name,
112 Source::Literal {
113 values: vs.iter().map(|n| LiteralValue::Int(*n)).collect(),
114 },
115 )
116 }
117
118 #[test]
119 fn from_ast_compiles_once() {
120 let ast = clause("k", &[1, 2, 3]);
121 let compiled = CompiledComprehension::from_ast(&ast);
122 assert!(!compiled.program().is_empty());
123 }
124
125 #[test]
126 fn cloning_compiled_shares_arc() {
127 let ast = clause("k", &[1, 2, 3]);
128 let a = CompiledComprehension::from_ast(&ast);
129 let b = a.clone();
130 // Same Arc — strong_count goes up.
131 let count = Arc::strong_count(&a.program);
132 assert!(count >= 2, "expected shared Arc, count = {count}");
133 drop(b);
134 }
135
136 #[test]
137 fn two_coordinate_streams_share_program() {
138 let ast = clause("k", &[1, 2, 3]);
139 let compiled = CompiledComprehension::from_ast(&ast);
140 let _s1 = compiled.coordinate_stream();
141 let _s2 = compiled.coordinate_stream();
142 // Both streams hold an Arc; count is at least 3 (compiled +
143 // two streamers, possibly more if internal clones happen).
144 let count = Arc::strong_count(&compiled.program);
145 assert!(
146 count >= 3,
147 "expected shared program across streamers, count = {count}"
148 );
149 }
150}