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 }