Skip to main content

oxicuda_levelzero/
device.rs

1//! Level Zero device wrapper.
2//!
3//! Loads `libze_loader.so` (Linux) or `ze_loader.dll` (Windows) at runtime via
4//! `libloading` and exposes a safe Rust handle.  On macOS every constructor
5//! returns [`LevelZeroError::UnsupportedPlatform`].
6
7use crate::error::{LevelZeroError, LevelZeroResult};
8
9// ─── Platform-specific imports and type definitions ──────────────────────────
10
11#[cfg(any(target_os = "linux", target_os = "windows"))]
12use std::{ffi::c_void, sync::Arc};
13
14#[cfg(any(target_os = "linux", target_os = "windows"))]
15use libloading::Library;
16
17// ─── Level Zero opaque handle types (Linux + Windows only) ───────────────────
18
19#[cfg(any(target_os = "linux", target_os = "windows"))]
20type ZeDriverHandle = *mut c_void;
21
22#[cfg(any(target_os = "linux", target_os = "windows"))]
23type ZeDeviceHandle = *mut c_void;
24
25#[cfg(any(target_os = "linux", target_os = "windows"))]
26type ZeContextHandle = *mut c_void;
27
28#[cfg(any(target_os = "linux", target_os = "windows"))]
29pub(crate) type ZeCommandQueueHandle = *mut c_void;
30
31#[cfg(any(target_os = "linux", target_os = "windows"))]
32pub(crate) type ZeCommandListHandle = *mut c_void;
33
34#[cfg(any(target_os = "linux", target_os = "windows"))]
35pub(crate) type ZeModuleHandle = *mut c_void;
36
37#[cfg(any(target_os = "linux", target_os = "windows"))]
38pub(crate) type ZeKernelHandle = *mut c_void;
39
40// ─── Level Zero result / type constants ──────────────────────────────────────
41
42#[cfg(any(target_os = "linux", target_os = "windows"))]
43const ZE_RESULT_SUCCESS: u32 = 0;
44
45#[cfg(any(target_os = "linux", target_os = "windows"))]
46const ZE_DEVICE_TYPE_GPU: u32 = 1;
47
48// The following values mirror the stable oneAPI Level Zero `ze_structure_type_t`
49// enum exactly (as consumed by the real `libze_loader.so.1`): CONTEXT_DESC=0xd,
50// COMMAND_QUEUE_DESC=0xe, COMMAND_LIST_DESC=0xf, DEVICE_PROPERTIES=0x3,
51// DEVICE_MEM_ALLOC_DESC=0x15, HOST_MEM_ALLOC_DESC=0x16, MODULE_DESC=0x1d,
52// KERNEL_DESC=0x1f.
53#[cfg(any(target_os = "linux", target_os = "windows"))]
54const ZE_STRUCTURE_TYPE_CONTEXT_DESC: u32 = 0xd;
55
56#[cfg(any(target_os = "linux", target_os = "windows"))]
57const ZE_STRUCTURE_TYPE_COMMAND_QUEUE_DESC: u32 = 0xe;
58
59#[cfg(any(target_os = "linux", target_os = "windows"))]
60pub(crate) const ZE_STRUCTURE_TYPE_COMMAND_LIST_DESC: u32 = 0xf;
61
62#[cfg(any(target_os = "linux", target_os = "windows"))]
63pub(crate) const ZE_STRUCTURE_TYPE_DEVICE_MEM_ALLOC_DESC: u32 = 0x15;
64
65#[cfg(any(target_os = "linux", target_os = "windows"))]
66pub(crate) const ZE_STRUCTURE_TYPE_HOST_MEM_ALLOC_DESC: u32 = 0x16;
67
68#[cfg(any(target_os = "linux", target_os = "windows"))]
69const ZE_STRUCTURE_TYPE_DEVICE_PROPERTIES: u32 = 0x3;
70
71#[cfg(any(target_os = "linux", target_os = "windows"))]
72pub(crate) const ZE_STRUCTURE_TYPE_MODULE_DESC: u32 = 0x1d;
73
74#[cfg(any(target_os = "linux", target_os = "windows"))]
75pub(crate) const ZE_STRUCTURE_TYPE_KERNEL_DESC: u32 = 0x1f;
76
77#[cfg(any(target_os = "linux", target_os = "windows"))]
78pub(crate) const ZE_MODULE_FORMAT_IL_SPIRV: u32 = 0;
79
80// ─── Level Zero descriptor and property structs (Linux + Windows only) ───────
81
82#[cfg(any(target_os = "linux", target_os = "windows"))]
83#[repr(C)]
84pub(crate) struct ZeContextDesc {
85    pub(crate) stype: u32,
86    pub(crate) p_next: *const c_void,
87}
88
89#[cfg(any(target_os = "linux", target_os = "windows"))]
90#[repr(C)]
91pub(crate) struct ZeCommandQueueDesc {
92    stype: u32,
93    p_next: *const c_void,
94    ordinal: u32,
95    index: u32,
96    flags: u32,
97    mode: u32,
98    priority: u32,
99}
100
101#[cfg(any(target_os = "linux", target_os = "windows"))]
102#[repr(C)]
103pub(crate) struct ZeCommandListDesc {
104    pub stype: u32,
105    pub p_next: *const c_void,
106    pub command_queue_group_ordinal: u32,
107    pub flags: u32,
108}
109
110#[cfg(any(target_os = "linux", target_os = "windows"))]
111#[repr(C)]
112pub(crate) struct ZeDeviceMemAllocDesc {
113    pub stype: u32,
114    pub p_next: *const c_void,
115    pub flags: u32,
116    pub ordinal: u32,
117}
118
119#[cfg(any(target_os = "linux", target_os = "windows"))]
120#[repr(C)]
121pub(crate) struct ZeHostMemAllocDesc {
122    pub stype: u32,
123    pub p_next: *const c_void,
124    pub flags: u32,
125}
126
127#[cfg(any(target_os = "linux", target_os = "windows"))]
128#[repr(C)]
129pub(crate) struct ZeDeviceProperties {
130    // Exact field order of the stable `ze_device_properties_t` ABI. `name` is the
131    // LAST field (real offset ~112); getting the intermediate fields wrong would
132    // read the device name from the wrong bytes.
133    stype: u32,
134    p_next: *const c_void,
135    device_type: u32,
136    vendor_id: u32,
137    device_id: u32,
138    _flags: u32,
139    _sub_device_id: u32,
140    _core_clock_rate: u32,
141    _max_mem_alloc_size: u64,
142    _max_hardware_contexts: u32,
143    _max_command_queue_priority: u32,
144    _num_threads_per_eu: u32,
145    _physical_eu_simd_width: u32,
146    _num_eus_per_sub_slice: u32,
147    _num_sub_slices_per_slice: u32,
148    _num_slices: u32,
149    _timer_resolution: u64,
150    _timestamp_valid_bits: u32,
151    _kernel_timestamp_valid_bits: u32,
152    _uuid: [u8; 16],
153    name: [u8; 256],
154}
155
156#[cfg(any(target_os = "linux", target_os = "windows"))]
157#[repr(C)]
158pub(crate) struct ZeModuleDesc {
159    pub stype: u32,
160    pub p_next: *const c_void,
161    pub format: u32,
162    pub input_size: usize,
163    pub p_input_module: *const u8,
164    pub p_build_flags: *const u8,
165    pub p_constants: *const c_void,
166}
167
168#[cfg(any(target_os = "linux", target_os = "windows"))]
169#[repr(C)]
170pub(crate) struct ZeKernelDesc {
171    pub stype: u32,
172    pub p_next: *const c_void,
173    pub flags: u32,
174    pub p_kernel_name: *const u8,
175}
176
177#[cfg(any(target_os = "linux", target_os = "windows"))]
178#[repr(C)]
179pub(crate) struct ZeGroupCount {
180    pub group_count_x: u32,
181    pub group_count_y: u32,
182    pub group_count_z: u32,
183}
184
185// ─── Level Zero function pointer types (Linux + Windows only) ────────────────
186
187#[cfg(any(target_os = "linux", target_os = "windows"))]
188type ZeInitFn = unsafe extern "C" fn(flags: u32) -> u32;
189
190#[cfg(any(target_os = "linux", target_os = "windows"))]
191type ZeDriverGetFn = unsafe extern "C" fn(count: *mut u32, drivers: *mut ZeDriverHandle) -> u32;
192
193#[cfg(any(target_os = "linux", target_os = "windows"))]
194type ZeDeviceGetFn = unsafe extern "C" fn(
195    driver: ZeDriverHandle,
196    count: *mut u32,
197    devices: *mut ZeDeviceHandle,
198) -> u32;
199
200#[cfg(any(target_os = "linux", target_os = "windows"))]
201type ZeDeviceGetPropertiesFn =
202    unsafe extern "C" fn(device: ZeDeviceHandle, props: *mut ZeDeviceProperties) -> u32;
203
204#[cfg(any(target_os = "linux", target_os = "windows"))]
205type ZeContextCreateFn = unsafe extern "C" fn(
206    driver: ZeDriverHandle,
207    desc: *const ZeContextDesc,
208    context: *mut ZeContextHandle,
209) -> u32;
210
211#[cfg(any(target_os = "linux", target_os = "windows"))]
212type ZeContextDestroyFn = unsafe extern "C" fn(context: ZeContextHandle) -> u32;
213
214#[cfg(any(target_os = "linux", target_os = "windows"))]
215type ZeCommandQueueCreateFn = unsafe extern "C" fn(
216    context: ZeContextHandle,
217    device: ZeDeviceHandle,
218    desc: *const ZeCommandQueueDesc,
219    queue: *mut ZeCommandQueueHandle,
220) -> u32;
221
222#[cfg(any(target_os = "linux", target_os = "windows"))]
223type ZeCommandQueueDestroyFn = unsafe extern "C" fn(queue: ZeCommandQueueHandle) -> u32;
224
225#[cfg(any(target_os = "linux", target_os = "windows"))]
226type ZeCommandQueueSynchronizeFn =
227    unsafe extern "C" fn(queue: ZeCommandQueueHandle, timeout: u64) -> u32;
228
229#[cfg(any(target_os = "linux", target_os = "windows"))]
230type ZeCommandQueueExecuteCommandListsFn = unsafe extern "C" fn(
231    queue: ZeCommandQueueHandle,
232    count: u32,
233    lists: *const ZeCommandListHandle,
234    fence: usize,
235) -> u32;
236
237#[cfg(any(target_os = "linux", target_os = "windows"))]
238type ZeCommandListCreateFn = unsafe extern "C" fn(
239    context: ZeContextHandle,
240    device: ZeDeviceHandle,
241    desc: *const ZeCommandListDesc,
242    list: *mut ZeCommandListHandle,
243) -> u32;
244
245#[cfg(any(target_os = "linux", target_os = "windows"))]
246type ZeCommandListDestroyFn = unsafe extern "C" fn(list: ZeCommandListHandle) -> u32;
247
248#[cfg(any(target_os = "linux", target_os = "windows"))]
249type ZeCommandListCloseFn = unsafe extern "C" fn(list: ZeCommandListHandle) -> u32;
250
251#[cfg(any(target_os = "linux", target_os = "windows"))]
252type ZeCommandListResetFn = unsafe extern "C" fn(list: ZeCommandListHandle) -> u32;
253
254#[cfg(any(target_os = "linux", target_os = "windows"))]
255type ZeCommandListAppendMemoryCopyFn = unsafe extern "C" fn(
256    list: ZeCommandListHandle,
257    dst: *mut c_void,
258    src: *const c_void,
259    size: usize,
260    signal_event: usize,
261    wait_count: u32,
262    wait_events: *const usize,
263) -> u32;
264
265#[cfg(any(target_os = "linux", target_os = "windows"))]
266type ZeMemAllocDeviceFn = unsafe extern "C" fn(
267    context: ZeContextHandle,
268    desc: *const ZeDeviceMemAllocDesc,
269    size: usize,
270    alignment: usize,
271    device: ZeDeviceHandle,
272    ptr: *mut *mut c_void,
273) -> u32;
274
275#[cfg(any(target_os = "linux", target_os = "windows"))]
276type ZeMemAllocHostFn = unsafe extern "C" fn(
277    context: ZeContextHandle,
278    desc: *const ZeHostMemAllocDesc,
279    size: usize,
280    alignment: usize,
281    ptr: *mut *mut c_void,
282) -> u32;
283
284#[cfg(any(target_os = "linux", target_os = "windows"))]
285type ZeMemFreeFn = unsafe extern "C" fn(context: ZeContextHandle, ptr: *mut c_void) -> u32;
286
287#[cfg(any(target_os = "linux", target_os = "windows"))]
288type ZeModuleCreateFn = unsafe extern "C" fn(
289    context: ZeContextHandle,
290    device: ZeDeviceHandle,
291    desc: *const ZeModuleDesc,
292    module: *mut ZeModuleHandle,
293    build_log: *mut *mut c_void,
294) -> u32;
295
296#[cfg(any(target_os = "linux", target_os = "windows"))]
297type ZeModuleDestroyFn = unsafe extern "C" fn(module: ZeModuleHandle) -> u32;
298
299#[cfg(any(target_os = "linux", target_os = "windows"))]
300type ZeKernelCreateFn = unsafe extern "C" fn(
301    module: ZeModuleHandle,
302    desc: *const ZeKernelDesc,
303    kernel: *mut ZeKernelHandle,
304) -> u32;
305
306#[cfg(any(target_os = "linux", target_os = "windows"))]
307type ZeKernelDestroyFn = unsafe extern "C" fn(kernel: ZeKernelHandle) -> u32;
308
309#[cfg(any(target_os = "linux", target_os = "windows"))]
310type ZeKernelSetGroupSizeFn =
311    unsafe extern "C" fn(kernel: ZeKernelHandle, x: u32, y: u32, z: u32) -> u32;
312
313#[cfg(any(target_os = "linux", target_os = "windows"))]
314type ZeKernelSetArgumentValueFn = unsafe extern "C" fn(
315    kernel: ZeKernelHandle,
316    arg_index: u32,
317    arg_size: usize,
318    p_arg_value: *const c_void,
319) -> u32;
320
321#[cfg(any(target_os = "linux", target_os = "windows"))]
322type ZeCommandListAppendLaunchKernelFn = unsafe extern "C" fn(
323    list: ZeCommandListHandle,
324    kernel: ZeKernelHandle,
325    launch_func_args: *const ZeGroupCount,
326    signal_event: usize,
327    wait_count: u32,
328    wait_events: *const usize,
329) -> u32;
330
331// ─── L0Api — dynamically-loaded function table (Linux + Windows only) ─────────
332
333/// Holds the loaded `libze_loader` library and all extracted function pointers.
334///
335/// The `Library` field keeps the shared object alive for the lifetime of this struct.
336#[cfg(any(target_os = "linux", target_os = "windows"))]
337pub(crate) struct L0Api {
338    /// The loaded shared library — must outlive all function pointer calls.
339    _lib: Library,
340    pub ze_init: ZeInitFn,
341    pub ze_driver_get: ZeDriverGetFn,
342    pub ze_device_get: ZeDeviceGetFn,
343    pub ze_device_get_properties: ZeDeviceGetPropertiesFn,
344    pub ze_context_create: ZeContextCreateFn,
345    pub ze_context_destroy: ZeContextDestroyFn,
346    pub ze_command_queue_create: ZeCommandQueueCreateFn,
347    pub ze_command_queue_destroy: ZeCommandQueueDestroyFn,
348    pub ze_command_queue_synchronize: ZeCommandQueueSynchronizeFn,
349    pub ze_command_queue_execute_command_lists: ZeCommandQueueExecuteCommandListsFn,
350    pub ze_command_list_create: ZeCommandListCreateFn,
351    pub ze_command_list_destroy: ZeCommandListDestroyFn,
352    pub ze_command_list_close: ZeCommandListCloseFn,
353    #[allow(dead_code)]
354    pub ze_command_list_reset: ZeCommandListResetFn,
355    pub ze_command_list_append_memory_copy: ZeCommandListAppendMemoryCopyFn,
356    pub ze_mem_alloc_device: ZeMemAllocDeviceFn,
357    pub ze_mem_alloc_host: ZeMemAllocHostFn,
358    pub ze_mem_free: ZeMemFreeFn,
359    pub ze_module_create: ZeModuleCreateFn,
360    pub ze_module_destroy: ZeModuleDestroyFn,
361    pub ze_kernel_create: ZeKernelCreateFn,
362    pub ze_kernel_destroy: ZeKernelDestroyFn,
363    pub ze_kernel_set_group_size: ZeKernelSetGroupSizeFn,
364    pub ze_kernel_set_argument_value: ZeKernelSetArgumentValueFn,
365    pub ze_command_list_append_launch_kernel: ZeCommandListAppendLaunchKernelFn,
366}
367
368#[cfg(any(target_os = "linux", target_os = "windows"))]
369impl L0Api {
370    /// Load the Level Zero loader library and extract all function pointers.
371    ///
372    /// # Safety
373    ///
374    /// The returned `L0Api` must not outlive the process image that loaded it.
375    /// All function pointers are valid only as long as the `Library` is alive
376    /// (which is guaranteed by the `_lib` field).
377    unsafe fn load() -> LevelZeroResult<Self> {
378        #[cfg(target_os = "linux")]
379        let lib_name = "libze_loader.so.1";
380        #[cfg(target_os = "windows")]
381        let lib_name = "ze_loader.dll";
382
383        // SAFETY: We are loading a well-known system library by its standard
384        // filename. The resulting Library keeps the shared object alive.
385        let lib = unsafe {
386            Library::new(lib_name)
387                .map_err(|e| LevelZeroError::LibraryNotFound(format!("{lib_name}: {e}")))?
388        };
389
390        macro_rules! sym {
391            ($name:literal, $ty:ty) => {{
392                // SAFETY: We are loading a symbol from the Level Zero loader
393                // that we know to exist with the correct type signature.
394                // The symbol lifetime is bounded by `lib`.
395                *unsafe {
396                    lib.get::<$ty>($name).map_err(|e| {
397                        LevelZeroError::LibraryNotFound(format!(
398                            "symbol {}: {e}",
399                            stringify!($name)
400                        ))
401                    })?
402                }
403            }};
404        }
405
406        let ze_init = sym!(b"zeInit\0", ZeInitFn);
407        let ze_driver_get = sym!(b"zeDriverGet\0", ZeDriverGetFn);
408        let ze_device_get = sym!(b"zeDeviceGet\0", ZeDeviceGetFn);
409        let ze_device_get_properties = sym!(b"zeDeviceGetProperties\0", ZeDeviceGetPropertiesFn);
410        let ze_context_create = sym!(b"zeContextCreate\0", ZeContextCreateFn);
411        let ze_context_destroy = sym!(b"zeContextDestroy\0", ZeContextDestroyFn);
412        let ze_command_queue_create = sym!(b"zeCommandQueueCreate\0", ZeCommandQueueCreateFn);
413        let ze_command_queue_destroy = sym!(b"zeCommandQueueDestroy\0", ZeCommandQueueDestroyFn);
414        let ze_command_queue_synchronize =
415            sym!(b"zeCommandQueueSynchronize\0", ZeCommandQueueSynchronizeFn);
416        let ze_command_queue_execute_command_lists = sym!(
417            b"zeCommandQueueExecuteCommandLists\0",
418            ZeCommandQueueExecuteCommandListsFn
419        );
420        let ze_command_list_create = sym!(b"zeCommandListCreate\0", ZeCommandListCreateFn);
421        let ze_command_list_destroy = sym!(b"zeCommandListDestroy\0", ZeCommandListDestroyFn);
422        let ze_command_list_close = sym!(b"zeCommandListClose\0", ZeCommandListCloseFn);
423        let ze_command_list_reset = sym!(b"zeCommandListReset\0", ZeCommandListResetFn);
424        let ze_command_list_append_memory_copy = sym!(
425            b"zeCommandListAppendMemoryCopy\0",
426            ZeCommandListAppendMemoryCopyFn
427        );
428        let ze_mem_alloc_device = sym!(b"zeMemAllocDevice\0", ZeMemAllocDeviceFn);
429        let ze_mem_alloc_host = sym!(b"zeMemAllocHost\0", ZeMemAllocHostFn);
430        let ze_mem_free = sym!(b"zeMemFree\0", ZeMemFreeFn);
431        let ze_module_create = sym!(b"zeModuleCreate\0", ZeModuleCreateFn);
432        let ze_module_destroy = sym!(b"zeModuleDestroy\0", ZeModuleDestroyFn);
433        let ze_kernel_create = sym!(b"zeKernelCreate\0", ZeKernelCreateFn);
434        let ze_kernel_destroy = sym!(b"zeKernelDestroy\0", ZeKernelDestroyFn);
435        let ze_kernel_set_group_size = sym!(b"zeKernelSetGroupSize\0", ZeKernelSetGroupSizeFn);
436        let ze_kernel_set_argument_value =
437            sym!(b"zeKernelSetArgumentValue\0", ZeKernelSetArgumentValueFn);
438        let ze_command_list_append_launch_kernel = sym!(
439            b"zeCommandListAppendLaunchKernel\0",
440            ZeCommandListAppendLaunchKernelFn
441        );
442
443        Ok(Self {
444            _lib: lib,
445            ze_init,
446            ze_driver_get,
447            ze_device_get,
448            ze_device_get_properties,
449            ze_context_create,
450            ze_context_destroy,
451            ze_command_queue_create,
452            ze_command_queue_destroy,
453            ze_command_queue_synchronize,
454            ze_command_queue_execute_command_lists,
455            ze_command_list_create,
456            ze_command_list_destroy,
457            ze_command_list_close,
458            ze_command_list_reset,
459            ze_command_list_append_memory_copy,
460            ze_mem_alloc_device,
461            ze_mem_alloc_host,
462            ze_mem_free,
463            ze_module_create,
464            ze_module_destroy,
465            ze_kernel_create,
466            ze_kernel_destroy,
467            ze_kernel_set_group_size,
468            ze_kernel_set_argument_value,
469            ze_command_list_append_launch_kernel,
470        })
471    }
472}
473
474// ─── LevelZeroDevice ─────────────────────────────────────────────────────────
475
476/// An Intel GPU device accessed via the Level Zero API.
477///
478/// On non-Linux/Windows platforms, [`LevelZeroDevice::new`] always returns
479/// [`LevelZeroError::UnsupportedPlatform`].
480pub struct LevelZeroDevice {
481    /// The loaded Level Zero API (Linux and Windows only).
482    #[cfg(any(target_os = "linux", target_os = "windows"))]
483    pub(crate) api: Arc<L0Api>,
484    /// The Level Zero context handle (Linux and Windows only).
485    #[cfg(any(target_os = "linux", target_os = "windows"))]
486    pub(crate) context: ZeContextHandle,
487    /// The selected GPU device handle (Linux and Windows only).
488    #[cfg(any(target_os = "linux", target_os = "windows"))]
489    pub(crate) device: ZeDeviceHandle,
490    /// The command queue handle (Linux and Windows only).
491    #[cfg(any(target_os = "linux", target_os = "windows"))]
492    pub(crate) queue: ZeCommandQueueHandle,
493    /// Human-readable device name.
494    device_name: String,
495}
496
497impl LevelZeroDevice {
498    /// Open the first Intel GPU found via the Level Zero loader.
499    ///
500    /// Returns [`LevelZeroError::UnsupportedPlatform`] on macOS.
501    pub fn new() -> LevelZeroResult<Self> {
502        #[cfg(any(target_os = "linux", target_os = "windows"))]
503        {
504            // SAFETY: L0Api::load performs dlopen/LoadLibrary and symbol
505            // resolution. All further calls are guarded by result checks.
506            let api = Arc::new(unsafe { L0Api::load()? });
507
508            // Step 1: Initialize Level Zero runtime.
509            // SAFETY: ze_init is a valid function pointer from the loaded library.
510            let rc = unsafe { (api.ze_init)(0) };
511            if rc != ZE_RESULT_SUCCESS {
512                return Err(LevelZeroError::ZeError(rc, "zeInit failed".into()));
513            }
514
515            // Step 2: Enumerate drivers.
516            let mut driver_count: u32 = 0;
517            // SAFETY: Passing null for the driver array to query the count.
518            let rc =
519                unsafe { (api.ze_driver_get)(&mut driver_count as *mut u32, std::ptr::null_mut()) };
520            if rc != ZE_RESULT_SUCCESS {
521                return Err(LevelZeroError::ZeError(
522                    rc,
523                    "zeDriverGet (count) failed".into(),
524                ));
525            }
526            if driver_count == 0 {
527                return Err(LevelZeroError::NoSuitableDevice);
528            }
529
530            let mut drivers: Vec<ZeDriverHandle> =
531                vec![std::ptr::null_mut(); driver_count as usize];
532            // SAFETY: `drivers` is allocated for exactly `driver_count` elements.
533            let rc =
534                unsafe { (api.ze_driver_get)(&mut driver_count as *mut u32, drivers.as_mut_ptr()) };
535            if rc != ZE_RESULT_SUCCESS {
536                return Err(LevelZeroError::ZeError(
537                    rc,
538                    "zeDriverGet (enumerate) failed".into(),
539                ));
540            }
541
542            let driver = drivers[0];
543
544            // Step 3: Enumerate devices and find the first GPU.
545            let mut device_count: u32 = 0;
546            // SAFETY: Passing null to query the device count for this driver.
547            let rc = unsafe {
548                (api.ze_device_get)(driver, &mut device_count as *mut u32, std::ptr::null_mut())
549            };
550            if rc != ZE_RESULT_SUCCESS {
551                return Err(LevelZeroError::ZeError(
552                    rc,
553                    "zeDeviceGet (count) failed".into(),
554                ));
555            }
556            if device_count == 0 {
557                return Err(LevelZeroError::NoSuitableDevice);
558            }
559
560            let mut devices: Vec<ZeDeviceHandle> =
561                vec![std::ptr::null_mut(); device_count as usize];
562            // SAFETY: `devices` is allocated for exactly `device_count` elements.
563            let rc = unsafe {
564                (api.ze_device_get)(driver, &mut device_count as *mut u32, devices.as_mut_ptr())
565            };
566            if rc != ZE_RESULT_SUCCESS {
567                return Err(LevelZeroError::ZeError(
568                    rc,
569                    "zeDeviceGet (enumerate) failed".into(),
570                ));
571            }
572
573            // Step 4: Find the first GPU device and read its properties.
574            let mut chosen_device: Option<ZeDeviceHandle> = None;
575            let mut device_name = String::from("Intel GPU");
576
577            for &dev in &devices {
578                // SAFETY: ZeDeviceProperties is #[repr(C)] and fully initialized
579                // (zeroed) before passing to the API.
580                let mut props =
581                    unsafe { std::mem::MaybeUninit::<ZeDeviceProperties>::zeroed().assume_init() };
582                props.stype = ZE_STRUCTURE_TYPE_DEVICE_PROPERTIES;
583                props.p_next = std::ptr::null();
584
585                // SAFETY: `dev` is a valid device handle; `props` is properly
586                // initialized with the correct stype field.
587                let rc = unsafe {
588                    (api.ze_device_get_properties)(dev, &mut props as *mut ZeDeviceProperties)
589                };
590                if rc != ZE_RESULT_SUCCESS {
591                    continue;
592                }
593
594                if props.device_type == ZE_DEVICE_TYPE_GPU {
595                    // Extract null-terminated name string.
596                    let name_len = props
597                        .name
598                        .iter()
599                        .position(|&b| b == 0)
600                        .unwrap_or(props.name.len());
601                    device_name = String::from_utf8_lossy(&props.name[..name_len]).into_owned();
602                    chosen_device = Some(dev);
603                    break;
604                }
605            }
606
607            let device = chosen_device.ok_or(LevelZeroError::NoSuitableDevice)?;
608
609            // Step 5: Create a context.
610            let ctx_desc = ZeContextDesc {
611                stype: ZE_STRUCTURE_TYPE_CONTEXT_DESC,
612                p_next: std::ptr::null(),
613            };
614            let mut context: ZeContextHandle = std::ptr::null_mut();
615            // SAFETY: `ctx_desc` is valid; `context` is a valid output pointer.
616            let rc = unsafe {
617                (api.ze_context_create)(driver, &ctx_desc, &mut context as *mut ZeContextHandle)
618            };
619            if rc != ZE_RESULT_SUCCESS {
620                return Err(LevelZeroError::ZeError(rc, "zeContextCreate failed".into()));
621            }
622
623            // Step 6: Create a command queue.
624            let queue_desc = ZeCommandQueueDesc {
625                stype: ZE_STRUCTURE_TYPE_COMMAND_QUEUE_DESC,
626                p_next: std::ptr::null(),
627                ordinal: 0,
628                index: 0,
629                flags: 0,
630                mode: 0, // default mode
631                priority: 0,
632            };
633            let mut queue: ZeCommandQueueHandle = std::ptr::null_mut();
634            // SAFETY: `context`, `device`, and `queue_desc` are valid; `queue`
635            // is a valid output pointer.
636            let rc = unsafe {
637                (api.ze_command_queue_create)(
638                    context,
639                    device,
640                    &queue_desc,
641                    &mut queue as *mut ZeCommandQueueHandle,
642                )
643            };
644            if rc != ZE_RESULT_SUCCESS {
645                // Clean up context before returning error.
646                // SAFETY: context was successfully created above.
647                unsafe { (api.ze_context_destroy)(context) };
648                return Err(LevelZeroError::ZeError(
649                    rc,
650                    "zeCommandQueueCreate failed".into(),
651                ));
652            }
653
654            tracing::info!("Level Zero device selected: {device_name}");
655
656            Ok(Self {
657                api,
658                context,
659                device,
660                queue,
661                device_name,
662            })
663        }
664
665        #[cfg(not(any(target_os = "linux", target_os = "windows")))]
666        {
667            Err(LevelZeroError::UnsupportedPlatform)
668        }
669    }
670
671    /// Human-readable device name (e.g. `"Intel UHD Graphics 770"`).
672    pub fn name(&self) -> &str {
673        &self.device_name
674    }
675}
676
677// ─── Drop ────────────────────────────────────────────────────────────────────
678
679impl Drop for LevelZeroDevice {
680    fn drop(&mut self) {
681        #[cfg(any(target_os = "linux", target_os = "windows"))]
682        {
683            // SAFETY: `queue` and `context` were successfully created in `new()`
684            // and have not been freed yet.  We destroy in reverse creation order.
685            unsafe {
686                (self.api.ze_command_queue_destroy)(self.queue);
687                (self.api.ze_context_destroy)(self.context);
688            }
689        }
690    }
691}
692
693// ─── Send + Sync ─────────────────────────────────────────────────────────────
694
695// SAFETY: `LevelZeroDevice` holds raw Level Zero opaque pointers that are
696// conceptually owned by this struct.  The Level Zero specification states that
697// context and command-queue handles may be used from any thread as long as
698// external synchronization is provided.  We guarantee exclusive ownership via
699// `Arc<LevelZeroDevice>` and `Mutex` in the memory manager.
700unsafe impl Send for LevelZeroDevice {}
701// SAFETY: See `Send` impl above.  Shared immutable access is safe because all
702// mutable operations go through synchronised command lists.
703unsafe impl Sync for LevelZeroDevice {}
704
705// ─── Debug ───────────────────────────────────────────────────────────────────
706
707impl std::fmt::Debug for LevelZeroDevice {
708    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
709        write!(f, "LevelZeroDevice({})", self.device_name)
710    }
711}
712
713// ─── Tests ───────────────────────────────────────────────────────────────────
714
715#[cfg(test)]
716mod tests {
717    use super::*;
718
719    #[test]
720    #[cfg(any(target_os = "linux", target_os = "windows"))]
721    fn level_zero_device_graceful_init() {
722        match LevelZeroDevice::new() {
723            Ok(dev) => {
724                assert!(!dev.name().is_empty());
725                let dbg = format!("{dev:?}");
726                assert!(dbg.contains("LevelZeroDevice"));
727            }
728            Err(LevelZeroError::LibraryNotFound(_)) => {
729                // Acceptable: Level Zero loader not installed on this machine.
730            }
731            Err(LevelZeroError::NoSuitableDevice) => {
732                // Acceptable: no Intel GPU present.
733            }
734            Err(LevelZeroError::ZeError(_, _)) => {
735                // Acceptable: Level Zero runtime error (e.g. missing driver).
736            }
737            Err(e) => {
738                // Any other error must not panic — just log it.
739                let _ = format!("Level Zero device init error (non-fatal): {e}");
740            }
741        }
742    }
743
744    #[test]
745    #[cfg(not(any(target_os = "linux", target_os = "windows")))]
746    fn level_zero_device_unsupported_on_macos() {
747        let result = LevelZeroDevice::new();
748        assert!(matches!(result, Err(LevelZeroError::UnsupportedPlatform)));
749    }
750}