1#![allow(clippy::missing_safety_doc)]
6
7use libloading::Library;
8use std::ffi::c_void;
9
10pub type CUresult = i32;
13type CUdeviceptr = u64;
14
15macro_rules! driver_api {
16 ($( $name:ident : fn( $($arg:ty),* ) ; )*) => {
17 #[allow(non_snake_case)]
20 pub struct Cuda {
21 _lib: Library,
22 $( $name: unsafe extern "C" fn($($arg),*) -> CUresult, )*
23 }
24
25 impl Cuda {
26 pub fn load() -> Result<Self, String> {
29 let lib = ["libcuda.so.1", "libcuda.so"]
30 .iter()
31 .find_map(|n| unsafe { Library::new(n).ok() })
32 .ok_or_else(|| {
33 "libcuda not found: benchmarks need an NVIDIA driver".to_string()
34 })?;
35 unsafe {
36 Ok(Cuda {
37 $( $name: *lib
38 .get(concat!(stringify!($name), "\0").as_bytes())
39 .map_err(|e| format!("missing {}: {e}", stringify!($name)))?, )*
40 _lib: lib,
41 })
42 }
43 }
44 }
45 };
46}
47
48driver_api! {
49 cuInit: fn(u32);
50 cuDriverGetVersion: fn(*mut i32);
51 cuDeviceGet: fn(*mut i32, i32);
52 cuDeviceGetName: fn(*mut u8, i32, i32);
53 cuDeviceGetAttribute: fn(*mut i32, i32, i32);
54 cuCtxCreate_v2: fn(*mut *mut c_void, u32, i32);
55 cuCtxDestroy_v2: fn(*mut c_void);
56 cuCtxSynchronize: fn();
57 cuModuleLoadData: fn(*mut *mut c_void, *const c_void);
58 cuModuleUnload: fn(*mut c_void);
59 cuModuleGetFunction: fn(*mut *mut c_void, *mut c_void, *const u8);
60 cuMemAlloc_v2: fn(*mut CUdeviceptr, usize);
61 cuMemFree_v2: fn(CUdeviceptr);
62 cuMemcpyHtoD_v2: fn(CUdeviceptr, *const c_void, usize);
63 cuMemcpyDtoH_v2: fn(*mut c_void, CUdeviceptr, usize);
64 cuLaunchKernel: fn(*mut c_void, u32, u32, u32, u32, u32, u32, u32, *mut c_void, *mut *mut c_void, *mut *mut c_void);
65 cuEventCreate: fn(*mut *mut c_void, u32);
66 cuEventDestroy_v2: fn(*mut c_void);
67 cuEventRecord: fn(*mut c_void, *mut c_void);
68 cuEventSynchronize: fn(*mut c_void);
69 cuEventElapsedTime: fn(*mut f32, *mut c_void, *mut c_void);
70}
71
72const CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR: i32 = 75;
73const CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR: i32 = 76;
74
75fn check(what: &str, code: CUresult) -> Result<(), String> {
76 if code == 0 {
77 Ok(())
78 } else {
79 Err(format!("{what} failed: CUresult {code}"))
80 }
81}
82
83pub struct Device {
89 cuda: Cuda,
90 ctx: *mut c_void,
91 pub name: String,
93 pub cc: String,
95 pub driver_version: String,
97}
98
99pub struct Module<'d> {
101 device: &'d Device,
102 module: *mut c_void,
103 pub function: *mut c_void,
105}
106
107pub struct Buffer<'d> {
109 device: &'d Device,
110 pub ptr: CUdeviceptr,
112 pub bytes: usize,
114}
115
116impl Device {
117 pub fn open() -> Result<Self, String> {
123 let cuda = Cuda::load()?;
124 unsafe {
125 check("cuInit", (cuda.cuInit)(0))?;
126 let mut version = 0i32;
127 check(
128 "cuDriverGetVersion",
129 (cuda.cuDriverGetVersion)(&mut version),
130 )?;
131 let mut dev = 0i32;
132 check("cuDeviceGet", (cuda.cuDeviceGet)(&mut dev, 0))?;
133 let mut name = [0u8; 128];
134 check(
135 "cuDeviceGetName",
136 (cuda.cuDeviceGetName)(name.as_mut_ptr(), name.len() as i32, dev),
137 )?;
138 let (mut major, mut minor) = (0i32, 0i32);
139 check(
140 "cc major",
141 (cuda.cuDeviceGetAttribute)(
142 &mut major,
143 CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR,
144 dev,
145 ),
146 )?;
147 check(
148 "cc minor",
149 (cuda.cuDeviceGetAttribute)(
150 &mut minor,
151 CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR,
152 dev,
153 ),
154 )?;
155 let mut ctx = std::ptr::null_mut();
156 check("cuCtxCreate", (cuda.cuCtxCreate_v2)(&mut ctx, 0, dev))?;
157 let name = String::from_utf8_lossy(
158 &name[..name.iter().position(|&b| b == 0).unwrap_or(name.len())],
159 )
160 .to_string();
161 Ok(Device {
162 cuda,
163 ctx,
164 name,
165 cc: format!("{major}.{minor}"),
166 driver_version: format!("{}.{}", version / 1000, (version % 1000) / 10),
167 })
168 }
169 }
170
171 pub fn load_module(&self, ptx: &str, entry: &str) -> Result<Module<'_>, String> {
173 let mut ptx_z = ptx.as_bytes().to_vec();
174 ptx_z.push(0);
175 let mut entry_z = entry.as_bytes().to_vec();
176 entry_z.push(0);
177 unsafe {
178 let mut module = std::ptr::null_mut();
179 check(
180 "cuModuleLoadData",
181 (self.cuda.cuModuleLoadData)(&mut module, ptx_z.as_ptr().cast()),
182 )?;
183 let mut function = std::ptr::null_mut();
184 let got = (self.cuda.cuModuleGetFunction)(&mut function, module, entry_z.as_ptr());
185 if got != 0 {
186 (self.cuda.cuModuleUnload)(module);
187 return Err(format!(
188 "cuModuleGetFunction({entry}) failed: CUresult {got}"
189 ));
190 }
191 Ok(Module {
192 device: self,
193 module,
194 function,
195 })
196 }
197 }
198
199 pub fn alloc(&self, bytes: usize) -> Result<Buffer<'_>, String> {
201 let mut ptr = 0u64;
202 unsafe { check("cuMemAlloc", (self.cuda.cuMemAlloc_v2)(&mut ptr, bytes))? };
203 Ok(Buffer {
204 device: self,
205 ptr,
206 bytes,
207 })
208 }
209
210 pub fn copy_in(&self, buffer: &Buffer<'_>, data: &[u8]) -> Result<(), String> {
212 assert!(data.len() <= buffer.bytes);
213 unsafe {
214 check(
215 "cuMemcpyHtoD",
216 (self.cuda.cuMemcpyHtoD_v2)(buffer.ptr, data.as_ptr().cast(), data.len()),
217 )
218 }
219 }
220
221 pub fn copy_out(&self, buffer: &Buffer<'_>, out: &mut [u8]) -> Result<(), String> {
223 assert!(out.len() <= buffer.bytes);
224 unsafe {
225 check(
226 "cuMemcpyDtoH",
227 (self.cuda.cuMemcpyDtoH_v2)(out.as_mut_ptr().cast(), buffer.ptr, out.len()),
228 )
229 }
230 }
231
232 pub fn synchronize(&self) -> Result<(), String> {
238 unsafe { check("cuCtxSynchronize", (self.cuda.cuCtxSynchronize)()) }
239 }
240
241 pub fn timed_launch(
244 &self,
245 module: &Module<'_>,
246 grid: [u32; 3],
247 block: [u32; 3],
248 params: &mut [*mut c_void],
249 ) -> Result<f64, String> {
250 unsafe {
251 let mut ev0 = std::ptr::null_mut();
252 let mut ev1 = std::ptr::null_mut();
253 check("cuEventCreate", (self.cuda.cuEventCreate)(&mut ev0, 0))?;
254 check("cuEventCreate", (self.cuda.cuEventCreate)(&mut ev1, 0))?;
255 let stream = std::ptr::null_mut();
256 check("cuEventRecord", (self.cuda.cuEventRecord)(ev0, stream))?;
257 let launched = (self.cuda.cuLaunchKernel)(
258 module.function,
259 grid[0],
260 grid[1],
261 grid[2],
262 block[0],
263 block[1],
264 block[2],
265 0, stream,
267 params.as_mut_ptr(),
268 std::ptr::null_mut(),
269 );
270 if launched != 0 {
271 (self.cuda.cuEventDestroy_v2)(ev0);
272 (self.cuda.cuEventDestroy_v2)(ev1);
273 return Err(format!("cuLaunchKernel failed: CUresult {launched}"));
274 }
275 check("cuEventRecord", (self.cuda.cuEventRecord)(ev1, stream))?;
276 check("cuEventSynchronize", (self.cuda.cuEventSynchronize)(ev1))?;
277 let mut ms = 0f32;
278 check(
279 "cuEventElapsedTime",
280 (self.cuda.cuEventElapsedTime)(&mut ms, ev0, ev1),
281 )?;
282 (self.cuda.cuEventDestroy_v2)(ev0);
283 (self.cuda.cuEventDestroy_v2)(ev1);
284 Ok(ms as f64)
285 }
286 }
287}
288
289impl Drop for Module<'_> {
290 fn drop(&mut self) {
291 unsafe {
292 (self.device.cuda.cuModuleUnload)(self.module);
293 }
294 }
295}
296
297impl Drop for Buffer<'_> {
298 fn drop(&mut self) {
299 unsafe {
300 (self.device.cuda.cuMemFree_v2)(self.ptr);
301 }
302 }
303}
304
305impl Drop for Device {
306 fn drop(&mut self) {
307 unsafe {
308 (self.cuda.cuCtxDestroy_v2)(self.ctx);
309 }
310 }
311}