Skip to main content

cudarc/driver/safe/
core.rs

1use crate::driver::{
2    result::{self, DriverError},
3    sys::{self, CUfunc_cache_enum, CUfunction_attribute_enum},
4};
5
6use std::{
7    ffi::CString,
8    marker::PhantomData,
9    ops::{Bound, RangeBounds},
10    string::String,
11    sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering},
12    sync::Arc,
13    vec::Vec,
14};
15
16/// Represents a CUDA context on a certain device.
17///
18/// - [`CudaContext::new()`] retains the device's primary context.
19/// - [`CudaContext::new_non_primary()`] creates an independent non-primary context.
20/// - [`CudaContext::new_cig()`] creates a non-primary context with CiG (CUDA in Graphics) parameters (CUDA 12.050+).
21/// - [`CudaContext::from_raw_context()`] wraps a pre-existing raw `CUcontext`.
22///
23/// This is the entrypoint to using any cuda calls, all objects maintain a pointer to `Arc<CudaContext>`
24/// to ensure proper lifetimes.
25///
26/// # On thread safety
27///
28/// This object is thread safe and can be shared/used on multiple threads. All safe apis call
29/// [CudaContext::bind_to_thread()] before doing work in a certain context.
30#[derive(Debug)]
31pub struct CudaContext {
32    pub(crate) cu_device: sys::CUdevice,
33    pub(crate) cu_ctx: sys::CUcontext,
34    pub(crate) ordinal: usize,
35    pub(crate) has_async_alloc: bool,
36    /// Whether this wraps a primary context (true) or a non-primary context (false).
37    /// Primary contexts are released via `cuDevicePrimaryCtxRelease`, while non-primary
38    /// contexts are destroyed via `cuCtxDestroy_v2`.
39    pub(crate) is_primary: bool,
40    pub(crate) num_streams: AtomicUsize,
41    pub(crate) event_tracking: AtomicBool,
42    pub(crate) error_state: AtomicU32,
43}
44
45unsafe impl Send for CudaContext {}
46unsafe impl Sync for CudaContext {}
47
48impl Drop for CudaContext {
49    fn drop(&mut self) {
50        self.record_err(self.bind_to_thread());
51        let ctx = std::mem::replace(&mut self.cu_ctx, std::ptr::null_mut());
52        if !ctx.is_null() {
53            if self.is_primary {
54                self.record_err(unsafe { result::primary_ctx::release(self.cu_device) });
55            } else {
56                // Non-primary contexts (e.g., CiG) are destroyed directly.
57                self.record_err(unsafe { sys::cuCtxDestroy_v2(ctx).result() });
58            }
59        }
60    }
61}
62
63impl PartialEq for CudaContext {
64    fn eq(&self, other: &Self) -> bool {
65        self.cu_device == other.cu_device
66            && self.cu_ctx == other.cu_ctx
67            && self.ordinal == other.ordinal
68    }
69}
70impl Eq for CudaContext {}
71
72impl CudaContext {
73    /// Creates a new context on the specified device ordinal.
74    pub fn new(ordinal: usize) -> Result<Arc<Self>, DriverError> {
75        result::init()?;
76        let cu_device = result::device::get(ordinal as i32)?;
77        let cu_ctx = unsafe { result::primary_ctx::retain(cu_device) }?;
78        let has_async_alloc = unsafe {
79            let memory_pools_supported = result::device::get_attribute(
80                cu_device,
81                sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_MEMORY_POOLS_SUPPORTED,
82            )?;
83            memory_pools_supported > 0
84        };
85        let ctx = Arc::new(CudaContext {
86            cu_device,
87            cu_ctx,
88            ordinal,
89            has_async_alloc,
90            is_primary: true,
91            num_streams: AtomicUsize::new(0),
92            event_tracking: AtomicBool::new(true),
93            error_state: AtomicU32::new(0),
94        });
95        ctx.bind_to_thread()?;
96        Ok(ctx)
97    }
98
99    /// Creates a new non-primary CUDA context on the specified device ordinal.
100    ///
101    /// Unlike [`CudaContext::new()`] which retains the device's primary context,
102    /// this creates an independent context via `cuCtxCreate_v4` (CUDA 12.050+)
103    /// or `cuCtxCreate_v3` (CUDA 11.040–12.040). On drop, the context is
104    /// destroyed via `cuCtxDestroy_v2`.
105    ///
106    /// `flags` controls scheduling policy and other options — use 0 for defaults
107    /// (`CU_CTX_SCHED_AUTO`). See [`sys::CUctx_flags`] for available flags.
108    #[cfg(any(
109        feature = "cuda-11040",
110        feature = "cuda-11050",
111        feature = "cuda-11060",
112        feature = "cuda-11070",
113        feature = "cuda-11080",
114        feature = "cuda-12000",
115        feature = "cuda-12010",
116        feature = "cuda-12020",
117        feature = "cuda-12030",
118        feature = "cuda-12040",
119        feature = "cuda-12050",
120        feature = "cuda-12060",
121        feature = "cuda-12080",
122        feature = "cuda-12090",
123        feature = "cuda-13000",
124        feature = "cuda-13010"
125    ))]
126    pub fn new_non_primary(ordinal: usize, flags: u32) -> Result<Arc<Self>, DriverError> {
127        result::init()?;
128        let cu_device = result::device::get(ordinal as i32)?;
129
130        #[cfg(any(
131            feature = "cuda-12050",
132            feature = "cuda-12060",
133            feature = "cuda-12080",
134            feature = "cuda-12090",
135            feature = "cuda-13000",
136            feature = "cuda-13010"
137        ))]
138        let cu_ctx = unsafe { result::ctx::create_v4(std::ptr::null_mut(), flags, cu_device) }?;
139
140        #[cfg(not(any(
141            feature = "cuda-12050",
142            feature = "cuda-12060",
143            feature = "cuda-12080",
144            feature = "cuda-12090",
145            feature = "cuda-13000",
146            feature = "cuda-13010"
147        )))]
148        let cu_ctx = unsafe { result::ctx::create_v3(flags, cu_device) }?;
149
150        let has_async_alloc = unsafe {
151            let memory_pools_supported = result::device::get_attribute(
152                cu_device,
153                sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_MEMORY_POOLS_SUPPORTED,
154            )?;
155            memory_pools_supported > 0
156        };
157        let ctx = Arc::new(CudaContext {
158            cu_device,
159            cu_ctx,
160            ordinal,
161            has_async_alloc,
162            is_primary: false,
163            num_streams: AtomicUsize::new(0),
164            event_tracking: AtomicBool::new(true),
165            error_state: AtomicU32::new(0),
166        });
167        ctx.bind_to_thread()?;
168        Ok(ctx)
169    }
170
171    /// Creates a new CUDA context with CiG (CUDA in Graphics) parameters.
172    ///
173    /// This uses `cuCtxCreate_v4` to create a non-primary context that shares
174    /// resources with a graphics API (e.g., D3D12). Requires CUDA 12.050+.
175    ///
176    /// `flags` controls scheduling policy and other options — use 0 for defaults.
177    /// `cig_params` specifies the CiG shared data type and pointer.
178    ///
179    /// On drop, the context is destroyed via `cuCtxDestroy_v2`.
180    #[cfg(any(
181        feature = "cuda-12050",
182        feature = "cuda-12060",
183        feature = "cuda-12080",
184        feature = "cuda-12090",
185        feature = "cuda-13000",
186        feature = "cuda-13010"
187    ))]
188    pub fn new_cig(
189        ordinal: usize,
190        flags: u32,
191        cig_params: &mut sys::CUctxCigParam,
192    ) -> Result<Arc<Self>, DriverError> {
193        result::init()?;
194        let cu_device = result::device::get(ordinal as i32)?;
195        let mut ctx_create_params = sys::CUctxCreateParams_st {
196            execAffinityParams: std::ptr::null_mut(),
197            numExecAffinityParams: 0,
198            cigParams: cig_params as *mut sys::CUctxCigParam,
199        };
200        let cu_ctx = unsafe { result::ctx::create_v4(&mut ctx_create_params, flags, cu_device) }?;
201        let has_async_alloc = unsafe {
202            let memory_pools_supported = result::device::get_attribute(
203                cu_device,
204                sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_MEMORY_POOLS_SUPPORTED,
205            )?;
206            memory_pools_supported > 0
207        };
208        let ctx = Arc::new(CudaContext {
209            cu_device,
210            cu_ctx,
211            ordinal,
212            has_async_alloc,
213            is_primary: false,
214            num_streams: AtomicUsize::new(0),
215            event_tracking: AtomicBool::new(true),
216            error_state: AtomicU32::new(0),
217        });
218        ctx.bind_to_thread()?;
219        Ok(ctx)
220    }
221
222    /// Wrap a pre-existing raw CUcontext (e.g., a CiG context created via `cuCtxCreate_v4`).
223    ///
224    /// The context must already be valid and will be made current on the calling thread.
225    /// On drop, calls `cuCtxDestroy_v2` instead of `cuDevicePrimaryCtxRelease`.
226    ///
227    /// # Safety
228    ///
229    /// - `cu_ctx` must be a valid CUDA context that was created (not yet destroyed).
230    /// - `cu_device` must be the device the context was created for.
231    /// - The caller must not destroy or release the context after calling this function;
232    ///   ownership is transferred to the returned `Arc<CudaContext>`.
233    pub unsafe fn from_raw_context(
234        ordinal: usize,
235        cu_device: sys::CUdevice,
236        cu_ctx: sys::CUcontext,
237    ) -> Result<Arc<Self>, DriverError> {
238        let has_async_alloc = {
239            let memory_pools_supported = result::device::get_attribute(
240                cu_device,
241                sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_MEMORY_POOLS_SUPPORTED,
242            )?;
243            memory_pools_supported > 0
244        };
245        let ctx = Arc::new(CudaContext {
246            cu_device,
247            cu_ctx,
248            ordinal,
249            has_async_alloc,
250            is_primary: false,
251            num_streams: AtomicUsize::new(0),
252            event_tracking: AtomicBool::new(true),
253            error_state: AtomicU32::new(0),
254        });
255        ctx.bind_to_thread()?;
256        Ok(ctx)
257    }
258
259    /// Returns whether this context wraps a primary context.
260    ///
261    /// Primary contexts are created via `cuDevicePrimaryCtxRetain` and released on drop.
262    /// Non-primary contexts (e.g., CiG) are destroyed via `cuCtxDestroy_v2` on drop.
263    pub fn is_primary(&self) -> bool {
264        self.is_primary
265    }
266
267    /// Returns whether this context supports asynchronous memory allocation.
268    ///
269    /// By default, the value of this parameter is filled by querying the
270    /// `CU_DEVICE_ATTRIBUTE_MEMORY_POOLS_SUPPORTED` attribute and checking if the number of pools
271    /// is greater than 0.
272    /// Memory allocations performed through the default [CudaStream] will use `cuMemAllocAsync`
273    /// over `cuMemAlloc` if this method returns `true`.
274    pub fn has_async_alloc(&self) -> bool {
275        self.has_async_alloc
276    }
277
278    /// The number of devices available.
279    pub fn device_count() -> Result<i32, DriverError> {
280        result::init()?;
281        result::device::get_count()
282    }
283
284    /// Get the `ordinal` index of the device this is on.
285    pub fn ordinal(&self) -> usize {
286        self.ordinal
287    }
288
289    /// Get the name of this device.
290    pub fn name(&self) -> Result<String, result::DriverError> {
291        self.check_err()?;
292        result::device::get_name(self.cu_device)
293    }
294
295    /// Get the UUID of this device.
296    pub fn uuid(&self) -> Result<sys::CUuuid, result::DriverError> {
297        self.check_err()?;
298        result::device::get_uuid(self.cu_device)
299    }
300
301    /// Get the compute capability of this device as a (major,minor) tuple
302    pub fn compute_capability(&self) -> Result<(i32, i32), result::DriverError> {
303        self.check_err()?;
304        let capability_major =
305            self.attribute(sys::CUdevice_attribute::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR)?;
306        let capability_minor =
307            self.attribute(sys::CUdevice_attribute::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR)?;
308
309        Ok((capability_major, capability_minor))
310    }
311
312    /// Get the total memory available on this device, in bytes.
313    pub fn total_mem(&self) -> Result<usize, DriverError> {
314        self.check_err()?;
315        unsafe { result::device::total_mem(self.cu_device) }
316    }
317
318    /// Returns the free and total device memory in bytes as a `(free, total)` tuple.
319    /// Note: this calls [CudaContext::bind_to_thread()] to ensure the query
320    /// runs against this device's context.
321    pub fn mem_get_info(&self) -> Result<(usize, usize), DriverError> {
322        self.bind_to_thread()?;
323        result::mem_get_info()
324    }
325    /// Get the underlying [sys::CUdevice] of this [CudaContext].
326    ///
327    /// # Safety
328    /// While this function is marked as safe, actually using the
329    /// returned object is unsafe.
330    ///
331    /// **You must not free/release the device pointer**, as it is still
332    /// owned by the [CudaContext].
333    pub fn cu_device(&self) -> sys::CUdevice {
334        self.cu_device
335    }
336
337    /// Get the underlying [sys::CUcontext] of this [CudaContext].
338    ///
339    /// # Safety
340    /// While this function is marked as safe, actually using the
341    /// returned object is unsafe.
342    ///
343    /// **You must not free/release the context pointer**, as it is still
344    /// owned by the [CudaContext].
345    pub fn cu_ctx(&self) -> sys::CUcontext {
346        self.cu_ctx
347    }
348
349    /// Binds this context to the calling thread. Calling this is key for thread safety.
350    pub fn bind_to_thread(&self) -> Result<(), DriverError> {
351        self.check_err()?;
352        if match result::ctx::get_current()? {
353            Some(curr_ctx) => curr_ctx != self.cu_ctx,
354            None => true,
355        } {
356            unsafe { result::ctx::set_current(self.cu_ctx) }?;
357        }
358        Ok(())
359    }
360
361    /// Get the value of the specified attribute of the device in [CudaContext].
362    pub fn attribute(&self, attrib: sys::CUdevice_attribute) -> Result<i32, result::DriverError> {
363        self.check_err()?;
364        unsafe { result::device::get_attribute(self.cu_device, attrib) }
365    }
366
367    /// Synchronize this context. Will only block CPU if you call [CudaContext::set_flags()] with
368    /// [sys::CUctx_flags::CU_CTX_SCHED_BLOCKING_SYNC].
369    pub fn synchronize(&self) -> Result<(), DriverError> {
370        self.bind_to_thread()?;
371        result::ctx::synchronize()
372    }
373
374    /// Ensures calls to [CudaContext::synchronize()] block the calling thread.
375    ///
376    /// Sets [sys::CUctx_flags::CU_CTX_SCHED_BLOCKING_SYNC]
377    #[cfg(not(any(
378        feature = "cuda-11040",
379        feature = "cuda-11050",
380        feature = "cuda-11060",
381        feature = "cuda-11070",
382        feature = "cuda-11080",
383        feature = "cuda-12000"
384    )))]
385    pub fn set_blocking_synchronize(&self) -> Result<(), DriverError> {
386        self.set_flags(sys::CUctx_flags::CU_CTX_SCHED_BLOCKING_SYNC)
387    }
388
389    /// Set flags for this context
390    #[cfg(not(any(
391        feature = "cuda-11040",
392        feature = "cuda-11050",
393        feature = "cuda-11060",
394        feature = "cuda-11070",
395        feature = "cuda-11080",
396        feature = "cuda-12000"
397    )))]
398    pub fn set_flags(&self, flags: sys::CUctx_flags) -> Result<(), DriverError> {
399        self.bind_to_thread()?;
400        result::ctx::set_flags(flags)
401    }
402
403    /// Gets the value of a context limit.
404    ///
405    /// See [cuda docs](https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__CTX.html#group__CUDA__CTX_1g9f2d47d1745752aa16da7ed0d111b6a8)
406    pub fn get_limit(&self, limit: sys::CUlimit) -> Result<usize, DriverError> {
407        self.bind_to_thread()?;
408        result::ctx::get_limit(limit)
409    }
410
411    /// Sets the value of a context limit.
412    ///
413    /// Common limits:
414    /// - `CU_LIMIT_STACK_SIZE` - Stack size for each thread
415    /// - `CU_LIMIT_PRINTF_FIFO_SIZE` - Size of printf buffer
416    /// - `CU_LIMIT_MALLOC_HEAP_SIZE` - Heap size for malloc() in kernels
417    ///
418    /// See [cuda docs](https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__CTX.html#group__CUDA__CTX_1g0651954dfb9788173e60a9af7201e65a)
419    pub fn set_limit(&self, limit: sys::CUlimit, value: usize) -> Result<(), DriverError> {
420        self.bind_to_thread()?;
421        result::ctx::set_limit(limit, value)
422    }
423
424    /// Gets the L1/shared memory cache configuration preference.
425    ///
426    /// See [cuda docs](https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__CTX.html#group__CUDA__CTX_1g40b6b141698f76744dea6e39b9a25360)
427    pub fn get_cache_config(&self) -> Result<sys::CUfunc_cache, DriverError> {
428        self.bind_to_thread()?;
429        result::ctx::get_cache_config()
430    }
431
432    /// Sets the L1/shared memory cache configuration preference.
433    ///
434    /// Options:
435    /// - `CU_FUNC_CACHE_PREFER_NONE` - No preference
436    /// - `CU_FUNC_CACHE_PREFER_SHARED` - Prefer larger shared memory, smaller L1
437    /// - `CU_FUNC_CACHE_PREFER_L1` - Prefer larger L1, smaller shared memory
438    /// - `CU_FUNC_CACHE_PREFER_EQUAL` - Equal split between L1 and shared
439    ///
440    /// See [cuda docs](https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__CTX.html#group__CUDA__CTX_1g54699acf7e2ef27279d013ca2095f4a3)
441    pub fn set_cache_config(&self, config: sys::CUfunc_cache) -> Result<(), DriverError> {
442        self.bind_to_thread()?;
443        result::ctx::set_cache_config(config)
444    }
445
446    /// Whether multiple streams have been created in this context. If so,
447    /// the [CudaSlice::read] and [CudaSlice::write] events will be activated.
448    ///
449    /// This only get's set to true by [CudaContext::new_stream()].
450    pub fn is_in_multi_stream_mode(&self) -> bool {
451        self.num_streams.load(Ordering::Relaxed) > 0
452    }
453
454    /// Whether event tracking is being managed by this context
455    /// (via [CudaContext::enable_event_tracking()], which is the default behavior),
456    /// or `false` if the user is manually managing stream synchronization
457    /// (via [CudaContext::disable_event_tracking()]).
458    pub fn is_event_tracking(&self) -> bool {
459        self.event_tracking.load(Ordering::Relaxed)
460    }
461
462    /// Whether the context is automatically managing multiple stream synchronization.
463    /// Both of these must be true:
464    /// - [CudaContext::is_in_multi_stream_mode()]
465    /// - [CudaContext::is_event_tracking()]
466    pub fn is_managing_stream_synchronization(&self) -> bool {
467        self.is_in_multi_stream_mode() && self.is_event_tracking()
468    }
469
470    /// When turned on, all [CudaSlice] **created after calling this function** will
471    /// record usages using [CudaEvent] to ensure proper synchronization between streams.
472    ///
473    /// # Safety
474    ///
475    /// If [CudaContext::disable_event_tracking()] was called previously, then any
476    /// [CudaSlice] created after that and before this current call won't have [CudaEvent]
477    /// tracking their uses. Those [CudaSlice] will not manage their synchronization, even
478    /// after this call.
479    pub unsafe fn enable_event_tracking(&self) {
480        self.event_tracking.store(true, Ordering::Relaxed);
481    }
482
483    /// When turned on, all [CudaSlice] **created after calling this function** will
484    /// not track uses via [CudaEvent]s.
485    ///
486    /// # Safety
487    ///
488    /// It is up to the user to ensure proper synchronization between multiple streams:
489    /// - Ensure that no [CudaSlice] is freed before a use on another stream is finished.
490    /// - Ensure that a [CudaSlice] is not used on another stream before allocation on the
491    ///   allocating stream finishes.
492    /// - Ensure that a [CudaSlice] is not written two concurrently by multiple streams.
493    pub unsafe fn disable_event_tracking(&self) {
494        self.event_tracking.store(false, Ordering::Relaxed);
495    }
496
497    /// Checks to see if there have been any calls that stored an Err in a function
498    /// that couldn't return a result (e.g. Drop calls).
499    ///
500    /// If there are any errors stored, this method will return the Err value, and
501    /// then clear the stored error state.
502    pub fn check_err(&self) -> Result<(), DriverError> {
503        let error_state = self.error_state.swap(0, Ordering::Relaxed);
504        if error_state == 0 {
505            Ok(())
506        } else {
507            Err(result::DriverError(unsafe {
508                std::mem::transmute::<u32, sys::cudaError_enum>(error_state)
509            }))
510        }
511    }
512
513    /// Records a result for later inspection when a Result can be returned.
514    pub fn record_err<T>(&self, result: Result<T, DriverError>) {
515        if let Err(err) = result {
516            self.error_state.store(err.0 as u32, Ordering::Relaxed)
517        }
518    }
519}
520
521/// A lightweight synchronization primitive used to synchronize between [CudaStream]s.
522///
523/// - Create using [CudaContext::new_event()].
524/// - Record a point of time in a stream using [CudaEvent::record()].
525/// - Either call [CudaEvent::synchronize()] or [CudaStream::wait()] to use.
526///
527/// Note that calls to [CudaEvent::record()] will not change any **previous calls** to [CudaStream::wait()].
528///
529/// # Thread safety
530/// This object is thread safe
531#[derive(Debug)]
532pub struct CudaEvent {
533    pub(crate) cu_event: sys::CUevent,
534    pub(crate) ctx: Arc<CudaContext>,
535}
536
537unsafe impl Send for CudaEvent {}
538unsafe impl Sync for CudaEvent {}
539
540impl Drop for CudaEvent {
541    fn drop(&mut self) {
542        self.ctx.record_err(self.ctx.bind_to_thread());
543        self.ctx
544            .record_err(unsafe { result::event::destroy(self.cu_event) });
545    }
546}
547
548impl CudaContext {
549    /// Creates a new [CudaEvent] with no work recorded. If `flags` is None, the event is created with
550    /// [sys::CUevent_flags::CU_EVENT_DISABLE_TIMING].
551    pub fn new_event(
552        self: &Arc<Self>,
553        flags: Option<sys::CUevent_flags>,
554    ) -> Result<CudaEvent, DriverError> {
555        let flags = flags.unwrap_or(sys::CUevent_flags::CU_EVENT_DISABLE_TIMING);
556        self.bind_to_thread()?;
557        let cu_event = result::event::create(flags)?;
558        Ok(CudaEvent {
559            cu_event,
560            ctx: self.clone(),
561        })
562    }
563}
564
565impl CudaEvent {
566    /// The underlying cu_event object.
567    ///
568    /// # Safety
569    /// Do not destroy this value
570    pub fn cu_event(&self) -> sys::CUevent {
571        self.cu_event
572    }
573
574    /// The context this was created in.
575    pub fn context(&self) -> &Arc<CudaContext> {
576        &self.ctx
577    }
578
579    /// Records the current amount of work in [CudaStream] into this event.
580    ///
581    /// **This does not affect any previous calls to [CudaStream::wait()]**
582    ///
583    /// If `stream` belongs to a different [CudaContext], this will fail with
584    /// [sys::cudaError_enum::CUDA_ERROR_INVALID_CONTEXT].
585    ///
586    /// See [cuda docs](https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__EVENT.html#group__CUDA__EVENT_1g95424d3be52c4eb95d83861b70fb89d1)
587    pub fn record(&self, stream: &CudaStream) -> Result<(), DriverError> {
588        if self.ctx != stream.ctx {
589            return Err(DriverError(sys::cudaError_enum::CUDA_ERROR_INVALID_CONTEXT));
590        }
591        self.ctx.bind_to_thread()?;
592        unsafe { result::event::record(self.cu_event, stream.cu_stream) }
593    }
594
595    /// Will only block CPU thraed if [sys::CUevent_flags::CU_EVENT_BLOCKING_SYNC] was used to create this event.
596    pub fn synchronize(&self) -> Result<(), DriverError> {
597        self.ctx.bind_to_thread()?;
598        unsafe { result::event::synchronize(self.cu_event) }
599    }
600
601    /// The time between two events. `self` is the start event, and `end` is the end event.
602    /// This is effectively `end - self`.
603    pub fn elapsed_ms(&self, end: &Self) -> Result<f32, DriverError> {
604        if self.ctx != end.ctx {
605            return Err(DriverError(sys::cudaError_enum::CUDA_ERROR_INVALID_CONTEXT));
606        }
607        self.ctx.bind_to_thread()?;
608        self.synchronize()?;
609        end.synchronize()?;
610        unsafe { result::event::elapsed(self.cu_event, end.cu_event) }
611    }
612
613    /// Returns `true` if all recorded work has been completed, `false` otherwise.
614    pub fn is_complete(&self) -> bool {
615        unsafe { result::event::query(self.cu_event) }.is_ok()
616    }
617}
618
619/// A wrapper around [sys::CUstream] that you can schedule work on.
620///
621/// - Create with [CudaContext::new_stream()], [CudaContext::default_stream()], or [CudaStream::fork()].
622///
623/// **Work done on this is asynchronous with respect to the host.**
624///
625/// See [CUDA C/C++ Streams and Concurrency](https://developer.download.nvidia.com/CUDA/training/StreamsAndConcurrencyWebinar.pdf)
626/// See [3. Stream synchronization behavior](https://docs.nvidia.com/cuda/cuda-runtime-api/stream-sync-behavior.html)
627/// See [6.6. Event Management](https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__EVENT.html)
628/// See [Out-of-order execution](https://en.wikipedia.org/wiki/Out-of-order_execution)
629/// See [Dependence analysis](https://en.wikipedia.org/wiki/Dependence_analysis)
630#[derive(Debug, PartialEq, Eq)]
631pub struct CudaStream {
632    pub(crate) cu_stream: sys::CUstream,
633    pub(crate) ctx: Arc<CudaContext>,
634}
635
636unsafe impl Send for CudaStream {}
637unsafe impl Sync for CudaStream {}
638
639impl Drop for CudaStream {
640    fn drop(&mut self) {
641        self.ctx.record_err(self.ctx.bind_to_thread());
642        let cu_stream = std::mem::replace(&mut self.cu_stream, std::ptr::null_mut());
643        if !cu_stream.is_null() && cu_stream != (0x2 as _) {
644            self.ctx.num_streams.fetch_sub(1, Ordering::Relaxed);
645            self.ctx
646                .record_err(unsafe { result::stream::destroy(cu_stream) });
647        }
648    }
649}
650
651impl CudaContext {
652    /// Get's the default stream for this context (the null ptr stream). Note that context's
653    /// on the same device can all submit to the same default stream from separate context objects.
654    pub fn default_stream(self: &Arc<Self>) -> Arc<CudaStream> {
655        Arc::new(CudaStream {
656            cu_stream: std::ptr::null_mut(),
657            ctx: self.clone(),
658        })
659    }
660
661    /// Get's the per-thread stream handle. See https://docs.nvidia.com/cuda/cuda-runtime-api/stream-sync-behavior.html#stream-sync-behavior
662    pub fn per_thread_stream(self: &Arc<Self>) -> Arc<CudaStream> {
663        Arc::new(CudaStream {
664            // See https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__TYPES.html#group__CUDART__TYPES_1g7b7129befd6f52708309acafd1c46197
665            cu_stream: 0x2 as _,
666            ctx: self.clone(),
667        })
668    }
669
670    /// Create a new [sys::CUstream_flags::CU_STREAM_NON_BLOCKING] stream.
671    ///
672    /// This will swap the calling context to multi stream mode [CudaContext::is_in_multi_stream_mode()].
673    /// If the context is not already in multiple stream mode, then this function will also call [CudaContext::synchronize()].
674    pub fn new_stream(self: &Arc<Self>) -> Result<Arc<CudaStream>, DriverError> {
675        self.bind_to_thread()?;
676        let prev_num_streams = self.num_streams.fetch_add(1, Ordering::Relaxed);
677        if prev_num_streams == 0 && self.is_event_tracking() {
678            self.synchronize()?;
679        }
680        let cu_stream = result::stream::create(result::stream::StreamKind::NonBlocking)?;
681        Ok(Arc::new(CudaStream {
682            cu_stream,
683            ctx: self.clone(),
684        }))
685    }
686
687    /// Create a new [sys::CUstream_flags::CU_STREAM_NON_BLOCKING] stream with the
688    /// specified priority.
689    ///
690    /// Lower numerical values indicate higher priority. Use
691    /// [`result::stream::get_priority_range`] to query the valid range.
692    ///
693    /// This will swap the calling context to multi stream mode
694    /// [`CudaContext::is_in_multi_stream_mode()`].
695    pub fn new_stream_with_priority(
696        self: &Arc<Self>,
697        priority: i32,
698    ) -> Result<Arc<CudaStream>, DriverError> {
699        self.bind_to_thread()?;
700        let prev_num_streams = self.num_streams.fetch_add(1, Ordering::Relaxed);
701        if prev_num_streams == 0 && self.is_event_tracking() {
702            self.synchronize()?;
703        }
704        let cu_stream = result::stream::create_with_priority(
705            result::stream::StreamKind::NonBlocking,
706            priority,
707        )?;
708        Ok(Arc::new(CudaStream {
709            cu_stream,
710            ctx: self.clone(),
711        }))
712    }
713}
714
715impl CudaStream {
716    /// Create's a new stream and then makes the new stream wait on `self`
717    pub fn fork(&self) -> Result<Arc<Self>, DriverError> {
718        self.ctx.bind_to_thread()?;
719        self.ctx.num_streams.fetch_add(1, Ordering::Relaxed);
720        let cu_stream = result::stream::create(result::stream::StreamKind::NonBlocking)?;
721        let stream = Arc::new(CudaStream {
722            cu_stream,
723            ctx: self.ctx.clone(),
724        });
725        stream.join(self)?;
726        Ok(stream)
727    }
728
729    /// The underlying cuda stream object
730    /// # Safety
731    /// Do not destroy this value.
732    pub fn cu_stream(&self) -> sys::CUstream {
733        self.cu_stream
734    }
735
736    /// The context the stream belongs to.
737    pub fn context(&self) -> &Arc<CudaContext> {
738        &self.ctx
739    }
740
741    /// Will only block CPU if you call [CudaContext::set_flags()] with
742    /// [sys::CUctx_flags::CU_CTX_SCHED_BLOCKING_SYNC].
743    ///
744    /// See [cuda docs](https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__STREAM.html#group__CUDA__STREAM_1g15e49dd91ec15991eb7c0a741beb7dad)
745    pub fn synchronize(&self) -> Result<(), DriverError> {
746        self.ctx.bind_to_thread()?;
747        unsafe { result::stream::synchronize(self.cu_stream) }
748    }
749
750    /// Creates a new [CudaEvent] and records the current work in the stream to the event.
751    pub fn record_event(
752        &self,
753        flags: Option<sys::CUevent_flags>,
754    ) -> Result<CudaEvent, DriverError> {
755        let event = self.ctx.new_event(flags)?;
756        event.record(self)?;
757        Ok(event)
758    }
759
760    /// Waits for the work recorded in [CudaEvent] to be completed.
761    ///
762    /// You can record new work in `event` after calling this method without
763    /// affecting this call.
764    ///
765    /// See [cuda docs](https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__STREAM.html#group__CUDA__STREAM_1g6a898b652dfc6aa1d5c8d97062618b2f)
766    pub fn wait(&self, event: &CudaEvent) -> Result<(), DriverError> {
767        self.ctx.bind_to_thread()?;
768        unsafe {
769            result::stream::wait_event(
770                self.cu_stream,
771                event.cu_event,
772                sys::CUevent_wait_flags::CU_EVENT_WAIT_DEFAULT,
773            )
774        }
775    }
776
777    /// Ensures this stream waits for the current workload in `other` to complete.
778    /// This is shorthand for `self.wait(other.record_event())`
779    pub fn join(&self, other: &CudaStream) -> Result<(), DriverError> {
780        self.wait(&other.record_event(None)?)
781    }
782}
783
784/// `Vec<T>` on a cuda device. You can allocate and modify this with [CudaStream].
785///
786/// This object is thread safe.
787#[derive(Debug)]
788pub struct CudaSlice<T> {
789    pub(crate) cu_device_ptr: sys::CUdeviceptr,
790    pub(crate) len: usize,
791    pub(crate) read: Option<CudaEvent>,
792    pub(crate) write: Option<CudaEvent>,
793    pub(crate) stream: Arc<CudaStream>,
794    pub(crate) marker: PhantomData<*const T>,
795}
796
797unsafe impl<T> Send for CudaSlice<T> {}
798unsafe impl<T> Sync for CudaSlice<T> {}
799
800impl<T> Drop for CudaSlice<T> {
801    fn drop(&mut self) {
802        let ctx = &self.stream.ctx;
803        if let Some(read) = self.read.as_ref() {
804            ctx.record_err(self.stream.wait(read));
805        }
806        if let Some(write) = self.write.as_ref() {
807            ctx.record_err(self.stream.wait(write));
808        }
809        if ctx.has_async_alloc {
810            ctx.record_err(unsafe {
811                result::free_async(self.cu_device_ptr, self.stream.cu_stream)
812            });
813        } else {
814            ctx.record_err(self.stream.synchronize());
815            ctx.record_err(unsafe { result::free_sync(self.cu_device_ptr) });
816        }
817    }
818}
819
820impl<T> CudaSlice<T> {
821    /// The number of elements of `T` in this object.
822    pub fn len(&self) -> usize {
823        self.len
824    }
825
826    /// The number of bytes in this object.
827    pub fn num_bytes(&self) -> usize {
828        self.len * std::mem::size_of::<T>()
829    }
830
831    /// True if there are no elements in the object.
832    pub fn is_empty(&self) -> bool {
833        self.len == 0
834    }
835
836    /// The device ordinal this belongs to
837    pub fn ordinal(&self) -> usize {
838        self.stream.ctx.ordinal
839    }
840
841    /// The context this belongs to
842    pub fn context(&self) -> &Arc<CudaContext> {
843        &self.stream.ctx
844    }
845
846    /// The stream this object was allocated on and later will be dropped on.
847    pub fn stream(&self) -> &Arc<CudaStream> {
848        &self.stream
849    }
850}
851
852impl<T: DeviceRepr> CudaSlice<T> {
853    /// Allocates copy of self and schedules a device to device copy of memory.
854    pub fn try_clone(&self) -> Result<Self, result::DriverError> {
855        self.stream.clone_dtod(self)
856    }
857}
858
859impl<T: DeviceRepr> Clone for CudaSlice<T> {
860    fn clone(&self) -> Self {
861        self.try_clone().unwrap()
862    }
863}
864
865impl<T: Clone + Default + DeviceRepr> TryFrom<CudaSlice<T>> for Vec<T> {
866    type Error = result::DriverError;
867    fn try_from(value: CudaSlice<T>) -> Result<Self, Self::Error> {
868        value.stream.clone_dtoh(&value)
869    }
870}
871
872/// `&[T]` on a cuda device. An immutable sub-view into a [CudaSlice] created by [CudaSlice::as_view()]/[CudaSlice::slice()].
873#[derive(Debug)]
874pub struct CudaView<'a, T> {
875    pub(crate) ptr: sys::CUdeviceptr,
876    pub(crate) len: usize,
877    pub(crate) read: &'a Option<CudaEvent>,
878    pub(crate) write: &'a Option<CudaEvent>,
879    pub(crate) stream: &'a Arc<CudaStream>,
880    marker: PhantomData<&'a [T]>,
881}
882
883impl<T> CudaSlice<T> {
884    pub fn as_view(&self) -> CudaView<'_, T> {
885        CudaView {
886            ptr: self.cu_device_ptr,
887            len: self.len,
888            read: &self.read,
889            write: &self.write,
890            stream: &self.stream,
891            marker: PhantomData,
892        }
893    }
894}
895
896impl<T> CudaView<'_, T> {
897    /// The number of elements `T` in this view.
898    pub fn len(&self) -> usize {
899        self.len
900    }
901
902    pub fn is_empty(&self) -> bool {
903        self.len == 0
904    }
905
906    fn resize(&self, start: usize, end: usize) -> Self {
907        assert!(start <= end && end <= self.len);
908        Self {
909            ptr: self.ptr + (start * std::mem::size_of::<T>()) as u64,
910            len: end - start,
911            read: self.read,
912            write: self.write,
913            stream: self.stream,
914            marker: PhantomData,
915        }
916    }
917}
918
919/// `&mut [T]` on a cuda device. A mutable sub-view into a [CudaSlice] created by [CudaSlice::as_view_mut()]/[CudaSlice::slice_mut()].
920#[derive(Debug)]
921pub struct CudaViewMut<'a, T> {
922    pub(crate) ptr: sys::CUdeviceptr,
923    pub(crate) len: usize,
924    pub(crate) read: &'a Option<CudaEvent>,
925    pub(crate) write: &'a Option<CudaEvent>,
926    pub(crate) stream: &'a Arc<CudaStream>,
927    marker: PhantomData<&'a mut [T]>,
928}
929
930impl<T> CudaSlice<T> {
931    pub fn as_view_mut(&mut self) -> CudaViewMut<'_, T> {
932        CudaViewMut {
933            ptr: self.cu_device_ptr,
934            len: self.len,
935            read: &self.read,
936            write: &self.write,
937            stream: &self.stream,
938            marker: PhantomData,
939        }
940    }
941}
942
943impl<T> CudaViewMut<'_, T> {
944    /// Number of elements `T` that are in this view.
945    pub fn len(&self) -> usize {
946        self.len
947    }
948    pub fn is_empty(&self) -> bool {
949        self.len == 0
950    }
951
952    /// Downgrade this to a `&[T]`
953    pub fn as_view<'b>(&'b self) -> CudaView<'b, T> {
954        CudaView {
955            ptr: self.ptr,
956            len: self.len,
957            read: self.read,
958            write: self.write,
959            stream: self.stream,
960            marker: PhantomData,
961        }
962    }
963}
964
965/// Marker trait to indicate that the type is valid
966/// when all of its bits are set to 0.
967///
968/// # Safety
969/// Not all types are valid when all bits are set to 0.
970/// Be very sure when implementing this trait!
971pub unsafe trait ValidAsZeroBits {}
972unsafe impl ValidAsZeroBits for bool {}
973unsafe impl ValidAsZeroBits for i8 {}
974unsafe impl ValidAsZeroBits for i16 {}
975unsafe impl ValidAsZeroBits for i32 {}
976unsafe impl ValidAsZeroBits for i64 {}
977unsafe impl ValidAsZeroBits for i128 {}
978unsafe impl ValidAsZeroBits for isize {}
979unsafe impl ValidAsZeroBits for u8 {}
980unsafe impl ValidAsZeroBits for u16 {}
981unsafe impl ValidAsZeroBits for u32 {}
982unsafe impl ValidAsZeroBits for u64 {}
983unsafe impl ValidAsZeroBits for u128 {}
984unsafe impl ValidAsZeroBits for usize {}
985unsafe impl ValidAsZeroBits for f32 {}
986unsafe impl ValidAsZeroBits for f64 {}
987#[cfg(feature = "f16")]
988unsafe impl ValidAsZeroBits for half::f16 {}
989#[cfg(feature = "f16")]
990unsafe impl ValidAsZeroBits for half::bf16 {}
991unsafe impl<T: ValidAsZeroBits, const M: usize> ValidAsZeroBits for [T; M] {}
992/// Implement `ValidAsZeroBits` for tuples if all elements are `ValidAsZeroBits`,
993///
994/// # Note
995/// This will also implement `ValidAsZeroBits` for a tuple with one element
996macro_rules! impl_tuples {
997    ($t:tt) => {
998        impl_tuples!(@ $t);
999    };
1000    // the $l is in front of the reptition to prevent parsing ambiguities
1001    ($l:tt $(,$t:tt)+) => {
1002        impl_tuples!($($t),+);
1003        impl_tuples!(@ $l $(,$t)+);
1004    };
1005    (@ $($t:tt),+) => {
1006        unsafe impl<$($t: ValidAsZeroBits,)+> ValidAsZeroBits for ($($t,)+) {}
1007    };
1008}
1009impl_tuples!(A, B, C, D, E, F, G, H, I, J, K, L);
1010
1011/// Something that can be copied to device memory and
1012/// turned into a parameter for [result::launch_kernel].
1013///
1014/// # Safety
1015///
1016/// This is unsafe because a struct should likely
1017/// be `#[repr(C)]` to be represented in cuda memory,
1018/// and not all types are valid.
1019pub unsafe trait DeviceRepr {}
1020unsafe impl DeviceRepr for bool {}
1021unsafe impl DeviceRepr for i8 {}
1022unsafe impl DeviceRepr for i16 {}
1023unsafe impl DeviceRepr for i32 {}
1024unsafe impl DeviceRepr for i64 {}
1025unsafe impl DeviceRepr for i128 {}
1026unsafe impl DeviceRepr for isize {}
1027unsafe impl DeviceRepr for u8 {}
1028unsafe impl DeviceRepr for u16 {}
1029unsafe impl DeviceRepr for u32 {}
1030unsafe impl DeviceRepr for u64 {}
1031unsafe impl DeviceRepr for u128 {}
1032unsafe impl DeviceRepr for usize {}
1033unsafe impl DeviceRepr for f32 {}
1034unsafe impl DeviceRepr for f64 {}
1035#[cfg(feature = "f16")]
1036unsafe impl DeviceRepr for half::f16 {}
1037#[cfg(feature = "f16")]
1038unsafe impl DeviceRepr for half::bf16 {}
1039
1040#[cfg(feature = "f8")]
1041unsafe impl DeviceRepr for float8::F8E4M3 {}
1042#[cfg(feature = "f8")]
1043unsafe impl ValidAsZeroBits for float8::F8E4M3 {}
1044
1045#[cfg(feature = "f8")]
1046unsafe impl DeviceRepr for float8::F8E5M2 {}
1047#[cfg(feature = "f8")]
1048unsafe impl ValidAsZeroBits for float8::F8E5M2 {}
1049
1050#[cfg(feature = "f4")]
1051unsafe impl DeviceRepr for float4::F4E2M1 {}
1052#[cfg(feature = "f4")]
1053unsafe impl ValidAsZeroBits for float4::F4E2M1 {}
1054
1055#[cfg(feature = "f4")]
1056unsafe impl DeviceRepr for float4::E8M0 {}
1057#[cfg(feature = "f4")]
1058unsafe impl ValidAsZeroBits for float4::E8M0 {}
1059
1060#[cfg(feature = "f4")]
1061unsafe impl DeviceRepr for float4::F4E2M1x2 {}
1062#[cfg(feature = "f4")]
1063unsafe impl ValidAsZeroBits for float4::F4E2M1x2 {}
1064
1065unsafe impl<const N: usize, T> DeviceRepr for [T; N] where T: DeviceRepr {}
1066
1067/// Base trait for abstracting over [CudaSlice]/[CudaView]/[CudaViewMut].
1068///
1069/// Don't use this directly - use [DevicePtr]/[DevicePtrMut].
1070pub trait DeviceSlice<T> {
1071    fn len(&self) -> usize;
1072    fn num_bytes(&self) -> usize {
1073        self.len() * std::mem::size_of::<T>()
1074    }
1075    fn is_empty(&self) -> bool {
1076        self.len() == 0
1077    }
1078    fn stream(&self) -> &Arc<CudaStream>;
1079}
1080
1081impl<T> DeviceSlice<T> for CudaSlice<T> {
1082    fn len(&self) -> usize {
1083        self.len
1084    }
1085    fn stream(&self) -> &Arc<CudaStream> {
1086        &self.stream
1087    }
1088}
1089
1090impl<T> DeviceSlice<T> for CudaView<'_, T> {
1091    fn len(&self) -> usize {
1092        self.len
1093    }
1094    fn stream(&self) -> &Arc<CudaStream> {
1095        self.stream
1096    }
1097}
1098
1099impl<T> DeviceSlice<T> for CudaViewMut<'_, T> {
1100    fn len(&self) -> usize {
1101        self.len
1102    }
1103    fn stream(&self) -> &Arc<CudaStream> {
1104        self.stream
1105    }
1106}
1107
1108/// A synchronization primitive to enable stream & event synchronization.
1109/// Primarily used with [DevicePtr] and [DevicePtrMut]
1110#[derive(Debug)]
1111#[must_use]
1112pub enum SyncOnDrop<'a> {
1113    /// Will record the stream's workload to the event on drop.
1114    Record(Option<(&'a CudaEvent, &'a CudaStream)>),
1115    /// Will call stream synchronize on drop.
1116    Sync(Option<&'a CudaStream>),
1117}
1118
1119impl<'a> SyncOnDrop<'a> {
1120    /// Construct a [SyncOnDrop::Record] variant
1121    pub fn record_event(event: &'a Option<CudaEvent>, stream: &'a CudaStream) -> Self {
1122        SyncOnDrop::Record(event.as_ref().map(|e| (e, stream)))
1123    }
1124    /// Construct a [SyncOnDrop::Sync] variant
1125    pub fn sync_stream(stream: &'a CudaStream) -> Self {
1126        SyncOnDrop::Sync(Some(stream))
1127    }
1128}
1129
1130impl Drop for SyncOnDrop<'_> {
1131    fn drop(&mut self) {
1132        match self {
1133            SyncOnDrop::Record(target) => {
1134                if let Some((event, stream)) = std::mem::take(target) {
1135                    stream.ctx.record_err(event.record(stream));
1136                }
1137            }
1138            SyncOnDrop::Sync(target) => {
1139                if let Some(stream) = std::mem::take(target) {
1140                    stream.ctx.record_err(stream.synchronize());
1141                }
1142            }
1143        }
1144    }
1145}
1146
1147/// Abstraction over [CudaSlice]/[CudaView]
1148pub trait DevicePtr<T>: DeviceSlice<T> {
1149    /// Retrieve the device pointer with the intent to read the device memory
1150    /// associated with it.
1151    ///
1152    /// Implementations of this method should ensure `stream` waits for any previous
1153    /// writes of this memory before continuing (do not need to wait for any previous reads).
1154    ///
1155    /// The [SyncOnDrop] item of the return tuple should be dropped **after** the read of
1156    /// the [sys::CUdeviceptr] is scheduled.
1157    ///
1158    /// In most cases you can use like:
1159    /// ```ignore
1160    /// let (src, _record_src) = src.device_ptr(&stream);
1161    /// ```
1162    /// Which will drop the [SyncOnDrop] at the end of the scope.
1163    fn device_ptr<'a>(&'a self, stream: &'a CudaStream) -> (sys::CUdeviceptr, SyncOnDrop<'a>);
1164}
1165
1166impl<T> DevicePtr<T> for CudaSlice<T> {
1167    fn device_ptr<'a>(&'a self, stream: &'a CudaStream) -> (sys::CUdeviceptr, SyncOnDrop<'a>) {
1168        if self.stream.context().is_managing_stream_synchronization() {
1169            if let Some(write) = self.write.as_ref() {
1170                stream.ctx.record_err(stream.wait(write));
1171            }
1172        }
1173        (
1174            self.cu_device_ptr,
1175            SyncOnDrop::record_event(&self.read, stream),
1176        )
1177    }
1178}
1179
1180impl<T> DevicePtr<T> for CudaView<'_, T> {
1181    fn device_ptr<'a>(&'a self, stream: &'a CudaStream) -> (sys::CUdeviceptr, SyncOnDrop<'a>) {
1182        if self.stream.context().is_managing_stream_synchronization() {
1183            if let Some(write) = self.write.as_ref() {
1184                stream.ctx.record_err(stream.wait(write));
1185            }
1186        }
1187        (self.ptr, SyncOnDrop::record_event(self.read, stream))
1188    }
1189}
1190
1191impl<'a, T> CudaView<'a, T> {
1192    /// Identical behavior to [DevicePtr::device_ptr()], but the lifetime on the returned
1193    /// [SyncOnDrop], matches the lifetime of the view.
1194    pub fn view_ptr(self, stream: &'a CudaStream) -> (sys::CUdeviceptr, SyncOnDrop<'a>) {
1195        if self.stream.context().is_managing_stream_synchronization() {
1196            if let Some(write) = self.write.as_ref() {
1197                stream.ctx.record_err(stream.wait(write));
1198            }
1199        }
1200        (self.ptr, SyncOnDrop::record_event(self.read, stream))
1201    }
1202}
1203
1204impl<T> DevicePtr<T> for CudaViewMut<'_, T> {
1205    fn device_ptr<'a>(&'a self, stream: &'a CudaStream) -> (sys::CUdeviceptr, SyncOnDrop<'a>) {
1206        if self.stream.context().is_managing_stream_synchronization() {
1207            if let Some(write) = self.write.as_ref() {
1208                stream.ctx.record_err(stream.wait(write));
1209            }
1210        }
1211        (self.ptr, SyncOnDrop::record_event(self.read, stream))
1212    }
1213}
1214
1215impl<'a, T> CudaViewMut<'a, T> {
1216    /// Identical behavior to [DevicePtr::device_ptr()], but the lifetime on the returned
1217    /// [SyncOnDrop], matches the lifetime of the view.
1218    pub fn view_ptr(self, stream: &'a CudaStream) -> (sys::CUdeviceptr, SyncOnDrop<'a>) {
1219        if self.stream.context().is_managing_stream_synchronization() {
1220            if let Some(write) = self.write.as_ref() {
1221                stream.ctx.record_err(stream.wait(write));
1222            }
1223        }
1224        (self.ptr, SyncOnDrop::record_event(self.read, stream))
1225    }
1226}
1227
1228/// Abstraction over [CudaSlice]/[CudaViewMut]
1229pub trait DevicePtrMut<T>: DeviceSlice<T> {
1230    /// Retrieve the device pointer with the intent to modify the device memory
1231    /// associated with it.
1232    ///
1233    /// Implementations of this method should ensure `stream` waits for any previous
1234    /// reads/writes of this memory before continuing.
1235    ///
1236    /// The [SyncOnDrop] item of the return tuple should be dropped **after** the write of
1237    /// the [sys::CUdeviceptr] is scheduled.
1238    ///
1239    /// In most cases you can use like:
1240    /// ```ignore
1241    /// let (src, _record_src) = src.device_ptr_mut(&stream);
1242    /// ```
1243    /// Which will drop the [SyncOnDrop] at the end of the scope.
1244    fn device_ptr_mut<'a>(
1245        &'a mut self,
1246        stream: &'a CudaStream,
1247    ) -> (sys::CUdeviceptr, SyncOnDrop<'a>);
1248}
1249
1250impl<T> DevicePtrMut<T> for CudaSlice<T> {
1251    fn device_ptr_mut<'a>(
1252        &'a mut self,
1253        stream: &'a CudaStream,
1254    ) -> (sys::CUdeviceptr, SyncOnDrop<'a>) {
1255        if self.stream.context().is_managing_stream_synchronization() {
1256            if let Some(read) = self.read.as_ref() {
1257                stream.ctx.record_err(stream.wait(read));
1258            }
1259            if let Some(write) = self.write.as_ref() {
1260                stream.ctx.record_err(stream.wait(write));
1261            }
1262        }
1263        (
1264            self.cu_device_ptr,
1265            SyncOnDrop::record_event(&self.write, stream),
1266        )
1267    }
1268}
1269
1270impl<T> DevicePtrMut<T> for CudaViewMut<'_, T> {
1271    fn device_ptr_mut<'a>(
1272        &'a mut self,
1273        stream: &'a CudaStream,
1274    ) -> (sys::CUdeviceptr, SyncOnDrop<'a>) {
1275        if self.stream.context().is_managing_stream_synchronization() {
1276            if let Some(read) = self.read.as_ref() {
1277                stream.ctx.record_err(stream.wait(read));
1278            }
1279            if let Some(write) = self.write.as_ref() {
1280                stream.ctx.record_err(stream.wait(write));
1281            }
1282        }
1283        (self.ptr, SyncOnDrop::record_event(self.write, stream))
1284    }
1285}
1286
1287impl<'a, T> CudaViewMut<'a, T> {
1288    /// Identical behavior to [DevicePtrMut::device_ptr_mut()], but the lifetime on the returned
1289    /// [SyncOnDrop], matches the lifetime of the view.
1290    pub fn view_ptr_mut(self, stream: &'a CudaStream) -> (sys::CUdeviceptr, SyncOnDrop<'a>) {
1291        if self.stream.context().is_managing_stream_synchronization() {
1292            if let Some(read) = self.read.as_ref() {
1293                stream.ctx.record_err(stream.wait(read));
1294            }
1295            if let Some(write) = self.write.as_ref() {
1296                stream.ctx.record_err(stream.wait(write));
1297            }
1298        }
1299        (self.ptr, SyncOnDrop::record_event(self.write, stream))
1300    }
1301}
1302
1303/// Abstraction over `&[T]`, `&Vec<T>` and [`PinnedHostSlice<T>`].
1304pub trait HostSlice<T> {
1305    fn len(&self) -> usize;
1306    fn is_empty(&self) -> bool {
1307        self.len() == 0
1308    }
1309
1310    /// # Safety
1311    /// This is **only** safe if the resulting slice is used with `stream`. Otherwise
1312    /// You may run into device synchronization errors
1313    unsafe fn stream_synced_slice<'a>(
1314        &'a self,
1315        stream: &'a CudaStream,
1316    ) -> (&'a [T], SyncOnDrop<'a>);
1317
1318    /// # Safety
1319    /// This is **only** safe if the resulting slice is used with `stream`. Otherwise
1320    /// You may run into device synchronization errors
1321    unsafe fn stream_synced_mut_slice<'a>(
1322        &'a mut self,
1323        stream: &'a CudaStream,
1324    ) -> (&'a mut [T], SyncOnDrop<'a>);
1325}
1326
1327impl<T, const N: usize> HostSlice<T> for [T; N] {
1328    fn len(&self) -> usize {
1329        N
1330    }
1331    unsafe fn stream_synced_slice<'a>(
1332        &'a self,
1333        _stream: &'a CudaStream,
1334    ) -> (&'a [T], SyncOnDrop<'a>) {
1335        (self, SyncOnDrop::Sync(None))
1336    }
1337    unsafe fn stream_synced_mut_slice<'a>(
1338        &'a mut self,
1339        _stream: &'a CudaStream,
1340    ) -> (&'a mut [T], SyncOnDrop<'a>) {
1341        (self, SyncOnDrop::Sync(None))
1342    }
1343}
1344
1345impl<T> HostSlice<T> for [T] {
1346    fn len(&self) -> usize {
1347        self.len()
1348    }
1349    unsafe fn stream_synced_slice<'a>(
1350        &'a self,
1351        _stream: &'a CudaStream,
1352    ) -> (&'a [T], SyncOnDrop<'a>) {
1353        (self, SyncOnDrop::Sync(None))
1354    }
1355    unsafe fn stream_synced_mut_slice<'a>(
1356        &'a mut self,
1357        _stream: &'a CudaStream,
1358    ) -> (&'a mut [T], SyncOnDrop<'a>) {
1359        (self, SyncOnDrop::Sync(None))
1360    }
1361}
1362
1363impl<T> HostSlice<T> for Vec<T> {
1364    fn len(&self) -> usize {
1365        self.len()
1366    }
1367    unsafe fn stream_synced_slice<'a>(
1368        &'a self,
1369        _stream: &'a CudaStream,
1370    ) -> (&'a [T], SyncOnDrop<'a>) {
1371        (self, SyncOnDrop::Sync(None))
1372    }
1373    unsafe fn stream_synced_mut_slice<'a>(
1374        &'a mut self,
1375        _stream: &'a CudaStream,
1376    ) -> (&'a mut [T], SyncOnDrop<'a>) {
1377        (self, SyncOnDrop::Sync(None))
1378    }
1379}
1380
1381/// Rust side data that the `cuda` driver knows is pinned. This is different
1382/// than `Pin<Vec<T>>` mainly because cuda driver manages this memory and ensures
1383/// it is page locked.
1384///
1385/// Allocate this with [CudaContext::alloc_pinned()], and do device copies with
1386/// [CudaStream::clone_htod()]/[CudaStream::memcpy_htod()]/[CudaStream::memcpy_dtoh()]
1387#[derive(Debug)]
1388pub struct PinnedHostSlice<T> {
1389    pub(crate) ptr: *mut T,
1390    pub(crate) len: usize,
1391    pub(crate) event: CudaEvent,
1392}
1393
1394unsafe impl<T> Send for PinnedHostSlice<T> {}
1395unsafe impl<T> Sync for PinnedHostSlice<T> {}
1396
1397impl<T> Drop for PinnedHostSlice<T> {
1398    fn drop(&mut self) {
1399        let ctx = &self.event.ctx;
1400        ctx.record_err(self.event.synchronize());
1401        ctx.record_err(unsafe { result::free_host(self.ptr as _) });
1402    }
1403}
1404
1405impl CudaContext {
1406    /// Allocates page locked host memory with [sys::CU_MEMHOSTALLOC_WRITECOMBINED] flags.
1407    ///
1408    /// See [cuda docs](https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__MEM.html#group__CUDA__MEM_1g572ca4011bfcb25034888a14d4e035b9)
1409    ///
1410    /// # Safety
1411    /// 1. This is unsafe because the memory is unset after this call.
1412    pub unsafe fn alloc_pinned<T: DeviceRepr>(
1413        self: &Arc<Self>,
1414        len: usize,
1415    ) -> Result<PinnedHostSlice<T>, DriverError> {
1416        self.bind_to_thread()?;
1417        let ptr = result::malloc_host(
1418            len * std::mem::size_of::<T>(),
1419            sys::CU_MEMHOSTALLOC_WRITECOMBINED,
1420        )?;
1421        let ptr = ptr as *mut T;
1422        assert!(!ptr.is_null());
1423        assert!(len * std::mem::size_of::<T>() < isize::MAX as usize);
1424        assert!(ptr.is_aligned());
1425        let event = self.new_event(Some(sys::CUevent_flags::CU_EVENT_BLOCKING_SYNC))?;
1426        Ok(PinnedHostSlice { ptr, len, event })
1427    }
1428}
1429
1430impl<T> PinnedHostSlice<T> {
1431    /// The context this was created in.
1432    pub fn context(&self) -> &Arc<CudaContext> {
1433        &self.event.ctx
1434    }
1435
1436    /// The number of elements `T` in this slice.
1437    pub fn len(&self) -> usize {
1438        self.len
1439    }
1440
1441    /// The number of bytes in this slice.
1442    pub fn num_bytes(&self) -> usize {
1443        self.len * std::mem::size_of::<T>()
1444    }
1445
1446    pub fn is_empty(&self) -> bool {
1447        self.len() == 0
1448    }
1449}
1450
1451impl<T: ValidAsZeroBits> PinnedHostSlice<T> {
1452    /// Waits for any scheduled work to complete and then returns a refernce
1453    /// to the host side data.
1454    pub fn as_ptr(&self) -> Result<*const T, DriverError> {
1455        self.event.synchronize()?;
1456        Ok(self.ptr)
1457    }
1458
1459    /// Waits for any scheduled work to complete and then returns a refernce
1460    /// to the host side data.
1461    pub fn as_mut_ptr(&mut self) -> Result<*mut T, DriverError> {
1462        self.event.synchronize()?;
1463        Ok(self.ptr)
1464    }
1465
1466    /// Waits for any scheduled work to complete and then returns a refernce
1467    /// to the host side data.
1468    pub fn as_slice(&self) -> Result<&[T], DriverError> {
1469        self.event.synchronize()?;
1470        Ok(unsafe { std::slice::from_raw_parts(self.ptr, self.len) })
1471    }
1472
1473    /// Waits for any scheduled work to complete and then returns a refernce
1474    /// to the host side data.
1475    pub fn as_mut_slice(&mut self) -> Result<&mut [T], DriverError> {
1476        self.event.synchronize()?;
1477        Ok(unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) })
1478    }
1479}
1480
1481impl<T> HostSlice<T> for PinnedHostSlice<T> {
1482    fn len(&self) -> usize {
1483        self.len
1484    }
1485
1486    unsafe fn stream_synced_slice<'a>(
1487        &'a self,
1488        stream: &'a CudaStream,
1489    ) -> (&'a [T], SyncOnDrop<'a>) {
1490        stream.ctx.record_err(stream.wait(&self.event));
1491        (
1492            std::slice::from_raw_parts(self.ptr, self.len),
1493            SyncOnDrop::Record(Some((&self.event, stream))),
1494        )
1495    }
1496    unsafe fn stream_synced_mut_slice<'a>(
1497        &'a mut self,
1498        stream: &'a CudaStream,
1499    ) -> (&'a mut [T], SyncOnDrop<'a>) {
1500        stream.ctx.record_err(stream.wait(&self.event));
1501        (
1502            std::slice::from_raw_parts_mut(self.ptr, self.len),
1503            SyncOnDrop::Record(Some((&self.event, stream))),
1504        )
1505    }
1506}
1507
1508impl CudaStream {
1509    /// Allocates an empty [CudaSlice] with 0 length.
1510    pub fn null<T>(self: &Arc<Self>) -> Result<CudaSlice<T>, result::DriverError> {
1511        self.ctx.bind_to_thread()?;
1512        let cu_device_ptr = if self.ctx.has_async_alloc {
1513            unsafe { result::malloc_async(self.cu_stream, 0) }?
1514        } else {
1515            unsafe { result::malloc_sync(0) }?
1516        };
1517        Ok(CudaSlice {
1518            cu_device_ptr,
1519            len: 0,
1520            read: None,
1521            write: None,
1522            stream: self.clone(),
1523            marker: PhantomData,
1524        })
1525    }
1526
1527    /// Allocates a [CudaSlice] with `len` elements of type `T`.
1528    /// # Safety
1529    /// This is unsafe because the memory is unset.
1530    pub unsafe fn alloc<T: DeviceRepr>(
1531        self: &Arc<Self>,
1532        len: usize,
1533    ) -> Result<CudaSlice<T>, DriverError> {
1534        self.ctx.bind_to_thread()?;
1535        let cu_device_ptr = if self.ctx.has_async_alloc {
1536            result::malloc_async(self.cu_stream, len * std::mem::size_of::<T>())?
1537        } else {
1538            result::malloc_sync(len * std::mem::size_of::<T>())?
1539        };
1540        let (read, write) = if self.ctx.is_event_tracking() {
1541            (
1542                Some(self.ctx.new_event(None)?),
1543                Some(self.ctx.new_event(None)?),
1544            )
1545        } else {
1546            (None, None)
1547        };
1548        Ok(CudaSlice {
1549            cu_device_ptr,
1550            len,
1551            read,
1552            write,
1553            stream: self.clone(),
1554            marker: PhantomData,
1555        })
1556    }
1557
1558    /// Allocates a [CudaSlice] with `len` elements of type `T`. All values are zero'd out.
1559    pub fn alloc_zeros<T: DeviceRepr + ValidAsZeroBits>(
1560        self: &Arc<Self>,
1561        len: usize,
1562    ) -> Result<CudaSlice<T>, DriverError> {
1563        let mut dst = unsafe { self.alloc(len) }?;
1564        self.memset_zeros(&mut dst)?;
1565        Ok(dst)
1566    }
1567
1568    /// Set's all the memory in `dst` to 0. `dst` can be a [CudaSlice] or [CudaViewMut]
1569    pub fn memset_zeros<T: DeviceRepr + ValidAsZeroBits, Dst: DevicePtrMut<T>>(
1570        self: &Arc<Self>,
1571        dst: &mut Dst,
1572    ) -> Result<(), DriverError> {
1573        self.ctx.bind_to_thread()?;
1574        let num_bytes = dst.num_bytes();
1575        let (dptr, _record) = dst.device_ptr_mut(self);
1576        unsafe { result::memset_d8_async(dptr, 0, num_bytes, self.cu_stream) }?;
1577        Ok(())
1578    }
1579
1580    /// Copy a `[T]`/`Vec<T>`/[`PinnedHostSlice<T>`] to a new [`CudaSlice`].
1581    #[deprecated = "Use clone_htod"]
1582    pub fn memcpy_stod<T: DeviceRepr, Src: HostSlice<T> + ?Sized>(
1583        self: &Arc<Self>,
1584        src: &Src,
1585    ) -> Result<CudaSlice<T>, DriverError> {
1586        let mut dst = unsafe { self.alloc(src.len()) }?;
1587        self.memcpy_htod(src, &mut dst)?;
1588        Ok(dst)
1589    }
1590
1591    /// Copy a `[T]`/`Vec<T>`/[`PinnedHostSlice<T>`] to a new [`CudaSlice`].
1592    pub fn clone_htod<T: DeviceRepr, Src: HostSlice<T> + ?Sized>(
1593        self: &Arc<Self>,
1594        src: &Src,
1595    ) -> Result<CudaSlice<T>, DriverError> {
1596        let mut dst = unsafe { self.alloc(src.len()) }?;
1597        self.memcpy_htod(src, &mut dst)?;
1598        Ok(dst)
1599    }
1600
1601    /// Copy a `[T]`/`Vec<T>`/[`PinnedHostSlice<T>`] into an existing [`CudaSlice`]/[`CudaViewMut`].
1602    pub fn memcpy_htod<T: DeviceRepr, Src: HostSlice<T> + ?Sized, Dst: DevicePtrMut<T>>(
1603        self: &Arc<Self>,
1604        src: &Src,
1605        dst: &mut Dst,
1606    ) -> Result<(), DriverError> {
1607        assert!(dst.len() >= src.len());
1608        self.ctx.bind_to_thread()?;
1609        let (src, _record_src) = unsafe { src.stream_synced_slice(self) };
1610        let (dst, _record_dst) = dst.device_ptr_mut(self);
1611        unsafe { result::memcpy_htod_async(dst, src, self.cu_stream) }
1612    }
1613
1614    /// Copy a [`CudaSlice`]/[`CudaView`] to a new [`Vec<T>`].
1615    #[deprecated = "Use clone_dtoh"]
1616    pub fn memcpy_dtov<T: DeviceRepr, Src: DevicePtr<T>>(
1617        self: &Arc<Self>,
1618        src: &Src,
1619    ) -> Result<Vec<T>, DriverError> {
1620        let mut dst = Vec::with_capacity(src.len());
1621        #[allow(clippy::uninit_vec)]
1622        unsafe {
1623            dst.set_len(src.len())
1624        };
1625        self.memcpy_dtoh(src, &mut dst)?;
1626        Ok(dst)
1627    }
1628
1629    /// Copy a [`CudaSlice`]/[`CudaView`] to a new [`Vec<T>`].
1630    pub fn clone_dtoh<T: DeviceRepr, Src: DevicePtr<T>>(
1631        self: &Arc<Self>,
1632        src: &Src,
1633    ) -> Result<Vec<T>, DriverError> {
1634        let mut dst = Vec::with_capacity(src.len());
1635        #[allow(clippy::uninit_vec)]
1636        unsafe {
1637            dst.set_len(src.len())
1638        };
1639        self.memcpy_dtoh(src, &mut dst)?;
1640        Ok(dst)
1641    }
1642
1643    /// Copy a [`CudaSlice`]/[`CudaView`] to a existing `[T]`/[`Vec<T>`]/[`PinnedHostSlice<T>`].
1644    pub fn memcpy_dtoh<T: DeviceRepr, Src: DevicePtr<T>, Dst: HostSlice<T> + ?Sized>(
1645        self: &Arc<Self>,
1646        src: &Src,
1647        dst: &mut Dst,
1648    ) -> Result<(), DriverError> {
1649        assert!(dst.len() >= src.len());
1650        self.ctx.bind_to_thread()?;
1651        let (src, _record_src) = src.device_ptr(self);
1652        let (dst, _record_dst) = unsafe { dst.stream_synced_mut_slice(self) };
1653        unsafe { result::memcpy_dtoh_async(dst, src, self.cu_stream) }
1654    }
1655
1656    /// Copy a [`CudaSlice`]/[`CudaView`] to a existing [`CudaSlice`]/[`CudaViewMut`].
1657    pub fn memcpy_dtod<T, Src: DevicePtr<T>, Dst: DevicePtrMut<T>>(
1658        self: &Arc<Self>,
1659        src: &Src,
1660        dst: &mut Dst,
1661    ) -> Result<(), DriverError> {
1662        assert!(dst.len() >= src.len());
1663        self.ctx.bind_to_thread()?;
1664
1665        let num_bytes = src.num_bytes();
1666
1667        let src_ctx = src.stream().context();
1668        let dst_ctx = self.context();
1669
1670        if src_ctx == dst_ctx {
1671            let (src_ptr, _record_src) = src.device_ptr(self);
1672            let (dst_ptr, _record_dst) = dst.device_ptr_mut(self);
1673            unsafe { result::memcpy_dtod_async(dst_ptr, src_ptr, num_bytes, self.cu_stream) }
1674        } else {
1675            // NOTE: Although we want the current stream to wait on src to be ready,
1676            // we can't use src.device_ptr(self). When `_record_src` is dropped,
1677            // we record an event from the src_stream onto dst_stream (i.e., self). This is not
1678            // allowed in CUDA, and will return a CUDA_ERROR_INVALID_HANDLE
1679            // https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__CTX.html#group__CUDA__CTX_1gf3ee63561a7a371fa9d4dc0e31f94afd
1680            let (src_ptr, _record_src) = src.device_ptr(src.stream());
1681            let (dst_ptr, _record_dst) = dst.device_ptr_mut(self);
1682            // NOTE: Although we can't record events on streams they weren't created on,
1683            // we can *wait* on events from any stream. We can leverage this and wait on
1684            // a src event.
1685            // OPTIM: Ideally we could wait on the src write_events, but we can artificially
1686            // insert an event which guarantees src is available.
1687            self.wait(&src.stream().record_event(None)?)?;
1688            unsafe {
1689                result::memcpy_peer_async(
1690                    dst_ctx.cu_ctx,
1691                    dst_ptr,
1692                    src_ctx.cu_ctx,
1693                    src_ptr,
1694                    num_bytes,
1695                    self.cu_stream,
1696                )
1697            }
1698        }
1699    }
1700
1701    /// Copy a [`CudaSlice`]/[`CudaView`] to a new [`CudaSlice`].
1702    pub fn clone_dtod<T: DeviceRepr, Src: DevicePtr<T>>(
1703        self: &Arc<Self>,
1704        src: &Src,
1705    ) -> Result<CudaSlice<T>, DriverError> {
1706        let mut dst = unsafe { self.alloc(src.len()) }?;
1707        self.memcpy_dtod(src, &mut dst)?;
1708        Ok(dst)
1709    }
1710}
1711
1712impl<T> CudaSlice<T> {
1713    /// Creates a [CudaView] at the specified offset from the start of `self`.
1714    ///
1715    /// Panics if `range.start >= self.len`.
1716    ///
1717    /// # Example
1718    ///
1719    /// ```rust
1720    /// # use cudarc::driver::safe::{CudaContext, CudaSlice, CudaView};
1721    /// # fn do_something(view: &CudaView<u8>) {}
1722    /// # let ctx = CudaContext::new(0).unwrap();
1723    /// # let stream = ctx.default_stream();
1724    /// let mut slice = stream.alloc_zeros::<u8>(100).unwrap();
1725    /// let mut view = slice.slice(0..50);
1726    /// do_something(&view);
1727    /// ```
1728    ///
1729    /// Like a normal slice, borrow checking prevents the underlying [CudaSlice] from being dropped.
1730    /// ```rust,compile_fail
1731    /// # use cudarc::driver::safe::{CudaContext, CudaSlice, CudaView};
1732    /// # fn do_something(view: &CudaView<u8>) {}
1733    /// # let ctx = CudaContext::new(0).unwrap();
1734    /// # let stream = ctx.default_stream();
1735    /// let view = {
1736    ///     let mut slice = stream.alloc_zeros::<u8>(100).unwrap();
1737    ///     // cannot return view, since it borrows from slice
1738    ///     slice.slice(0..50)
1739    /// };
1740    /// do_something(&view);
1741    /// ```
1742    pub fn slice(&self, bounds: impl RangeBounds<usize>) -> CudaView<'_, T> {
1743        self.as_view().slice(bounds)
1744    }
1745
1746    /// Fallible version of [CudaSlice::slice()].
1747    pub fn try_slice(&self, bounds: impl RangeBounds<usize>) -> Option<CudaView<'_, T>> {
1748        self.as_view().try_slice(bounds)
1749    }
1750
1751    /// Creates a [CudaViewMut] at the specified offset from the start of `self`.
1752    ///
1753    /// Panics if `range` and `0...self.len()` are not overlapping.
1754    ///
1755    /// # Example
1756    ///
1757    /// ```rust
1758    /// # use cudarc::driver::safe::{CudaContext, CudaSlice, CudaViewMut};
1759    /// # fn do_something(view: &mut CudaViewMut<u8>) {}
1760    /// # let ctx = CudaContext::new(0).unwrap();
1761    /// # let stream = ctx.default_stream();
1762    /// let mut slice = stream.alloc_zeros::<u8>(100).unwrap();
1763    /// let mut view = slice.slice_mut(0..50);
1764    /// do_something(&mut view);
1765    /// ```
1766    ///
1767    /// Like a normal mutable slice, borrow checking prevents the underlying [CudaSlice] from being dropped.
1768    /// ```rust,compile_fail
1769    /// # use cudarc::driver::safe::{CudaContext, CudaSlice, CudaViewMut};
1770    /// # fn do_something(view: &mut CudaViewMut<u8>) {}
1771    /// # let ctx = CudaContext::new(0).unwrap();
1772    /// # let stream = ctx.default_stream();
1773    /// let mut view = {
1774    ///     let mut slice = stream.alloc_zeros::<u8>(100).unwrap();
1775    ///     // cannot return view, since it borrows from slice
1776    ///     slice.slice_mut(0..50)
1777    /// };
1778    /// do_something(&mut view);
1779    /// ```
1780    ///
1781    /// Like with normal mutable slices, one cannot mutably slice twice into the same [CudaSlice]:
1782    /// ```rust,compile_fail
1783    /// # use cudarc::driver::safe::{CudaContext, CudaSlice, CudaViewMut};
1784    /// # fn do_something(view: CudaViewMut<u8>, view2: CudaViewMut<u8>) {}
1785    /// # let ctx = CudaContext::new(0).unwrap();
1786    /// # let stream = ctx.default_stream();
1787    /// let mut slice = stream.alloc_zeros::<u8>(100).unwrap();
1788    /// let mut view1 = slice.slice_mut(0..50);
1789    /// // cannot borrow twice from slice
1790    /// let mut view2 = slice.slice_mut(50..100);
1791    /// do_something(view1, view2);
1792    /// ```
1793    /// If you need non-overlapping mutable views into a [CudaSlice], you can use [CudaSlice::split_at_mut()].
1794    pub fn slice_mut(&mut self, bounds: impl RangeBounds<usize>) -> CudaViewMut<'_, T> {
1795        self.try_slice_mut(bounds).unwrap()
1796    }
1797
1798    /// Fallible version of [CudaSlice::slice_mut]
1799    pub fn try_slice_mut(&mut self, bounds: impl RangeBounds<usize>) -> Option<CudaViewMut<'_, T>> {
1800        to_range(bounds, self.len).map(|(start, end)| CudaViewMut {
1801            ptr: self.cu_device_ptr + (start * std::mem::size_of::<T>()) as u64,
1802            len: end - start,
1803            read: &self.read,
1804            write: &self.write,
1805            stream: &self.stream,
1806            marker: PhantomData,
1807        })
1808    }
1809
1810    /// Reinterprets the slice of memory into a different type. `len` is the number
1811    /// of elements of the new type `S` that are expected. If not enough bytes
1812    /// are allocated in `self` for the view, then this returns `None`.
1813    ///
1814    /// # Safety
1815    /// This is unsafe because not the memory for the view may not be a valid interpretation
1816    /// for the type `S`.
1817    pub unsafe fn transmute<S>(&self, len: usize) -> Option<CudaView<'_, S>> {
1818        self.as_view().transmute(len)
1819    }
1820
1821    /// Reinterprets the slice of memory into a different type. `len` is the number
1822    /// of elements of the new type `S` that are expected. If not enough bytes
1823    /// are allocated in `self` for the view, then this returns `None`.
1824    ///
1825    /// # Safety
1826    /// This is unsafe because not the memory for the view may not be a valid interpretation
1827    /// for the type `S`.
1828    pub unsafe fn transmute_mut<S>(&mut self, len: usize) -> Option<CudaViewMut<'_, S>> {
1829        (len * std::mem::size_of::<S>() <= self.len * std::mem::size_of::<T>()).then_some(
1830            CudaViewMut {
1831                ptr: self.cu_device_ptr,
1832                len,
1833                read: &self.read,
1834                write: &self.write,
1835                stream: &self.stream,
1836                marker: PhantomData,
1837            },
1838        )
1839    }
1840
1841    pub fn split_at(&self, mid: usize) -> (CudaView<'_, T>, CudaView<'_, T>) {
1842        self.as_view().split_at(mid)
1843    }
1844
1845    /// Fallible version of [CudaSlice::split_at]. Returns `None` if `mid > self.len`.
1846    pub fn try_split_at(&self, mid: usize) -> Option<(CudaView<'_, T>, CudaView<'_, T>)> {
1847        self.as_view().try_split_at(mid)
1848    }
1849
1850    /// Splits the [CudaSlice] into two at the given index, returning two [CudaViewMut] for the two halves.
1851    ///
1852    /// Panics if `mid > self.len`.
1853    ///
1854    /// This method can be used to create non-overlapping mutable views into a [CudaSlice].
1855    /// ```rust
1856    /// # use cudarc::driver::safe::{CudaContext, CudaSlice, CudaViewMut};
1857    /// # fn do_something(view: CudaViewMut<u8>, view2: CudaViewMut<u8>) {}
1858    /// # let ctx = CudaContext::new(0).unwrap();
1859    /// # let stream = ctx.default_stream();
1860    /// let mut slice = stream.alloc_zeros::<u8>(100).unwrap();
1861    /// // split the slice into two non-overlapping, mutable views
1862    /// let (mut view1, mut view2) = slice.split_at_mut(50);
1863    /// do_something(view1, view2);
1864    /// ```
1865    pub fn split_at_mut(&mut self, mid: usize) -> (CudaViewMut<'_, T>, CudaViewMut<'_, T>) {
1866        self.try_split_at_mut(mid).unwrap()
1867    }
1868
1869    /// Fallible version of [CudaSlice::split_at_mut].
1870    ///
1871    /// Returns `None` if `mid > self.len`.
1872    pub fn try_split_at_mut(
1873        &mut self,
1874        mid: usize,
1875    ) -> Option<(CudaViewMut<'_, T>, CudaViewMut<'_, T>)> {
1876        let length = self.len;
1877        (mid <= length).then(|| {
1878            let a = CudaViewMut {
1879                ptr: self.cu_device_ptr,
1880                len: mid,
1881                read: &self.read,
1882                write: &self.write,
1883                stream: &self.stream,
1884                marker: PhantomData,
1885            };
1886            let b = CudaViewMut {
1887                ptr: self.cu_device_ptr + (mid * std::mem::size_of::<T>()) as u64,
1888                len: length - mid,
1889                read: &self.read,
1890                write: &self.write,
1891                stream: &self.stream,
1892                marker: PhantomData,
1893            };
1894            (a, b)
1895        })
1896    }
1897}
1898
1899impl<'a, T> CudaView<'a, T> {
1900    /// Creates a [CudaView] at the specified offset from the start of `self`.
1901    ///
1902    /// Panics if `range.start >= self.len`.
1903    ///
1904    /// # Example
1905    ///
1906    /// ```rust
1907    /// # use cudarc::driver::safe::{CudaContext, CudaSlice, CudaView};
1908    /// # fn do_something(view: &CudaView<u8>) {}
1909    /// # let ctx = CudaContext::new(0).unwrap();
1910    /// # let stream = ctx.default_stream();
1911    /// let mut slice = stream.alloc_zeros::<u8>(100).unwrap();
1912    /// let mut view = slice.slice(0..50);
1913    /// let mut view2 = view.slice(0..25);
1914    /// do_something(&view);
1915    /// ```
1916    pub fn slice(&self, bounds: impl RangeBounds<usize>) -> Self {
1917        self.try_slice(bounds).unwrap()
1918    }
1919
1920    /// Fallible version of [CudaView::slice]
1921    pub fn try_slice(&self, bounds: impl RangeBounds<usize>) -> Option<Self> {
1922        to_range(bounds, self.len).map(|(start, end)| self.resize(start, end))
1923    }
1924
1925    /// Reinterprets the slice of memory into a different type. `len` is the number
1926    /// of elements of the new type `S` that are expected. If not enough bytes
1927    /// are allocated in `self` for the view, then this returns `None`.
1928    ///
1929    /// # Safety
1930    /// This is unsafe because not the memory for the view may not be a valid interpretation
1931    /// for the type `S`.
1932    pub unsafe fn transmute<S>(&self, len: usize) -> Option<CudaView<'a, S>> {
1933        (len * std::mem::size_of::<S>() <= self.len * std::mem::size_of::<T>()).then_some(
1934            CudaView {
1935                ptr: self.ptr,
1936                len,
1937                read: self.read,
1938                write: self.write,
1939                stream: self.stream,
1940                marker: PhantomData,
1941            },
1942        )
1943    }
1944
1945    pub fn split_at(&self, mid: usize) -> (Self, Self) {
1946        self.try_split_at(mid).unwrap()
1947    }
1948
1949    /// Fallible version of [CudaSlice::split_at].
1950    ///
1951    /// Returns `None` if `mid > self.len`.
1952    pub fn try_split_at(&self, mid: usize) -> Option<(Self, Self)> {
1953        (mid <= self.len()).then(|| (self.resize(0, mid), self.resize(mid, self.len)))
1954    }
1955
1956    /// Returns an iterarow over subviews of size `chunk_size`. Differs from [std::slice::ChunksExact],
1957    /// in that it asserts that the chunk_size must divide evenly into the length, instead of returning
1958    /// a remainder.
1959    pub fn chunks_exact(&self, chunk_size: usize) -> impl Iterator<Item = CudaView<'a, T>> + '_ {
1960        assert!(self.len.is_multiple_of(chunk_size));
1961        let num_chunks = self.len / chunk_size;
1962        (0..num_chunks).map(move |i| self.resize(i * chunk_size, (i + 1) * chunk_size))
1963    }
1964}
1965
1966impl<'a, T> CudaViewMut<'a, T> {
1967    /// Creates a [CudaView] at the specified offset from the start of `self`.
1968    ///
1969    /// Panics if `range` and `0...self.len()` are not overlapping.
1970    ///
1971    /// # Example
1972    ///
1973    /// ```rust
1974    /// # use cudarc::driver::safe::{CudaContext, CudaSlice, CudaViewMut};
1975    /// # fn do_something(view: &mut CudaViewMut<u8>) {}
1976    /// # let ctx = CudaContext::new(0).unwrap();
1977    /// # let stream = ctx.default_stream();
1978    /// let mut slice = stream.alloc_zeros::<u8>(100).unwrap();
1979    /// let mut view = slice.slice_mut(0..50);
1980    /// let mut view2 = view.slice_mut(0..25);
1981    /// do_something(&mut view2);
1982    /// ```
1983    ///
1984    /// One cannot slice twice into the same [CudaViewMut]:
1985    /// ```rust,compile_fail
1986    /// # use cudarc::driver::safe::{CudaContext, CudaSlice, CudaViewMut};
1987    /// # fn do_something(view: CudaViewMut<u8>, view2: CudaViewMut<u8>) {}
1988    /// # let ctx = CudaContext::new(0).unwrap();
1989    /// # let stream = ctx.default_stream();
1990    /// let mut slice = stream.alloc_zeros::<u8>(100).unwrap();
1991    /// let mut view = slice.slice_mut(0..50);
1992    /// // cannot borrow twice from same view
1993    /// let mut view1 = slice.slice_mut(0..25);
1994    /// let mut view2 = slice.slice_mut(25..50);
1995    /// do_something(view1, view2);
1996    /// ```
1997    /// If you need non-overlapping mutable views into a [CudaViewMut], you can use [CudaViewMut::split_at_mut()].
1998    pub fn slice<'b>(&'b self, bounds: impl RangeBounds<usize>) -> CudaView<'b, T> {
1999        self.try_slice(bounds).unwrap()
2000    }
2001
2002    /// Fallible version of [CudaViewMut::slice]
2003    pub fn try_slice<'b>(&'b self, bounds: impl RangeBounds<usize>) -> Option<CudaView<'b, T>> {
2004        to_range(bounds, self.len).map(move |(start, end)| self.as_view().resize(start, end))
2005    }
2006
2007    /// Reinterprets the slice of memory into a different type. `len` is the number
2008    /// of elements of the new type `S` that are expected. If not enough bytes
2009    /// are allocated in `self` for the view, then this returns `None`.
2010    ///
2011    /// # Safety
2012    /// This is unsafe because not the memory for the view may not be a valid interpretation
2013    /// for the type `S`.
2014    pub unsafe fn transmute<'b, S>(&'b self, len: usize) -> Option<CudaView<'b, S>> {
2015        (len * std::mem::size_of::<S>() <= self.len * std::mem::size_of::<T>()).then_some(
2016            CudaView {
2017                ptr: self.ptr,
2018                len,
2019                read: self.read,
2020                write: self.write,
2021                stream: self.stream,
2022                marker: PhantomData,
2023            },
2024        )
2025    }
2026
2027    /// Creates a [CudaViewMut] at the specified offset from the start of `self`.
2028    ///
2029    /// Panics if `range` and `0...self.len()` are not overlapping.
2030    pub fn slice_mut<'b>(&'b mut self, bounds: impl RangeBounds<usize>) -> CudaViewMut<'b, T> {
2031        self.try_slice_mut(bounds).unwrap()
2032    }
2033
2034    /// Fallible version of [CudaViewMut::slice_mut]
2035    pub fn try_slice_mut<'b>(
2036        &'b mut self,
2037        bounds: impl RangeBounds<usize>,
2038    ) -> Option<CudaViewMut<'b, T>> {
2039        to_range(bounds, self.len).map(|(start, end)| CudaViewMut {
2040            ptr: self.ptr + (start * std::mem::size_of::<T>()) as u64,
2041            len: end - start,
2042            read: self.read,
2043            write: self.write,
2044            stream: self.stream,
2045            marker: PhantomData,
2046        })
2047    }
2048
2049    /// Splits the [CudaViewMut] into two at the given index.
2050    ///
2051    /// Panics if `mid > self.len`.
2052    ///
2053    /// This method can be used to create non-overlapping mutable views into a [CudaViewMut].
2054    /// ```rust
2055    /// # use cudarc::driver::safe::{CudaContext, CudaSlice, CudaViewMut};
2056    /// # fn do_something(view: CudaViewMut<u8>, view2: CudaViewMut<u8>) {}
2057    /// # let ctx = CudaContext::new(0).unwrap();
2058    /// # let stream = ctx.default_stream();
2059    /// let mut slice = stream.alloc_zeros::<u8>(100).unwrap();
2060    /// let mut view = slice.slice_mut(0..50);
2061    /// // split the view into two non-overlapping, mutable views
2062    /// let (mut view1, mut view2) = view.split_at_mut(25);
2063    /// do_something(view1, view2);
2064    pub fn split_at_mut<'b>(&'b mut self, mid: usize) -> (CudaViewMut<'b, T>, CudaViewMut<'b, T>) {
2065        self.try_split_at_mut(mid).unwrap()
2066    }
2067
2068    /// Fallible version of [CudaViewMut::split_at_mut].
2069    ///
2070    /// Returns `None` if `mid > self.len`
2071    pub fn try_split_at_mut<'b>(
2072        &'b mut self,
2073        mid: usize,
2074    ) -> Option<(CudaViewMut<'b, T>, CudaViewMut<'b, T>)> {
2075        let length = self.len;
2076        (mid <= length).then(|| {
2077            let a = CudaViewMut {
2078                ptr: self.ptr,
2079                len: mid,
2080                read: self.read,
2081                write: self.write,
2082                stream: self.stream,
2083                marker: PhantomData,
2084            };
2085            let b = CudaViewMut {
2086                ptr: self.ptr + (mid * std::mem::size_of::<T>()) as u64,
2087                len: length - mid,
2088                read: self.read,
2089                write: self.write,
2090                stream: self.stream,
2091                marker: PhantomData,
2092            };
2093            (a, b)
2094        })
2095    }
2096
2097    /// Returns an iterarow over subviews of size `chunk_size`. Differs from [std::slice::ChunksExactMut],
2098    /// in that it asserts that the chunk_size must divide evenly into the length, instead of returning
2099    /// a remainder.
2100    pub fn chunks_exact_mut(self, chunk_size: usize) -> impl Iterator<Item = CudaViewMut<'a, T>> {
2101        assert!(self.len.is_multiple_of(chunk_size));
2102        let num_chunks = self.len / chunk_size;
2103        (0..num_chunks).map(move |i| CudaViewMut {
2104            ptr: self.ptr + (i * chunk_size * std::mem::size_of::<T>()) as u64,
2105            len: chunk_size,
2106            read: self.read,
2107            write: self.write,
2108            stream: self.stream,
2109            marker: PhantomData,
2110        })
2111    }
2112
2113    /// Reinterprets the slice of memory into a different type. `len` is the number
2114    /// of elements of the new type `S` that are expected. If not enough bytes
2115    /// are allocated in `self` for the view, then this returns `None`.
2116    ///
2117    /// # Safety
2118    /// This is unsafe because not the memory for the view may not be a valid interpretation
2119    /// for the type `S`.
2120    pub unsafe fn transmute_mut<'b, S>(&'b mut self, len: usize) -> Option<CudaViewMut<'b, S>> {
2121        (len * std::mem::size_of::<S>() <= self.len * std::mem::size_of::<T>()).then_some(
2122            CudaViewMut {
2123                ptr: self.ptr,
2124                len,
2125                read: self.read,
2126                write: self.write,
2127                stream: self.stream,
2128                marker: PhantomData,
2129            },
2130        )
2131    }
2132}
2133
2134pub(super) fn to_range(range: impl RangeBounds<usize>, len: usize) -> Option<(usize, usize)> {
2135    let start = match range.start_bound() {
2136        Bound::Included(&n) => n,
2137        Bound::Excluded(&n) => n + 1,
2138        Bound::Unbounded => 0,
2139    };
2140    let end = match range.end_bound() {
2141        Bound::Included(&n) => n + 1,
2142        Bound::Excluded(&n) => n,
2143        Bound::Unbounded => len,
2144    };
2145    (start <= end && end <= len).then_some((start, end))
2146}
2147
2148/// Wrapper around [sys::CUmodule]. Create with [CudaContext::load_module()].
2149///
2150/// Call [CudaModule::load_function] to load a [CudaFunction].
2151#[derive(Debug)]
2152pub struct CudaModule {
2153    pub(crate) cu_module: sys::CUmodule,
2154    pub(crate) ctx: Arc<CudaContext>,
2155}
2156
2157unsafe impl Send for CudaModule {}
2158unsafe impl Sync for CudaModule {}
2159
2160impl Drop for CudaModule {
2161    fn drop(&mut self) {
2162        self.ctx.record_err(self.ctx.bind_to_thread());
2163        self.ctx
2164            .record_err(unsafe { result::module::unload(self.cu_module) });
2165    }
2166}
2167
2168impl CudaContext {
2169    /// Dynamically load a compiled ptx into this context.
2170    ///
2171    /// - `ptx` contains the compiled ptx
2172    #[cfg(feature = "nvrtc")]
2173    pub fn load_module(
2174        self: &Arc<Self>,
2175        ptx: crate::nvrtc::Ptx,
2176    ) -> Result<Arc<CudaModule>, result::DriverError> {
2177        self.bind_to_thread()?;
2178
2179        let cu_module = match ptx.0 {
2180            crate::nvrtc::PtxKind::Image(image) => unsafe {
2181                result::module::load_data(image.as_ptr() as *const _)
2182            },
2183            crate::nvrtc::PtxKind::Src(src) => {
2184                let c_src = CString::new(src).unwrap();
2185                unsafe { result::module::load_data(c_src.as_ptr() as *const _) }
2186            }
2187            crate::nvrtc::PtxKind::File(path) => {
2188                let name_c = CString::new(path.to_str().unwrap()).unwrap();
2189                result::module::load(name_c)
2190            }
2191            crate::nvrtc::PtxKind::Binary(data) => unsafe {
2192                result::module::load_data(data.as_ptr() as *const _)
2193            },
2194        }?;
2195        Ok(Arc::new(CudaModule {
2196            cu_module,
2197            ctx: self.clone(),
2198        }))
2199    }
2200}
2201
2202/// Wrapper around [sys::CUfunction]. Used by [CudaStream::launch_builder] to execute kernels.
2203#[derive(Debug, Clone)]
2204pub struct CudaFunction {
2205    pub(crate) cu_function: sys::CUfunction,
2206    #[allow(unused)]
2207    pub(crate) module: Arc<CudaModule>,
2208}
2209
2210unsafe impl Send for CudaFunction {}
2211unsafe impl Sync for CudaFunction {}
2212
2213impl CudaModule {
2214    /// Loads a function from the loaded module with the given name.
2215    pub fn load_function(self: &Arc<Self>, fn_name: &str) -> Result<CudaFunction, DriverError> {
2216        let fn_name_c = CString::new(fn_name).unwrap();
2217        let cu_function = unsafe { result::module::get_function(self.cu_module, fn_name_c) }?;
2218        Ok(CudaFunction {
2219            cu_function,
2220            module: self.clone(),
2221        })
2222    }
2223
2224    /// Gets a global/constant symbol from the loaded module as a [CudaSlice<u8>].
2225    ///
2226    /// This can be used to access `__constant__` memory declared in CUDA kernels.
2227    /// The returned slice can be transmuted to the appropriate type via views.
2228    ///
2229    /// # Example
2230    ///
2231    /// ```ignore
2232    /// // In CUDA: __constant__ float my_const[4];
2233    /// let symbol = module.get_global("my_const", &stream)?;
2234    /// let mut symbol_view = symbol.as_view_mut();
2235    /// let mut symbol_f32 = unsafe { symbol_view.transmute_mut::<f32>(4).unwrap() };
2236    /// stream.memcpy_htod(&[1.0f32, 2.0, 3.0, 4.0], &mut symbol_f32)?;
2237    /// ```
2238    pub fn get_global<'a>(
2239        self: &'a Arc<Self>,
2240        name: &str,
2241        stream: &'a Arc<CudaStream>,
2242    ) -> Result<CudaViewMut<'a, u8>, DriverError> {
2243        let name_c =
2244            CString::new(name).map_err(|_| DriverError(sys::CUresult::CUDA_ERROR_INVALID_VALUE))?;
2245        let (cu_device_ptr, bytes) = unsafe { result::module::get_global(self.cu_module, name_c) }?;
2246        Ok(CudaViewMut {
2247            ptr: cu_device_ptr,
2248            len: bytes,
2249            read: &None,
2250            write: &None,
2251            stream,
2252            marker: PhantomData,
2253        })
2254    }
2255}
2256
2257impl CudaFunction {
2258    pub fn occupancy_available_dynamic_smem_per_block(
2259        &self,
2260        num_blocks: u32,
2261        block_size: u32,
2262    ) -> Result<usize, result::DriverError> {
2263        let mut dynamic_smem_size: usize = 0;
2264
2265        unsafe {
2266            sys::cuOccupancyAvailableDynamicSMemPerBlock(
2267                &mut dynamic_smem_size,
2268                self.cu_function,
2269                num_blocks as std::ffi::c_int,
2270                block_size as std::ffi::c_int,
2271            )
2272            .result()?
2273        };
2274
2275        Ok(dynamic_smem_size)
2276    }
2277
2278    pub fn occupancy_max_active_blocks_per_multiprocessor(
2279        &self,
2280        block_size: u32,
2281        dynamic_smem_size: usize,
2282        flags: Option<sys::CUoccupancy_flags_enum>,
2283    ) -> Result<u32, result::DriverError> {
2284        let mut num_blocks: std::ffi::c_int = 0;
2285        let flags = flags.unwrap_or(sys::CUoccupancy_flags_enum::CU_OCCUPANCY_DEFAULT);
2286
2287        unsafe {
2288            sys::cuOccupancyMaxActiveBlocksPerMultiprocessorWithFlags(
2289                &mut num_blocks,
2290                self.cu_function,
2291                block_size as std::ffi::c_int,
2292                dynamic_smem_size,
2293                flags as std::ffi::c_uint,
2294            )
2295            .result()?
2296        };
2297
2298        Ok(num_blocks as u32)
2299    }
2300
2301    #[cfg(not(any(
2302        feature = "cuda-11070",
2303        feature = "cuda-11060",
2304        feature = "cuda-11050",
2305        feature = "cuda-11040"
2306    )))]
2307    pub fn occupancy_max_active_clusters(
2308        &self,
2309        config: crate::driver::LaunchConfig,
2310        stream: &CudaStream,
2311    ) -> Result<u32, result::DriverError> {
2312        let mut num_clusters: std::ffi::c_int = 0;
2313
2314        let cfg = sys::CUlaunchConfig {
2315            gridDimX: config.grid_dim.0,
2316            gridDimY: config.grid_dim.1,
2317            gridDimZ: config.grid_dim.2,
2318            blockDimX: config.block_dim.0,
2319            blockDimY: config.block_dim.1,
2320            blockDimZ: config.block_dim.2,
2321            sharedMemBytes: config.shared_mem_bytes,
2322            hStream: stream.cu_stream,
2323            attrs: std::ptr::null_mut(),
2324            numAttrs: 0,
2325        };
2326
2327        unsafe {
2328            sys::cuOccupancyMaxActiveClusters(&mut num_clusters, self.cu_function, &cfg).result()?
2329        };
2330
2331        Ok(num_clusters as u32)
2332    }
2333
2334    pub fn occupancy_max_potential_block_size(
2335        &self,
2336        block_size_to_dynamic_smem_size: extern "C" fn(block_size: std::ffi::c_int) -> usize,
2337        dynamic_smem_size: usize,
2338        block_size_limit: u32,
2339        flags: Option<sys::CUoccupancy_flags_enum>,
2340    ) -> Result<(u32, u32), result::DriverError> {
2341        let mut min_grid_size: std::ffi::c_int = 0;
2342        let mut block_size: std::ffi::c_int = 0;
2343        let flags = flags.unwrap_or(sys::CUoccupancy_flags_enum::CU_OCCUPANCY_DEFAULT);
2344
2345        unsafe {
2346            sys::cuOccupancyMaxPotentialBlockSizeWithFlags(
2347                &mut min_grid_size,
2348                &mut block_size,
2349                self.cu_function,
2350                Some(block_size_to_dynamic_smem_size),
2351                dynamic_smem_size,
2352                block_size_limit as std::ffi::c_int,
2353                flags as std::ffi::c_uint,
2354            )
2355            .result()?
2356        };
2357
2358        Ok((min_grid_size as u32, block_size as u32))
2359    }
2360
2361    #[cfg(not(any(
2362        feature = "cuda-11070",
2363        feature = "cuda-11060",
2364        feature = "cuda-11050",
2365        feature = "cuda-11040"
2366    )))]
2367    pub fn occupancy_max_potential_cluster_size(
2368        &self,
2369        config: crate::driver::LaunchConfig,
2370        stream: &CudaStream,
2371    ) -> Result<u32, result::DriverError> {
2372        let mut cluster_size: std::ffi::c_int = 0;
2373
2374        let cfg = sys::CUlaunchConfig {
2375            gridDimX: config.grid_dim.0,
2376            gridDimY: config.grid_dim.1,
2377            gridDimZ: config.grid_dim.2,
2378            blockDimX: config.block_dim.0,
2379            blockDimY: config.block_dim.1,
2380            blockDimZ: config.block_dim.2,
2381            sharedMemBytes: config.shared_mem_bytes,
2382            hStream: stream.cu_stream,
2383            attrs: std::ptr::null_mut(),
2384            numAttrs: 0,
2385        };
2386
2387        unsafe {
2388            sys::cuOccupancyMaxPotentialClusterSize(&mut cluster_size, self.cu_function, &cfg)
2389                .result()?
2390        };
2391
2392        Ok(cluster_size as u32)
2393    }
2394
2395    /// Get the value of a specific attribute of this [CudaFunction].
2396    ///
2397    /// See [CUDA docs](https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__EXEC.html#group__CUDA__EXEC_1g5e92a1b0d8d1b82cb00dcfb2de15961b)
2398    pub fn get_attribute(
2399        &self,
2400        attribute: CUfunction_attribute_enum,
2401    ) -> Result<i32, result::DriverError> {
2402        self.module.ctx.bind_to_thread()?;
2403        unsafe { result::function::get_function_attribute(self.cu_function, attribute) }
2404    }
2405
2406    /// Get the number of registers used per thread.
2407    pub fn num_regs(&self) -> Result<i32, result::DriverError> {
2408        self.get_attribute(CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_NUM_REGS)
2409    }
2410
2411    /// Get the size of statically-allocated shared memory in bytes.
2412    pub fn shared_size_bytes(&self) -> Result<i32, result::DriverError> {
2413        self.get_attribute(CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES)
2414    }
2415
2416    /// Get the size of constant memory in bytes used by this function.
2417    pub fn const_size_bytes(&self) -> Result<i32, result::DriverError> {
2418        self.get_attribute(CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_CONST_SIZE_BYTES)
2419    }
2420
2421    /// Get the size of local memory in bytes used per thread.
2422    pub fn local_size_bytes(&self) -> Result<i32, result::DriverError> {
2423        self.get_attribute(CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES)
2424    }
2425
2426    /// Get the maximum number of threads per block for this function.
2427    pub fn max_threads_per_block(&self) -> Result<i32, result::DriverError> {
2428        self.get_attribute(CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK)
2429    }
2430
2431    /// Get the PTX virtual architecture version for which the function was compiled.
2432    pub fn ptx_version(&self) -> Result<i32, result::DriverError> {
2433        self.get_attribute(CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_PTX_VERSION)
2434    }
2435
2436    /// Get the binary architecture version for which the function was compiled.
2437    pub fn binary_version(&self) -> Result<i32, result::DriverError> {
2438        self.get_attribute(CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_BINARY_VERSION)
2439    }
2440
2441    /// Set the value of a specific attribute of this [CudaFunction].
2442    pub fn set_attribute(
2443        &self,
2444        attribute: CUfunction_attribute_enum,
2445        value: i32,
2446    ) -> Result<(), result::DriverError> {
2447        unsafe { result::function::set_function_attribute(self.cu_function, attribute, value) }
2448    }
2449
2450    /// Set the cache config of this [CudaFunction].
2451    pub fn set_function_cache_config(
2452        &self,
2453        attribute: CUfunc_cache_enum,
2454    ) -> Result<(), result::DriverError> {
2455        unsafe { result::function::set_function_cache_config(self.cu_function, attribute) }
2456    }
2457}
2458
2459impl<T> CudaSlice<T> {
2460    /// Takes ownership of the underlying [sys::CUdeviceptr]. **It is up
2461    /// to the owner to free this value**.
2462    ///
2463    /// Drops the underlying host_buf if there is one.
2464    pub fn leak(self) -> sys::CUdeviceptr {
2465        let mut s = std::mem::ManuallyDrop::new(self);
2466        let ptr = s.cu_device_ptr;
2467
2468        // Ensure pending operations are complete before resources are released.
2469        if let Some(read) = s.read.as_ref() {
2470            s.stream.ctx.record_err(s.stream.wait(read));
2471        }
2472        if let Some(write) = s.write.as_ref() {
2473            s.stream.ctx.record_err(s.stream.wait(write));
2474        }
2475
2476        // Manually drop fields that own resources.
2477        unsafe {
2478            std::ptr::drop_in_place(&mut s.read);
2479            std::ptr::drop_in_place(&mut s.write);
2480            std::ptr::drop_in_place(&mut s.stream);
2481        }
2482
2483        ptr
2484    }
2485}
2486
2487impl CudaStream {
2488    /// Creates a [CudaSlice] from a [sys::CUdeviceptr]. Useful in conjunction with
2489    /// [`CudaSlice::leak()`].
2490    ///
2491    /// # Safety
2492    /// - `cu_device_ptr` must be a valid allocation
2493    /// - `cu_device_ptr` must space for `len * std::mem::size_of<T>()` bytes
2494    /// - The memory may not be valid for type `T`, so some sort of memset operation
2495    ///   should be called on the memory.
2496    pub unsafe fn upgrade_device_ptr<T>(
2497        self: &Arc<Self>,
2498        cu_device_ptr: sys::CUdeviceptr,
2499        len: usize,
2500    ) -> CudaSlice<T> {
2501        let (read, write) = if self.ctx.is_event_tracking() {
2502            (
2503                Some(self.ctx.new_event(None).unwrap()),
2504                Some(self.ctx.new_event(None).unwrap()),
2505            )
2506        } else {
2507            (None, None)
2508        };
2509        CudaSlice {
2510            cu_device_ptr,
2511            len,
2512            read,
2513            write,
2514            stream: self.clone(),
2515            marker: PhantomData,
2516        }
2517    }
2518}
2519
2520#[cfg(test)]
2521mod tests {
2522    use std::time::Instant;
2523
2524    use super::*;
2525
2526    #[test]
2527    fn test_transmutes() {
2528        let ctx = CudaContext::new(0).unwrap();
2529        let stream = ctx.default_stream();
2530        let mut slice = stream.alloc_zeros::<u8>(100).unwrap();
2531        assert!(unsafe { slice.transmute::<f32>(25) }.is_some());
2532        assert!(unsafe { slice.transmute::<f32>(26) }.is_none());
2533        assert!(unsafe { slice.transmute_mut::<f32>(25) }.is_some());
2534        assert!(unsafe { slice.transmute_mut::<f32>(26) }.is_none());
2535
2536        {
2537            let view = slice.slice(0..100);
2538            assert!(unsafe { view.transmute::<f32>(25) }.is_some());
2539            assert!(unsafe { view.transmute::<f32>(26) }.is_none());
2540        }
2541
2542        {
2543            let mut view_mut = slice.slice_mut(0..100);
2544            assert!(unsafe { view_mut.transmute::<f32>(25) }.is_some());
2545            assert!(unsafe { view_mut.transmute::<f32>(26) }.is_none());
2546            assert!(unsafe { view_mut.transmute_mut::<f32>(25) }.is_some());
2547            assert!(unsafe { view_mut.transmute_mut::<f32>(26) }.is_none());
2548        }
2549    }
2550
2551    #[test]
2552    fn test_threading() {
2553        let ctx1 = CudaContext::new(0).unwrap();
2554        let ctx2 = ctx1.clone();
2555
2556        let thread1 = std::thread::spawn(move || {
2557            ctx1.bind_to_thread()?;
2558            ctx1.default_stream().alloc_zeros::<f32>(10)
2559        });
2560        let thread2 = std::thread::spawn(move || {
2561            ctx2.bind_to_thread()?;
2562            ctx2.default_stream().alloc_zeros::<f32>(10)
2563        });
2564
2565        let _: crate::driver::CudaSlice<f32> = thread1.join().unwrap().unwrap();
2566        let _: crate::driver::CudaSlice<f32> = thread2.join().unwrap().unwrap();
2567    }
2568
2569    #[test]
2570    fn test_post_build_arc_count() {
2571        let ctx = CudaContext::new(0).unwrap();
2572        assert_eq!(Arc::strong_count(&ctx), 1);
2573    }
2574
2575    #[test]
2576    fn test_post_alloc_arc_counts() {
2577        let ctx = CudaContext::new(0).unwrap();
2578        assert_eq!(Arc::strong_count(&ctx), 1);
2579        let stream = ctx.default_stream();
2580        assert_eq!(Arc::strong_count(&ctx), 2);
2581        let t = stream.alloc_zeros::<f32>(1).unwrap();
2582        assert_eq!(Arc::strong_count(&ctx), 4);
2583        assert_eq!(Arc::strong_count(&stream), 2);
2584        drop(t);
2585        assert_eq!(Arc::strong_count(&ctx), 2);
2586        assert_eq!(Arc::strong_count(&stream), 1);
2587        drop(stream);
2588        assert_eq!(Arc::strong_count(&ctx), 1);
2589    }
2590
2591    #[test]
2592    #[ignore = "must be executed by itself"]
2593    fn test_post_alloc_memory() {
2594        let ctx = CudaContext::new(0).unwrap();
2595        let stream = ctx.default_stream();
2596
2597        let (free1, total1) = ctx.mem_get_info().unwrap();
2598
2599        let t = stream.clone_htod(&[0.0f32; 5]).unwrap();
2600        let (free2, total2) = ctx.mem_get_info().unwrap();
2601        assert_eq!(total1, total2);
2602        assert!(free2 < free1);
2603
2604        drop(t);
2605        ctx.synchronize().unwrap();
2606
2607        let (free3, total3) = ctx.mem_get_info().unwrap();
2608        assert_eq!(total2, total3);
2609        assert!(free3 > free2);
2610        assert_eq!(free3, free1);
2611    }
2612
2613    #[test]
2614    fn test_ctx_copy_to_views() {
2615        let ctx = CudaContext::new(0).unwrap();
2616        let stream = ctx.default_stream();
2617
2618        let smalls = [
2619            stream.clone_htod(&[-1.0f32, -0.8]).unwrap(),
2620            stream.clone_htod(&[-0.6, -0.4]).unwrap(),
2621            stream.clone_htod(&[-0.2, 0.0]).unwrap(),
2622            stream.clone_htod(&[0.2, 0.4]).unwrap(),
2623            stream.clone_htod(&[0.6, 0.8]).unwrap(),
2624        ];
2625        let mut big = stream.alloc_zeros::<f32>(10).unwrap();
2626
2627        let mut offset = 0;
2628        for small in smalls.iter() {
2629            let mut sub = big.slice_mut(offset..offset + small.len());
2630            stream.memcpy_dtod(small, &mut sub).unwrap();
2631            offset += small.len();
2632        }
2633
2634        assert_eq!(
2635            stream.clone_dtoh(&big).unwrap(),
2636            [-1.0, -0.8, -0.6, -0.4, -0.2, 0.0, 0.2, 0.4, 0.6, 0.8]
2637        );
2638    }
2639
2640    #[test]
2641    fn test_leak_and_upgrade() {
2642        let ctx = CudaContext::new(0).unwrap();
2643        let stream = ctx.default_stream();
2644
2645        let a = stream.clone_htod(&[1.0f32, 2.0, 3.0, 4.0, 5.0]).unwrap();
2646
2647        let ptr = a.leak();
2648        let b = unsafe { stream.upgrade_device_ptr::<f32>(ptr, 3) };
2649        assert_eq!(stream.clone_dtoh(&b).unwrap(), &[1.0, 2.0, 3.0]);
2650
2651        let ptr = b.leak();
2652        let c = unsafe { stream.upgrade_device_ptr::<f32>(ptr, 5) };
2653        assert_eq!(stream.clone_dtoh(&c).unwrap(), &[1.0, 2.0, 3.0, 4.0, 5.0]);
2654    }
2655
2656    /// See https://github.com/chelsea0x3b/cudarc/issues/160
2657    #[test]
2658    fn test_slice_is_freed_with_correct_context() {
2659        let ctx0 = CudaContext::new(0).unwrap();
2660        let slice = ctx0.default_stream().clone_htod(&[1.0; 10]).unwrap();
2661        let ctx1 = CudaContext::new(0).unwrap();
2662        ctx1.bind_to_thread().unwrap();
2663        drop(ctx0);
2664        drop(slice);
2665        drop(ctx1);
2666    }
2667
2668    /// See https://github.com/chelsea0x3b/cudarc/issues/161
2669    #[test]
2670    fn test_copy_uses_correct_context() {
2671        let ctx0 = CudaContext::new(0).unwrap();
2672        let _ctx1 = CudaContext::new(0).unwrap();
2673        let slice = ctx0.default_stream().clone_htod(&[1.0; 10]).unwrap();
2674        let _out = ctx0.default_stream().clone_dtoh(&slice).unwrap();
2675    }
2676
2677    #[test]
2678    fn test_htod_copy_pinned() {
2679        let truth = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
2680        let ctx = CudaContext::new(0).unwrap();
2681        let stream = ctx.default_stream();
2682        let mut pinned = unsafe { ctx.alloc_pinned::<f32>(10) }.unwrap();
2683        pinned.as_mut_slice().unwrap().clone_from_slice(&truth);
2684        assert_eq!(pinned.as_slice().unwrap(), &truth);
2685        let dst = stream.clone_htod(&pinned).unwrap();
2686        let host = stream.clone_dtoh(&dst).unwrap();
2687        assert_eq!(&host, &truth);
2688    }
2689
2690    #[test]
2691    fn test_pinned_copy_is_faster() {
2692        let ctx = CudaContext::new(0).unwrap();
2693        let stream = ctx.new_stream().unwrap();
2694
2695        let n = 100_000;
2696        let n_samples = 5;
2697        let not_pinned = std::vec![0.0f32; n];
2698
2699        let start = Instant::now();
2700        for _ in 0..n_samples {
2701            let _ = stream.clone_htod(&not_pinned).unwrap();
2702            stream.synchronize().unwrap();
2703        }
2704        let unpinned_elapsed = start.elapsed() / n_samples;
2705
2706        let pinned = unsafe { ctx.alloc_pinned::<f32>(n) }.unwrap();
2707
2708        let start = Instant::now();
2709        for _ in 0..n_samples {
2710            let _ = stream.clone_htod(&pinned).unwrap();
2711            stream.synchronize().unwrap();
2712        }
2713        let pinned_elapsed = start.elapsed() / n_samples;
2714
2715        // pinned memory transfer speed should be at least 2x faster, but this depends
2716        // on device
2717        assert!(
2718            pinned_elapsed.as_secs_f32() * 1.5 < unpinned_elapsed.as_secs_f32(),
2719            "{unpinned_elapsed:?} vs {pinned_elapsed:?}"
2720        );
2721    }
2722
2723    #[test]
2724    fn test_primary_context_is_primary() {
2725        let ctx = CudaContext::new(0).unwrap();
2726        assert!(ctx.is_primary());
2727    }
2728
2729    /// Helper to create a non-primary context for testing `from_raw_context`.
2730    /// Uses `cuCtxCreate_v4` (CUDA 12.050+) or `cuCtxCreate_v3` (CUDA 11.040–12.040).
2731    #[cfg(any(
2732        feature = "cuda-11040",
2733        feature = "cuda-11050",
2734        feature = "cuda-11060",
2735        feature = "cuda-11070",
2736        feature = "cuda-11080",
2737        feature = "cuda-12000",
2738        feature = "cuda-12010",
2739        feature = "cuda-12020",
2740        feature = "cuda-12030",
2741        feature = "cuda-12040",
2742        feature = "cuda-12050",
2743        feature = "cuda-12060",
2744        feature = "cuda-12080",
2745        feature = "cuda-12090",
2746    ))]
2747    fn create_non_primary_context() -> (sys::CUdevice, sys::CUcontext) {
2748        result::init().unwrap();
2749        let cu_device = result::device::get(0).unwrap();
2750
2751        #[cfg(any(
2752            feature = "cuda-12050",
2753            feature = "cuda-12060",
2754            feature = "cuda-12080",
2755            feature = "cuda-12090",
2756            feature = "cuda-13000",
2757            feature = "cuda-13010",
2758        ))]
2759        let cu_ctx = unsafe { result::ctx::create_v4(std::ptr::null_mut(), 0, cu_device) }
2760            .expect("cuCtxCreate_v4 failed");
2761
2762        #[cfg(not(any(
2763            feature = "cuda-12050",
2764            feature = "cuda-12060",
2765            feature = "cuda-12080",
2766            feature = "cuda-12090",
2767            feature = "cuda-13000",
2768            feature = "cuda-13010",
2769        )))]
2770        let cu_ctx =
2771            unsafe { result::ctx::create_v3(0, cu_device) }.expect("cuCtxCreate_v3 failed");
2772
2773        assert!(!cu_ctx.is_null());
2774        (cu_device, cu_ctx)
2775    }
2776
2777    #[test]
2778    #[cfg(any(
2779        feature = "cuda-11040",
2780        feature = "cuda-11050",
2781        feature = "cuda-11060",
2782        feature = "cuda-11070",
2783        feature = "cuda-11080",
2784        feature = "cuda-12000",
2785        feature = "cuda-12010",
2786        feature = "cuda-12020",
2787        feature = "cuda-12030",
2788        feature = "cuda-12040",
2789        feature = "cuda-12050",
2790        feature = "cuda-12060",
2791        feature = "cuda-12080",
2792        feature = "cuda-12090",
2793    ))]
2794    fn test_from_raw_context_creates_and_destroys() {
2795        let (cu_device, cu_ctx) = create_non_primary_context();
2796
2797        let ctx = unsafe { CudaContext::from_raw_context(0, cu_device, cu_ctx) }.unwrap();
2798        assert!(!ctx.is_primary());
2799        // Verify the context is bound and usable.
2800        ctx.bind_to_thread().unwrap();
2801        // Drop should call cuCtxDestroy_v2, not primary_ctx::release.
2802        drop(ctx);
2803    }
2804
2805    #[test]
2806    #[cfg(any(
2807        feature = "cuda-11040",
2808        feature = "cuda-11050",
2809        feature = "cuda-11060",
2810        feature = "cuda-11070",
2811        feature = "cuda-11080",
2812        feature = "cuda-12000",
2813        feature = "cuda-12010",
2814        feature = "cuda-12020",
2815        feature = "cuda-12030",
2816        feature = "cuda-12040",
2817        feature = "cuda-12050",
2818        feature = "cuda-12060",
2819        feature = "cuda-12080",
2820        feature = "cuda-12090",
2821    ))]
2822    fn test_from_raw_context_bind_to_thread() {
2823        let (cu_device, cu_ctx) = create_non_primary_context();
2824
2825        let ctx = unsafe { CudaContext::from_raw_context(0, cu_device, cu_ctx) }.unwrap();
2826
2827        // Verify bind_to_thread works from another thread.
2828        let ctx2 = ctx.clone();
2829        let handle = std::thread::spawn(move || {
2830            ctx2.bind_to_thread().unwrap();
2831            let stream = ctx2.default_stream();
2832            let data = stream.clone_htod(&[1.0f32, 2.0, 3.0]).unwrap();
2833            let result = stream.clone_dtoh(&data).unwrap();
2834            assert_eq!(result, std::vec![1.0f32, 2.0, 3.0]);
2835        });
2836        handle.join().unwrap();
2837    }
2838
2839    #[test]
2840    #[cfg(any(
2841        feature = "cuda-11040",
2842        feature = "cuda-11050",
2843        feature = "cuda-11060",
2844        feature = "cuda-11070",
2845        feature = "cuda-11080",
2846        feature = "cuda-12000",
2847        feature = "cuda-12010",
2848        feature = "cuda-12020",
2849        feature = "cuda-12030",
2850        feature = "cuda-12040",
2851        feature = "cuda-12050",
2852        feature = "cuda-12060",
2853        feature = "cuda-12080",
2854        feature = "cuda-12090",
2855        feature = "cuda-13000",
2856        feature = "cuda-13010",
2857    ))]
2858    fn test_new_non_primary_creates_and_destroys() {
2859        let ctx = CudaContext::new_non_primary(0, 0).unwrap();
2860        assert!(!ctx.is_primary());
2861        ctx.bind_to_thread().unwrap();
2862        drop(ctx);
2863    }
2864
2865    #[test]
2866    #[cfg(any(
2867        feature = "cuda-11040",
2868        feature = "cuda-11050",
2869        feature = "cuda-11060",
2870        feature = "cuda-11070",
2871        feature = "cuda-11080",
2872        feature = "cuda-12000",
2873        feature = "cuda-12010",
2874        feature = "cuda-12020",
2875        feature = "cuda-12030",
2876        feature = "cuda-12040",
2877        feature = "cuda-12050",
2878        feature = "cuda-12060",
2879        feature = "cuda-12080",
2880        feature = "cuda-12090",
2881        feature = "cuda-13000",
2882        feature = "cuda-13010",
2883    ))]
2884    fn test_new_non_primary_htod_dtoh() {
2885        let ctx = CudaContext::new_non_primary(0, 0).unwrap();
2886        let stream = ctx.default_stream();
2887        let data = stream.clone_htod(&[1.0f32, 2.0, 3.0]).unwrap();
2888        let result = stream.clone_dtoh(&data).unwrap();
2889        assert_eq!(result, std::vec![1.0f32, 2.0, 3.0]);
2890    }
2891
2892    #[test]
2893    #[cfg(any(
2894        feature = "cuda-11040",
2895        feature = "cuda-11050",
2896        feature = "cuda-11060",
2897        feature = "cuda-11070",
2898        feature = "cuda-11080",
2899        feature = "cuda-12000",
2900        feature = "cuda-12010",
2901        feature = "cuda-12020",
2902        feature = "cuda-12030",
2903        feature = "cuda-12040",
2904        feature = "cuda-12050",
2905        feature = "cuda-12060",
2906        feature = "cuda-12080",
2907        feature = "cuda-12090",
2908        feature = "cuda-13000",
2909        feature = "cuda-13010",
2910    ))]
2911    fn test_new_non_primary_cross_thread() {
2912        let ctx = CudaContext::new_non_primary(0, 0).unwrap();
2913        let ctx2 = ctx.clone();
2914        let handle = std::thread::spawn(move || {
2915            ctx2.bind_to_thread().unwrap();
2916            let stream = ctx2.default_stream();
2917            let data = stream.clone_htod(&[4.0f32, 5.0, 6.0]).unwrap();
2918            let result = stream.clone_dtoh(&data).unwrap();
2919            assert_eq!(result, std::vec![4.0f32, 5.0, 6.0]);
2920        });
2921        handle.join().unwrap();
2922    }
2923}