1use std::{
11 collections::HashMap,
12 sync::{Arc, Mutex},
13};
14
15#[cfg(any(target_os = "linux", target_os = "windows"))]
16use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
17
18use crate::{
19 device::LevelZeroDevice,
20 error::{LevelZeroError, LevelZeroResult},
21};
22
23#[cfg(any(target_os = "linux", target_os = "windows"))]
26use std::ffi::c_void;
27
28#[cfg(any(target_os = "linux", target_os = "windows"))]
29use crate::device::{
30 ZE_STRUCTURE_TYPE_COMMAND_LIST_DESC, ZE_STRUCTURE_TYPE_DEVICE_MEM_ALLOC_DESC,
31 ZE_STRUCTURE_TYPE_HOST_MEM_ALLOC_DESC, ZeCommandListDesc, ZeCommandListHandle,
32 ZeDeviceMemAllocDesc, ZeHostMemAllocDesc,
33};
34
35struct L0BufferRecord {
39 #[cfg(any(target_os = "linux", target_os = "windows"))]
41 device_ptr: *mut c_void,
42 #[cfg(any(target_os = "linux", target_os = "windows"))]
44 size: u64,
45}
46
47#[cfg(any(target_os = "linux", target_os = "windows"))]
50unsafe impl Send for L0BufferRecord {}
51
52pub struct LevelZeroMemoryManager {
61 #[cfg(any(target_os = "linux", target_os = "windows"))]
62 device: Arc<LevelZeroDevice>,
63 buffers: Mutex<HashMap<u64, L0BufferRecord>>,
64 #[cfg(any(target_os = "linux", target_os = "windows"))]
65 next_handle: AtomicU64,
66}
67
68impl LevelZeroMemoryManager {
69 #[cfg(any(target_os = "linux", target_os = "windows"))]
71 pub fn new(device: Arc<LevelZeroDevice>) -> Self {
72 Self {
73 device,
74 buffers: Mutex::new(HashMap::new()),
75 next_handle: AtomicU64::new(1),
76 }
77 }
78
79 #[cfg(not(any(target_os = "linux", target_os = "windows")))]
83 pub fn new(_device: Arc<LevelZeroDevice>) -> Self {
84 Self {
85 buffers: Mutex::new(HashMap::new()),
86 }
87 }
88
89 #[cfg(any(target_os = "linux", target_os = "windows"))]
94 pub fn device_ptr(&self, handle: u64) -> LevelZeroResult<*mut c_void> {
95 let buffers = self
96 .buffers
97 .lock()
98 .map_err(|_| LevelZeroError::CommandListError("mutex poisoned".into()))?;
99 let rec = buffers
100 .get(&handle)
101 .ok_or_else(|| LevelZeroError::InvalidArgument(format!("unknown handle {handle}")))?;
102 Ok(rec.device_ptr)
103 }
104
105 pub fn alloc(&self, bytes: usize) -> LevelZeroResult<u64> {
109 #[cfg(any(target_os = "linux", target_os = "windows"))]
110 {
111 let api = &self.device.api;
112 let context = self.device.context;
113 let device_handle = self.device.device;
114
115 let desc = ZeDeviceMemAllocDesc {
116 stype: ZE_STRUCTURE_TYPE_DEVICE_MEM_ALLOC_DESC,
117 p_next: std::ptr::null(),
118 flags: 0,
119 ordinal: 0,
120 };
121
122 let mut ptr: *mut c_void = std::ptr::null_mut();
123 let rc = unsafe {
126 (api.ze_mem_alloc_device)(
127 context,
128 &desc,
129 bytes,
130 64, device_handle,
132 &mut ptr as *mut *mut c_void,
133 )
134 };
135
136 if rc != 0 {
137 return Err(LevelZeroError::ZeError(
138 rc,
139 "zeMemAllocDevice failed".into(),
140 ));
141 }
142
143 let handle = self.next_handle.fetch_add(1, Relaxed);
144 self.buffers
145 .lock()
146 .map_err(|_| LevelZeroError::CommandListError("mutex poisoned".into()))?
147 .insert(
148 handle,
149 L0BufferRecord {
150 device_ptr: ptr,
151 size: bytes as u64,
152 },
153 );
154
155 Ok(handle)
156 }
157
158 #[cfg(not(any(target_os = "linux", target_os = "windows")))]
159 {
160 let _ = bytes;
161 Err(LevelZeroError::UnsupportedPlatform)
162 }
163 }
164
165 pub fn free(&self, handle: u64) -> LevelZeroResult<()> {
169 #[cfg(any(target_os = "linux", target_os = "windows"))]
170 {
171 let record = self
172 .buffers
173 .lock()
174 .map_err(|_| LevelZeroError::CommandListError("mutex poisoned".into()))?
175 .remove(&handle);
176
177 if let Some(rec) = record {
178 let api = &self.device.api;
179 let context = self.device.context;
180 let rc = unsafe { (api.ze_mem_free)(context, rec.device_ptr) };
183 if rc != 0 {
184 return Err(LevelZeroError::ZeError(rc, "zeMemFree failed".into()));
185 }
186 }
187 Ok(())
188 }
189
190 #[cfg(not(any(target_os = "linux", target_os = "windows")))]
191 {
192 let _ = handle;
193 Err(LevelZeroError::UnsupportedPlatform)
194 }
195 }
196
197 pub fn copy_to_device(&self, handle: u64, src: &[u8]) -> LevelZeroResult<()> {
202 #[cfg(any(target_os = "linux", target_os = "windows"))]
203 {
204 let buffers = self
208 .buffers
209 .lock()
210 .map_err(|_| LevelZeroError::CommandListError("mutex poisoned".into()))?;
211 let (device_ptr, alloc_size) = {
212 let rec = buffers.get(&handle).ok_or_else(|| {
213 LevelZeroError::InvalidArgument(format!("unknown handle {handle}"))
214 })?;
215 (rec.device_ptr, rec.size)
216 };
217
218 if src.len() as u64 > alloc_size {
221 return Err(LevelZeroError::InvalidArgument(format!(
222 "copy_to_device: src length {} exceeds device allocation size {} (handle {handle})",
223 src.len(),
224 alloc_size
225 )));
226 }
227
228 let api = &self.device.api;
229 let context = self.device.context;
230 let device_handle = self.device.device;
231 let queue = self.device.queue;
232 let copy_len = src.len();
233
234 let host_desc = ZeHostMemAllocDesc {
236 stype: ZE_STRUCTURE_TYPE_HOST_MEM_ALLOC_DESC,
237 p_next: std::ptr::null(),
238 flags: 0,
239 };
240 let mut host_ptr: *mut c_void = std::ptr::null_mut();
241 let rc = unsafe {
244 (api.ze_mem_alloc_host)(
245 context,
246 &host_desc,
247 copy_len,
248 64,
249 &mut host_ptr as *mut *mut c_void,
250 )
251 };
252 if rc != 0 {
253 return Err(LevelZeroError::ZeError(
254 rc,
255 "zeMemAllocHost (staging) failed".into(),
256 ));
257 }
258
259 unsafe {
263 std::ptr::copy_nonoverlapping(src.as_ptr(), host_ptr as *mut u8, copy_len);
264 }
265
266 let list_desc = ZeCommandListDesc {
268 stype: ZE_STRUCTURE_TYPE_COMMAND_LIST_DESC,
269 p_next: std::ptr::null(),
270 command_queue_group_ordinal: 0,
271 flags: 0,
272 };
273 let mut list: ZeCommandListHandle = std::ptr::null_mut();
274 let rc = unsafe {
277 (api.ze_command_list_create)(
278 context,
279 device_handle,
280 &list_desc,
281 &mut list as *mut ZeCommandListHandle,
282 )
283 };
284 if rc != 0 {
285 unsafe { (api.ze_mem_free)(context, host_ptr) };
287 return Err(LevelZeroError::CommandListError(format!(
288 "zeCommandListCreate failed: 0x{rc:08x}"
289 )));
290 }
291
292 let rc = unsafe {
296 (api.ze_command_list_append_memory_copy)(
297 list,
298 device_ptr,
299 host_ptr as *const c_void,
300 copy_len,
301 0, 0, std::ptr::null(),
304 )
305 };
306 if rc != 0 {
307 unsafe {
309 (api.ze_command_list_destroy)(list);
310 (api.ze_mem_free)(context, host_ptr);
311 }
312 return Err(LevelZeroError::CommandListError(format!(
313 "zeCommandListAppendMemoryCopy failed: 0x{rc:08x}"
314 )));
315 }
316
317 let rc = unsafe { (api.ze_command_list_close)(list) };
320 if rc != 0 {
321 unsafe {
322 (api.ze_command_list_destroy)(list);
323 (api.ze_mem_free)(context, host_ptr);
324 }
325 return Err(LevelZeroError::CommandListError(format!(
326 "zeCommandListClose failed: 0x{rc:08x}"
327 )));
328 }
329
330 let rc = unsafe { (api.ze_command_queue_execute_command_lists)(queue, 1, &list, 0) };
332 if rc != 0 {
333 unsafe {
334 (api.ze_command_list_destroy)(list);
335 (api.ze_mem_free)(context, host_ptr);
336 }
337 return Err(LevelZeroError::CommandListError(format!(
338 "zeCommandQueueExecuteCommandLists failed: 0x{rc:08x}"
339 )));
340 }
341
342 let rc = unsafe { (api.ze_command_queue_synchronize)(queue, u64::MAX) };
345 if rc != 0 {
346 unsafe {
347 (api.ze_command_list_destroy)(list);
348 (api.ze_mem_free)(context, host_ptr);
349 }
350 return Err(LevelZeroError::CommandListError(format!(
351 "zeCommandQueueSynchronize failed: 0x{rc:08x}"
352 )));
353 }
354
355 unsafe {
358 (api.ze_command_list_destroy)(list);
359 (api.ze_mem_free)(context, host_ptr);
360 }
361
362 Ok(())
363 }
364
365 #[cfg(not(any(target_os = "linux", target_os = "windows")))]
366 {
367 let _ = (handle, src);
368 Err(LevelZeroError::UnsupportedPlatform)
369 }
370 }
371
372 pub fn copy_from_device(&self, dst: &mut [u8], handle: u64) -> LevelZeroResult<()> {
376 #[cfg(any(target_os = "linux", target_os = "windows"))]
377 {
378 let buffers = self
382 .buffers
383 .lock()
384 .map_err(|_| LevelZeroError::CommandListError("mutex poisoned".into()))?;
385 let (device_ptr, alloc_size) = {
386 let rec = buffers.get(&handle).ok_or_else(|| {
387 LevelZeroError::InvalidArgument(format!("unknown handle {handle}"))
388 })?;
389 (rec.device_ptr, rec.size)
390 };
391
392 if dst.len() as u64 > alloc_size {
396 return Err(LevelZeroError::InvalidArgument(format!(
397 "copy_from_device: dst length {} exceeds device allocation size {} (handle {handle})",
398 dst.len(),
399 alloc_size
400 )));
401 }
402
403 let api = &self.device.api;
404 let context = self.device.context;
405 let device_handle = self.device.device;
406 let queue = self.device.queue;
407 let copy_len = dst.len();
408
409 let host_desc = ZeHostMemAllocDesc {
411 stype: ZE_STRUCTURE_TYPE_HOST_MEM_ALLOC_DESC,
412 p_next: std::ptr::null(),
413 flags: 0,
414 };
415 let mut host_ptr: *mut c_void = std::ptr::null_mut();
416 let rc = unsafe {
419 (api.ze_mem_alloc_host)(
420 context,
421 &host_desc,
422 copy_len,
423 64,
424 &mut host_ptr as *mut *mut c_void,
425 )
426 };
427 if rc != 0 {
428 return Err(LevelZeroError::ZeError(
429 rc,
430 "zeMemAllocHost (staging) failed".into(),
431 ));
432 }
433
434 let list_desc = ZeCommandListDesc {
436 stype: ZE_STRUCTURE_TYPE_COMMAND_LIST_DESC,
437 p_next: std::ptr::null(),
438 command_queue_group_ordinal: 0,
439 flags: 0,
440 };
441 let mut list: ZeCommandListHandle = std::ptr::null_mut();
442 let rc = unsafe {
445 (api.ze_command_list_create)(
446 context,
447 device_handle,
448 &list_desc,
449 &mut list as *mut ZeCommandListHandle,
450 )
451 };
452 if rc != 0 {
453 unsafe { (api.ze_mem_free)(context, host_ptr) };
454 return Err(LevelZeroError::CommandListError(format!(
455 "zeCommandListCreate failed: 0x{rc:08x}"
456 )));
457 }
458
459 let rc = unsafe {
463 (api.ze_command_list_append_memory_copy)(
464 list,
465 host_ptr,
466 device_ptr as *const c_void,
467 copy_len,
468 0, 0, std::ptr::null(),
471 )
472 };
473 if rc != 0 {
474 unsafe {
475 (api.ze_command_list_destroy)(list);
476 (api.ze_mem_free)(context, host_ptr);
477 }
478 return Err(LevelZeroError::CommandListError(format!(
479 "zeCommandListAppendMemoryCopy failed: 0x{rc:08x}"
480 )));
481 }
482
483 let rc = unsafe { (api.ze_command_list_close)(list) };
486 if rc != 0 {
487 unsafe {
488 (api.ze_command_list_destroy)(list);
489 (api.ze_mem_free)(context, host_ptr);
490 }
491 return Err(LevelZeroError::CommandListError(format!(
492 "zeCommandListClose failed: 0x{rc:08x}"
493 )));
494 }
495
496 let rc = unsafe { (api.ze_command_queue_execute_command_lists)(queue, 1, &list, 0) };
498 if rc != 0 {
499 unsafe {
500 (api.ze_command_list_destroy)(list);
501 (api.ze_mem_free)(context, host_ptr);
502 }
503 return Err(LevelZeroError::CommandListError(format!(
504 "zeCommandQueueExecuteCommandLists failed: 0x{rc:08x}"
505 )));
506 }
507
508 let rc = unsafe { (api.ze_command_queue_synchronize)(queue, u64::MAX) };
511 if rc != 0 {
512 unsafe {
513 (api.ze_command_list_destroy)(list);
514 (api.ze_mem_free)(context, host_ptr);
515 }
516 return Err(LevelZeroError::CommandListError(format!(
517 "zeCommandQueueSynchronize failed: 0x{rc:08x}"
518 )));
519 }
520
521 unsafe {
525 std::ptr::copy_nonoverlapping(host_ptr as *const u8, dst.as_mut_ptr(), copy_len);
526 }
527
528 unsafe {
531 (api.ze_command_list_destroy)(list);
532 (api.ze_mem_free)(context, host_ptr);
533 }
534
535 Ok(())
536 }
537
538 #[cfg(not(any(target_os = "linux", target_os = "windows")))]
539 {
540 let _ = (dst, handle);
541 Err(LevelZeroError::UnsupportedPlatform)
542 }
543 }
544}
545
546impl Drop for LevelZeroMemoryManager {
549 fn drop(&mut self) {
550 #[cfg(any(target_os = "linux", target_os = "windows"))]
551 {
552 let api = &self.device.api;
553 let context = self.device.context;
554
555 if let Ok(mut map) = self.buffers.lock() {
556 for (handle, rec) in map.drain() {
557 tracing::warn!(
558 "LevelZeroMemoryManager: leaked buffer handle {handle} ({} bytes)",
559 rec.size
560 );
561 unsafe { (api.ze_mem_free)(context, rec.device_ptr) };
563 }
564 }
565 }
566
567 #[cfg(not(any(target_os = "linux", target_os = "windows")))]
568 {
569 }
571 }
572}
573
574impl std::fmt::Debug for LevelZeroMemoryManager {
577 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
578 let count = self.buffers.lock().map(|b| b.len()).unwrap_or(0);
579 write!(f, "LevelZeroMemoryManager(buffers={count})")
580 }
581}
582
583unsafe impl Send for LevelZeroMemoryManager {}
588unsafe impl Sync for LevelZeroMemoryManager {}
590
591#[cfg(test)]
594mod tests {
595 use super::*;
596
597 fn try_get_device() -> Option<Arc<LevelZeroDevice>> {
598 LevelZeroDevice::new().ok().map(Arc::new)
599 }
600
601 #[test]
602 fn alloc_and_free_requires_device() {
603 let Some(dev) = try_get_device() else {
604 return;
605 };
606 let mm = LevelZeroMemoryManager::new(dev);
607 let h = mm.alloc(256).expect("alloc 256 bytes");
608 assert!(h > 0);
609 mm.free(h).expect("free");
610 mm.free(h).expect("double-free is a no-op");
612 }
613
614 #[test]
615 fn copy_roundtrip_requires_device() {
616 let Some(dev) = try_get_device() else {
617 return;
618 };
619 let mm = LevelZeroMemoryManager::new(dev);
620
621 let src: Vec<u8> = (0u8..64).collect();
622 let h = mm.alloc(src.len()).expect("alloc");
623 mm.copy_to_device(h, &src).expect("copy_to_device");
624
625 let mut dst = vec![0u8; src.len()];
626 mm.copy_from_device(&mut dst, h).expect("copy_from_device");
627
628 assert_eq!(src, dst);
629 mm.free(h).expect("free");
630 }
631
632 #[test]
633 fn unknown_handle_returns_error() {
634 let Some(dev) = try_get_device() else {
635 return;
636 };
637 let mm = LevelZeroMemoryManager::new(dev);
638 let err = mm.copy_to_device(9999, b"hello").unwrap_err();
639 assert!(matches!(err, LevelZeroError::InvalidArgument(_)));
640 }
641
642 #[test]
643 fn debug_impl_smoke() {
644 let Some(dev) = try_get_device() else {
645 return;
646 };
647 let mm = LevelZeroMemoryManager::new(dev);
648 let s = format!("{mm:?}");
649 assert!(s.contains("LevelZeroMemoryManager"));
650 }
651}