1use std::sync::Arc;
8
9use vyre_foundation::ir::{BufferDecl, DataType, Expr, Node, Program};
10
11use super::handlers::{claimed_slot_bindings, claimed_slot_body, load_miss_body, OpcodeHandler};
12use super::io::{
13 io_word, IO_DESTINATION_CAPABILITY_TABLE, IO_QUEUE_DMA_TAG, IO_SLOT_COUNT, IO_SLOT_WORDS,
14 IO_SOURCE_CAPABILITY_TABLE,
15};
16use super::ir_util::atomic_load_relaxed;
17use super::protocol::*;
18use super::workspace_adapter::ResidentWorkspaceAdapter;
19mod cache;
20mod jit;
21mod priority;
22pub use jit::{build_program_jit, build_program_jit_slots, persistent_body_jit};
23pub use priority::{
24 build_program_priority, build_program_priority_slots, persistent_body_priority,
25 persistent_body_priority_slots,
26};
27
28#[must_use]
30pub fn build_program() -> Program {
31 build_program_sharded(256, &[])
32}
33
34#[must_use]
42pub fn build_program_sharded(workgroup_size_x: u32, opcodes: &[OpcodeHandler]) -> Program {
43 build_program_sharded_slots(workgroup_size_x, workgroup_size_x.max(1), opcodes)
44}
45
46#[must_use]
52pub fn build_program_sharded_slots(
53 workgroup_size_x: u32,
54 slot_count: u32,
55 opcodes: &[OpcodeHandler],
56) -> Program {
57 build_program_sharded_slots_with_io(workgroup_size_x, slot_count, opcodes, false)
58}
59
60#[must_use]
66pub fn build_program_sharded_slots_shared(
67 workgroup_size_x: u32,
68 slot_count: u32,
69 opcodes: &[OpcodeHandler],
70) -> Arc<Program> {
71 if opcodes.is_empty() {
72 return cache::cached_empty_sharded_program_shared(workgroup_size_x, slot_count, false);
73 }
74 Arc::new(build_program_sharded_slots(
75 workgroup_size_x,
76 slot_count,
77 opcodes,
78 ))
79}
80
81#[must_use]
83pub fn build_program_sharded_with_workspace_adapter(
84 workgroup_size_x: u32,
85 slot_count: u32,
86 opcodes: &[OpcodeHandler],
87 adapter: &impl ResidentWorkspaceAdapter,
88) -> Program {
89 wrap_persistent_megakernel_program_with_buffers(
90 default_buffers_with_workspace_adapter(slot_count, adapter),
91 workgroup_size_x,
92 persistent_body_with_workspace_adapter(workgroup_size_x, opcodes, adapter),
93 )
94}
95
96#[must_use]
103pub fn build_program_sharded_once_slots(
104 workgroup_size_x: u32,
105 slot_count: u32,
106 opcodes: &[OpcodeHandler],
107) -> Program {
108 if opcodes.is_empty() {
109 return cache::cached_empty_sharded_once_program(workgroup_size_x, slot_count);
110 }
111 wrap_megakernel_program(
112 workgroup_size_x,
113 slot_count,
114 finite_body_with_io(workgroup_size_x, opcodes, false),
115 )
116}
117
118#[must_use]
121pub fn build_program_sharded_once_slots_shared(
122 workgroup_size_x: u32,
123 slot_count: u32,
124 opcodes: &[OpcodeHandler],
125) -> Arc<Program> {
126 if opcodes.is_empty() {
127 return cache::cached_empty_sharded_once_program_shared(workgroup_size_x, slot_count);
128 }
129 Arc::new(build_program_sharded_once_slots(
130 workgroup_size_x,
131 slot_count,
132 opcodes,
133 ))
134}
135
136#[must_use]
144pub fn build_program_sharded_once_slots_control_report_shared(
145 workgroup_size_x: u32,
146 slot_count: u32,
147 opcodes: &[OpcodeHandler],
148) -> Arc<Program> {
149 if opcodes.is_empty() {
150 return cache::cached_empty_sharded_once_control_report_program_shared(
151 workgroup_size_x,
152 slot_count,
153 );
154 }
155 let mut buffers = default_buffers(slot_count);
156 for buffer in buffers.iter_mut().skip(1) {
157 buffer.output_byte_range = Some(0..0);
158 }
159 Arc::new(prepare_megakernel_program(Program::wrapped(
160 buffers,
161 [workgroup_size_x, 1, 1],
162 finite_body_with_io(workgroup_size_x, opcodes, false),
163 )))
164}
165
166#[must_use]
172pub fn build_program_sharded_no_io(workgroup_size_x: u32, opcodes: &[OpcodeHandler]) -> Program {
173 build_program_sharded_slots(workgroup_size_x, workgroup_size_x.max(1), opcodes)
174}
175
176#[must_use]
181pub fn build_program_sharded_with_io_polling(
182 workgroup_size_x: u32,
183 opcodes: &[OpcodeHandler],
184) -> Program {
185 build_program_sharded_slots_with_io(workgroup_size_x, workgroup_size_x.max(1), opcodes, true)
186}
187
188#[must_use]
196#[cfg(test)]
197pub fn build_program_with_self_loading_miss_handler(
198 workgroup_size_x: u32,
199 slot_count: u32,
200 opcodes: &[OpcodeHandler],
201) -> Program {
202 match try_build_program_with_self_loading_miss_handler(workgroup_size_x, slot_count, opcodes) {
203 Ok(program) => program,
204 Err(error) => panic!("{error}"),
205 }
206}
207
208pub fn try_build_program_with_self_loading_miss_handler(
210 workgroup_size_x: u32,
211 slot_count: u32,
212 opcodes: &[OpcodeHandler],
213) -> Result<Program, String> {
214 let mut extended = Vec::new();
215 let extended_len = opcodes.len().checked_add(1).ok_or_else(|| {
216 "megakernel self-loading opcode extension count overflowed usize. Fix: split opcode handler sets before building the megakernel."
217 .to_string()
218 })?;
219 vyre_foundation::allocation::try_reserve_vec_to_capacity(&mut extended, extended_len).map_err(|error| {
220 format!(
221 "megakernel self-loading opcode extension allocation failed: {error}. Fix: split opcode handler sets before building the megakernel."
222 )
223 })?;
224 extended.extend_from_slice(opcodes);
225 extended.push(OpcodeHandler {
226 opcode: super::protocol::opcode::LOAD_MISS,
227 body: load_miss_body(),
228 });
229 Ok(wrap_persistent_megakernel_program(
230 workgroup_size_x,
231 slot_count,
232 persistent_body_with_io(workgroup_size_x, &extended, false),
233 ))
234}
235
236fn build_program_sharded_slots_with_io(
237 workgroup_size_x: u32,
238 slot_count: u32,
239 opcodes: &[OpcodeHandler],
240 include_io_polling: bool,
241) -> Program {
242 if opcodes.is_empty() {
243 return cache::cached_empty_sharded_program(
244 workgroup_size_x,
245 slot_count,
246 include_io_polling,
247 );
248 }
249 wrap_persistent_megakernel_program(
250 workgroup_size_x,
251 slot_count,
252 persistent_body_with_io(workgroup_size_x, opcodes, include_io_polling),
253 )
254}
255
256fn wrap_persistent_megakernel_program(
257 workgroup_size_x: u32,
258 slot_count: u32,
259 body: Vec<Node>,
260) -> Program {
261 wrap_megakernel_program(workgroup_size_x, slot_count, vec![Node::forever(body)])
262}
263
264fn wrap_persistent_megakernel_program_with_buffers(
265 buffers: Vec<BufferDecl>,
266 workgroup_size_x: u32,
267 body: Vec<Node>,
268) -> Program {
269 prepare_megakernel_program(Program::wrapped(
270 buffers,
271 [workgroup_size_x, 1, 1],
272 vec![Node::forever(body)],
273 ))
274}
275
276fn wrap_megakernel_program(workgroup_size_x: u32, slot_count: u32, body: Vec<Node>) -> Program {
277 prepare_megakernel_program(Program::wrapped(
278 default_buffers(slot_count),
279 [workgroup_size_x, 1, 1],
280 body,
281 ))
282}
283
284fn prepare_megakernel_program(program: Program) -> Program {
285 super::planner::elide_value_flow_barriers(program).0
288}
289
290fn default_buffers(slot_count: u32) -> Vec<BufferDecl> {
304 let ring_slots = slot_count.max(1);
305 let control = BufferDecl::read_write("control", 0, DataType::U32).with_count(CONTROL_MIN_WORDS);
306 let ring_buffer = BufferDecl::read_write("ring_buffer", 1, DataType::U32)
307 .with_count(ring_slots.saturating_mul(SLOT_WORDS));
308 let debug_log =
309 BufferDecl::read_write("debug_log", 2, DataType::U32).with_count(debug::BUFFER_WORDS);
310 let io_queue = BufferDecl::read_write("io_queue", 3, DataType::U32).with_count(64 * 8);
311 vec![control, ring_buffer, debug_log, io_queue]
312}
313
314fn default_buffers_with_workspace_adapter(
315 slot_count: u32,
316 adapter: &impl ResidentWorkspaceAdapter,
317) -> Vec<BufferDecl> {
318 let mut buffers = default_buffers(slot_count);
319 buffers.push(adapter.buffer_decl());
320 buffers
321}
322
323#[must_use]
326pub fn persistent_body(workgroup_size_x: u32, opcodes: &[OpcodeHandler]) -> Vec<Node> {
327 persistent_body_with_io(workgroup_size_x, opcodes, false)
328}
329
330pub fn try_persistent_body(
332 workgroup_size_x: u32,
333 opcodes: &[OpcodeHandler],
334) -> Result<Vec<Node>, String> {
335 try_persistent_body_with_io(workgroup_size_x, opcodes, false)
336}
337
338fn persistent_body_with_io(
339 workgroup_size_x: u32,
340 opcodes: &[OpcodeHandler],
341 include_io_polling: bool,
342) -> Vec<Node> {
343 let mut body = persistent_lane_prologue(workgroup_size_x);
344 let additional_nodes = if include_io_polling { 3 } else { 2 };
345 if let Some(body_capacity) = body.len().checked_add(additional_nodes) {
346 let _ = vyre_foundation::allocation::try_reserve_vec_to_capacity(&mut body, body_capacity);
347 }
348 body.push(direct_slot_base_binding());
349 body.push(Node::Block(execute_slot_body(opcodes)));
350 if include_io_polling {
351 body.push(Node::Block(process_io_requests()));
352 }
353 body
354}
355
356fn finite_body_with_io(
357 workgroup_size_x: u32,
358 opcodes: &[OpcodeHandler],
359 include_io_polling: bool,
360) -> Vec<Node> {
361 let mut body = vec![Node::let_bind("lane_id", lane_id_expr(workgroup_size_x))];
362 let additional_nodes = if include_io_polling { 3 } else { 2 };
363 if let Some(body_capacity) = body.len().checked_add(additional_nodes) {
364 let _ = vyre_foundation::allocation::try_reserve_vec_to_capacity(&mut body, body_capacity);
365 }
366 body.push(direct_slot_base_binding());
367 body.push(Node::Block(execute_slot_body(opcodes)));
368 if include_io_polling {
369 body.push(Node::Block(process_io_requests()));
370 }
371 body
372}
373
374fn try_persistent_body_with_io(
375 workgroup_size_x: u32,
376 opcodes: &[OpcodeHandler],
377 include_io_polling: bool,
378) -> Result<Vec<Node>, String> {
379 let mut body = persistent_lane_prologue(workgroup_size_x);
380 let additional_nodes = if include_io_polling { 3 } else { 2 };
381 let body_capacity = body.len().checked_add(additional_nodes).ok_or_else(|| {
382 "megakernel persistent body node reservation overflowed usize. Fix: reduce fused IO/body staging before building the megakernel."
383 .to_string()
384 })?;
385 vyre_foundation::allocation::try_reserve_vec_to_capacity(&mut body, body_capacity).map_err(|error| {
386 format!(
387 "megakernel persistent body node reservation failed: {error}. Fix: reduce fused IO/body staging before building the megakernel."
388 )
389 })?;
390 body.push(direct_slot_base_binding());
391 body.push(Node::Block(execute_slot_body(opcodes)));
392 if include_io_polling {
393 body.push(Node::Block(process_io_requests()));
394 }
395 Ok(body)
396}
397
398fn persistent_lane_prologue(workgroup_size_x: u32) -> Vec<Node> {
399 vec![
400 Node::let_bind(
401 "shutdown_flag",
402 atomic_load_relaxed("control", Expr::u32(control::SHUTDOWN)),
403 ),
404 Node::if_then(
405 Expr::ne(Expr::var("shutdown_flag"), Expr::u32(0)),
406 vec![Node::Return],
407 ),
408 Node::let_bind("lane_id", lane_id_expr(workgroup_size_x)),
409 ]
410}
411
412fn direct_slot_base_binding() -> Node {
413 Node::let_bind(
414 "slot_base",
415 Expr::mul(Expr::var("lane_id"), Expr::u32(SLOT_WORDS)),
416 )
417}
418
419fn slot_tenant_id_load() -> Expr {
420 Expr::load(
421 "ring_buffer",
422 Expr::add(Expr::var("slot_base"), Expr::u32(TENANT_WORD)),
423 )
424}
425
426fn tenant_authorized_body(tenant_id: Expr, authorized_body: Vec<Node>) -> Vec<Node> {
427 vec![
428 Node::let_bind("tenant_id", tenant_id),
429 Node::let_bind(
430 "tenant_base",
431 atomic_load_relaxed("control", Expr::u32(control::TENANT_BASE)),
432 ),
433 Node::let_bind(
434 "tenant_mask",
435 atomic_load_relaxed(
436 "control",
437 Expr::add(Expr::var("tenant_base"), Expr::var("tenant_id")),
438 ),
439 ),
440 Node::if_then(
441 Expr::ne(Expr::var("tenant_mask"), Expr::u32(0)),
442 authorized_body,
443 ),
444 ]
445}
446
447fn lane_id_expr(workgroup_size_x: u32) -> Expr {
448 Expr::add(
449 Expr::mul(Expr::workgroup_x(), Expr::u32(workgroup_size_x)),
450 Expr::local_x(),
451 )
452}
453
454fn persistent_body_with_workspace_adapter(
455 workgroup_size_x: u32,
456 opcodes: &[OpcodeHandler],
457 adapter: &impl ResidentWorkspaceAdapter,
458) -> Vec<Node> {
459 let mut body = adapter.bootstrap_nodes();
460 body.extend(adapter.guard_nodes());
461 body.extend(adapter.dispatch_nodes());
462 body.extend(persistent_body_with_io(workgroup_size_x, opcodes, false));
463 body
464}
465
466fn process_io_requests() -> Vec<Node> {
467 let nodes = vec![Node::loop_for(
468 "io_idx",
469 Expr::u32(0),
470 Expr::u32(IO_SLOT_COUNT),
471 vec![
472 Node::let_bind(
473 "io_base",
474 Expr::mul(Expr::var("io_idx"), Expr::u32(IO_SLOT_WORDS)),
475 ),
476 Node::let_bind(
477 "io_status_idx",
478 Expr::add(Expr::var("io_base"), Expr::u32(io_word::STATUS)),
479 ),
480 Node::let_bind(
482 "prev_io_status",
483 Expr::atomic_compare_exchange(
484 "io_queue",
485 Expr::var("io_status_idx"),
486 Expr::u32(slot::PUBLISHED),
487 Expr::u32(slot::CLAIMED),
488 ),
489 ),
490 Node::if_then(
491 Expr::eq(Expr::var("prev_io_status"), Expr::u32(slot::PUBLISHED)),
492 vec![
493 Node::let_bind(
494 "io_src_handle",
495 Expr::load(
496 "io_queue",
497 Expr::add(Expr::var("io_base"), Expr::u32(io_word::SRC_HANDLE)),
498 ),
499 ),
500 Node::let_bind(
501 "io_dst_handle",
502 Expr::load(
503 "io_queue",
504 Expr::add(Expr::var("io_base"), Expr::u32(io_word::DST_HANDLE)),
505 ),
506 ),
507 Node::AsyncLoad {
508 source: IO_SOURCE_CAPABILITY_TABLE.into(),
509 destination: IO_DESTINATION_CAPABILITY_TABLE.into(),
510 offset: Box::new(Expr::load(
511 "io_queue",
512 Expr::add(Expr::var("io_base"), Expr::u32(io_word::OFFSET_LO)),
513 )),
514 size: Box::new(Expr::load(
515 "io_queue",
516 Expr::add(Expr::var("io_base"), Expr::u32(io_word::BYTE_COUNT)),
517 )),
518 tag: IO_QUEUE_DMA_TAG.into(),
519 },
520 Node::store(
522 "io_queue",
523 Expr::var("io_status_idx"),
524 Expr::u32(slot::DONE),
525 ),
526 ],
527 ),
528 ],
529 )];
530
531 nodes
532}
533
534fn execute_slot_body(opcodes: &[OpcodeHandler]) -> Vec<Node> {
535 vec![
536 Node::let_bind(
537 "status_index",
538 Expr::add(Expr::var("slot_base"), Expr::u32(STATUS_WORD)),
539 ),
540 Node::let_bind(
541 "observed_status",
542 atomic_load_relaxed("ring_buffer", Expr::var("status_index")),
543 ),
544 Node::if_then(
545 Expr::eq(Expr::var("observed_status"), Expr::u32(slot::PUBLISHED)),
546 tenant_authorized_claim_body(slot_tenant_id_load(), claimed_slot_body(opcodes)),
547 ),
548 ]
549}
550
551fn tenant_authorized_claim_body(tenant_id: Expr, claimed_body: Vec<Node>) -> Vec<Node> {
552 tenant_authorized_body(
553 tenant_id,
554 vec![
555 Node::let_bind(
559 "prev_status",
560 Expr::atomic_compare_exchange(
561 "ring_buffer",
562 Expr::var("status_index"),
563 Expr::u32(slot::PUBLISHED),
564 Expr::u32(slot::CLAIMED),
565 ),
566 ),
567 Node::if_then(
568 Expr::eq(Expr::var("prev_status"), Expr::u32(slot::PUBLISHED)),
569 claimed_body,
570 ),
571 ],
572 )
573}
574
575fn execute_already_claimed_slot_body(tenant_id: Expr, claimed_body: Vec<Node>) -> Vec<Node> {
576 let mut body = vec![Node::let_bind(
577 "status_index",
578 Expr::add(Expr::var("slot_base"), Expr::u32(STATUS_WORD)),
579 )];
580 body.extend(tenant_authorized_body(tenant_id, claimed_body));
581 body
582}
583
584#[cfg(test)]
585mod tests;