gpu_async_reactor/core/
line.rs1use core::sync::atomic::{AtomicU64, Ordering};
15
16pub const LINE_WORDS: usize = 8;
18
19#[repr(C, align(64))]
21#[derive(Debug, Default)]
22pub struct CompletionLine {
23 pub words: [AtomicU64; LINE_WORDS],
25}
26
27impl AsRef<CompletionLine> for CompletionLine {
28 fn as_ref(&self) -> &CompletionLine {
29 self
30 }
31}
32
33const _: () = assert!(core::mem::size_of::<CompletionLine>() == 64);
34const _: () = assert!(core::mem::align_of::<CompletionLine>() == 64);
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub struct Slot(u8);
39
40impl Slot {
41 #[must_use]
43 pub const fn new(i: usize) -> Option<Self> {
44 if i < LINE_WORDS {
45 #[expect(clippy::cast_possible_truncation, reason = "i < LINE_WORDS = 8")]
47 Some(Self(i as u8))
48 } else {
49 None
50 }
51 }
52
53 #[must_use]
55 pub const fn index(self) -> usize {
56 self.0 as usize
57 }
58}
59
60#[derive(Clone, Copy, Debug)]
64pub struct MemoryDescriptor<'l> {
65 line: &'l CompletionLine,
66 slot: Slot,
67 target: u64,
68}
69
70impl<'l> MemoryDescriptor<'l> {
71 #[must_use]
73 pub const fn new(line: &'l CompletionLine, slot: Slot, target: u64) -> Self {
74 Self { line, slot, target }
75 }
76
77 #[must_use]
79 pub const fn line(&self) -> &'l CompletionLine {
80 self.line
81 }
82
83 #[must_use]
85 pub const fn slot(&self) -> Slot {
86 self.slot
87 }
88
89 #[must_use]
91 pub const fn target(&self) -> u64 {
92 self.target
93 }
94
95 #[must_use]
98 pub fn is_complete(&self) -> bool {
99 reached(self.line, self.slot, self.target)
100 }
101}
102
103#[must_use]
105pub fn reached(line: &CompletionLine, slot: Slot, target: u64) -> bool {
106 let w = line.words[slot.index()].load(Ordering::Acquire);
107 w.wrapping_sub(target) < (1 << 63)
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn a_word_below_its_target_is_pending_and_at_or_past_it_is_complete() {
116 let line = CompletionLine::default();
117 let s = Slot::new(3).expect("slot 3");
118 let d = MemoryDescriptor::new(&line, s, 5);
119 assert!(!d.is_complete(), "0 < 5");
120 line.words[3].store(4, Ordering::Release);
121 assert!(!d.is_complete(), "4 < 5");
122 line.words[3].store(5, Ordering::Release);
123 assert!(d.is_complete(), "5 == 5");
124 line.words[3].store(6, Ordering::Release);
125 assert!(d.is_complete(), "a later store still completes it");
126 line.words[2].store(99, Ordering::Release);
127 assert!(MemoryDescriptor::new(&line, Slot::new(2).expect("2"), 99).is_complete());
128 }
129
130 #[test]
131 fn reached_is_wrapping_order() {
132 let line = CompletionLine::default();
133 let s = Slot::new(0).expect("slot 0");
134 line.words[0].store(1, Ordering::Release);
135 assert!(reached(&line, s, u64::MAX), "1 follows u64::MAX after a wrap");
136 line.words[0].store(u64::MAX, Ordering::Release);
137 assert!(!reached(&line, s, 1), "u64::MAX precedes 1");
138 }
139
140 #[test]
141 fn a_slot_past_the_line_is_refused() {
142 assert!(Slot::new(LINE_WORDS).is_none());
143 assert_eq!(Slot::new(LINE_WORDS - 1).map(Slot::index), Some(LINE_WORDS - 1));
144 }
145}