1use crate::handle::PackedHandle;
12use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
13use std::time::{Duration, Instant};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[repr(u32)]
18pub enum SignalCondition {
19 Eq = 0,
20 Ne = 1,
21 Lt = 2,
22 Gte = 3,
23}
24
25impl SignalCondition {
26 pub fn from_u32(value: u32) -> Option<Self> {
27 match value {
28 0 => Some(Self::Eq),
29 1 => Some(Self::Ne),
30 2 => Some(Self::Lt),
31 3 => Some(Self::Gte),
32 _ => None,
33 }
34 }
35
36 pub fn satisfied(self, observed: i64, compare: i64) -> bool {
37 match self {
38 Self::Eq => observed == compare,
39 Self::Ne => observed != compare,
40 Self::Lt => observed < compare,
41 Self::Gte => observed >= compare,
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum SignalWaitOutcome {
48 Satisfied(i64),
49 TimedOut(i64),
50 Cancelled(i64),
51}
52
53#[derive(Debug)]
54pub struct SoftGpuSignal {
55 pub handle: PackedHandle,
56 value: AtomicI64,
57 cancelled: AtomicBool,
58 pub is_doorbell: bool,
60 pub queue_id: Option<u64>,
61}
62
63impl SoftGpuSignal {
64 pub fn new(handle: PackedHandle, initial: i64) -> Self {
65 Self {
66 handle,
67 value: AtomicI64::new(initial),
68 cancelled: AtomicBool::new(false),
69 is_doorbell: false,
70 queue_id: None,
71 }
72 }
73
74 pub fn new_doorbell(handle: PackedHandle, initial: i64, queue_id: u64) -> Self {
75 Self {
76 handle,
77 value: AtomicI64::new(initial),
78 cancelled: AtomicBool::new(false),
79 is_doorbell: true,
80 queue_id: Some(queue_id),
81 }
82 }
83
84 pub fn load(&self) -> i64 {
85 self.value.load(Ordering::SeqCst)
86 }
87
88 pub fn store(&self, value: i64) {
89 self.value.store(value, Ordering::SeqCst);
90 }
91
92 pub fn cancel(&self) {
93 self.cancelled.store(true, Ordering::SeqCst);
94 }
95
96 pub fn is_cancelled(&self) -> bool {
97 self.cancelled.load(Ordering::SeqCst)
98 }
99
100 pub fn wait(
101 &self,
102 condition: SignalCondition,
103 compare: i64,
104 timeout_hint_ns: u64,
105 _wait_state_hint: u32,
106 ) -> SignalWaitOutcome {
107 let deadline = if timeout_hint_ns == u64::MAX {
108 None
109 } else {
110 Some(Instant::now() + Duration::from_nanos(timeout_hint_ns))
111 };
112 loop {
113 if self.is_cancelled() {
114 return SignalWaitOutcome::Cancelled(self.load());
115 }
116 let observed = self.load();
117 if condition.satisfied(observed, compare) {
118 return SignalWaitOutcome::Satisfied(observed);
119 }
120 if let Some(deadline) = deadline {
121 if Instant::now() >= deadline {
122 return SignalWaitOutcome::TimedOut(observed);
123 }
124 }
125 std::thread::yield_now();
126 }
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use crate::handle::HandleKind;
134 use std::thread;
135 use std::time::Duration;
136
137 #[test]
138 fn wait_eq_returns_when_satisfied() {
139 let sig = SoftGpuSignal::new(PackedHandle::pack(HandleKind::Signal, 1, 0), 0);
140 sig.store(7);
141 match sig.wait(SignalCondition::Eq, 7, 1_000_000_000, 1) {
142 SignalWaitOutcome::Satisfied(v) => assert_eq!(v, 7),
143 other => panic!("unexpected {other:?}"),
144 }
145 }
146
147 #[test]
148 fn wait_times_out() {
149 let sig = SoftGpuSignal::new(PackedHandle::pack(HandleKind::Signal, 1, 0), 0);
150 match sig.wait(SignalCondition::Eq, 1, 1_000_000, 1) {
151 SignalWaitOutcome::TimedOut(v) => assert_eq!(v, 0),
152 other => panic!("unexpected {other:?}"),
153 }
154 }
155
156 #[test]
157 fn wait_cancelled_during_spin() {
158 let sig = SoftGpuSignal::new(PackedHandle::pack(HandleKind::Signal, 1, 0), 0);
159 let handle = sig.handle;
160 thread::scope(|s| {
161 s.spawn(|| {
162 thread::sleep(Duration::from_millis(5));
163 let _ = handle;
165 });
166 let sig_ref = &sig;
169 s.spawn(|| {
170 thread::sleep(Duration::from_millis(2));
171 sig_ref.cancel();
172 });
173 match sig_ref.wait(SignalCondition::Eq, 99, u64::MAX, 1) {
174 SignalWaitOutcome::Cancelled(_) => {}
175 other => panic!("unexpected {other:?}"),
176 }
177 });
178 }
179}