1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
//! CUDA events — lightweight synchronization objects you can record on
//! a stream and later wait on, or use to measure elapsed device time.
use std::sync::Arc;
use baracuda_cuda_sys::types::CUevent_flags;
use baracuda_cuda_sys::{CUevent, driver};
use crate::context::Context;
use crate::error::{Result, check};
use crate::stream::Stream;
/// A CUDA event.
#[derive(Clone)]
pub struct Event {
inner: Arc<EventInner>,
}
struct EventInner {
handle: CUevent,
context: Context,
/// When `false`, this is a non-owning (borrowed) wrapper produced by
/// [`Event::borrow_raw`]: `Drop` will not call `cuEventDestroy`.
owned: bool,
}
// SAFETY: CUevent is documented safe for multi-thread use.
unsafe impl Send for EventInner {}
unsafe impl Sync for EventInner {}
impl core::fmt::Debug for EventInner {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Event")
.field("handle", &self.handle)
.finish_non_exhaustive()
}
}
impl core::fmt::Debug for Event {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.inner.fmt(f)
}
}
impl Event {
/// Create a new event with default flags (timing enabled).
pub fn new(context: &Context) -> Result<Self> {
Self::with_flags(context, CUevent_flags::DEFAULT)
}
/// Create an event optimized for synchronization (no timing).
pub fn no_timing(context: &Context) -> Result<Self> {
Self::with_flags(context, CUevent_flags::DISABLE_TIMING)
}
/// Create an event with raw flags (see [`CUevent_flags`]).
pub fn with_flags(context: &Context, flags: u32) -> Result<Self> {
context.set_current()?;
let d = driver()?;
let cu = d.cu_event_create()?;
let mut event: CUevent = core::ptr::null_mut();
check(unsafe { cu(&mut event, flags) })?;
Ok(Self {
inner: Arc::new(EventInner {
handle: event,
context: context.clone(),
owned: true,
}),
})
}
/// Adopt a raw `CUevent`, **transferring ownership to baracuda**: the
/// returned [`Event`] (and every clone) shares it, and `cuEventDestroy`
/// runs when the last clone drops. `context` is the [`Context`] the event
/// belongs to; baracuda keeps a clone of that wrapper. Use this when another
/// library hands baracuda an event it created and no longer wants to manage.
///
/// To synchronize against an event another library still owns, use
/// [`Event::borrow_raw`] instead — baracuda won't destroy it.
///
/// # Safety
///
/// - `handle` must be a valid, live `CUevent` created on `context` that
/// baracuda may take sole ownership of: it must not be destroyed
/// elsewhere, nor already be wrapped by another owning baracuda handle
/// (either risks a double `cuEventDestroy`).
/// - The event's underlying CUDA context must outlive the returned `Event`
/// and all its clones. Passing an *owning* [`Context`] guarantees that; if
/// `context` is itself a [borrowed][`Context::borrow_raw`] (non-owning)
/// wrapper, the held clone does **not** extend the real context's
/// lifetime — the caller must keep it alive by other means.
pub unsafe fn from_raw(handle: CUevent, context: &Context) -> Self {
Self {
inner: Arc::new(EventInner {
handle,
context: context.clone(),
owned: true,
}),
}
}
/// Wrap a raw `CUevent` that **baracuda does not own**: `Drop` will not
/// call `cuEventDestroy`. Use this to let baracuda streams wait on, or
/// query, an event created and owned by another library (cudarc, cust, a
/// framework) that outlives this wrapper — the standard cross-library
/// synchronization path. `context` is the [`Context`] the event belongs to
/// (wrap a foreign one with [`Context::borrow_raw`] if needed).
///
/// # Safety
///
/// The caller guarantees `handle` is a valid `CUevent` on `context` that
/// stays live for the entire lifetime of the returned `Event` and all of
/// its clones. baracuda will not destroy it; the owning library keeps that
/// responsibility.
pub unsafe fn borrow_raw(handle: CUevent, context: &Context) -> Self {
Self {
inner: Arc::new(EventInner {
handle,
context: context.clone(),
owned: false,
}),
}
}
/// Record this event on the given stream. The event "happens" when all
/// prior work on `stream` has completed.
pub fn record(&self, stream: &Stream) -> Result<()> {
let d = driver()?;
let cu = d.cu_event_record()?;
check(unsafe { cu(self.inner.handle, stream.as_raw()) })
}
/// As [`record`](Self::record) but with a raw CUDA event-record flags
/// bitmask. See `CU_EVENT_RECORD_*` in NVIDIA's headers.
pub fn record_with_flags(&self, stream: &Stream, flags: u32) -> Result<()> {
let d = driver()?;
let cu = d.cu_event_record_with_flags()?;
check(unsafe { cu(self.inner.handle, stream.as_raw(), flags) })
}
/// Block the calling host thread until this event has completed.
pub fn synchronize(&self) -> Result<()> {
let d = driver()?;
let cu = d.cu_event_synchronize()?;
check(unsafe { cu(self.inner.handle) })
}
/// `Ok(true)` if the event has completed.
pub fn is_complete(&self) -> Result<bool> {
use baracuda_cuda_sys::CUresult;
let d = driver()?;
let cu = d.cu_event_query()?;
match unsafe { cu(self.inner.handle) } {
CUresult::SUCCESS => Ok(true),
CUresult::ERROR_NOT_READY => Ok(false),
other => Err(crate::error::Error::Status { status: other }),
}
}
/// Elapsed milliseconds of device work between `start` (recorded first)
/// and `end` (recorded later). Both events must have been created with
/// timing enabled.
pub fn elapsed_time_ms(start: &Event, end: &Event) -> Result<f32> {
let d = driver()?;
let cu = d.cu_event_elapsed_time()?;
let mut ms: f32 = 0.0;
check(unsafe { cu(&mut ms, start.inner.handle, end.inner.handle) })?;
Ok(ms)
}
/// The [`Context`] this event lives in.
#[inline]
pub fn context(&self) -> &Context {
&self.inner.context
}
/// Raw `CUevent`. Use with care.
#[inline]
pub fn as_raw(&self) -> CUevent {
self.inner.handle
}
}
impl Drop for EventInner {
fn drop(&mut self) {
// A borrowed (non-owning) wrapper must not destroy the foreign handle.
if !self.owned {
return;
}
if let Ok(d) = driver() {
if let Ok(cu) = d.cu_event_destroy() {
// SAFETY: owned handle (cuEventCreate or transferred via from_raw).
let _ = unsafe { cu(self.handle) };
}
}
}
}