Skip to main content

oxicuda_runtime/
event.rs

1//! CUDA event management.
2//!
3//! Implements the CUDA Runtime event API:
4//! - `cudaEventCreate` / `cudaEventCreateWithFlags`
5//! - `cudaEventDestroy`
6//! - `cudaEventRecord`
7//! - `cudaEventSynchronize`
8//! - `cudaEventQuery`
9//! - `cudaEventElapsedTime`
10
11use oxicuda_driver::ffi::CUevent;
12use oxicuda_driver::loader::try_driver;
13
14use crate::error::{CudaRtError, CudaRtResult};
15use crate::stream::CudaStream;
16
17// ─── EventFlags ──────────────────────────────────────────────────────────────
18
19/// Flags for event creation.
20///
21/// Mirrors `cudaEventFlags`.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
23pub struct EventFlags(pub u32);
24
25impl EventFlags {
26    /// Default flags.
27    pub const DEFAULT: Self = Self(0x0);
28    /// Event will not record timing data (lower overhead).
29    pub const DISABLE_TIMING: Self = Self(0x2);
30    /// Event can be used for interprocess synchronisation.
31    pub const INTERPROCESS: Self = Self(0x4);
32}
33
34// ─── CudaEvent ───────────────────────────────────────────────────────────────
35
36/// A CUDA event handle.
37///
38/// Wraps the raw `CUevent` from the driver API.  The event is **not**
39/// automatically destroyed on drop — call [`event_destroy`] explicitly.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub struct CudaEvent(CUevent);
42
43impl CudaEvent {
44    /// Construct from a raw driver event handle.
45    ///
46    /// # Safety
47    ///
48    /// The handle must be valid and not be used after the owning context is
49    /// destroyed.
50    #[must_use]
51    pub const unsafe fn from_raw(raw: CUevent) -> Self {
52        Self(raw)
53    }
54
55    /// Returns the underlying raw `CUevent`.
56    #[must_use]
57    pub fn raw(self) -> CUevent {
58        self.0
59    }
60
61    /// Returns `true` if the event handle is null (invalid).
62    #[must_use]
63    pub fn is_null(self) -> bool {
64        self.0.is_null()
65    }
66}
67
68// ─── Event creation / destruction ────────────────────────────────────────────
69
70/// Create a CUDA event with default flags.
71///
72/// Mirrors `cudaEventCreate`.
73///
74/// # Errors
75///
76/// Propagates driver errors.
77pub fn event_create() -> CudaRtResult<CudaEvent> {
78    event_create_with_flags(EventFlags::DEFAULT)
79}
80
81/// Create a CUDA event with the given flags.
82///
83/// Mirrors `cudaEventCreateWithFlags`.
84///
85/// # Errors
86///
87/// Propagates driver errors.
88pub fn event_create_with_flags(flags: EventFlags) -> CudaRtResult<CudaEvent> {
89    let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
90    let mut event = CUevent::default();
91    // SAFETY: FFI; event pointer is valid.
92    let rc = unsafe { (api.cu_event_create)(&raw mut event, flags.0) };
93    if rc != 0 {
94        return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::InvalidResourceHandle));
95    }
96    Ok(CudaEvent(event))
97}
98
99/// Destroy a CUDA event.
100///
101/// Mirrors `cudaEventDestroy`.
102///
103/// # Errors
104///
105/// Propagates driver errors.
106pub fn event_destroy(event: CudaEvent) -> CudaRtResult<()> {
107    if event.is_null() {
108        return Ok(());
109    }
110    let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
111    // SAFETY: FFI; event is valid.
112    let rc = unsafe { (api.cu_event_destroy_v2)(event.raw()) };
113    if rc != 0 {
114        return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::InvalidResourceHandle));
115    }
116    Ok(())
117}
118
119// ─── Event recording and synchronisation ─────────────────────────────────────
120
121/// Record `event` at the current position in `stream`.
122///
123/// Mirrors `cudaEventRecord`.
124///
125/// # Errors
126///
127/// Propagates driver errors.
128pub fn event_record(event: CudaEvent, stream: CudaStream) -> CudaRtResult<()> {
129    let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
130    // SAFETY: FFI; event and stream are valid.
131    let rc = unsafe { (api.cu_event_record)(event.raw(), stream.raw()) };
132    if rc != 0 {
133        return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::InvalidResourceHandle));
134    }
135    Ok(())
136}
137
138/// Record `event` at the current position in `stream` with flags.
139///
140/// Mirrors `cudaEventRecordWithFlags`.
141///
142/// # Errors
143///
144/// Propagates driver errors.
145pub fn event_record_with_flags(
146    event: CudaEvent,
147    stream: CudaStream,
148    flags: u32,
149) -> CudaRtResult<()> {
150    let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
151    // SAFETY: FFI. cu_event_record_with_flags is optional (CUDA 11.1+).
152    let f = api
153        .cu_event_record_with_flags
154        .ok_or(CudaRtError::NotSupported)?;
155    let rc = unsafe { f(event.raw(), stream.raw(), flags) };
156    if rc != 0 {
157        return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::InvalidResourceHandle));
158    }
159    Ok(())
160}
161
162/// Block the calling thread until `event` is recorded.
163///
164/// Mirrors `cudaEventSynchronize`.
165///
166/// # Errors
167///
168/// Propagates driver errors.
169pub fn event_synchronize(event: CudaEvent) -> CudaRtResult<()> {
170    let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
171    // SAFETY: FFI.
172    let rc = unsafe { (api.cu_event_synchronize)(event.raw()) };
173    if rc != 0 {
174        return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::NotReady));
175    }
176    Ok(())
177}
178
179/// Query whether `event` has been recorded.
180///
181/// Mirrors `cudaEventQuery`.
182///
183/// Returns `Ok(true)` if complete, `Ok(false)` if not yet reached.
184///
185/// # Errors
186///
187/// Propagates driver errors other than `NotReady`.
188pub fn event_query(event: CudaEvent) -> CudaRtResult<bool> {
189    let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
190    // SAFETY: FFI.
191    let rc = unsafe { (api.cu_event_query)(event.raw()) };
192    match rc {
193        0 => Ok(true),
194        600 => Ok(false), // CUDA_ERROR_NOT_READY
195        other => Err(CudaRtError::from_code(other).unwrap_or(CudaRtError::Unknown)),
196    }
197}
198
199/// Compute the elapsed time between two events in milliseconds.
200///
201/// Mirrors `cudaEventElapsedTime`.
202///
203/// Both events must have been recorded.  If either was created with
204/// `EventFlags::DISABLE_TIMING`, this returns [`CudaRtError::InvalidResourceHandle`].
205///
206/// # Errors
207///
208/// Propagates driver errors.
209pub fn event_elapsed_time(start: CudaEvent, end: CudaEvent) -> CudaRtResult<f32> {
210    let api = try_driver().map_err(|_| CudaRtError::DriverNotAvailable)?;
211    let mut ms: f32 = 0.0;
212    // SAFETY: FFI; ms is a valid stack-allocated f32.
213    let rc = unsafe { (api.cu_event_elapsed_time)(&raw mut ms, start.raw(), end.raw()) };
214    if rc != 0 {
215        return Err(CudaRtError::from_code(rc).unwrap_or(CudaRtError::InvalidResourceHandle));
216    }
217    Ok(ms)
218}
219
220// ─── Tests ───────────────────────────────────────────────────────────────────
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn event_flags_values() {
228        assert_eq!(EventFlags::DEFAULT.0, 0x0);
229        assert_eq!(EventFlags::DISABLE_TIMING.0, 0x2);
230        assert_eq!(EventFlags::INTERPROCESS.0, 0x4);
231    }
232
233    #[test]
234    fn event_create_without_gpu_returns_error() {
235        // Without a GPU this will fail, but must not panic.
236        let _ = event_create();
237    }
238
239    #[test]
240    fn event_destroy_null_is_noop() {
241        // SAFETY: null is a well-defined state; no FFI call made.
242        let ev = unsafe { CudaEvent::from_raw(CUevent::default()) };
243        let _ = event_destroy(ev); // must not panic
244    }
245}