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