ulib/
lib.rs

1//! # ulib: General library for universal computing.
2//!
3//! This library basically implements traits and structs for holding vectors on hosts and different kinds of devices.
4//! It is intended to be used with ucc builder which generates wrapper bindings using this library.
5//!
6//! CUDA support must be manually enabled using the
7//! feature `cuda`.
8
9#[allow(unused_imports)]
10use lazy_static::lazy_static;
11
12// For our derive macros to refer to cust even when cust is
13// not listed as a dependency in our dependent crates.
14#[cfg(feature = "cuda")]
15pub extern crate cust;
16
17pub use ulib_derive::UniversalCopy;
18
19#[cfg(feature = "cuda")]
20use cust::memory::{ DeviceCopy, DeviceSlice };
21
22#[cfg(feature = "cuda")]
23pub const MAX_NUM_CUDA_DEVICES: usize = 4;
24#[cfg(feature = "cuda")]
25pub const MAX_DEVICES: usize = MAX_NUM_CUDA_DEVICES + 1;
26
27#[cfg(not(feature = "cuda"))]
28pub const MAX_DEVICES: usize = 1;
29
30/// All supported device types.
31#[derive(Copy, Clone, Debug, PartialEq, Eq)]
32pub enum Device {
33    CPU,
34    #[cfg(feature = "cuda")]
35    CUDA(u8 /* device id */)
36}
37
38/// A generic device context
39pub struct DeviceContext {
40    #[cfg(feature = "cuda")]
41    #[allow(dead_code)]
42    cuda_context: Option<cust::context::Context>,
43}
44
45impl Device {
46    #[inline]
47    fn to_id(self) -> usize {
48        use Device::*;
49        match self {
50            CPU => 0,
51            #[cfg(feature = "cuda")]
52            CUDA(c) => {
53                assert!((c as usize) < MAX_NUM_CUDA_DEVICES,
54                        "invalid cuda device id");
55                c as usize + 1
56            }
57        }
58    }
59    
60    #[inline]
61    fn from_id(id: usize) -> Device {
62        use Device::*;
63        match id {
64            0 => CPU,
65            #[cfg(feature = "cuda")]
66            c @ 1..=MAX_NUM_CUDA_DEVICES => CUDA(c as u8 - 1),
67            id @ _ => panic!("device id {} is invalid.", id)
68        }
69    }
70
71    #[inline]
72    pub fn get_context(self) -> DeviceContext {
73        use Device::*;
74        match self {
75            CPU => DeviceContext {
76                #[cfg(feature = "cuda")]
77                cuda_context: None
78            },
79            #[cfg(feature = "cuda")]
80            CUDA(c) => DeviceContext {
81                cuda_context: Some(cust::context::Context::new(
82                    CUDA_DEVICES[c as usize].0).unwrap())
83            }
84        }
85    }
86
87    #[inline]
88    pub fn synchronize(self) {
89        use Device::*;
90        match self {
91            CPU => {},
92            #[cfg(feature = "cuda")]
93            CUDA(c) => {
94                let _context = cust::context::Context::new(
95                    CUDA_DEVICES[c as usize].0).unwrap();
96                cust::context::CurrentContext::synchronize().unwrap();
97            }
98        }
99    }
100}
101
102/// The trait for universally bit-copyable element.
103///
104/// For cuda build, this is equivalent to `Copy + cust::DeviceCopy`.
105/// You can use the derive macro like this:
106///
107/// ```
108/// use ulib::UniversalCopy;
109/// 
110/// #[derive(UniversalCopy, Clone)]
111/// struct Test {
112///     a: i32,
113///     b: usize
114/// }
115/// ```
116#[cfg(feature = "cuda")]
117pub trait UniversalCopy: Copy + DeviceCopy { }
118#[cfg(feature = "cuda")]
119impl<T: Copy + DeviceCopy> UniversalCopy for T { }
120
121/// The trait for universally bit-copyable element.
122///
123/// For cpu-only build, this is equivalent to a pure `Copy`.
124/// You can use the derive macro like this:
125///
126/// ```
127/// use ulib::UniversalCopy;
128/// 
129/// #[derive(UniversalCopy, Clone)]
130/// struct Test {
131///     a: i32,
132///     b: usize
133/// }
134/// ```
135#[cfg(not(feature = "cuda"))]
136pub trait UniversalCopy: Copy { }
137#[cfg(not(feature = "cuda"))]
138impl<T: Copy> UniversalCopy for T { }
139
140#[cfg(feature = "cuda")]
141lazy_static! {
142    /// vector of all devices and their primary contexts.
143    ///
144    /// the contexts follow the CUDA Driver API, not the runtime API.
145    /// all contexts are kept here so they are never deallocated.
146    static ref CUDA_DEVICES: Vec<(cust::device::Device, cust::context::Context)> = {
147        // initialize the CUDA driver here and only here.
148        cust::init(cust::CudaFlags::empty()).unwrap();
149        let mut ret = cust::device::Device::devices().unwrap()
150            .map(|d| {
151                let d = d.unwrap();
152                (d, cust::context::Context::new(d).unwrap())
153            })
154            .collect::<Vec<_>>();
155        if ret.len() > MAX_NUM_CUDA_DEVICES as usize {
156            clilog::warn!(ULIB_CUDA_TRUNC,
157                          "the number of available cuda gpus {} \
158                           exceed max supported {}, truncated.",
159                          ret.len(), MAX_NUM_CUDA_DEVICES);
160            ret.truncate(MAX_NUM_CUDA_DEVICES as usize);
161        }
162        ret
163    };
164    
165    /// the number of CUDA devices.
166    pub static ref NUM_CUDA_DEVICES: usize = CUDA_DEVICES.len();
167}
168
169/// A trait for objects that can be borrowed as an immutable CUDA slice.
170#[cfg(feature = "cuda")]
171pub trait AsCUDASlice<T: UniversalCopy> {
172    /// Get an immutable CUDA slice on a specific GPU.
173    /// 
174    /// There is no borrow checker taking place, so nothing
175    /// prevents one from using it mutably. Just don't do it.
176    /// It would not only lead to data races, but also safety
177    /// issues due to the possible Vec-like reallocation.
178    /// 
179    /// If one needs to update the content, use [`AsCUDASliceMut`]
180    /// instead as it tracks the dirty flags correctly.
181    fn as_cuda_slice(&self, cuda_device: Device) -> DeviceSlice<T>;
182}
183
184/// A trait for objects that can be borrowed as a mutable CUDA slice.
185#[cfg(feature = "cuda")]
186pub trait AsCUDASliceMut<T: UniversalCopy> {
187    /// Get an immutable CUDA slice on a specific GPU.
188    fn as_cuda_slice_mut(&mut self, cuda_device: Device) ->
189        DeviceSlice<T>;
190}
191
192/// A trait to get raw pointer for any device.
193pub trait AsUPtr<T: UniversalCopy> {
194    /// Get an immutable raw pointer.
195    fn as_uptr(&self, device: Device) -> *const T;
196}
197
198/// A trait to get mutable raw pointer for any device.
199pub trait AsUPtrMut<T: UniversalCopy> {
200    /// Get a mutable raw pointer.
201    fn as_mut_uptr(&mut self, device: Device) -> *mut T;
202}
203
204mod uvec;
205pub use uvec::UVec;