Skip to main content

sva_samples/machine/
ops.rs

1// Concern: the op array one node renderer lowers to, each identical op once, and the width each slot holds | Non-concern: running it (mod.rs) | IO: (&NodeRenderer, &Layout) -> ops, widths, operands
2
3use std::collections::HashMap;
4
5use crate::error::SampleError;
6use crate::grid::Grid;
7use crate::machine::renderer::{
8    Between, Binary, Formula, Index, Map, NodeRenderer, Site, SiteId, Slot, Stepped, Unary,
9};
10
11#[derive(Clone, Copy, Debug, PartialEq)]
12pub(crate) enum Op {
13    Const(f64),
14    Time,
15    /// `None` where its constants overflow on the program's grid.
16    Wrap(Option<Stepped>),
17    /// The draw of `seed` at the step of the rate nearest each sample; `None` where that map
18    /// overflows.
19    Noise {
20        seed: u64,
21        at: Option<Map>,
22    },
23    Read {
24        slot: Slot,
25        at: Map,
26    },
27    /// A read times a constant, the bits of `Mul` over the two.
28    ReadScaled {
29        slot: Slot,
30        at: Map,
31        by: f64,
32    },
33    /// The sample at the program's index `at`, over the `arity` step instants below it.
34    Indexed {
35        slot: Slot,
36        at: usize,
37        arity: usize,
38        reach: Option<(i64, i64)>,
39    },
40    Instant {
41        at: usize,
42        arity: usize,
43    },
44    /// The program's formula `at`, at the instant the operand below it names.
45    Formula {
46        at: usize,
47    },
48    Add(usize),
49    Mul(usize),
50    Sub,
51    Div,
52    Pow,
53    Map(Unary),
54    Zip(Binary),
55    Crop {
56        window: (i64, i64),
57        a: f64,
58        b: f64,
59        rise: f64,
60        fall: f64,
61    },
62    Join(usize),
63    Channel(usize),
64    Filter {
65        site: SiteId,
66        from: i64,
67    },
68    Physics {
69        site: SiteId,
70        from: i64,
71        arity: usize,
72    },
73}
74
75/// What the engine already knows from typing: the grid the node steps on, how wide it is,
76/// how wide each collapsed read is, and which call sites the renderer opens.
77#[derive(Clone, Debug, PartialEq)]
78pub struct Layout {
79    pub grid: Grid,
80    pub width: usize,
81    pub read_widths: Vec<usize>,
82    pub sites: Vec<Site>,
83}
84
85/// Equal widths pass, a mono side widens, anything else refuses.
86fn meet(a: usize, b: usize) -> Result<usize, SampleError> {
87    match (a, b) {
88        (a, b) if a == b => Ok(a),
89        (1, b) => Ok(b),
90        (a, 1) => Ok(a),
91        (left, right) => Err(SampleError::WidthMismatch { left, right }),
92    }
93}
94
95/// Each op's width and operands, and the formulas a `Formula` op names by position.
96#[derive(Default)]
97pub(crate) struct Lowered {
98    pub(crate) ops: Vec<Op>,
99    pub(crate) widths: Vec<usize>,
100    pub(crate) args: Vec<Vec<usize>>,
101    pub(crate) formulas: Vec<Formula>,
102    pub(crate) indices: Vec<Index<usize>>,
103    held: HashMap<String, usize>,
104}
105
106/// Operands before the ops reading them, so the runner needs no recursion and every operand's
107/// width is settled by the time the op that consumes it is reached; the root is the last op.
108pub(crate) fn lowered(
109    renderer: &NodeRenderer,
110    layout: &Layout,
111) -> Result<(Lowered, usize), SampleError> {
112    let mut out = Lowered::default();
113    let root = lower(renderer, layout, &mut out)?;
114    debug_assert_eq!(root, out.ops.len() - 1, "an op is never its own operand");
115    let width = out.widths[root];
116    Ok((out, width))
117}
118
119impl Lowered {
120    /// `op`'s slot; an identical op already pushed is that slot, evaluated once.
121    fn push(&mut self, op: Op, width: usize, args: Vec<usize>) -> usize {
122        let key = self.key(&op, &args);
123        if let Some(slot) = key.as_ref().and_then(|key| self.held.get(key)) {
124            if matches!(op, Op::Indexed { .. } | Op::Instant { .. }) {
125                self.indices.pop();
126            }
127            if matches!(op, Op::Formula { .. }) {
128                self.formulas.pop();
129            }
130            return *slot;
131        }
132        self.ops.push(op);
133        self.widths.push(width);
134        self.args.push(args);
135        let slot = self.ops.len() - 1;
136        if let Some(key) = key {
137            self.held.insert(key, slot);
138        }
139        slot
140    }
141
142    /// What `op` computes, numbers by their bits, a formula by the first equal one; `None` for
143    /// a state, never shared.
144    fn key(&self, op: &Op, args: &[usize]) -> Option<String> {
145        let bits = |v: &[f64]| v.iter().map(|v| v.to_bits()).collect::<Vec<u64>>();
146        let what = match op {
147            Op::Const(v) => format!("const {:?}", bits(&[*v])),
148            Op::ReadScaled { slot, at, by } => format!("scaled {slot:?} {at:?} {:?}", bits(&[*by])),
149            Op::Crop {
150                window,
151                a,
152                b,
153                rise,
154                fall,
155            } => format!("crop {window:?} {:?}", bits(&[*a, *b, *rise, *fall])),
156            Op::Indexed {
157                slot, at, reach, ..
158            } => format!("indexed {slot:?} {reach:?} {:?}", self.indices[*at]),
159            Op::Instant { at, .. } => format!("instant {:?}", self.indices[*at]),
160            Op::Formula { at } => {
161                let first = self.formulas.iter().position(|f| *f == self.formulas[*at]);
162                format!("formula {}", first.expect("the formula itself"))
163            }
164            Op::Filter { .. } | Op::Physics { .. } => return None,
165            other => format!("{other:?}"),
166        };
167        Some(format!("{what} {args:?}"))
168    }
169}
170
171fn indexed(
172    index: &Index,
173    layout: &Layout,
174    out: &mut Lowered,
175) -> Result<(usize, Vec<usize>), SampleError> {
176    let mut operands = Vec::new();
177    let program = index.mapped(&mut |time| {
178        operands.push(lower(time, layout, out)?);
179        Ok::<usize, SampleError>(operands.len() - 1)
180    })?;
181    out.indices.push(program);
182    Ok((out.indices.len() - 1, operands))
183}
184
185/// The slot holding `r`.
186fn lower(r: &NodeRenderer, layout: &Layout, out: &mut Lowered) -> Result<usize, SampleError> {
187    if let NodeRenderer::Mul(parts) = r
188        && let Some((slot, at, by)) = scaled_read(parts)
189    {
190        let width = slot_width(slot, layout);
191        return Ok(out.push(Op::ReadScaled { slot, at, by }, width, Vec::new()));
192    }
193    let (op, operands) = match r {
194        NodeRenderer::Formula { formula, time, .. } => {
195            let operands = vec![lower(time, layout, out)?];
196            out.formulas.push(formula.clone());
197            let at = out.formulas.len() - 1;
198            (Op::Formula { at }, operands)
199        }
200        NodeRenderer::Indexed { slot, index, reach } => {
201            let (at, operands) = indexed(index, layout, out)?;
202            let op = Op::Indexed {
203                slot: *slot,
204                at,
205                arity: operands.len(),
206                reach: *reach,
207            };
208            (op, operands)
209        }
210        NodeRenderer::Instant(index) => {
211            let (at, operands) = indexed(index, layout, out)?;
212            let arity = operands.len();
213            (Op::Instant { at, arity }, operands)
214        }
215        other => {
216            let operands = other
217                .operands()
218                .into_iter()
219                .map(|p| lower(p, layout, out))
220                .collect::<Result<Vec<_>, _>>()?;
221            (op_of(other, layout), operands)
222        }
223    };
224    let widths: Vec<usize> = operands.iter().map(|s| out.widths[*s]).collect();
225    let w = width(r, &widths, layout)?;
226    Ok(out.push(op, w, operands))
227}
228
229fn op_of(r: &NodeRenderer, layout: &Layout) -> Op {
230    match r {
231        NodeRenderer::Const(v) => Op::Const(*v),
232        NodeRenderer::Time => Op::Time,
233        NodeRenderer::Wrap(wrap) => Op::Wrap(wrap.on(layout.grid)),
234        NodeRenderer::Noise(seed) => Op::Noise {
235            seed: *seed,
236            at: Map::rounded(layout.grid.a, 0, layout.grid.d, Between::Even),
237        },
238        NodeRenderer::Read { slot, map } => Op::Read {
239            slot: *slot,
240            at: *map,
241        },
242        NodeRenderer::Add(parts) => Op::Add(parts.len()),
243        NodeRenderer::Mul(parts) => Op::Mul(parts.len()),
244        NodeRenderer::Join(parts) => Op::Join(parts.len()),
245        NodeRenderer::Sub(..) => Op::Sub,
246        NodeRenderer::Div(..) => Op::Div,
247        NodeRenderer::Pow(..) => Op::Pow,
248        NodeRenderer::Map(f, _) => Op::Map(*f),
249        NodeRenderer::Zip(f, ..) => Op::Zip(*f),
250        NodeRenderer::Crop {
251            window,
252            a,
253            b,
254            rise,
255            fall,
256            ..
257        } => Op::Crop {
258            window: *window,
259            a: *a,
260            b: *b,
261            rise: *rise,
262            fall: *fall,
263        },
264        NodeRenderer::Channel { k, .. } => Op::Channel(*k),
265        NodeRenderer::Filter { site, from, .. } => Op::Filter {
266            site: *site,
267            from: *from,
268        },
269        NodeRenderer::Physics { site, from, args } => Op::Physics {
270            site: *site,
271            from: *from,
272            arity: args.len(),
273        },
274        NodeRenderer::Formula { .. } | NodeRenderer::Indexed { .. } | NodeRenderer::Instant(_) => {
275            unreachable!("lowered with the program tables they name")
276        }
277    }
278}
279
280/// The width `r` holds over operands `operands` wide, in `NodeRenderer::operands` order.
281pub(crate) fn width(
282    r: &NodeRenderer,
283    operands: &[usize],
284    layout: &Layout,
285) -> Result<usize, SampleError> {
286    let met = |from: usize| operands.iter().try_fold(from, |w, &o| meet(w, o));
287    Ok(match r {
288        NodeRenderer::Const(_)
289        | NodeRenderer::Time
290        | NodeRenderer::Wrap(_)
291        | NodeRenderer::Noise(_) => 1,
292        NodeRenderer::Read { slot, .. } => slot_width(*slot, layout),
293        NodeRenderer::Formula { width, .. } => match operands {
294            [times] if *times != 1 && times != width => {
295                return Err(SampleError::WidthMismatch {
296                    left: *times,
297                    right: *width,
298                });
299            }
300            _ => *width,
301        },
302        NodeRenderer::Indexed { slot, .. } => slot_width(*slot, layout),
303        NodeRenderer::Instant(_) | NodeRenderer::Physics { .. } => 1,
304        NodeRenderer::Add(_) | NodeRenderer::Mul(_) => met(1)?,
305        NodeRenderer::Sub(..)
306        | NodeRenderer::Div(..)
307        | NodeRenderer::Pow(..)
308        | NodeRenderer::Zip(..)
309        | NodeRenderer::Filter { .. } => met(operands[0])?,
310        NodeRenderer::Map(..) | NodeRenderer::Crop { .. } => operands[0],
311        NodeRenderer::Join(_) => operands.iter().sum(),
312        NodeRenderer::Channel { k, .. } => match operands[0] {
313            w if *k >= w => return Err(SampleError::ChannelOutOfRange { k: *k, width: w }),
314            _ => 1,
315        },
316    })
317}
318
319fn slot_width(slot: Slot, layout: &Layout) -> usize {
320    match slot {
321        Slot::Read(id) => layout.read_widths[id.0 as usize],
322        Slot::Own => layout.width,
323    }
324}
325
326fn scaled_read(parts: &[NodeRenderer]) -> Option<(Slot, Map, f64)> {
327    match parts {
328        [NodeRenderer::Read { slot, map }, NodeRenderer::Const(by)]
329        | [NodeRenderer::Const(by), NodeRenderer::Read { slot, map }] => Some((*slot, *map, *by)),
330        _ => None,
331    }
332}