use serde::{Deserialize, Serialize};
use super::op::Op;
use super::program::Program;
use crate::iteration::comprehension::strategy::ZipMode;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResourceBound {
pub stack_depth: usize,
pub streaming_op_count: usize,
pub barriers: Vec<Bound>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Bound {
pub op_index: usize,
pub description: String,
pub working_set_size: Option<u64>,
}
impl ResourceBound {
pub fn total_barrier_working_set(&self) -> Option<u64> {
let mut total: u64 = 0;
for b in &self.barriers {
let w = b.working_set_size?;
total = total.saturating_add(w);
}
Some(total)
}
}
pub fn check_bounds(program: &Program) -> ResourceBound {
let mut bounds = ResourceBound {
stack_depth: program.stack_depth(),
streaming_op_count: 0,
barriers: Vec::new(),
};
for (i, op) in program.ops().iter().enumerate() {
if op.is_barrier() {
bounds.barriers.push(barrier_for(i, op));
} else if !matches!(op, Op::Dispense) {
bounds.streaming_op_count += 1;
}
}
bounds
}
fn barrier_for(op_index: usize, op: &Op) -> Bound {
match op {
Op::OrderMaterialize {
strategy,
truncation,
indexed,
..
} => {
let ws = *truncation;
let description = format!(
"ORDER_MATERIALIZE({}, indexed={indexed}, truncation={truncation:?})",
strategy.as_str()
);
Bound {
op_index,
description,
working_set_size: ws,
}
}
Op::Zip {
n,
mode: ZipMode::Cycle,
} => {
Bound {
op_index,
description: format!("ZIP(Cycle, {n})"),
working_set_size: None,
}
}
_ => unreachable!("non-barrier op classified as barrier"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::iteration::comprehension::source::Source;
use crate::iteration::comprehension::strategy::StrategyName;
fn push_clause(name: &str) -> Op {
Op::PushClause {
name: name.into(),
source: Source::Literal { values: vec![] },
}
}
#[test]
fn empty_program_has_zero_bounds() {
let p = Program::new(vec![]);
let b = check_bounds(&p);
assert_eq!(b.stack_depth, 0);
assert_eq!(b.streaming_op_count, 0);
assert!(b.barriers.is_empty());
}
#[test]
fn streaming_program_has_no_barriers() {
let p = Program::new(vec![
push_clause("a"),
push_clause("b"),
Op::Cartesian { n: 2 },
Op::Filter { predicate: "true".into() },
Op::Dispense,
]);
let b = check_bounds(&p);
assert_eq!(b.stack_depth, 2);
assert!(b.barriers.is_empty());
assert_eq!(b.streaming_op_count, 4); }
#[test]
fn order_materialize_reports_barrier() {
let p = Program::new(vec![
push_clause("a"),
push_clause("b"),
Op::Cartesian { n: 2 },
Op::OrderMaterialize {
strategy: StrategyName::Halton,
truncation: Some(50),
indexed: true,
input_index_fn: None,
},
Op::Dispense,
]);
let b = check_bounds(&p);
assert_eq!(b.barriers.len(), 1);
assert_eq!(b.barriers[0].working_set_size, Some(50));
assert_eq!(b.total_barrier_working_set(), Some(50));
}
#[test]
fn zip_cycle_reports_barrier_with_unknown_size_at_ir_layer() {
let p = Program::new(vec![
push_clause("a"),
push_clause("b"),
Op::Zip { n: 2, mode: ZipMode::Cycle },
Op::Dispense,
]);
let b = check_bounds(&p);
assert_eq!(b.barriers.len(), 1);
assert!(b.barriers[0].working_set_size.is_none());
assert!(b.total_barrier_working_set().is_none());
}
#[test]
fn multiple_barriers_sum() {
let p = Program::new(vec![
push_clause("a"),
Op::OrderMaterialize {
strategy: StrategyName::Halton,
truncation: Some(10),
indexed: true,
input_index_fn: None,
},
Op::OrderMaterialize {
strategy: StrategyName::Shuffle,
truncation: Some(20),
indexed: true,
input_index_fn: None,
},
Op::Dispense,
]);
let b = check_bounds(&p);
assert_eq!(b.barriers.len(), 2);
assert_eq!(b.total_barrier_working_set(), Some(30));
}
}