1use super::io;
4use super::protocol;
5use super::protocol_api::{validate_control_bytes, validate_debug_log_bytes};
6use crate::PipelineError;
7
8#[derive(Debug, Clone, Default, PartialEq, Eq)]
10pub struct MegakernelReadback {
11 pub control_bytes: Vec<u8>,
13 pub ring_bytes: Vec<u8>,
15 pub debug_log_bytes: Vec<u8>,
17 pub io_queue_bytes: Vec<u8>,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23pub struct MegakernelReadbackCounters {
24 pub control_bytes: usize,
26 pub ring_bytes: usize,
28 pub debug_log_bytes: usize,
30 pub io_queue_bytes: usize,
32 pub total_bytes: usize,
34}
35
36impl MegakernelReadback {
37 pub fn from_outputs(outputs: Vec<Vec<u8>>, slot_count: u32) -> Result<Self, PipelineError> {
44 Self::validate_output_refs(&outputs, slot_count)?;
45 let [control, ring, debug_log, io_queue] =
46 <[Vec<u8>; 4]>::try_from(outputs).map_err(|outputs| {
47 PipelineError::Backend(format!(
48 "megakernel readback returned {} buffers after validation, expected 4. Fix: keep output ownership immutable between validation and decode.",
49 outputs.len()
50 ))
51 })?;
52 Ok(Self {
53 control_bytes: control,
54 ring_bytes: ring,
55 debug_log_bytes: debug_log,
56 io_queue_bytes: io_queue,
57 })
58 }
59
60 pub fn from_outputs_into(
67 mut outputs: Vec<Vec<u8>>,
68 slot_count: u32,
69 out: &mut Self,
70 ) -> Result<(), PipelineError> {
71 Self::drain_outputs_into(&mut outputs, slot_count, out)
72 }
73
74 pub fn drain_outputs_into(
82 outputs: &mut Vec<Vec<u8>>,
83 slot_count: u32,
84 out: &mut Self,
85 ) -> Result<(), PipelineError> {
86 Self::validate_output_refs(outputs, slot_count)?;
87 if outputs.len() != 4 {
88 return Err(PipelineError::Backend(format!(
89 "megakernel readback returned {} buffers after validation, expected 4. Fix: keep output ownership immutable during drain.",
90 outputs.len()
91 )));
92 }
93 std::mem::swap(&mut out.control_bytes, &mut outputs[0]);
94 std::mem::swap(&mut out.ring_bytes, &mut outputs[1]);
95 std::mem::swap(&mut out.debug_log_bytes, &mut outputs[2]);
96 std::mem::swap(&mut out.io_queue_bytes, &mut outputs[3]);
97 Ok(())
98 }
99
100 pub fn slot_count(&self) -> Result<u32, PipelineError> {
106 let slot_words = usize::try_from(protocol::SLOT_WORDS).map_err(|_| {
107 PipelineError::Backend(
108 "megakernel SLOT_WORDS overflowed usize. Fix: reduce SLOT_WORDS.".to_string(),
109 )
110 })?;
111 let slot_bytes = slot_words
112 .checked_mul(std::mem::size_of::<u32>())
113 .ok_or_else(|| {
114 PipelineError::Backend(
115 "megakernel slot byte width overflowed usize. Fix: reduce SLOT_WORDS."
116 .to_string(),
117 )
118 })?;
119 if self.ring_bytes.len() % slot_bytes != 0 {
120 return Err(PipelineError::Backend(format!(
121 "megakernel readback ring has {} bytes, not a multiple of {slot_bytes}. Fix: rebuild the ring with Megakernel::encode_empty_ring.",
122 self.ring_bytes.len()
123 )));
124 }
125 u32::try_from(self.ring_bytes.len() / slot_bytes).map_err(|_| {
126 PipelineError::Backend(
127 "megakernel readback slot count overflowed u32. Fix: split the ring into smaller shards."
128 .to_string(),
129 )
130 })
131 }
132
133 pub fn counters(&self) -> Result<MegakernelReadbackCounters, PipelineError> {
144 let control_bytes = self.control_bytes.len();
145 let ring_bytes = self.ring_bytes.len();
146 let debug_log_bytes = self.debug_log_bytes.len();
147 let io_queue_bytes = self.io_queue_bytes.len();
148 let total_bytes = control_bytes
149 .checked_add(ring_bytes)
150 .and_then(|s| s.checked_add(debug_log_bytes))
151 .and_then(|s| s.checked_add(io_queue_bytes))
152 .ok_or_else(|| {
153 PipelineError::Backend(format!(
154 "megakernel readback total bytes overflowed usize \
155 (control={control_bytes} ring={ring_bytes} \
156 debug_log={debug_log_bytes} io_queue={io_queue_bytes}). \
157 Fix: split the readback into smaller shards."
158 ))
159 })?;
160 Ok(MegakernelReadbackCounters {
161 control_bytes,
162 ring_bytes,
163 debug_log_bytes,
164 io_queue_bytes,
165 total_bytes,
166 })
167 }
168
169 fn validate_output_refs(outputs: &[Vec<u8>], slot_count: u32) -> Result<(), PipelineError> {
170 let [control, ring, debug_log, io_queue] = outputs else {
171 return Err(PipelineError::Backend(format!(
172 "megakernel readback returned {} buffers, expected 4. Fix: keep builder output declarations aligned with control/ring/debug/io ABI order.",
173 outputs.len()
174 )));
175 };
176 validate_control_bytes(control)?;
177 validate_debug_log_bytes(debug_log)?;
178 io::validate_io_queue_bytes(io_queue)?;
179 let expected_ring_bytes = protocol::ring_byte_len(slot_count).ok_or_else(|| {
180 PipelineError::Backend(
181 "megakernel ring byte length overflowed usize during readback validation. Fix: split the ring into smaller shards."
182 .to_string(),
183 )
184 })?;
185 if ring.len() != expected_ring_bytes {
186 return Err(PipelineError::Backend(format!(
187 "megakernel readback ring has {} bytes, expected {expected_ring_bytes}. Fix: read back the full ring buffer for the compiled slot count.",
188 ring.len()
189 )));
190 }
191 Ok(())
192 }
193}
194
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 fn valid_outputs(slot_count: u32) -> Vec<Vec<u8>> {
201 vec![
202 crate::megakernel::Megakernel::try_encode_control(false, 1, 4).unwrap(),
203 crate::megakernel::Megakernel::try_encode_empty_ring(slot_count).unwrap(),
204 crate::megakernel::Megakernel::try_encode_empty_debug_log(
205 protocol::debug::RECORD_CAPACITY,
206 )
207 .unwrap(),
208 io::try_encode_empty_io_queue(io::IO_SLOT_COUNT).unwrap(),
209 ]
210 }
211
212 #[test]
213 fn drain_outputs_into_retains_reusable_output_slots() {
214 let mut outputs = valid_outputs(4);
215 let [control_len, ring_len, debug_len, io_len] =
216 [outputs[0].len(), outputs[1].len(), outputs[2].len(), outputs[3].len()];
217 let mut readback = MegakernelReadback::default();
218
219 MegakernelReadback::drain_outputs_into(&mut outputs, 4, &mut readback)
220 .expect("Fix: valid megakernel outputs must decode");
221
222 assert_eq!(outputs.len(), 4);
223 assert!(outputs.iter().all(Vec::is_empty));
224 assert_eq!(readback.control_bytes.len(), control_len);
227 assert_eq!(readback.ring_bytes.len(), ring_len);
228 assert_eq!(readback.debug_log_bytes.len(), debug_len);
229 assert_eq!(readback.io_queue_bytes.len(), io_len);
230 let expected = valid_outputs(4);
232 assert_eq!(readback.control_bytes, expected[0]);
233 assert_eq!(readback.ring_bytes, expected[1]);
234 assert_eq!(readback.debug_log_bytes, expected[2]);
235 assert_eq!(readback.io_queue_bytes, expected[3]);
236 }
237
238 #[test]
239 fn readback_counters_report_total_volume() {
240 let readback = MegakernelReadback::from_outputs(valid_outputs(4), 4)
241 .expect("Fix: valid megakernel outputs must decode");
242 let counters = readback
243 .counters()
244 .expect("Fix: valid readback counters must not overflow usize");
245
246 assert_eq!(counters.control_bytes, readback.control_bytes.len());
247 assert_eq!(counters.ring_bytes, readback.ring_bytes.len());
248 assert_eq!(counters.debug_log_bytes, readback.debug_log_bytes.len());
249 assert_eq!(counters.io_queue_bytes, readback.io_queue_bytes.len());
250 assert_eq!(
251 counters.total_bytes,
252 readback.control_bytes.len()
253 + readback.ring_bytes.len()
254 + readback.debug_log_bytes.len()
255 + readback.io_queue_bytes.len()
256 );
257 }
258
259 #[test]
260 fn readback_counters_overflow_is_a_structured_error_not_usize_max() {
261 let half = usize::MAX / 2;
280 let overflow = half.checked_add(half + 2); assert!(overflow.is_none(), "arithmetic precondition: these values must overflow");
282 }
288}