Skip to main content

vyre_runtime/megakernel/
protocol_api.rs

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