polydat_core/iteration/comprehension/ir/
bounds.rs1use serde::{Deserialize, Serialize};
22
23use super::op::Op;
24use super::program::Program;
25use crate::iteration::comprehension::strategy::ZipMode;
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub struct ResourceBound {
30 pub stack_depth: usize,
32
33 pub streaming_op_count: usize,
37
38 pub barriers: Vec<Bound>,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct Bound {
47 pub op_index: usize,
49 pub description: String,
52 pub working_set_size: Option<u64>,
58}
59
60impl ResourceBound {
61 pub fn total_barrier_working_set(&self) -> Option<u64> {
64 let mut total: u64 = 0;
65 for b in &self.barriers {
66 let w = b.working_set_size?;
67 total = total.saturating_add(w);
68 }
69 Some(total)
70 }
71}
72
73pub fn check_bounds(program: &Program) -> ResourceBound {
75 let mut bounds = ResourceBound {
76 stack_depth: program.stack_depth(),
77 streaming_op_count: 0,
78 barriers: Vec::new(),
79 };
80
81 for (i, op) in program.ops().iter().enumerate() {
82 if op.is_barrier() {
83 bounds.barriers.push(barrier_for(i, op));
84 } else if !matches!(op, Op::Dispense) {
85 bounds.streaming_op_count += 1;
86 }
87 }
88
89 bounds
90}
91
92fn barrier_for(op_index: usize, op: &Op) -> Bound {
93 match op {
94 Op::OrderMaterialize {
95 strategy,
96 truncation,
97 indexed,
98 ..
99 } => {
100 let ws = *truncation;
105 let description = format!(
106 "ORDER_MATERIALIZE({}, indexed={indexed}, truncation={truncation:?})",
107 strategy.as_str()
108 );
109 Bound {
110 op_index,
111 description,
112 working_set_size: ws,
113 }
114 }
115 Op::Zip {
116 n,
117 mode: ZipMode::Cycle,
118 } => {
119 Bound {
125 op_index,
126 description: format!("ZIP(Cycle, {n})"),
127 working_set_size: None,
128 }
129 }
130 _ => unreachable!("non-barrier op classified as barrier"),
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::iteration::comprehension::source::Source;
138 use crate::iteration::comprehension::strategy::StrategyName;
139
140 fn push_clause(name: &str) -> Op {
141 Op::PushClause {
142 name: name.into(),
143 source: Source::Literal { values: vec![] },
144 }
145 }
146
147 #[test]
148 fn empty_program_has_zero_bounds() {
149 let p = Program::new(vec![]);
150 let b = check_bounds(&p);
151 assert_eq!(b.stack_depth, 0);
152 assert_eq!(b.streaming_op_count, 0);
153 assert!(b.barriers.is_empty());
154 }
155
156 #[test]
157 fn streaming_program_has_no_barriers() {
158 let p = Program::new(vec![
159 push_clause("a"),
160 push_clause("b"),
161 Op::Cartesian { n: 2 },
162 Op::Filter {
163 predicate: "true".into(),
164 },
165 Op::Dispense,
166 ]);
167 let b = check_bounds(&p);
168 assert_eq!(b.stack_depth, 2);
169 assert!(b.barriers.is_empty());
170 assert_eq!(b.streaming_op_count, 4); }
172
173 #[test]
174 fn order_materialize_reports_barrier() {
175 let p = Program::new(vec![
176 push_clause("a"),
177 push_clause("b"),
178 Op::Cartesian { n: 2 },
179 Op::OrderMaterialize {
180 strategy: StrategyName::Halton,
181 truncation: Some(50),
182 indexed: true,
183 input_index_fn: None,
184 },
185 Op::Dispense,
186 ]);
187 let b = check_bounds(&p);
188 assert_eq!(b.barriers.len(), 1);
189 assert_eq!(b.barriers[0].working_set_size, Some(50));
190 assert_eq!(b.total_barrier_working_set(), Some(50));
191 }
192
193 #[test]
194 fn zip_cycle_reports_barrier_with_unknown_size_at_ir_layer() {
195 let p = Program::new(vec![
196 push_clause("a"),
197 push_clause("b"),
198 Op::Zip {
199 n: 2,
200 mode: ZipMode::Cycle,
201 },
202 Op::Dispense,
203 ]);
204 let b = check_bounds(&p);
205 assert_eq!(b.barriers.len(), 1);
206 assert!(b.barriers[0].working_set_size.is_none());
208 assert!(b.total_barrier_working_set().is_none());
209 }
210
211 #[test]
212 fn multiple_barriers_sum() {
213 let p = Program::new(vec![
214 push_clause("a"),
215 Op::OrderMaterialize {
216 strategy: StrategyName::Halton,
217 truncation: Some(10),
218 indexed: true,
219 input_index_fn: None,
220 },
221 Op::OrderMaterialize {
222 strategy: StrategyName::Shuffle,
223 truncation: Some(20),
224 indexed: true,
225 input_index_fn: None,
226 },
227 Op::Dispense,
228 ]);
229 let b = check_bounds(&p);
230 assert_eq!(b.barriers.len(), 2);
231 assert_eq!(b.total_barrier_working_set(), Some(30));
232 }
233}