Skip to main content

vyre_libs/parsing/c/preprocess/gpu_pipeline/
dispatch.rs

1use vyre_foundation::ir::Program;
2
3/// Raw-program execution oracle for C frontend parity and conformance.
4///
5/// Production execution must compile the frontend output through the canonical
6/// artifact lifecycle. This seam exists only to compare frontend stage programs
7/// against independent reference or target oracles.
8pub trait ProgramOracle {
9    /// Run `program` with owned inputs and return one buffer per output.
10    fn dispatch(&self, program: &Program, inputs: &[Vec<u8>]) -> Result<Vec<Vec<u8>>, String>;
11
12    /// Run `program` with borrowed input buffers.
13    ///
14    /// The default stages the borrowed slices for oracles that implement only
15    /// the owned path.
16    fn dispatch_borrowed(
17        &self,
18        program: &Program,
19        inputs: &[&[u8]],
20    ) -> Result<Vec<Vec<u8>>, String> {
21        let owned: Vec<Vec<u8>> = inputs.iter().map(|slice| slice.to_vec()).collect();
22        ProgramOracle::dispatch(self, program, &owned)
23    }
24
25    /// Run `program` with borrowed inputs and write outputs into caller-owned slots.
26    ///
27    /// The default moves each returned buffer into `outputs`, preserving existing
28    /// slot allocations where possible.
29    fn dispatch_borrowed_into(
30        &self,
31        program: &Program,
32        inputs: &[&[u8]],
33        outputs: &mut Vec<Vec<u8>>,
34    ) -> Result<(), String> {
35        let result = self.dispatch_borrowed(program, inputs)?;
36        replace_outputs_preserving_slots(outputs, result);
37        Ok(())
38    }
39
40    /// Whether the oracle requires declared output buffers as input values.
41    ///
42    /// Target oracles allocate declared outputs themselves. The reference
43    /// interpreter consumes one value per non-workgroup buffer and requires
44    /// zero-initialized output buffers explicitly.
45    fn requires_output_inputs(&self) -> bool {
46        false
47    }
48}
49
50fn replace_outputs_preserving_slots(outputs: &mut Vec<Vec<u8>>, result: Vec<Vec<u8>>) {
51    let mut incoming = result.into_iter();
52    let mut reused = 0usize;
53    for (slot, mut next) in outputs.iter_mut().zip(incoming.by_ref()) {
54        if next.len() <= slot.capacity() {
55            slot.clear();
56            slot.extend_from_slice(&next);
57        } else {
58            std::mem::swap(slot, &mut next);
59        }
60        reused += 1;
61    }
62    outputs.truncate(reused);
63    outputs.extend(incoming);
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn replace_outputs_preserving_slots_reuses_retained_buffers() {
72        let mut outputs = vec![Vec::with_capacity(16), Vec::with_capacity(8)];
73        outputs[0].extend_from_slice(&[9, 9]);
74        outputs[1].extend_from_slice(&[8]);
75        let outer_ptr = outputs.as_ptr() as usize;
76        let first_ptr = outputs[0].as_ptr() as usize;
77        let second_ptr = outputs[1].as_ptr() as usize;
78
79        replace_outputs_preserving_slots(&mut outputs, vec![vec![1, 2, 3], vec![4]]);
80
81        assert_eq!(outputs, vec![vec![1, 2, 3], vec![4]]);
82        assert_eq!(outputs.as_ptr() as usize, outer_ptr);
83        assert_eq!(outputs[0].as_ptr() as usize, first_ptr);
84        assert_eq!(outputs[1].as_ptr() as usize, second_ptr);
85    }
86
87    #[test]
88    fn replace_outputs_preserving_slots_moves_oversized_buffers() {
89        let mut outputs = vec![Vec::with_capacity(1)];
90        outputs[0].push(9);
91        let incoming = vec![vec![1, 2, 3, 4]];
92        let incoming_ptr = incoming[0].as_ptr() as usize;
93
94        replace_outputs_preserving_slots(&mut outputs, incoming);
95
96        assert_eq!(outputs, vec![vec![1, 2, 3, 4]]);
97        assert_eq!(
98            outputs[0].as_ptr() as usize,
99            incoming_ptr,
100            "Fix: oversized C-preprocessor GPU outputs must be moved into retained slots instead of copied through too-small buffers."
101        );
102    }
103}