1use std::fmt;
32use std::ops::{BitAnd, BitOr, Deref, DerefMut};
33
34use oxicuda_driver::error::{CudaError, CudaResult};
35use oxicuda_driver::ffi::{
36 CU_MEMHOSTREGISTER_DEVICEMAP, CU_MEMHOSTREGISTER_IOMEMORY, CU_MEMHOSTREGISTER_PORTABLE,
37 CU_MEMHOSTREGISTER_READ_ONLY, CUdeviceptr,
38};
39
40#[cfg(not(target_os = "macos"))]
41use oxicuda_driver::ffi;
42#[cfg(not(target_os = "macos"))]
43use oxicuda_driver::loader::try_driver;
44#[cfg(not(target_os = "macos"))]
45use std::ffi::c_void;
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
53pub struct RegisterFlags(u32);
54
55impl RegisterFlags {
56 pub const PORTABLE: Self = Self(CU_MEMHOSTREGISTER_PORTABLE);
58
59 pub const DEVICE_MAP: Self = Self(CU_MEMHOSTREGISTER_DEVICEMAP);
62
63 pub const IO_MEMORY: Self = Self(CU_MEMHOSTREGISTER_IOMEMORY);
65
66 pub const READ_ONLY: Self = Self(CU_MEMHOSTREGISTER_READ_ONLY);
68
69 pub const DEFAULT: Self = Self(CU_MEMHOSTREGISTER_PORTABLE | CU_MEMHOSTREGISTER_DEVICEMAP);
71
72 pub const NONE: Self = Self(0);
74
75 #[inline]
77 pub const fn bits(self) -> u32 {
78 self.0
79 }
80
81 #[inline]
83 pub const fn from_bits(bits: u32) -> Self {
84 Self(bits)
85 }
86
87 #[inline]
89 pub const fn contains(self, other: Self) -> bool {
90 (self.0 & other.0) == other.0
91 }
92}
93
94impl BitOr for RegisterFlags {
95 type Output = Self;
96
97 #[inline]
98 fn bitor(self, rhs: Self) -> Self {
99 Self(self.0 | rhs.0)
100 }
101}
102
103impl BitAnd for RegisterFlags {
104 type Output = Self;
105
106 #[inline]
107 fn bitand(self, rhs: Self) -> Self {
108 Self(self.0 & rhs.0)
109 }
110}
111
112impl fmt::Display for RegisterFlags {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 let mut parts = Vec::new();
115 if self.contains(Self::PORTABLE) {
116 parts.push("PORTABLE");
117 }
118 if self.contains(Self::DEVICE_MAP) {
119 parts.push("DEVICE_MAP");
120 }
121 if self.contains(Self::IO_MEMORY) {
122 parts.push("IO_MEMORY");
123 }
124 if self.contains(Self::READ_ONLY) {
125 parts.push("READ_ONLY");
126 }
127 if parts.is_empty() {
128 write!(f, "NONE")
129 } else {
130 write!(f, "{}", parts.join(" | "))
131 }
132 }
133}
134
135pub struct RegisteredMemory<T: Copy> {
151 ptr: *mut T,
153 len: usize,
155 flags: RegisterFlags,
157 device_ptr: CUdeviceptr,
159}
160
161unsafe impl<T: Copy + Send> Send for RegisteredMemory<T> {}
164unsafe impl<T: Copy + Sync> Sync for RegisteredMemory<T> {}
165
166impl<T: Copy> RegisteredMemory<T> {
167 #[inline]
169 pub fn as_ptr(&self) -> *const T {
170 self.ptr
171 }
172
173 #[inline]
175 pub fn as_mut_ptr(&mut self) -> *mut T {
176 self.ptr
177 }
178
179 #[inline]
183 pub fn device_ptr(&self) -> CUdeviceptr {
184 self.device_ptr
185 }
186
187 #[inline]
189 pub fn len(&self) -> usize {
190 self.len
191 }
192
193 #[inline]
195 pub fn is_empty(&self) -> bool {
196 self.len == 0
197 }
198
199 #[inline]
201 pub fn flags(&self) -> RegisterFlags {
202 self.flags
203 }
204
205 #[inline]
207 pub fn as_slice(&self) -> &[T] {
208 unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
211 }
212
213 #[inline]
215 pub fn as_mut_slice(&mut self) -> &mut [T] {
216 unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
219 }
220}
221
222impl<T: Copy> Deref for RegisteredMemory<T> {
223 type Target = [T];
224
225 #[inline]
226 fn deref(&self) -> &[T] {
227 self.as_slice()
228 }
229}
230
231impl<T: Copy> DerefMut for RegisteredMemory<T> {
232 #[inline]
233 fn deref_mut(&mut self) -> &mut [T] {
234 self.as_mut_slice()
235 }
236}
237
238impl<T: Copy> Drop for RegisteredMemory<T> {
239 fn drop(&mut self) {
240 #[cfg(not(target_os = "macos"))]
241 {
242 if let Ok(api) = try_driver() {
243 let rc = unsafe { (api.cu_mem_host_unregister)(self.ptr.cast::<c_void>()) };
244 if rc != 0 {
245 tracing::warn!(
246 cuda_error = rc,
247 len = self.len,
248 "cuMemHostUnregister failed during RegisteredMemory drop"
249 );
250 }
251 }
252 }
253 }
254}
255
256pub fn register<T: Copy>(
273 ptr: *mut T,
274 len: usize,
275 flags: RegisterFlags,
276) -> CudaResult<RegisteredMemory<T>> {
277 if len == 0 {
278 return Err(CudaError::InvalidValue);
279 }
280 if ptr.is_null() {
281 return Err(CudaError::InvalidValue);
282 }
283 let byte_size = len
284 .checked_mul(std::mem::size_of::<T>())
285 .ok_or(CudaError::InvalidValue)?;
286
287 #[cfg(target_os = "macos")]
288 {
289 let _ = byte_size;
292 Ok(RegisteredMemory {
293 ptr,
294 len,
295 flags,
296 device_ptr: ptr as CUdeviceptr,
297 })
298 }
299
300 #[cfg(not(target_os = "macos"))]
301 {
302 let api = try_driver()?;
303
304 let rc =
306 unsafe { (api.cu_mem_host_register_v2)(ptr.cast::<c_void>(), byte_size, flags.bits()) };
307 oxicuda_driver::check(rc)?;
308
309 let device_ptr = if flags.contains(RegisterFlags::DEVICE_MAP) {
311 let mut dptr: CUdeviceptr = 0;
312 let rc2 = unsafe {
313 (api.cu_mem_host_get_device_pointer_v2)(&mut dptr, ptr.cast::<c_void>(), 0)
314 };
315 if let Err(e) = oxicuda_driver::check(rc2) {
316 let rc_unreg = unsafe { (api.cu_mem_host_unregister)(ptr.cast::<c_void>()) };
321 if rc_unreg != 0 {
322 tracing::warn!(
323 cuda_error = rc_unreg,
324 "cuMemHostUnregister failed while rolling back after \
325 cuMemHostGetDevicePointer error"
326 );
327 }
328 return Err(e);
329 }
330 dptr
331 } else {
332 0
333 };
334
335 Ok(RegisteredMemory {
336 ptr,
337 len,
338 flags,
339 device_ptr,
340 })
341 }
342}
343
344pub fn register_slice<T: Copy>(
350 slice: &mut [T],
351 flags: RegisterFlags,
352) -> CudaResult<RegisteredMemory<T>> {
353 register(slice.as_mut_ptr(), slice.len(), flags)
354}
355
356pub fn register_vec<T: Copy>(
365 vec: &mut Vec<T>,
366 flags: RegisterFlags,
367) -> CudaResult<RegisteredMemory<T>> {
368 register(vec.as_mut_ptr(), vec.len(), flags)
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
377pub enum RegisteredMemoryType {
378 Host,
380 Device,
382 Unified,
384 Unregistered,
386}
387
388impl fmt::Display for RegisteredMemoryType {
389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390 match self {
391 Self::Host => write!(f, "Host"),
392 Self::Device => write!(f, "Device"),
393 Self::Unified => write!(f, "Unified"),
394 Self::Unregistered => write!(f, "Unregistered"),
395 }
396 }
397}
398
399#[derive(Debug, Clone, Copy)]
401pub struct RegisteredPointerInfo {
402 pub device_ptr: CUdeviceptr,
404 pub is_managed: bool,
406 pub memory_type: RegisteredMemoryType,
408}
409
410pub fn query_registered_pointer_info(ptr: *const u8) -> CudaResult<RegisteredPointerInfo> {
418 if ptr.is_null() {
419 return Err(CudaError::InvalidValue);
420 }
421
422 #[cfg(target_os = "macos")]
423 {
424 Ok(RegisteredPointerInfo {
426 device_ptr: ptr as CUdeviceptr,
427 is_managed: false,
428 memory_type: RegisteredMemoryType::Host,
429 })
430 }
431
432 #[cfg(not(target_os = "macos"))]
433 {
434 let api = try_driver()?;
435 let dev_ptr_val = ptr as CUdeviceptr;
436
437 let mut mem_type: u32 = 0;
439 let rc = unsafe {
440 (api.cu_pointer_get_attribute)(
441 (&mut mem_type as *mut u32).cast::<c_void>(),
442 ffi::CU_POINTER_ATTRIBUTE_MEMORY_TYPE,
443 dev_ptr_val,
444 )
445 };
446 let memory_type = if rc != 0 {
447 RegisteredMemoryType::Unregistered
449 } else {
450 match mem_type {
451 ffi::CU_MEMORYTYPE_HOST => RegisteredMemoryType::Host,
452 ffi::CU_MEMORYTYPE_DEVICE => RegisteredMemoryType::Device,
453 ffi::CU_MEMORYTYPE_UNIFIED => RegisteredMemoryType::Unified,
454 _ => RegisteredMemoryType::Unregistered,
455 }
456 };
457
458 let mut managed: u32 = 0;
460 let rc2 = unsafe {
461 (api.cu_pointer_get_attribute)(
462 (&mut managed as *mut u32).cast::<c_void>(),
463 ffi::CU_POINTER_ATTRIBUTE_IS_MANAGED,
464 dev_ptr_val,
465 )
466 };
467 let is_managed = rc2 == 0 && managed != 0;
468
469 let mut dptr: CUdeviceptr = 0;
471 let rc3 = unsafe {
472 (api.cu_pointer_get_attribute)(
473 (&mut dptr as *mut CUdeviceptr).cast::<c_void>(),
474 ffi::CU_POINTER_ATTRIBUTE_DEVICE_POINTER,
475 dev_ptr_val,
476 )
477 };
478 if rc3 != 0 {
479 dptr = 0;
480 }
481
482 Ok(RegisteredPointerInfo {
483 device_ptr: dptr,
484 is_managed,
485 memory_type,
486 })
487 }
488}
489
490#[cfg(test)]
495mod tests {
496 use super::*;
497
498 #[test]
501 fn flags_default_contains_portable_and_device_map() {
502 assert!(RegisterFlags::DEFAULT.contains(RegisterFlags::PORTABLE));
503 assert!(RegisterFlags::DEFAULT.contains(RegisterFlags::DEVICE_MAP));
504 assert!(!RegisterFlags::DEFAULT.contains(RegisterFlags::IO_MEMORY));
505 assert!(!RegisterFlags::DEFAULT.contains(RegisterFlags::READ_ONLY));
506 }
507
508 #[test]
509 fn flags_bitor_combines() {
510 let combined = RegisterFlags::PORTABLE | RegisterFlags::READ_ONLY;
511 assert!(combined.contains(RegisterFlags::PORTABLE));
512 assert!(combined.contains(RegisterFlags::READ_ONLY));
513 assert!(!combined.contains(RegisterFlags::IO_MEMORY));
514 }
515
516 #[test]
517 fn flags_bitand_intersects() {
518 let a = RegisterFlags::PORTABLE | RegisterFlags::DEVICE_MAP;
519 let b = RegisterFlags::PORTABLE | RegisterFlags::READ_ONLY;
520 let intersected = a & b;
521 assert!(intersected.contains(RegisterFlags::PORTABLE));
522 assert!(!intersected.contains(RegisterFlags::DEVICE_MAP));
523 assert!(!intersected.contains(RegisterFlags::READ_ONLY));
524 }
525
526 #[test]
527 fn flags_display() {
528 assert_eq!(RegisterFlags::NONE.to_string(), "NONE");
529 assert_eq!(RegisterFlags::PORTABLE.to_string(), "PORTABLE");
530 let default_str = RegisterFlags::DEFAULT.to_string();
531 assert!(default_str.contains("PORTABLE"));
532 assert!(default_str.contains("DEVICE_MAP"));
533 }
534
535 #[test]
536 fn flags_bits_roundtrip() {
537 let flags = RegisterFlags::PORTABLE | RegisterFlags::IO_MEMORY;
538 let bits = flags.bits();
539 assert_eq!(RegisterFlags::from_bits(bits), flags);
540 }
541
542 #[test]
543 fn flags_none_is_zero() {
544 assert_eq!(RegisterFlags::NONE.bits(), 0);
545 }
546
547 #[test]
550 fn memory_type_display() {
551 assert_eq!(RegisteredMemoryType::Host.to_string(), "Host");
552 assert_eq!(RegisteredMemoryType::Device.to_string(), "Device");
553 assert_eq!(RegisteredMemoryType::Unified.to_string(), "Unified");
554 assert_eq!(
555 RegisteredMemoryType::Unregistered.to_string(),
556 "Unregistered"
557 );
558 }
559
560 #[test]
561 fn memory_type_equality() {
562 assert_eq!(RegisteredMemoryType::Host, RegisteredMemoryType::Host);
563 assert_ne!(RegisteredMemoryType::Host, RegisteredMemoryType::Device);
564 }
565
566 #[test]
569 fn register_zero_len_fails() {
570 let mut buf = [0u8; 16];
571 let result = register(buf.as_mut_ptr(), 0, RegisterFlags::DEFAULT);
572 assert!(matches!(result, Err(CudaError::InvalidValue)));
573 }
574
575 #[test]
576 fn register_null_ptr_fails() {
577 let result = register::<u8>(std::ptr::null_mut(), 10, RegisterFlags::DEFAULT);
578 assert!(matches!(result, Err(CudaError::InvalidValue)));
579 }
580
581 #[test]
582 fn register_slice_zero_len_fails() {
583 let mut empty: [f32; 0] = [];
584 let result = register_slice(&mut empty, RegisterFlags::DEFAULT);
585 assert!(matches!(result, Err(CudaError::InvalidValue)));
586 }
587
588 #[test]
589 fn register_vec_zero_len_fails() {
590 let mut v: Vec<i32> = Vec::new();
591 let result = register_vec(&mut v, RegisterFlags::DEFAULT);
592 assert!(matches!(result, Err(CudaError::InvalidValue)));
593 }
594
595 #[test]
596 fn query_null_ptr_fails() {
597 let result = query_registered_pointer_info(std::ptr::null());
598 assert!(matches!(result, Err(CudaError::InvalidValue)));
599 }
600
601 #[cfg(target_os = "macos")]
604 mod macos_tests {
605 use super::*;
606
607 #[test]
608 fn register_slice_succeeds_on_macos() {
609 let mut data = vec![1.0f32, 2.0, 3.0, 4.0];
610 let reg = register_slice(data.as_mut_slice(), RegisterFlags::DEFAULT);
611 let reg = reg.ok();
612 assert!(reg.is_some());
613 let reg = reg.inspect(|r| {
614 assert_eq!(r.len(), 4);
615 assert!(!r.is_empty());
616 assert_eq!(r.flags(), RegisterFlags::DEFAULT);
617 assert_eq!(r.as_slice(), &[1.0, 2.0, 3.0, 4.0]);
618 });
619 drop(reg);
620 }
621
622 #[test]
623 fn register_vec_succeeds_on_macos() {
624 let mut v = vec![10u32, 20, 30];
625 let reg = register_vec(&mut v, RegisterFlags::PORTABLE);
626 assert!(reg.is_ok());
627 if let Ok(r) = reg {
628 assert_eq!(r.len(), 3);
629 assert_eq!(r.flags(), RegisterFlags::PORTABLE);
630 assert_ne!(r.device_ptr(), 0);
631 }
632 }
633
634 #[test]
635 fn registered_memory_deref_works() {
636 let mut data = vec![100i64, 200, 300];
637 let reg = register_vec(&mut data, RegisterFlags::DEFAULT);
638 assert!(reg.is_ok());
639 if let Ok(r) = reg {
640 let slice: &[i64] = &r;
642 assert_eq!(slice.len(), 3);
643 assert_eq!(slice[0], 100);
644 assert_eq!(slice[2], 300);
645 }
646 }
647
648 #[test]
649 fn registered_memory_deref_mut_works() {
650 let mut data = vec![1u8, 2, 3, 4, 5];
651 let reg = register_slice(&mut data, RegisterFlags::DEFAULT);
652 assert!(reg.is_ok());
653 if let Ok(mut r) = reg {
654 r[0] = 99;
655 assert_eq!(r[0], 99);
656 let mslice: &mut [u8] = &mut r;
657 mslice[4] = 88;
658 assert_eq!(mslice[4], 88);
659 }
660 }
661
662 #[test]
663 fn query_pointer_info_on_macos() {
664 let data = [42u8; 64];
665 let info = query_registered_pointer_info(data.as_ptr());
666 assert!(info.is_ok());
667 if let Ok(info) = info {
668 assert!(!info.is_managed);
669 assert_eq!(info.memory_type, RegisteredMemoryType::Host);
670 assert_ne!(info.device_ptr, 0);
671 }
672 }
673
674 #[test]
675 fn registered_memory_as_ptr_mut_ptr() {
676 let mut data = vec![5.0f64; 10];
677 let original_ptr = data.as_mut_ptr();
678 let reg = register_vec(&mut data, RegisterFlags::DEFAULT);
679 assert!(reg.is_ok());
680 if let Ok(mut r) = reg {
681 assert_eq!(r.as_ptr(), original_ptr as *const f64);
682 assert_eq!(r.as_mut_ptr(), original_ptr);
683 }
684 }
685 }
686
687 #[cfg(feature = "gpu-tests")]
690 mod gpu_tests {
691 use super::*;
692
693 #[test]
694 fn register_and_unregister_on_gpu() {
695 if oxicuda_driver::init().is_err() || oxicuda_driver::Device::count().unwrap_or(0) == 0
700 {
701 return;
702 }
703 let Ok(dev) = oxicuda_driver::Device::get(0) else {
704 return;
705 };
706 let Ok(_ctx) = oxicuda_driver::Context::new(&dev) else {
707 return;
708 };
709 let mut data = vec![0.0f32; 4096];
712 let reg = register_vec(&mut data, RegisterFlags::DEFAULT);
713 assert!(reg.is_ok(), "registration failed: {:?}", reg.err());
714 if let Ok(r) = reg {
715 assert_eq!(r.len(), 4096);
716 assert!(r.device_ptr() != 0, "device_ptr should be non-zero");
717 }
718 }
719 }
720}