lib/sys/src/cuda.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 //! CUDA Driver API bindings: ABI types, the dynamically loaded entry points,
2 //! and `CUresult` mapped to typed errors.
3
4 const std = @import("std");
5 const capabilities = @import("capabilities.zig");
6 const dynamic = @import("dynamic.zig");
7
8 pub const required_capabilities = capabilities.host(&.{ .allocator, .dynamic_loading });
9
10 const log = std.log.scoped(.cuda_driver);
11
12 pub const CUdevice = i32;
13
14 pub const CUdevice_attribute = enum(c_int) {
15 max_shared_memory_per_block = 8,
16 compute_capability_major = 75,
17 compute_capability_minor = 76,
18 max_shared_memory_per_block_optin = 97,
19 _,
20 };
21
22 pub const CUfunction_attribute = enum(c_int) {
23 max_dynamic_shared_size_bytes = 8,
24 _,
25 };
26
27 pub const CUcontext = ?*anyopaque;
28
29 pub const CUmodule = ?*anyopaque;
30
31 pub const CUfunction = ?*anyopaque;
32
33 pub const CUdeviceptr = u64;
34
35 pub const CUstream = ?*anyopaque;
36
37 pub const CUevent = ?*anyopaque;
38
39 pub const CUjit_option = enum(c_int) {
40 info_log_buffer = 3,
41 info_log_buffer_size_bytes = 4,
42 error_log_buffer = 5,
43 error_log_buffer_size_bytes = 6,
44 log_verbose = 12,
45 };
46
47 pub const CUresult = enum(c_int) {
48 success = 0,
49 invalid_value = 1,
50 out_of_memory = 2,
51 not_initialized = 3,
52 deinitialized = 4,
53 no_device = 100,
54 invalid_device = 101,
55 invalid_image = 200,
56 invalid_context = 201,
57 no_binary_for_gpu = 209,
58 invalid_ptx = 218,
59 jit_compiler_not_found = 221,
60 unsupported_ptx_version = 222,
61 invalid_source = 300,
62 file_not_found = 301,
63 shared_object_symbol_not_found = 302,
64 shared_object_init_failed = 303,
65 invalid_handle = 400,
66 not_found = 500,
67 not_ready = 600,
68 illegal_address = 700,
69 launch_out_of_resources = 701,
70 launch_timeout = 702,
71 launch_incompatible_texturing = 703,
72 launch_failed = 719,
73 not_supported = 801,
74 unknown = 999,
75 _,
76 };
77
78 pub const Error = error{
79 CudaDriverNotFound,
80 CudaSymbolMissing,
81 CudaNotInitialized,
82 CudaDeinitialized,
83 CudaOutOfMemory,
84 CudaInvalidValue,
85 CudaInvalidContext,
86 CudaInvalidImage,
87 CudaInvalidHandle,
88 CudaInvalidPtx,
89 CudaUnsupportedPtxVersion,
90 CudaJitCompilerNotFound,
91 CudaLaunchFailed,
92 CudaLaunchOutOfResources,
93 CudaLaunchTimeout,
94 CudaLaunchIncompatibleTexturing,
95 CudaNoDevice,
96 CudaInvalidDevice,
97 CudaNoBinaryForGpu,
98 CudaIllegalAddress,
99 CudaNotSupported,
100 CudaSharedObjectSymbolNotFound,
101 CudaSharedObjectInitFailed,
102 CudaUnknown,
103 CudaStreamPoisoned,
104 };
105
106 pub const Driver = struct {
107 lib: dynamic.Library,
108
109 cuInit: *const fn (flags: c_uint) callconv(.c) CUresult,
110 cuDriverGetVersion: *const fn (out: *c_int) callconv(.c) CUresult,
111 cuDeviceGetCount: *const fn (out: *c_int) callconv(.c) CUresult,
112 cuDeviceGet: *const fn (out: *CUdevice, ordinal: c_int) callconv(.c) CUresult,
113 cuDeviceGetName: *const fn (name: [*]u8, len: c_int, dev: CUdevice) callconv(.c) CUresult,
114 cuDeviceGetAttribute: *const fn (out: *c_int, attrib: CUdevice_attribute, dev: CUdevice) callconv(.c) CUresult,
115 cuCtxCreate_v2: *const fn (out: *CUcontext, flags: c_uint, dev: CUdevice) callconv(.c) CUresult,
116 cuCtxDestroy_v2: *const fn (ctx: CUcontext) callconv(.c) CUresult,
117 cuCtxSynchronize: *const fn () callconv(.c) CUresult,
118 cuModuleLoadData: *const fn (out: *CUmodule, image: [*]const u8) callconv(.c) CUresult,
119 cuModuleLoadDataEx: *const fn (
120 out: *CUmodule,
121 image: [*]const u8,
122 num_options: c_uint,
123 options: [*]const CUjit_option,
124 option_values: [*]?*anyopaque,
125 ) callconv(.c) CUresult,
126 cuModuleUnload: *const fn (mod: CUmodule) callconv(.c) CUresult,
127 cuModuleGetFunction: *const fn (out: *CUfunction, mod: CUmodule, name: [*:0]const u8) callconv(.c) CUresult,
128 cuFuncSetAttribute: *const fn (
129 func: CUfunction,
130 attrib: CUfunction_attribute,
131 value: c_int,
132 ) callconv(.c) CUresult,
133 cuMemAlloc_v2: *const fn (out: *CUdeviceptr, bytes: usize) callconv(.c) CUresult,
134 cuMemFree_v2: *const fn (ptr: CUdeviceptr) callconv(.c) CUresult,
135 cuMemcpyHtoD_v2: *const fn (dst: CUdeviceptr, src: [*]const u8, bytes: usize) callconv(.c) CUresult,
136 cuMemcpyDtoH_v2: *const fn (dst: [*]u8, src: CUdeviceptr, bytes: usize) callconv(.c) CUresult,
137 cuMemsetD32_v2: *const fn (dst: CUdeviceptr, value: c_uint, count: usize) callconv(.c) CUresult,
138 cuLaunchKernel: *const fn (
139 func: CUfunction,
140 grid_x: c_uint,
141 grid_y: c_uint,
142 grid_z: c_uint,
143 block_x: c_uint,
144 block_y: c_uint,
145 block_z: c_uint,
146 shared_mem_bytes: c_uint,
147 stream: CUstream,
148 kernel_params: ?[*]?*anyopaque,
149 extra: ?[*]?*anyopaque,
150 ) callconv(.c) CUresult,
151 cuGetErrorName: *const fn (res: CUresult, out: *[*:0]const u8) callconv(.c) CUresult,
152 cuGetErrorString: *const fn (res: CUresult, out: *[*:0]const u8) callconv(.c) CUresult,
153
154 cuStreamCreate: *const fn (out: *CUstream, flags: c_uint) callconv(.c) CUresult,
155 cuStreamDestroy_v2: *const fn (stream: CUstream) callconv(.c) CUresult,
156 cuStreamSynchronize: *const fn (stream: CUstream) callconv(.c) CUresult,
157 cuStreamQuery: *const fn (stream: CUstream) callconv(.c) CUresult,
158 cuStreamWaitEvent: *const fn (stream: CUstream, event: CUevent, flags: c_uint) callconv(.c) CUresult,
159
160 cuEventCreate: *const fn (out: *CUevent, flags: c_uint) callconv(.c) CUresult,
161 cuEventDestroy_v2: *const fn (event: CUevent) callconv(.c) CUresult,
162 cuEventRecord: *const fn (event: CUevent, stream: CUstream) callconv(.c) CUresult,
163 cuEventSynchronize: *const fn (event: CUevent) callconv(.c) CUresult,
164 cuEventQuery: *const fn (event: CUevent) callconv(.c) CUresult,
165 cuEventElapsedTime: *const fn (out_ms: *f32, start: CUevent, end: CUevent) callconv(.c) CUresult,
166
167 pub fn open(allocator: std.mem.Allocator) Error!Driver {
168 _ = allocator;
169
170 var lib = dynamic.openCudaDriver() catch return error.CudaDriverNotFound;
171 errdefer lib.close();
172
173 return resolveAll(&lib);
174 }
175
176 fn resolveAll(lib: *dynamic.Library) Error!Driver {
177 var driver: Driver = undefined;
178 driver.lib = lib.*;
179
180 inline for (
181 @typeInfo(Driver).@"struct".field_names,
182 @typeInfo(Driver).@"struct".field_types,
183 @typeInfo(Driver).@"struct".field_attrs,
184 ) |field_name, field_name_type, field_name_attrs| {
185 const field = .{ .name = field_name, .type = field_name_type, .attrs = field_name_attrs };
186 if (comptime std.mem.eql(u8, field.name, "lib")) continue;
187 const symbol = lib.lookup(field.type, field.name) orelse {
188 log.err("cuda symbol missing: {s}", .{field.name});
189 return error.CudaSymbolMissing;
190 };
191 @field(driver, field.name) = symbol;
192 }
193
194 return driver;
195 }
196
197 pub fn close(self: *Driver) void {
198 self.lib.close();
199 }
200
201 pub fn fromResult(self: *const Driver, r: CUresult) Error!void {
202 if (r == .success) return;
203
204 const err = switch (r) {
205 .success => unreachable,
206 .invalid_value => Error.CudaInvalidValue,
207 .out_of_memory => Error.CudaOutOfMemory,
208 .not_initialized => Error.CudaNotInitialized,
209 .deinitialized => Error.CudaDeinitialized,
210 .no_device => Error.CudaNoDevice,
211 .invalid_device => Error.CudaInvalidDevice,
212 .invalid_image => Error.CudaInvalidImage,
213 .invalid_context => Error.CudaInvalidContext,
214 .no_binary_for_gpu => Error.CudaNoBinaryForGpu,
215 .invalid_ptx => Error.CudaInvalidPtx,
216 .jit_compiler_not_found => Error.CudaJitCompilerNotFound,
217 .unsupported_ptx_version => Error.CudaUnsupportedPtxVersion,
218 .invalid_source => Error.CudaInvalidImage,
219 .file_not_found => Error.CudaInvalidImage,
220 .shared_object_symbol_not_found => Error.CudaSharedObjectSymbolNotFound,
221 .shared_object_init_failed => Error.CudaSharedObjectInitFailed,
222 .invalid_handle => Error.CudaInvalidHandle,
223 .not_found => Error.CudaInvalidHandle,
224 .not_ready => Error.CudaUnknown,
225 .illegal_address => Error.CudaIllegalAddress,
226 .launch_out_of_resources => Error.CudaLaunchOutOfResources,
227 .launch_timeout => Error.CudaLaunchTimeout,
228 .launch_incompatible_texturing => Error.CudaLaunchIncompatibleTexturing,
229 .launch_failed => Error.CudaLaunchFailed,
230 .not_supported => Error.CudaNotSupported,
231 .unknown => Error.CudaUnknown,
232 _ => unknown: {
233 self.logUnknown(r);
234 break :unknown Error.CudaUnknown;
235 },
236 };
237 return err;
238 }
239
240 fn logUnknown(self: *const Driver, r: CUresult) void {
241 var name_ptr: [*:0]const u8 = "";
242 var text_ptr: [*:0]const u8 = "";
243 _ = self.cuGetErrorName(r, &name_ptr);
244 _ = self.cuGetErrorString(r, &text_ptr);
245 log.warn("unmapped CUresult {d}: {s} — {s}", .{
246 @backingInt(r),
247 std.mem.sliceTo(name_ptr, 0),
248 std.mem.sliceTo(text_ptr, 0),
249 });
250 }
251 };
252
253 comptime {
254 if (@sizeOf(CUresult) != @sizeOf(c_int)) {
255 @compileError("CUresult ABI drift: storage must be c_int");
256 }
257 if (@sizeOf(CUdevice_attribute) != @sizeOf(c_int)) {
258 @compileError("CUdevice_attribute ABI drift: storage must be c_int");
259 }
260 if (@sizeOf(CUfunction_attribute) != @sizeOf(c_int)) {
261 @compileError("CUfunction_attribute ABI drift: storage must be c_int");
262 }
263 if (@sizeOf(CUdeviceptr) != 8) {
264 @compileError("CUdeviceptr must be 64-bit on a 64-bit host");
265 }
266 }
267
268 pub fn platformSupported() bool {
269 return dynamic.hasHostCandidate(dynamic.cudaDriverCandidates());
270 }
271
272 fn skipIfHostHasDriver() !void {
273 if (!platformSupported()) return error.SkipZigTest;
274 if (dynamic.isCudaDriverLoadable()) return error.SkipZigTest;
275 }
276
277 test "Driver.open without libcuda returns CudaDriverNotFound" {
278 try skipIfHostHasDriver();
279 try std.testing.expectError(Error.CudaDriverNotFound, Driver.open(std.testing.allocator));
280 }
281
282 test "CUresult pins the documented ABI values" {
283 try std.testing.expectEqual(@as(c_int, 0), @backingInt(CUresult.success));
284 try std.testing.expectEqual(@as(c_int, 1), @backingInt(CUresult.invalid_value));
285 try std.testing.expectEqual(@as(c_int, 2), @backingInt(CUresult.out_of_memory));
286 try std.testing.expectEqual(@as(c_int, 3), @backingInt(CUresult.not_initialized));
287 try std.testing.expectEqual(@as(c_int, 4), @backingInt(CUresult.deinitialized));
288 try std.testing.expectEqual(@as(c_int, 100), @backingInt(CUresult.no_device));
289 try std.testing.expectEqual(@as(c_int, 200), @backingInt(CUresult.invalid_image));
290 try std.testing.expectEqual(@as(c_int, 218), @backingInt(CUresult.invalid_ptx));
291 try std.testing.expectEqual(@as(c_int, 222), @backingInt(CUresult.unsupported_ptx_version));
292 try std.testing.expectEqual(@as(c_int, 302), @backingInt(CUresult.shared_object_symbol_not_found));
293 try std.testing.expectEqual(@as(c_int, 303), @backingInt(CUresult.shared_object_init_failed));
294 try std.testing.expectEqual(@as(c_int, 701), @backingInt(CUresult.launch_out_of_resources));
295 try std.testing.expectEqual(@as(c_int, 702), @backingInt(CUresult.launch_timeout));
296 try std.testing.expectEqual(@as(c_int, 703), @backingInt(CUresult.launch_incompatible_texturing));
297 try std.testing.expectEqual(@as(c_int, 719), @backingInt(CUresult.launch_failed));
298 try std.testing.expectEqual(@as(c_int, 999), @backingInt(CUresult.unknown));
299 }
300
301 test "CUdevice_attribute pins compute capability ABI values" {
302 try std.testing.expectEqual(@as(c_int, 8), @backingInt(CUdevice_attribute.max_shared_memory_per_block));
303 try std.testing.expectEqual(@as(c_int, 75), @backingInt(CUdevice_attribute.compute_capability_major));
304 try std.testing.expectEqual(@as(c_int, 76), @backingInt(CUdevice_attribute.compute_capability_minor));
305 try std.testing.expectEqual(@as(c_int, 97), @backingInt(CUdevice_attribute.max_shared_memory_per_block_optin));
306 }
307
308 test "CUfunction_attribute pins dynamic shared memory ABI values" {
309 try std.testing.expectEqual(@as(c_int, 8), @backingInt(CUfunction_attribute.max_dynamic_shared_size_bytes));
310 }
311
312 test "CUresult is storage-compatible with c_int and non-exhaustive" {
313 try std.testing.expectEqual(@sizeOf(c_int), @sizeOf(CUresult));
314 const unknown_code: CUresult = @fromBackingInt(@intCast(42));
315 try std.testing.expectEqual(@as(c_int, 42), @backingInt(unknown_code));
316 }
317
318 test "fromResult on success returns without error" {
319 var stub: Driver = undefined;
320 try stub.fromResult(.success);
321 }
322
323 test "fromResult maps unsupported_ptx_version to CudaUnsupportedPtxVersion" {
324 var stub: Driver = undefined;
325 try std.testing.expectError(Error.CudaUnsupportedPtxVersion, stub.fromResult(.unsupported_ptx_version));
326 }
327
328 test "fromResult maps shared_object_symbol_not_found to CudaSharedObjectSymbolNotFound" {
329 var stub: Driver = undefined;
330 try std.testing.expectError(Error.CudaSharedObjectSymbolNotFound, stub.fromResult(.shared_object_symbol_not_found));
331 }
332
333 test "fromResult maps shared_object_init_failed to CudaSharedObjectInitFailed" {
334 var stub: Driver = undefined;
335 try std.testing.expectError(Error.CudaSharedObjectInitFailed, stub.fromResult(.shared_object_init_failed));
336 }
337
338 test "fromResult maps launch_out_of_resources to CudaLaunchOutOfResources" {
339 var stub: Driver = undefined;
340 try std.testing.expectError(Error.CudaLaunchOutOfResources, stub.fromResult(.launch_out_of_resources));
341 }
342
343 test "fromResult maps launch_timeout to CudaLaunchTimeout" {
344 var stub: Driver = undefined;
345 try std.testing.expectError(Error.CudaLaunchTimeout, stub.fromResult(.launch_timeout));
346 }
347
348 test "fromResult maps launch_incompatible_texturing to CudaLaunchIncompatibleTexturing" {
349 var stub: Driver = undefined;
350 try std.testing.expectError(Error.CudaLaunchIncompatibleTexturing, stub.fromResult(.launch_incompatible_texturing));
351 }
352
353 test "fromResult maps every documented error code" {
354 var stub: Driver = undefined;
355 const cases = [_]struct { r: CUresult, err: Error }{
356 .{ .r = .invalid_value, .err = Error.CudaInvalidValue },
357 .{ .r = .out_of_memory, .err = Error.CudaOutOfMemory },
358 .{ .r = .not_initialized, .err = Error.CudaNotInitialized },
359 .{ .r = .deinitialized, .err = Error.CudaDeinitialized },
360 .{ .r = .no_device, .err = Error.CudaNoDevice },
361 .{ .r = .invalid_device, .err = Error.CudaInvalidDevice },
362 .{ .r = .invalid_image, .err = Error.CudaInvalidImage },
363 .{ .r = .invalid_context, .err = Error.CudaInvalidContext },
364 .{ .r = .no_binary_for_gpu, .err = Error.CudaNoBinaryForGpu },
365 .{ .r = .invalid_ptx, .err = Error.CudaInvalidPtx },
366 .{ .r = .jit_compiler_not_found, .err = Error.CudaJitCompilerNotFound },
367 .{ .r = .unsupported_ptx_version, .err = Error.CudaUnsupportedPtxVersion },
368 .{ .r = .shared_object_symbol_not_found, .err = Error.CudaSharedObjectSymbolNotFound },
369 .{ .r = .shared_object_init_failed, .err = Error.CudaSharedObjectInitFailed },
370 .{ .r = .invalid_handle, .err = Error.CudaInvalidHandle },
371 .{ .r = .illegal_address, .err = Error.CudaIllegalAddress },
372 .{ .r = .launch_out_of_resources, .err = Error.CudaLaunchOutOfResources },
373 .{ .r = .launch_timeout, .err = Error.CudaLaunchTimeout },
374 .{ .r = .launch_incompatible_texturing, .err = Error.CudaLaunchIncompatibleTexturing },
375 .{ .r = .launch_failed, .err = Error.CudaLaunchFailed },
376 .{ .r = .not_supported, .err = Error.CudaNotSupported },
377 .{ .r = .unknown, .err = Error.CudaUnknown },
378 };
379 for (cases) |c| {
380 try std.testing.expectError(c.err, stub.fromResult(c.r));
381 }
382 }
383
384 test "Driver field signatures match the CUDA Driver loader ABI" {
385 const cuInitT = *const fn (flags: c_uint) callconv(.c) CUresult;
386 const cuDeviceGetT = *const fn (out: *CUdevice, ordinal: c_int) callconv(.c) CUresult;
387 const cuDeviceGetNameT = *const fn (name: [*]u8, len: c_int, dev: CUdevice) callconv(.c) CUresult;
388 const cuDeviceGetAttributeT = *const fn (out: *c_int, attrib: CUdevice_attribute, dev: CUdevice) callconv(.c) CUresult;
389 const cuModuleLoadDataT = *const fn (out: *CUmodule, image: [*]const u8) callconv(.c) CUresult;
390 const cuModuleLoadDataExT = *const fn (
391 out: *CUmodule,
392 image: [*]const u8,
393 num_options: c_uint,
394 options: [*]const CUjit_option,
395 option_values: [*]?*anyopaque,
396 ) callconv(.c) CUresult;
397 const cuFuncSetAttributeT = *const fn (
398 func: CUfunction,
399 attrib: CUfunction_attribute,
400 value: c_int,
401 ) callconv(.c) CUresult;
402 const cuLaunchKernelT = *const fn (
403 func: CUfunction,
404 grid_x: c_uint,
405 grid_y: c_uint,
406 grid_z: c_uint,
407 block_x: c_uint,
408 block_y: c_uint,
409 block_z: c_uint,
410 shared_mem_bytes: c_uint,
411 stream: CUstream,
412 kernel_params: ?[*]?*anyopaque,
413 extra: ?[*]?*anyopaque,
414 ) callconv(.c) CUresult;
415
416 comptime {
417 std.debug.assert(@FieldType(Driver, "cuInit") == cuInitT);
418 std.debug.assert(@FieldType(Driver, "cuDeviceGet") == cuDeviceGetT);
419 std.debug.assert(@FieldType(Driver, "cuDeviceGetName") == cuDeviceGetNameT);
420 std.debug.assert(@FieldType(Driver, "cuDeviceGetAttribute") == cuDeviceGetAttributeT);
421 std.debug.assert(@FieldType(Driver, "cuModuleLoadData") == cuModuleLoadDataT);
422 std.debug.assert(@FieldType(Driver, "cuModuleLoadDataEx") == cuModuleLoadDataExT);
423 std.debug.assert(@FieldType(Driver, "cuFuncSetAttribute") == cuFuncSetAttributeT);
424 std.debug.assert(@FieldType(Driver, "cuLaunchKernel") == cuLaunchKernelT);
425 }
426 }
427
428 test "CUjit_option pins log option ABI values" {
429 try std.testing.expectEqual(@as(c_int, 3), @backingInt(CUjit_option.info_log_buffer));
430 try std.testing.expectEqual(@as(c_int, 4), @backingInt(CUjit_option.info_log_buffer_size_bytes));
431 try std.testing.expectEqual(@as(c_int, 5), @backingInt(CUjit_option.error_log_buffer));
432 try std.testing.expectEqual(@as(c_int, 6), @backingInt(CUjit_option.error_log_buffer_size_bytes));
433 try std.testing.expectEqual(@as(c_int, 12), @backingInt(CUjit_option.log_verbose));
434 }
435
436 test "opaque handle widths match the documented ABI" {
437 try std.testing.expectEqual(@sizeOf(c_int), @sizeOf(CUdevice));
438 try std.testing.expectEqual(@sizeOf(u64), @sizeOf(CUdeviceptr));
439 try std.testing.expectEqual(@sizeOf(*anyopaque), @sizeOf(CUcontext));
440 try std.testing.expectEqual(@sizeOf(*anyopaque), @sizeOf(CUmodule));
441 try std.testing.expectEqual(@sizeOf(*anyopaque), @sizeOf(CUfunction));
442 try std.testing.expectEqual(@sizeOf(*anyopaque), @sizeOf(CUstream));
443 }
444
445 test "platformSupported reflects the dynamic library target set" {
446 try std.testing.expectEqual(
447 dynamic.hasHostCandidate(dynamic.cudaDriverCandidates()),
448 platformSupported(),
449 );
450 }