1use crate::ir::{Instr, Ir};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum Step {
12 Block(usize),
14 ForeignBlock(usize),
16 Sample(Vec<usize>),
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Schedule {
25 pub steps: Vec<Step>,
27}
28
29fn instr_dst(instr: &Instr) -> Option<usize> {
31 match *instr {
32 Instr::Const { dst, .. }
33 | Instr::LoadInput { dst, .. }
34 | Instr::ReadState { dst, .. }
35 | Instr::ReadDelay { dst, .. }
36 | Instr::Un { dst, .. }
37 | Instr::Bin { dst, .. }
38 | Instr::Move { dst, .. }
39 | Instr::CallSample { dst, .. }
40 | Instr::CallBlock { dst, .. }
41 | Instr::ReadParam { dst, .. } => Some(dst),
42 Instr::WriteState { .. } | Instr::WriteDelay { .. } => None,
43 }
44}
45
46fn instr_srcs(instr: &Instr) -> Vec<usize> {
48 match *instr {
49 Instr::Un { src, .. } | Instr::Move { src, .. } => vec![src],
50 Instr::Bin { a, b, .. } => vec![a, b],
51 Instr::WriteState { src, .. } | Instr::WriteDelay { src, .. } => vec![src],
52 Instr::CallSample { ref srcs, .. } => srcs.clone(),
53 Instr::CallBlock { src, .. } => vec![src],
54 _ => Vec::new(),
55 }
56}
57
58fn is_stateful(instr: &Instr) -> bool {
61 matches!(
62 instr,
63 Instr::ReadState { .. }
64 | Instr::WriteState { .. }
65 | Instr::ReadDelay { .. }
66 | Instr::WriteDelay { .. }
67 | Instr::CallSample { .. }
68 )
69}
70
71pub fn build_schedule(ir: &Ir) -> Schedule {
73 let n = ir.instrs.len();
74
75 let mut producer: Vec<Option<usize>> = vec![None; ir.num_regs];
77 for (i, instr) in ir.instrs.iter().enumerate() {
78 if let Some(d) = instr_dst(instr) {
79 producer[d] = Some(i);
80 }
81 }
82
83 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
85 for (i, instr) in ir.instrs.iter().enumerate() {
86 for s in instr_srcs(instr) {
87 if let Some(p) = producer[s] {
88 adj[i].push(p);
89 }
90 }
91 }
92
93 let mut read_state: Vec<Option<usize>> = vec![None; ir.state.state_slots];
96 let mut write_state: Vec<Option<usize>> = vec![None; ir.state.state_slots];
97 let mut read_delay: Vec<Option<usize>> = vec![None; ir.state.delay_lens.len()];
98 let mut write_delay: Vec<Option<usize>> = vec![None; ir.state.delay_lens.len()];
99 for (i, instr) in ir.instrs.iter().enumerate() {
100 match *instr {
101 Instr::ReadState { slot, .. } => read_state[slot] = Some(i),
102 Instr::WriteState { slot, .. } => write_state[slot] = Some(i),
103 Instr::ReadDelay { line, .. } => read_delay[line] = Some(i),
104 Instr::WriteDelay { line, .. } => write_delay[line] = Some(i),
105 _ => {}
106 }
107 }
108 let add_pair = |a: Option<usize>, b: Option<usize>, adj: &mut Vec<Vec<usize>>| {
109 if let (Some(a), Some(b)) = (a, b) {
110 adj[a].push(b);
111 adj[b].push(a);
112 }
113 };
114 for s in 0..ir.state.state_slots {
115 add_pair(read_state[s], write_state[s], &mut adj);
116 }
117 for l in 0..ir.state.delay_lens.len() {
118 add_pair(read_delay[l], write_delay[l], &mut adj);
119 }
120
121 let sccs = tarjan_scc(n, &adj);
124
125 let mut steps = Vec::with_capacity(sccs.len());
127 for scc in sccs {
128 if scc.len() == 1 && matches!(ir.instrs[scc[0]], Instr::CallBlock { .. }) {
129 steps.push(Step::ForeignBlock(scc[0]));
130 } else {
131 let recurrent = scc.len() > 1 || scc.iter().any(|&i| is_stateful(&ir.instrs[i]));
132 if recurrent {
133 let mut instrs = scc;
134 instrs.sort_unstable();
135 steps.push(Step::Sample(instrs));
136 } else {
137 steps.push(Step::Block(scc[0]));
138 }
139 }
140 }
141 Schedule { steps }
142}
143
144fn tarjan_scc(n: usize, adj: &[Vec<usize>]) -> Vec<Vec<usize>> {
150 const UNVISITED: i64 = -1;
151 let mut index = vec![UNVISITED; n];
152 let mut low = vec![0i64; n];
153 let mut on_stack = vec![false; n];
154 let mut stack: Vec<usize> = Vec::new();
155 let mut next_index: i64 = 0;
156 let mut out: Vec<Vec<usize>> = Vec::new();
157
158 for root in 0..n {
160 if index[root] != UNVISITED {
161 continue;
162 }
163 let mut call: Vec<(usize, usize)> = vec![(root, 0)];
164 while let Some(&mut (v, ref mut ni)) = call.last_mut() {
165 if *ni == 0 {
166 index[v] = next_index;
167 low[v] = next_index;
168 next_index += 1;
169 stack.push(v);
170 on_stack[v] = true;
171 }
172 if *ni < adj[v].len() {
173 let w = adj[v][*ni];
174 *ni += 1;
175 if index[w] == UNVISITED {
176 call.push((w, 0));
177 } else if on_stack[w] {
178 low[v] = low[v].min(index[w]);
179 }
180 } else {
181 if low[v] == index[v] {
182 let mut comp = Vec::new();
183 loop {
184 let w = stack.pop().unwrap();
185 on_stack[w] = false;
186 comp.push(w);
187 if w == v {
188 break;
189 }
190 }
191 out.push(comp);
192 }
193 let finished = v;
194 call.pop();
195 if let Some(&mut (parent, _)) = call.last_mut() {
196 low[parent] = low[parent].min(low[finished]);
197 }
198 }
199 }
200 }
201 out
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use crate::lexer::tokenize;
208 use crate::lower::{lower, lower_with};
209 use crate::parser::parse;
210 use crate::types::infer::{infer_program, infer_program_with};
211
212 struct TestSigs;
213 impl crate::builtin::SignatureSource for TestSigs {
214 fn builtin_sig(&self, name: &str) -> Option<&crate::builtin::BuiltinSig> {
215 use crate::builtin::{BuiltinKind, BuiltinSig};
216 match name {
217 "lowpass" => Some(Box::leak(Box::new(BuiltinSig {
218 name: "lowpass",
219 signal_ins: 1,
220 signal_outs: 1,
221 num_params: 2,
222 kind: BuiltinKind::Block,
223 }))),
224 "onepole" => Some(Box::leak(Box::new(BuiltinSig {
225 name: "onepole",
226 signal_ins: 1,
227 signal_outs: 1,
228 num_params: 2,
229 kind: BuiltinKind::Sample,
230 }))),
231 _ => None,
232 }
233 }
234 }
235
236 fn schedule_of(src: &str) -> Schedule {
237 let p = parse(&tokenize(src).unwrap()).unwrap();
238 let tp = infer_program(&p).unwrap();
239 let ir = lower(&tp).unwrap();
240 build_schedule(&ir)
241 }
242
243 fn schedule_of_with(src: &str) -> (Ir, Schedule) {
244 let p = parse(&tokenize(src).unwrap()).unwrap();
245 let tp = infer_program_with(&p, &TestSigs).unwrap();
246 let ir = lower_with(&tp, &TestSigs, 44_100.0).unwrap();
247 let sched = build_schedule(&ir);
248 (ir, sched)
249 }
250
251 fn n_sample(s: &Schedule) -> usize {
252 s.steps
253 .iter()
254 .filter(|st| matches!(st, Step::Sample(_)))
255 .count()
256 }
257 fn n_block(s: &Schedule) -> usize {
258 s.steps
259 .iter()
260 .filter(|st| matches!(st, Step::Block(_)))
261 .count()
262 }
263
264 #[test]
265 fn combinational_program_is_all_block() {
266 let s = schedule_of("process = _ * 0.5;");
267 assert_eq!(n_sample(&s), 0);
268 assert!(n_block(&s) >= 1);
269 }
270
271 #[test]
272 fn feedback_program_has_one_sample_region() {
273 let s = schedule_of("process = + ~ _;");
274 assert_eq!(n_sample(&s), 1);
275 }
276
277 #[test]
278 fn const_feeding_feedback_stays_block() {
279 let s = schedule_of("process = + ~ (_ * 0.5);");
282 assert_eq!(n_sample(&s), 1);
283 assert!(n_block(&s) >= 1); }
285
286 #[test]
287 fn feedforward_delay_is_isolated_sample_region() {
288 let s = schedule_of("process = _ @ 3;");
290 assert_eq!(n_sample(&s), 1);
291 }
292
293 #[test]
294 fn feedback_through_delay_is_one_region() {
295 let s = schedule_of("process = + ~ (_ @ 2);");
296 assert_eq!(n_sample(&s), 1);
297 }
298
299 #[test]
300 fn gain_then_integrator_splits_block_and_sample() {
301 let s = schedule_of("process = (_ * 0.5) : (+ ~ _);");
302 assert_eq!(n_sample(&s), 1);
303 assert!(n_block(&s) >= 1);
304 }
305
306 #[test]
307 fn steps_are_in_dependency_order() {
308 let s = schedule_of("process = abs(_) : _ * 2.0;");
311 assert!(!s.steps.is_empty());
312 assert_eq!(n_sample(&s), 0);
313 }
314
315 #[test]
316 fn sample_builtin_schedules_as_sample_region() {
317 let (_, s) = schedule_of_with("process = _ : onepole(200.0, 0.5);");
318 assert_eq!(n_sample(&s), 1);
319 }
320
321 #[test]
322 fn block_builtin_schedules_as_foreign_block() {
323 let (_, s) = schedule_of_with("process = _ : lowpass(1000.0, 0.7);");
324 assert!(s.steps.iter().any(|st| matches!(st, Step::ForeignBlock(_))));
325 assert!(n_block(&s) >= 1); assert_eq!(n_sample(&s), 0);
327 }
328
329 #[test]
330 fn block_builtin_in_feedback_lands_in_sample_region() {
331 let (ir, s) = schedule_of_with("process = + ~ lowpass(500.0, 0.7);");
332 assert!(s.steps.iter().any(|st| {
335 matches!(st, Step::Sample(ref instrs)
336 if instrs.iter().any(|&i| matches!(ir.instrs[i], Instr::CallBlock { .. })))
337 }));
338 }
339}