Skip to main content

vyre_runtime/megakernel/
protocol_api.rs

1//! Host protocol API wrappers for megakernel control/ring buffers.
2
3mod publish;
4
5use crate::PipelineError;
6
7use super::protocol::{self, DebugRecord};
8use super::Megakernel;
9
10impl Megakernel {
11    /// Encode a control-buffer payload.
12    ///
13    /// # Errors
14    ///
15    /// Returns [`PipelineError::QueueFull`] when the requested observable region
16    /// cannot fit in process address space.
17    pub fn encode_control(
18        shutdown: bool,
19        tenant_count: u32,
20        observable_slots: u32,
21    ) -> Result<Vec<u8>, PipelineError> {
22        protocol::encode_control(shutdown, tenant_count, observable_slots).map_err(protocol_error)
23    }
24
25    /// Fallible control-buffer encoder for callers accepting untrusted sizing.
26    ///
27    /// # Errors
28    ///
29    /// Returns [`PipelineError::QueueFull`] when the requested observable region
30    /// cannot fit in process address space.
31    pub fn try_encode_control(
32        shutdown: bool,
33        tenant_count: u32,
34        observable_slots: u32,
35    ) -> Result<Vec<u8>, PipelineError> {
36        Self::encode_control(shutdown, tenant_count, observable_slots)
37    }
38
39    /// Fallible control-buffer encoder into caller-owned storage.
40    ///
41    /// # Errors
42    ///
43    /// Returns [`PipelineError::QueueFull`] when the requested observable region
44    /// cannot fit in process address space.
45    pub fn try_encode_control_into(
46        shutdown: bool,
47        tenant_count: u32,
48        observable_slots: u32,
49        dst: &mut Vec<u8>,
50    ) -> Result<(), PipelineError> {
51        protocol::try_encode_control_into(shutdown, tenant_count, observable_slots, dst)
52            .map_err(protocol_error)
53    }
54
55    /// Encode an empty ring buffer with `slot_count` slots.
56    ///
57    /// # Errors
58    ///
59    /// Returns [`PipelineError::QueueFull`] when `slot_count * SLOT_WORDS * 4`
60    /// overflows.
61    pub fn encode_empty_ring(slot_count: u32) -> Result<Vec<u8>, PipelineError> {
62        protocol::encode_empty_ring(slot_count).map_err(protocol_error)
63    }
64
65    /// Fallible ring-buffer encoder for callers accepting untrusted slot counts.
66    ///
67    /// # Errors
68    ///
69    /// Returns [`PipelineError::QueueFull`] when `slot_count * SLOT_WORDS * 4`
70    /// overflows.
71    pub fn try_encode_empty_ring(slot_count: u32) -> Result<Vec<u8>, PipelineError> {
72        Self::encode_empty_ring(slot_count)
73    }
74
75    /// Fallible ring-buffer encoder into caller-owned storage.
76    ///
77    /// # Errors
78    ///
79    /// Returns [`PipelineError::QueueFull`] when `slot_count * SLOT_WORDS * 4`
80    /// overflows.
81    pub fn try_encode_empty_ring_into(
82        slot_count: u32,
83        dst: &mut Vec<u8>,
84    ) -> Result<(), PipelineError> {
85        protocol::try_encode_empty_ring_into(slot_count, dst).map_err(protocol_error)
86    }
87
88    /// Encode an empty PRINTF channel buffer.
89    ///
90    /// # Errors
91    ///
92    /// Returns [`PipelineError::QueueFull`] when the record capacity overflows.
93    pub fn encode_empty_debug_log(record_capacity: u32) -> Result<Vec<u8>, PipelineError> {
94        protocol::encode_empty_debug_log(record_capacity).map_err(protocol_error)
95    }
96
97    /// Fallible debug-log encoder for callers accepting untrusted capacities.
98    ///
99    /// # Errors
100    ///
101    /// Returns [`PipelineError::QueueFull`] when the record capacity overflows.
102    pub fn try_encode_empty_debug_log(record_capacity: u32) -> Result<Vec<u8>, PipelineError> {
103        Self::encode_empty_debug_log(record_capacity)
104    }
105
106    /// Fallible debug-log encoder into caller-owned storage.
107    ///
108    /// # Errors
109    ///
110    /// Returns [`PipelineError::QueueFull`] when the record capacity overflows.
111    pub fn try_encode_empty_debug_log_into(
112        record_capacity: u32,
113        dst: &mut Vec<u8>,
114    ) -> Result<(), PipelineError> {
115        protocol::try_encode_empty_debug_log_into(record_capacity, dst).map_err(protocol_error)
116    }
117
118    /// Decode the kernel's `done_count` from a control buffer.
119    #[must_use]
120    pub fn read_done_count(control_bytes: &[u8]) -> u32 {
121        protocol::read_done_count(control_bytes)
122    }
123
124    /// Strictly decode the kernel's `done_count` from a control buffer.
125    ///
126    /// # Errors
127    ///
128    /// Returns [`PipelineError`] when the control buffer is malformed or too
129    /// short to contain the done counter.
130    pub fn try_read_done_count(control_bytes: &[u8]) -> Result<u32, PipelineError> {
131        protocol::try_read_done_count(control_bytes).map_err(protocol_error)
132    }
133
134    /// Strictly count DONE slots in a ring-buffer readback.
135    ///
136    /// # Errors
137    ///
138    /// Returns [`PipelineError`] when the ring readback is malformed or too
139    /// short for `item_count` complete protocol slots.
140    pub fn try_count_done_ring_slots(
141        ring_bytes: &[u8],
142        item_count: usize,
143    ) -> Result<u64, PipelineError> {
144        protocol::try_count_done_ring_slots(ring_bytes, item_count).map_err(protocol_error)
145    }
146
147    /// Decode PRINTF records out of the debug-log buffer.
148    #[must_use]
149    pub fn read_debug_log(debug_bytes: &[u8]) -> Vec<DebugRecord> {
150        protocol::read_debug_log(debug_bytes)
151    }
152
153    /// Decode PRINTF records into caller-owned storage.
154    pub fn read_debug_log_into(debug_bytes: &[u8], out: &mut Vec<DebugRecord>) {
155        protocol::read_debug_log_into(debug_bytes, out);
156    }
157
158    /// Strictly decode PRINTF records out of the debug-log buffer.
159    ///
160    /// # Errors
161    ///
162    /// Returns [`PipelineError`] when the debug-log buffer is malformed or the
163    /// cursor points at a partial record.
164    pub fn try_read_debug_log(debug_bytes: &[u8]) -> Result<Vec<DebugRecord>, PipelineError> {
165        protocol::try_read_debug_log(debug_bytes).map_err(protocol_error)
166    }
167
168    /// Strictly decode PRINTF records into caller-owned storage.
169    ///
170    /// # Errors
171    ///
172    /// Returns [`PipelineError`] when the debug-log buffer is malformed or the
173    /// cursor points at a partial record.
174    pub fn try_read_debug_log_into(
175        debug_bytes: &[u8],
176        out: &mut Vec<DebugRecord>,
177    ) -> Result<(), PipelineError> {
178        protocol::try_read_debug_log_into(debug_bytes, out).map_err(protocol_error)
179    }
180
181    /// Read the epoch counter from a control buffer. The epoch
182    /// increments on each `BATCH_FENCE` execution  -  the host polls
183    /// this to detect batch completion without scanning the ring.
184    #[must_use]
185    pub fn read_epoch(control_bytes: &[u8]) -> u32 {
186        protocol::read_epoch(control_bytes)
187    }
188
189    /// Strictly read the epoch counter from a control buffer.
190    ///
191    /// # Errors
192    ///
193    /// Returns [`PipelineError`] when the control buffer is malformed or too
194    /// short to contain the epoch counter.
195    pub fn try_read_epoch(control_bytes: &[u8]) -> Result<u32, PipelineError> {
196        protocol::try_read_epoch(control_bytes).map_err(protocol_error)
197    }
198
199    /// Read an observable result word from a control buffer.
200    /// Opcodes like `LOAD_U32`, `COMPARE_SWAP`, and `BATCH_FENCE`
201    /// write results here.
202    #[must_use]
203    pub fn read_observable(control_bytes: &[u8], index: u32) -> u32 {
204        protocol::read_observable(control_bytes, index)
205    }
206
207    /// Strictly read an observable result word from a control buffer.
208    ///
209    /// # Errors
210    ///
211    /// Returns [`PipelineError`] when the buffer is malformed or the
212    /// observable index is outside the supplied readback.
213    pub fn try_read_observable(control_bytes: &[u8], index: u32) -> Result<u32, PipelineError> {
214        protocol::try_read_observable(control_bytes, index).map_err(protocol_error)
215    }
216
217    /// Read per-opcode metrics counters from a control buffer.
218    /// Returns a map of `opcode_id → execution_count` for any
219    /// non-zero counters.
220    #[must_use]
221    pub fn read_metrics(control_bytes: &[u8]) -> Vec<(u32, u32)> {
222        protocol::read_metrics(control_bytes)
223    }
224
225    /// Read per-opcode metrics counters into caller-owned storage.
226    pub fn read_metrics_into(control_bytes: &[u8], out: &mut Vec<(u32, u32)>) {
227        protocol::read_metrics_into(control_bytes, out);
228    }
229
230    /// Strictly read per-opcode metrics counters from a control buffer.
231    ///
232    /// # Errors
233    ///
234    /// Returns [`PipelineError`] when the buffer is malformed or too short for
235    /// the fixed metrics window.
236    pub fn try_read_metrics(control_bytes: &[u8]) -> Result<Vec<(u32, u32)>, PipelineError> {
237        protocol::try_read_metrics(control_bytes).map_err(protocol_error)
238    }
239
240    /// Strictly read per-opcode metrics counters into caller-owned storage.
241    ///
242    /// # Errors
243    ///
244    /// Returns [`PipelineError`] when the buffer is malformed or too short for
245    /// the fixed metrics window.
246    pub fn try_read_metrics_into(
247        control_bytes: &[u8],
248        out: &mut Vec<(u32, u32)>,
249    ) -> Result<(), PipelineError> {
250        protocol::try_read_metrics_into(control_bytes, out).map_err(protocol_error)
251    }
252}
253
254fn protocol_error(error: protocol::ProtocolError) -> PipelineError {
255    match error {
256        protocol::ProtocolError::ByteLengthOverflow { fix, .. } => PipelineError::QueueFull {
257            queue: "submission",
258            fix,
259        },
260        other => PipelineError::Backend(other.to_string()),
261    }
262}
263
264pub(super) fn validate_control_bytes(control_bytes: &[u8]) -> Result<(), PipelineError> {
265    let min = protocol::control_byte_len(0).ok_or_else(|| {
266        PipelineError::Backend(
267            "megakernel minimum control-buffer length overflowed usize. Fix: keep CONTROL_MIN_WORDS within host address limits."
268                .to_string(),
269        )
270    })?;
271    if control_bytes.len() < min || control_bytes.len() % 4 != 0 {
272        return Err(PipelineError::Backend(format!(
273            "megakernel control buffer has {} bytes, expected at least {min} bytes and 4-byte alignment. Fix: build it with Megakernel::encode_control.",
274            control_bytes.len()
275        )));
276    }
277    Ok(())
278}
279
280pub(super) fn validate_debug_log_bytes(debug_log_bytes: &[u8]) -> Result<(), PipelineError> {
281    let expected = protocol::debug_log_byte_len(protocol::debug::RECORD_CAPACITY)
282        .ok_or(PipelineError::QueueFull {
283            queue: "submission",
284            fix: "debug-log minimum length overflowed usize; keep debug ABI constants within host limits",
285        })?;
286    if debug_log_bytes.len() != expected {
287        return Err(PipelineError::Backend(format!(
288            "megakernel debug-log buffer has {} bytes, expected exactly {expected} bytes for {} PRINTF records. Fix: build it with Megakernel::encode_empty_debug_log(protocol::debug::RECORD_CAPACITY).",
289            debug_log_bytes.len(),
290            protocol::debug::RECORD_CAPACITY
291        )));
292    }
293    Ok(())
294}
295
296#[cfg(test)]
297mod tests;