1use super::staging_reserve::reserve_vec_capacity as reserve_descriptor_vec;
9use crate::PipelineError;
10
11use smallvec::SmallVec;
12
13const ARGS_PER_SLOT_USIZE: usize = 12;
14
15use super::{protocol, Megakernel};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum BuiltinOpcode {
20 Nop,
22 StoreU32,
24 AtomicAdd,
26 LoadU32,
28 CompareSwap,
30 Memcpy,
32 DfaStep,
34 BatchFence,
36 Printf,
38 Shutdown,
40}
41
42impl BuiltinOpcode {
43 #[must_use]
45 pub const fn into_wire(self) -> u32 {
46 match self {
47 Self::Nop => protocol::opcode::NOP,
48 Self::StoreU32 => protocol::opcode::STORE_U32,
49 Self::AtomicAdd => protocol::opcode::ATOMIC_ADD,
50 Self::LoadU32 => protocol::opcode::LOAD_U32,
51 Self::CompareSwap => protocol::opcode::COMPARE_SWAP,
52 Self::Memcpy => protocol::opcode::MEMCPY,
53 Self::DfaStep => protocol::opcode::DFA_STEP,
54 Self::BatchFence => protocol::opcode::BATCH_FENCE,
55 Self::Printf => protocol::opcode::PRINTF,
56 Self::Shutdown => protocol::opcode::SHUTDOWN,
57 }
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum SlotOpcode {
65 Builtin(BuiltinOpcode),
67 Custom(u32),
69}
70
71impl SlotOpcode {
72 #[must_use]
74 pub const fn into_wire(self) -> u32 {
75 match self {
76 Self::Builtin(op) => op.into_wire(),
77 Self::Custom(op) => op,
78 }
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct PackedOpDescriptor {
85 pub opcode: u8,
87 pub args: Vec<u32>,
89}
90
91impl PackedOpDescriptor {
92 #[must_use]
94 pub fn new(opcode: u8, args: Vec<u32>) -> Self {
95 Self { opcode, args }
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
101pub enum SlotDescriptor {
102 Single {
104 tenant_id: u32,
106 opcode: SlotOpcode,
108 args: Vec<u32>,
110 },
111 Packed {
113 tenant_id: u32,
115 ops: Vec<PackedOpDescriptor>,
117 },
118}
119
120impl SlotDescriptor {
121 #[must_use]
123 pub fn single(tenant_id: u32, opcode: SlotOpcode, args: Vec<u32>) -> Self {
124 Self::Single {
125 tenant_id,
126 opcode,
127 args,
128 }
129 }
130
131 #[must_use]
133 pub fn packed(tenant_id: u32, ops: Vec<PackedOpDescriptor>) -> Self {
134 Self::Packed { tenant_id, ops }
135 }
136
137 pub fn publish_into(&self, ring_bytes: &mut [u8], slot_idx: u32) -> Result<(), PipelineError> {
144 match self {
145 Self::Single {
146 tenant_id,
147 opcode,
148 args,
149 } => {
150 Megakernel::publish_slot(ring_bytes, slot_idx, *tenant_id, opcode.into_wire(), args)
151 }
152 Self::Packed { tenant_id, ops } => {
153 Megakernel::publish_packed_descriptors(ring_bytes, slot_idx, *tenant_id, ops)
154 }
155 }
156 }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct BatchDescriptor {
162 pub start_slot: u32,
164 pub items: Vec<SlotDescriptor>,
166}
167
168impl BatchDescriptor {
169 #[must_use]
171 pub fn new(start_slot: u32, items: Vec<SlotDescriptor>) -> Self {
172 Self { start_slot, items }
173 }
174
175 pub fn publish_into(&self, ring_bytes: &mut [u8]) -> Result<u32, PipelineError> {
181 let item_count = u32::try_from(self.items.len()).map_err(|_| PipelineError::QueueFull {
182 queue: "submission",
183 fix: "batch size exceeds u32::MAX slots",
184 })?;
185 if item_count > 0 {
186 self.start_slot
187 .checked_add(item_count - 1)
188 .ok_or(PipelineError::QueueFull {
189 queue: "submission",
190 fix: "batch start plus item count overflows u32; split the descriptor batch before publishing",
191 })?;
192 }
193 for (slot_offset, item) in (0..item_count).zip(self.items.iter()) {
194 let slot_idx = self
195 .start_slot
196 .checked_add(slot_offset)
197 .ok_or(PipelineError::QueueFull {
198 queue: "submission",
199 fix:
200 "batch slot index overflowed u32; split the descriptor batch before publishing",
201 })?;
202 item.publish_into(ring_bytes, slot_idx)?;
203 }
204 Ok(item_count)
205 }
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
210pub enum WindowClass {
211 Required,
213 Lookahead,
215}
216
217impl WindowClass {
218 #[must_use]
220 pub const fn into_wire(self) -> u32 {
221 match self {
222 Self::Required => 0,
223 Self::Lookahead => 1,
224 }
225 }
226}
227
228#[derive(Debug, Clone, PartialEq, Eq)]
234pub struct WindowDescriptor {
235 pub start_slot: u32,
237 pub tenant_id: u32,
239 pub opcode: SlotOpcode,
241 pub ticket: u32,
243 pub required: Vec<Vec<u32>>,
245 pub lookahead: Vec<Vec<u32>>,
247}
248
249impl WindowDescriptor {
250 #[must_use]
252 pub fn new(
253 start_slot: u32,
254 tenant_id: u32,
255 opcode: SlotOpcode,
256 ticket: u32,
257 required: Vec<Vec<u32>>,
258 lookahead: Vec<Vec<u32>>,
259 ) -> Self {
260 Self {
261 start_slot,
262 tenant_id,
263 opcode,
264 ticket,
265 required,
266 lookahead,
267 }
268 }
269
270 #[must_use]
281 pub fn into_batch(&self) -> BatchDescriptor {
282 self.try_into_batch().unwrap_or_else(|e| {
283 panic!(
284 "WindowDescriptor::into_batch failed: {e}. Fix: call try_into_batch() and propagate the error instead of using the infallible wrapper."
285 )
286 })
287 }
288
289 pub fn try_into_batch(&self) -> Result<BatchDescriptor, PipelineError> {
292 let item_count = self
293 .required
294 .len()
295 .checked_add(self.lookahead.len())
296 .ok_or(PipelineError::QueueFull {
297 queue: "submission",
298 fix:
299 "window item count overflowed usize; split the window before materializing a batch",
300 })?;
301 let mut items = Vec::new();
302 reserve_descriptor_vec(&mut items, item_count, "window batch item")?;
303 for payload in &self.required {
304 let mut args = window_payload_args(self.ticket, WindowClass::Required, payload)?;
305 args.push(self.ticket);
306 args.push(WindowClass::Required.into_wire());
307 args.extend(payload.iter().copied());
308 items.push(SlotDescriptor::single(self.tenant_id, self.opcode, args));
309 }
310 for payload in &self.lookahead {
311 let mut args = window_payload_args(self.ticket, WindowClass::Lookahead, payload)?;
312 args.push(self.ticket);
313 args.push(WindowClass::Lookahead.into_wire());
314 args.extend(payload.iter().copied());
315 items.push(SlotDescriptor::single(self.tenant_id, self.opcode, args));
316 }
317 Ok(BatchDescriptor::new(self.start_slot, items))
318 }
319
320 pub fn publish_into(&self, ring_bytes: &mut [u8]) -> Result<u32, PipelineError> {
322 let consumed = self
323 .required
324 .len()
325 .checked_add(self.lookahead.len())
326 .ok_or(PipelineError::QueueFull {
327 queue: "submission",
328 fix: "window item count overflowed usize; split the window before publishing",
329 })?;
330 let consumed_u32 = u32::try_from(consumed).map_err(|_| PipelineError::QueueFull {
331 queue: "submission",
332 fix: "window size exceeds u32::MAX slots; split the window before publishing",
333 })?;
334 if consumed_u32 == 0 {
335 return Ok(0);
336 }
337 self.start_slot
338 .checked_add(consumed_u32 - 1)
339 .ok_or(PipelineError::QueueFull {
340 queue: "submission",
341 fix: "window start plus item count overflows u32; split the window before publishing",
342 })?;
343
344 let mut slot_offset = 0u32;
345 let mut args = SmallVec::<[u32; ARGS_PER_SLOT_USIZE]>::new();
346 for payload in &self.required {
347 publish_window_payload(
348 ring_bytes,
349 self.start_slot,
350 &mut slot_offset,
351 self.tenant_id,
352 self.opcode,
353 self.ticket,
354 WindowClass::Required,
355 payload,
356 &mut args,
357 )?;
358 }
359 for payload in &self.lookahead {
360 publish_window_payload(
361 ring_bytes,
362 self.start_slot,
363 &mut slot_offset,
364 self.tenant_id,
365 self.opcode,
366 self.ticket,
367 WindowClass::Lookahead,
368 payload,
369 &mut args,
370 )?;
371 }
372 Ok(slot_offset)
373 }
374}
375
376fn window_payload_args(
377 _ticket: u32,
378 _class: WindowClass,
379 payload: &[u32],
380) -> Result<Vec<u32>, PipelineError> {
381 let required_args = payload
382 .len()
383 .checked_add(2)
384 .ok_or(PipelineError::QueueFull {
385 queue: "submission",
386 fix: "window payload argument count overflowed usize; split the payload before materializing a batch",
387 })?;
388 if required_args > ARGS_PER_SLOT_USIZE {
389 return Err(PipelineError::QueueFull {
390 queue: "submission",
391 fix: "too many args for one window payload; ticket plus class plus payload must fit in 12 u32 args",
392 });
393 }
394 let mut args = Vec::new();
395 reserve_descriptor_vec(&mut args, required_args, "window payload arg")?;
396 Ok(args)
397}
398
399fn publish_window_payload(
400 ring_bytes: &mut [u8],
401 start_slot: u32,
402 slot_offset: &mut u32,
403 tenant_id: u32,
404 opcode: SlotOpcode,
405 ticket: u32,
406 class: WindowClass,
407 payload: &[u32],
408 args: &mut SmallVec<[u32; ARGS_PER_SLOT_USIZE]>,
409) -> Result<(), PipelineError> {
410 let slot_idx = start_slot
411 .checked_add(*slot_offset)
412 .ok_or(PipelineError::QueueFull {
413 queue: "submission",
414 fix: "window slot index overflowed u32; split the window before publishing",
415 })?;
416 args.clear();
417 let required_args = payload
418 .len()
419 .checked_add(2)
420 .ok_or(PipelineError::QueueFull {
421 queue: "submission",
422 fix: "window payload argument count overflowed usize; split the payload before publishing",
423 })?;
424 if required_args > ARGS_PER_SLOT_USIZE {
425 return Err(PipelineError::QueueFull {
426 queue: "submission",
427 fix: "too many args for one window payload; ticket plus class plus payload must fit in 12 u32 args",
428 });
429 }
430 args.push(ticket);
431 args.push(class.into_wire());
432 args.extend_from_slice(payload);
433 Megakernel::publish_slot(ring_bytes, slot_idx, tenant_id, opcode.into_wire(), args)?;
434 *slot_offset = slot_offset.checked_add(1).ok_or(PipelineError::QueueFull {
435 queue: "submission",
436 fix: "window slot count overflowed u32; split the window before publishing",
437 })?;
438 Ok(())
439}
440
441#[cfg(test)]
442mod tests;