Skip to main content

optirs_gpu/memory/vendors/
oneapi_backend.rs

1// OneAPI backend for GPU memory management
2//
3// This module provides Intel OneAPI/SYCL-specific memory management functionality,
4// including device memory allocation, SYCL queues, and performance optimization
5// features specific to Intel GPUs and accelerators.
6//
7// # This is a host-memory simulation, not real OneAPI
8//
9// `optirs-gpu` is Pure Rust with no FFI dependencies by default, and this
10// crate ships no SYCL/Level Zero runtime bindings. There is therefore no
11// real `malloc_device`/`malloc_shared` underneath this module: "device",
12// "host", "shared" and "system" USM allocations are all the *same*
13// system-heap allocation (see `sim_alloc`/`sim_dealloc` below), and
14// `SyclDeviceProperties`/`OneApiStats` are example numbers, not a query of
15// real hardware. This module models the SYCL USM memory-management *API
16// shape* for testing that shape in isolation; treat every allocation as host
17// memory and every device number as illustrative.
18//
19// This extends to data movement: `usm_memcpy` copies **zero bytes**. It
20// builds a `SyclOperation` record, hands it to
21// `SyclQueueManager::execute_operation` (which returns immediately and never
22// dereferences `src_ptr`/`dst_ptr`), and increments
23// `OneApiStats::memory_transfers` — that counter says "this many
24// `usm_memcpy` calls were made," not "this many bytes moved." An earlier
25// revision of this module also injected a `std::thread::sleep` here to
26// imitate transfer/kernel-submission latency by operation kind; that fake
27// timing has been removed, so the distinction between
28// `MemcpyHostToDevice`/`MemcpyDeviceToHost`/`MemcpyDeviceToDevice`/
29// `UsmMemcpy`/`KernelSubmit` no longer affects anything observable.
30// `OneApiStats::queue_operations` and `OneApiStats::kernel_submissions` are
31// declared for API-shape completeness but nothing in this module ever
32// increments them — read a `0` there as "not tracked," not "none occurred."
33
34use std::collections::HashMap;
35use std::ffi::c_void;
36use std::sync::{Arc, Mutex};
37use std::time::{Duration, Instant};
38
39/// Upper bound this module's simulated allocator supports for a caller-
40/// requested alignment (both `sycl_malloc`'s `usm_properties.alignment` and
41/// `direct_allocate`'s local `64` are well under this in practice). Also the
42/// fixed header size reserved ahead of every non-empty allocation, so a
43/// request with alignment `<= SIM_ALLOC_ALIGN` needs no per-call alignment
44/// bookkeeping: over-aligning to `SIM_ALLOC_ALIGN` always satisfies a
45/// smaller request too.
46const SIM_ALLOC_ALIGN: usize = 256;
47
48/// Allocate `size` bytes at (at least) `align`-byte alignment through the
49/// system allocator, without the two ways the naive
50/// `std::alloc::alloc(Layout::from_size_align_unchecked(size, align))` this
51/// module used to call was undefined behaviour: a zero-size layout is
52/// unsound to pass to `GlobalAlloc::alloc`, and a real allocation failure
53/// returns null, which must never be treated as valid memory. See
54/// `cuda_backend::sim_alloc` for the full rationale (this mirrors it, with
55/// an added alignment check since this module's alignment is caller-chosen
56/// rather than a fixed constant).
57///
58/// The payload is prefixed with a `SIM_ALLOC_ALIGN`-byte header recording
59/// the requested size, so [`sim_dealloc`] can reconstruct the exact `Layout`
60/// this function used — the `Layout::from_size_align_unchecked(1, 1)` this
61/// module used at free time was a mismatched-layout deallocation, itself
62/// unconditionally undefined behaviour.
63fn sim_alloc(size: usize, align: usize) -> Result<*mut c_void, OneApiError> {
64    if !align.is_power_of_two() || align > SIM_ALLOC_ALIGN {
65        return Err(OneApiError::OutOfMemory(format!(
66            "unsupported allocation alignment {align}: this simulated backend supports \
67             power-of-two alignments up to {SIM_ALLOC_ALIGN} bytes"
68        )));
69    }
70    if size == 0 {
71        return Ok(SIM_ALLOC_ALIGN as *mut c_void);
72    }
73    let total = SIM_ALLOC_ALIGN.checked_add(size).ok_or_else(|| {
74        OneApiError::OutOfMemory(format!(
75            "{size}-byte request overflows the allocator's size limit"
76        ))
77    })?;
78    // Always allocate at `SIM_ALLOC_ALIGN`: since `align <= SIM_ALLOC_ALIGN`
79    // and both are powers of two, this over-aligned block also satisfies the
80    // caller's smaller request.
81    let layout = std::alloc::Layout::from_size_align(total, SIM_ALLOC_ALIGN)
82        .map_err(|e| OneApiError::OutOfMemory(format!("invalid allocation layout: {e}")))?;
83    // SAFETY: `layout` has non-zero size (checked above) and a valid
84    // (power-of-two) alignment constructed by `Layout::from_size_align`.
85    let base = unsafe { std::alloc::alloc(layout) };
86    if base.is_null() {
87        return Err(OneApiError::OutOfMemory(format!(
88            "allocator returned null for a {size}-byte request"
89        )));
90    }
91    // SAFETY: `base` is non-null and `layout`'s size is at least
92    // `SIM_ALLOC_ALIGN + size >= SIM_ALLOC_ALIGN >= size_of::<usize>()`, so
93    // writing one `usize` at the start of the block is in-bounds.
94    unsafe { (base as *mut usize).write(size) };
95    // SAFETY: `base` was allocated with `total = SIM_ALLOC_ALIGN + size`
96    // bytes, so offsetting by `SIM_ALLOC_ALIGN` stays within (or one past)
97    // the allocation.
98    Ok(unsafe { base.add(SIM_ALLOC_ALIGN) } as *mut c_void)
99}
100
101/// Free a pointer returned by [`sim_alloc`]. A no-op for a null pointer or
102/// the zero-size sentinel — neither was ever allocated.
103///
104/// Only ever called from this module (it is not `pub`) with a pointer
105/// `sim_alloc` returned that has not already been freed — the unsafety of
106/// the pointer arithmetic below is contained to that invariant, matching
107/// this module's existing style of confining `unsafe` to the raw
108/// `std::alloc` calls rather than marking `free()`'s public wrapper unsafe.
109fn sim_dealloc(ptr: *mut c_void) {
110    if ptr.is_null() || (ptr as usize) == SIM_ALLOC_ALIGN {
111        return;
112    }
113    // SAFETY: by this function's contract `ptr` came from `sim_alloc`.
114    let base = unsafe { (ptr as *mut u8).sub(SIM_ALLOC_ALIGN) };
115    // SAFETY: `sim_alloc` wrote a `usize` at `base` before returning.
116    let size = unsafe { (base as *const usize).read() };
117    if let Ok(layout) = std::alloc::Layout::from_size_align(SIM_ALLOC_ALIGN + size, SIM_ALLOC_ALIGN)
118    {
119        // SAFETY: `layout` is exactly the layout `sim_alloc` allocated
120        // `base` with.
121        unsafe { std::alloc::dealloc(base, layout) };
122    }
123}
124
125/// OneAPI memory backend implementation
126pub struct OneApiMemoryBackend {
127    /// Backend configuration
128    config: OneApiConfig,
129    /// Device properties
130    device_properties: SyclDeviceProperties,
131    /// Active SYCL contexts
132    contexts: HashMap<u32, SyclContext>,
133    /// Memory pools
134    memory_pools: HashMap<OneApiMemoryType, OneApiMemoryPool>,
135    /// Statistics
136    stats: OneApiStats,
137    /// Queue management
138    queue_manager: SyclQueueManager,
139}
140
141/// OneAPI backend configuration
142#[derive(Debug, Clone)]
143pub struct OneApiConfig {
144    /// Device ID to use
145    pub device_id: u32,
146    /// Enable unified shared memory (USM)
147    pub enable_usm: bool,
148    /// Enable device-specific USM
149    pub enable_device_usm: bool,
150    /// Enable host USM
151    pub enable_host_usm: bool,
152    /// Enable shared USM
153    pub enable_shared_usm: bool,
154    /// Enable memory pools
155    pub enable_memory_pools: bool,
156    /// Enable async memory operations
157    pub enable_async_ops: bool,
158    /// Memory pool growth size
159    pub pool_growth_size: usize,
160    /// Maximum number of queues
161    pub max_queues: u32,
162    /// Enable profiling
163    pub enable_profiling: bool,
164    /// Enable sub-groups
165    pub enable_sub_groups: bool,
166}
167
168impl Default for OneApiConfig {
169    fn default() -> Self {
170        Self {
171            device_id: 0,
172            enable_usm: true,
173            enable_device_usm: true,
174            enable_host_usm: true,
175            enable_shared_usm: true,
176            enable_memory_pools: true,
177            enable_async_ops: true,
178            pool_growth_size: 64 * 1024 * 1024, // 64MB
179            max_queues: 16,
180            enable_profiling: false,
181            enable_sub_groups: true,
182        }
183    }
184}
185
186/// SYCL device properties
187#[derive(Debug, Clone)]
188pub struct SyclDeviceProperties {
189    pub device_id: u32,
190    pub name: String,
191    pub vendor: String,
192    pub device_type: SyclDeviceType,
193    pub driver_version: String,
194    pub global_memory_size: usize,
195    pub local_memory_size: usize,
196    pub max_work_group_size: u32,
197    pub max_work_item_dimensions: u32,
198    pub max_work_item_sizes: [u32; 3],
199    pub compute_units: u32,
200    pub max_compute_units: u32,
201    pub sub_group_sizes: Vec<u32>,
202    pub preferred_sub_group_size: u32,
203    pub max_constant_buffer_size: usize,
204    pub has_fp64: bool,
205    pub has_fp16: bool,
206    pub has_atomic64: bool,
207    pub usm_device_allocations: bool,
208    pub usm_host_allocations: bool,
209    pub usm_shared_allocations: bool,
210    pub usm_system_allocations: bool,
211}
212
213/// SYCL device types
214#[derive(Debug, Clone, PartialEq)]
215pub enum SyclDeviceType {
216    GPU,
217    CPU,
218    Accelerator,
219    Host,
220    Custom,
221}
222
223/// OneAPI memory types
224#[derive(Debug, Clone, PartialEq, Eq, Hash)]
225pub enum OneApiMemoryType {
226    Device,
227    Host,
228    Shared,
229    System,
230    Buffer,
231}
232
233/// SYCL context for managing device state
234pub struct SyclContext {
235    /// Context handle (simulated)
236    pub handle: *mut c_void,
237    /// Device ID
238    pub device_id: u32,
239    /// Associated device
240    pub device_properties: SyclDeviceProperties,
241    /// Creation time
242    pub created_at: Instant,
243    /// Active queues
244    pub queues: Vec<SyclQueue>,
245    /// USM allocations
246    pub usm_allocations: HashMap<*mut c_void, UsmAllocation>,
247}
248
249/// USM (Unified Shared Memory) allocation info
250#[derive(Debug, Clone)]
251pub struct UsmAllocation {
252    pub ptr: *mut c_void,
253    pub size: usize,
254    pub usm_kind: UsmKind,
255    pub allocated_at: Instant,
256    pub device_id: u32,
257    pub alignment: usize,
258}
259
260/// USM allocation kinds
261#[derive(Debug, Clone, PartialEq)]
262pub enum UsmKind {
263    Device, // Device-only memory
264    Host,   // Host-accessible memory
265    Shared, // Automatically migrating memory
266    System, // System allocator memory
267}
268
269/// SYCL queue for asynchronous operations
270pub struct SyclQueue {
271    /// Queue handle (simulated)
272    pub handle: *mut c_void,
273    /// Queue ID
274    pub id: u32,
275    /// Queue properties
276    pub properties: SyclQueueProperties,
277    /// Creation time
278    pub created_at: Instant,
279    /// Operations queue
280    pub operations: std::collections::VecDeque<SyclOperation>,
281    /// Associated context
282    pub context_id: Option<u32>,
283}
284
285/// SYCL queue properties
286#[derive(Debug, Clone)]
287pub struct SyclQueueProperties {
288    pub in_order: bool,
289    pub enable_profiling: bool,
290    pub priority: SyclQueuePriority,
291}
292
293impl Default for SyclQueueProperties {
294    fn default() -> Self {
295        Self {
296            in_order: false,
297            enable_profiling: false,
298            priority: SyclQueuePriority::Normal,
299        }
300    }
301}
302
303/// SYCL queue priorities
304#[derive(Debug, Clone, PartialEq)]
305pub enum SyclQueuePriority {
306    Low,
307    Normal,
308    High,
309}
310
311/// SYCL asynchronous operation
312#[derive(Debug, Clone)]
313pub struct SyclOperation {
314    pub op_type: SyclOperationType,
315    pub src_ptr: Option<*mut c_void>,
316    pub dst_ptr: Option<*mut c_void>,
317    pub size: usize,
318    pub timestamp: Instant,
319    pub event_handle: Option<*mut c_void>,
320}
321
322/// Types of SYCL operations
323#[derive(Debug, Clone)]
324pub enum SyclOperationType {
325    MemcpyHostToDevice,
326    MemcpyDeviceToHost,
327    MemcpyDeviceToDevice,
328    UsmMemcpy,
329    UsmMemset,
330    KernelSubmit,
331    BarrierWait,
332    Fill,
333}
334
335/// OneAPI memory pool
336pub struct OneApiMemoryPool {
337    /// Memory type
338    memory_type: OneApiMemoryType,
339    /// Current size
340    current_size: usize,
341    /// Maximum size
342    max_size: usize,
343    /// Used size
344    used_size: usize,
345    /// Free blocks
346    free_blocks: std::collections::VecDeque<OneApiMemoryBlock>,
347    /// Allocated blocks
348    allocated_blocks: HashMap<*mut c_void, OneApiMemoryBlock>,
349    /// USM properties
350    usm_properties: UsmProperties,
351}
352
353/// OneAPI memory block
354#[derive(Debug, Clone)]
355pub struct OneApiMemoryBlock {
356    pub ptr: *mut c_void,
357    pub size: usize,
358    pub memory_type: OneApiMemoryType,
359    pub allocated_at: Instant,
360    pub last_access: Option<Instant>,
361    pub ref_count: u32,
362    pub usm_kind: Option<UsmKind>,
363    pub device_accessible: bool,
364    pub host_accessible: bool,
365}
366
367/// USM memory properties
368#[derive(Debug, Clone)]
369pub struct UsmProperties {
370    pub alignment: usize,
371    pub device_read_only: bool,
372    pub device_access: bool,
373    pub host_access: bool,
374    pub supports_atomics: bool,
375}
376
377impl Default for UsmProperties {
378    fn default() -> Self {
379        Self {
380            alignment: 64, // Common alignment for Intel GPUs
381            device_read_only: false,
382            device_access: true,
383            host_access: false,
384            supports_atomics: true,
385        }
386    }
387}
388
389impl OneApiMemoryPool {
390    pub fn new(memory_type: OneApiMemoryType, max_size: usize) -> Self {
391        let usm_properties = match memory_type {
392            OneApiMemoryType::Device => UsmProperties {
393                alignment: 64,
394                device_read_only: false,
395                device_access: true,
396                host_access: false,
397                supports_atomics: true,
398            },
399            OneApiMemoryType::Host => UsmProperties {
400                alignment: 64,
401                device_read_only: false,
402                device_access: true,
403                host_access: true,
404                supports_atomics: false,
405            },
406            OneApiMemoryType::Shared => UsmProperties {
407                alignment: 64,
408                device_read_only: false,
409                device_access: true,
410                host_access: true,
411                supports_atomics: true,
412            },
413            _ => UsmProperties::default(),
414        };
415
416        Self {
417            memory_type,
418            current_size: 0,
419            max_size,
420            used_size: 0,
421            free_blocks: std::collections::VecDeque::new(),
422            allocated_blocks: HashMap::new(),
423            usm_properties,
424        }
425    }
426
427    /// Allocate from pool
428    pub fn allocate(&mut self, size: usize) -> Result<*mut c_void, OneApiError> {
429        // Try to find suitable free block
430        for i in 0..self.free_blocks.len() {
431            if self.free_blocks[i].size >= size {
432                let Some(mut block) = self.free_blocks.remove(i) else {
433                    continue;
434                };
435
436                // Split block if much larger
437                if block.size > size * 2 {
438                    let remaining_block = OneApiMemoryBlock {
439                        ptr: unsafe { block.ptr.add(size) },
440                        size: block.size - size,
441                        memory_type: block.memory_type.clone(),
442                        allocated_at: block.allocated_at,
443                        last_access: None,
444                        ref_count: 0,
445                        usm_kind: block.usm_kind.clone(),
446                        device_accessible: block.device_accessible,
447                        host_accessible: block.host_accessible,
448                    };
449                    self.free_blocks.push_back(remaining_block);
450                    block.size = size;
451                }
452
453                block.last_access = Some(Instant::now());
454                block.ref_count = 1;
455
456                let ptr = block.ptr;
457                self.allocated_blocks.insert(ptr, block);
458                self.used_size += size;
459
460                return Ok(ptr);
461            }
462        }
463
464        // Need to allocate new memory
465        if self.current_size + size > self.max_size {
466            return Err(OneApiError::OutOfMemory(
467                "Pool size limit exceeded".to_string(),
468            ));
469        }
470
471        let ptr = self.sycl_malloc(size)?;
472        let usm_kind = match self.memory_type {
473            OneApiMemoryType::Device => Some(UsmKind::Device),
474            OneApiMemoryType::Host => Some(UsmKind::Host),
475            OneApiMemoryType::Shared => Some(UsmKind::Shared),
476            OneApiMemoryType::System => Some(UsmKind::System),
477            _ => None,
478        };
479
480        let block = OneApiMemoryBlock {
481            ptr,
482            size,
483            memory_type: self.memory_type.clone(),
484            allocated_at: Instant::now(),
485            last_access: Some(Instant::now()),
486            ref_count: 1,
487            usm_kind,
488            device_accessible: self.usm_properties.device_access,
489            host_accessible: self.usm_properties.host_access,
490        };
491
492        self.allocated_blocks.insert(ptr, block);
493        self.current_size += size;
494        self.used_size += size;
495
496        Ok(ptr)
497    }
498
499    /// Free back to pool
500    pub fn free(&mut self, ptr: *mut c_void) -> Result<(), OneApiError> {
501        if let Some(block) = self.allocated_blocks.remove(&ptr) {
502            self.used_size -= block.size;
503
504            // Add to free blocks
505            self.free_blocks.push_back(OneApiMemoryBlock {
506                ptr: block.ptr,
507                size: block.size,
508                memory_type: block.memory_type,
509                allocated_at: block.allocated_at,
510                last_access: None,
511                ref_count: 0,
512                usm_kind: block.usm_kind,
513                device_accessible: block.device_accessible,
514                host_accessible: block.host_accessible,
515            });
516
517            // Try to coalesce adjacent blocks
518            self.coalesce_free_blocks();
519
520            Ok(())
521        } else {
522            Err(OneApiError::InvalidPointer(
523                "Pointer not found in pool".to_string(),
524            ))
525        }
526    }
527
528    fn coalesce_free_blocks(&mut self) {
529        // Sort free blocks by address
530        let mut blocks: Vec<OneApiMemoryBlock> = self.free_blocks.drain(..).collect();
531        blocks.sort_by_key(|block| block.ptr as usize);
532
533        let mut coalesced = Vec::new();
534        let mut current_block: Option<OneApiMemoryBlock> = None;
535
536        for block in blocks {
537            match current_block.take() {
538                None => current_block = Some(block),
539                Some(mut prev_block) => {
540                    let prev_end = prev_block.ptr as usize + prev_block.size;
541                    let block_start = block.ptr as usize;
542
543                    if prev_end == block_start && prev_block.memory_type == block.memory_type {
544                        // Coalesce blocks
545                        prev_block.size += block.size;
546                        current_block = Some(prev_block);
547                    } else {
548                        coalesced.push(prev_block);
549                        current_block = Some(block);
550                    }
551                }
552            }
553        }
554
555        if let Some(block) = current_block {
556            coalesced.push(block);
557        }
558
559        self.free_blocks = coalesced.into();
560    }
561
562    fn sycl_malloc(&self, size: usize) -> Result<*mut c_void, OneApiError> {
563        // Simulate SYCL USM allocation
564        match self.memory_type {
565            OneApiMemoryType::Device => sim_alloc(size, self.usm_properties.alignment), // malloc_device
566            OneApiMemoryType::Host => sim_alloc(size, self.usm_properties.alignment), // malloc_host
567            OneApiMemoryType::Shared => sim_alloc(size, self.usm_properties.alignment), // malloc_shared
568            OneApiMemoryType::System => sim_alloc(size, self.usm_properties.alignment), // system malloc
569            _ => Err(OneApiError::UnsupportedOperation(
570                "Unsupported memory type for allocation".to_string(),
571            )),
572        }
573    }
574}
575
576/// SYCL queue manager
577pub struct SyclQueueManager {
578    /// Available queues
579    queues: Vec<SyclQueue>,
580    /// Queue pool for reuse
581    queue_pool: std::collections::VecDeque<SyclQueue>,
582    /// Next queue ID
583    next_queue_id: u32,
584    /// Configuration
585    config: SyclQueueConfig,
586}
587
588/// Queue manager configuration
589#[derive(Debug, Clone)]
590pub struct SyclQueueConfig {
591    pub default_priority: SyclQueuePriority,
592    pub enable_priorities: bool,
593    pub max_operations_per_queue: usize,
594    pub default_in_order: bool,
595}
596
597impl Default for SyclQueueConfig {
598    fn default() -> Self {
599        Self {
600            default_priority: SyclQueuePriority::Normal,
601            enable_priorities: true,
602            max_operations_per_queue: 1000,
603            default_in_order: false,
604        }
605    }
606}
607
608impl SyclQueueManager {
609    pub fn new(config: SyclQueueConfig) -> Self {
610        Self {
611            queues: Vec::new(),
612            queue_pool: std::collections::VecDeque::new(),
613            next_queue_id: 0,
614            config,
615        }
616    }
617
618    /// Create new queue
619    ///
620    /// Reuses a previously [`Self::destroy_queue`]d queue from `queue_pool`
621    /// when one is available (its operation queue is cleared and it is
622    /// given a fresh ID) instead of always allocating a new one.
623    pub fn create_queue(
624        &mut self,
625        properties: Option<SyclQueueProperties>,
626    ) -> Result<u32, OneApiError> {
627        let queue_id = self.next_queue_id;
628        self.next_queue_id += 1;
629
630        let queue_properties = properties.unwrap_or_else(|| SyclQueueProperties {
631            in_order: self.config.default_in_order,
632            enable_profiling: false,
633            priority: self.config.default_priority.clone(),
634        });
635
636        let mut queue = self.queue_pool.pop_front().unwrap_or_else(|| SyclQueue {
637            handle: std::ptr::null_mut(), // Would be actual SYCL queue
638            id: queue_id,
639            properties: queue_properties.clone(),
640            created_at: Instant::now(),
641            operations: std::collections::VecDeque::new(),
642            context_id: None,
643        });
644        queue.id = queue_id;
645        queue.properties = queue_properties;
646        queue.created_at = Instant::now();
647        queue.operations.clear();
648        queue.context_id = None;
649
650        self.queues.push(queue);
651        Ok(queue_id)
652    }
653
654    /// Destroy queue
655    ///
656    /// Returns the queue to `queue_pool` for [`Self::create_queue`] to
657    /// reuse instead of dropping it outright.
658    pub fn destroy_queue(&mut self, queue_id: u32) -> Result<(), OneApiError> {
659        if let Some(pos) = self.queues.iter().position(|q| q.id == queue_id) {
660            let queue = self.queues.remove(pos);
661            self.queue_pool.push_back(queue);
662            Ok(())
663        } else {
664            Err(OneApiError::InvalidQueue("Queue not found".to_string()))
665        }
666    }
667
668    /// Submit operation to queue
669    pub fn submit_operation(
670        &mut self,
671        queue_id: u32,
672        operation: SyclOperation,
673    ) -> Result<(), OneApiError> {
674        if let Some(queue) = self.queues.iter_mut().find(|q| q.id == queue_id) {
675            if queue.operations.len() >= self.config.max_operations_per_queue {
676                return Err(OneApiError::QueueFull(
677                    "Queue operation limit reached".to_string(),
678                ));
679            }
680
681            queue.operations.push_back(operation);
682            Ok(())
683        } else {
684            Err(OneApiError::InvalidQueue("Queue not found".to_string()))
685        }
686    }
687
688    /// Wait for queue completion
689    pub fn wait_for_queue(&mut self, queue_id: u32) -> Result<(), OneApiError> {
690        // First, collect all operations from the queue
691        let mut operations = Vec::new();
692        if let Some(queue) = self.queues.iter_mut().find(|q| q.id == queue_id) {
693            while let Some(operation) = queue.operations.pop_front() {
694                operations.push(operation);
695            }
696        } else {
697            return Err(OneApiError::InvalidQueue("Queue not found".to_string()));
698        }
699
700        // Now execute all operations
701        for operation in operations {
702            self.execute_operation(operation)?;
703        }
704
705        Ok(())
706    }
707
708    fn execute_operation(&self, _operation: SyclOperation) -> Result<(), OneApiError> {
709        // Host-memory simulation (see module docs): there is no real
710        // SYCL/Level Zero device to transfer to or from, so no data movement
711        // happens here. This used to also inject an artificial
712        // `std::thread::sleep` per operation type to mimic device-transfer
713        // and kernel-submission latency; that fake timing has been removed
714        // rather than left as an undisclosed simulated number, so callers
715        // now see the true (near-zero) cost of this simulation instead of a
716        // fabricated one.
717        Ok(())
718    }
719}
720
721/// OneAPI statistics
722#[derive(Debug, Clone, Default)]
723pub struct OneApiStats {
724    pub total_allocations: u64,
725    pub total_deallocations: u64,
726    pub bytes_allocated: u64,
727    pub bytes_deallocated: u64,
728    pub device_memory_used: usize,
729    pub host_memory_used: usize,
730    pub shared_memory_used: usize,
731    pub usm_allocations: u64,
732    pub queue_operations: u64,
733    pub kernel_submissions: u64,
734    pub memory_transfers: u64,
735    pub average_allocation_time: Duration,
736    pub peak_memory_usage: usize,
737}
738
739impl OneApiMemoryBackend {
740    /// Create new OneAPI backend
741    pub fn new(config: OneApiConfig) -> Result<Self, OneApiError> {
742        // Initialize OneAPI device
743        let device_properties = Self::query_device_properties(config.device_id)?;
744
745        // Create memory pools
746        let mut memory_pools = HashMap::new();
747        if config.enable_memory_pools {
748            let pool_size = device_properties.global_memory_size / 4; // Use 1/4 of total memory
749
750            if config.enable_device_usm {
751                memory_pools.insert(
752                    OneApiMemoryType::Device,
753                    OneApiMemoryPool::new(OneApiMemoryType::Device, pool_size),
754                );
755            }
756
757            if config.enable_host_usm {
758                memory_pools.insert(
759                    OneApiMemoryType::Host,
760                    OneApiMemoryPool::new(OneApiMemoryType::Host, pool_size),
761                );
762            }
763
764            if config.enable_shared_usm {
765                memory_pools.insert(
766                    OneApiMemoryType::Shared,
767                    OneApiMemoryPool::new(OneApiMemoryType::Shared, pool_size / 2),
768                );
769            }
770
771            memory_pools.insert(
772                OneApiMemoryType::System,
773                OneApiMemoryPool::new(OneApiMemoryType::System, pool_size / 4),
774            );
775        }
776
777        let queue_manager = SyclQueueManager::new(SyclQueueConfig::default());
778
779        Ok(Self {
780            config,
781            device_properties,
782            contexts: HashMap::new(),
783            memory_pools,
784            stats: OneApiStats::default(),
785            queue_manager,
786        })
787    }
788
789    /// Query device properties
790    fn query_device_properties(device_id: u32) -> Result<SyclDeviceProperties, OneApiError> {
791        // Simulate querying OneAPI/SYCL device properties
792        Ok(SyclDeviceProperties {
793            device_id,
794            name: format!("Intel GPU {}", device_id),
795            vendor: "Intel Corporation".to_string(),
796            device_type: SyclDeviceType::GPU,
797            driver_version: "1.3.0".to_string(),
798            global_memory_size: 12 * 1024 * 1024 * 1024, // 12GB
799            local_memory_size: 64 * 1024,                // 64KB
800            max_work_group_size: 1024,
801            max_work_item_dimensions: 3,
802            max_work_item_sizes: [1024, 1024, 1024],
803            compute_units: 96,
804            max_compute_units: 96,
805            sub_group_sizes: vec![8, 16, 32],
806            preferred_sub_group_size: 16,
807            max_constant_buffer_size: 64 * 1024,
808            has_fp64: true,
809            has_fp16: true,
810            has_atomic64: true,
811            usm_device_allocations: true,
812            usm_host_allocations: true,
813            usm_shared_allocations: true,
814            usm_system_allocations: true,
815        })
816    }
817
818    /// Allocate memory
819    pub fn allocate(
820        &mut self,
821        size: usize,
822        memory_type: OneApiMemoryType,
823    ) -> Result<*mut c_void, OneApiError> {
824        let start_time = Instant::now();
825
826        let ptr = if self.config.enable_memory_pools {
827            if let Some(pool) = self.memory_pools.get_mut(&memory_type) {
828                pool.allocate(size)?
829            } else {
830                return Err(OneApiError::UnsupportedMemoryType(
831                    "Memory type not supported".to_string(),
832                ));
833            }
834        } else {
835            // Direct allocation
836            self.direct_allocate(size, memory_type.clone())?
837        };
838
839        // Update statistics
840        self.stats.total_allocations += 1;
841        self.stats.bytes_allocated += size as u64;
842
843        match memory_type {
844            OneApiMemoryType::Device => self.stats.device_memory_used += size,
845            OneApiMemoryType::Host => self.stats.host_memory_used += size,
846            OneApiMemoryType::Shared => self.stats.shared_memory_used += size,
847            _ => {}
848        }
849
850        if matches!(
851            memory_type,
852            OneApiMemoryType::Device | OneApiMemoryType::Host | OneApiMemoryType::Shared
853        ) {
854            self.stats.usm_allocations += 1;
855        }
856
857        let allocation_time = start_time.elapsed();
858        let total_time = self.stats.average_allocation_time.as_nanos() as u64
859            * (self.stats.total_allocations - 1)
860            + allocation_time.as_nanos() as u64;
861        self.stats.average_allocation_time =
862            Duration::from_nanos(total_time / self.stats.total_allocations);
863
864        let current_usage = self.stats.device_memory_used
865            + self.stats.host_memory_used
866            + self.stats.shared_memory_used;
867        if current_usage > self.stats.peak_memory_usage {
868            self.stats.peak_memory_usage = current_usage;
869        }
870
871        Ok(ptr)
872    }
873
874    fn direct_allocate(
875        &self,
876        size: usize,
877        memory_type: OneApiMemoryType,
878    ) -> Result<*mut c_void, OneApiError> {
879        // Simulate direct SYCL allocation
880        let alignment = 64; // Common alignment for Intel GPUs
881
882        match memory_type {
883            OneApiMemoryType::Device => sim_alloc(size, alignment), // malloc_device
884            OneApiMemoryType::Host => sim_alloc(size, alignment),   // malloc_host
885            OneApiMemoryType::Shared => sim_alloc(size, alignment), // malloc_shared
886            OneApiMemoryType::System => sim_alloc(size, alignment), // system malloc
887            _ => Err(OneApiError::UnsupportedMemoryType(
888                "Unsupported memory type".to_string(),
889            )),
890        }
891    }
892
893    /// Free memory
894    pub fn free(
895        &mut self,
896        ptr: *mut c_void,
897        memory_type: OneApiMemoryType,
898    ) -> Result<(), OneApiError> {
899        if self.config.enable_memory_pools {
900            if let Some(pool) = self.memory_pools.get_mut(&memory_type) {
901                pool.free(ptr)?;
902            } else {
903                return Err(OneApiError::UnsupportedMemoryType(
904                    "Memory type not supported".to_string(),
905                ));
906            }
907        } else {
908            // Direct deallocation. `ptr` was returned by `sim_alloc` via
909            // `sycl_malloc`/`direct_allocate` above, and this is the first
910            // time it is freed.
911            sim_dealloc(ptr);
912        }
913
914        self.stats.total_deallocations += 1;
915        Ok(())
916    }
917
918    /// USM memory copy
919    pub fn usm_memcpy(
920        &mut self,
921        dst: *mut c_void,
922        src: *const c_void,
923        size: usize,
924        queue_id: u32,
925    ) -> Result<(), OneApiError> {
926        let operation = SyclOperation {
927            op_type: SyclOperationType::UsmMemcpy,
928            src_ptr: Some(src as *mut c_void),
929            dst_ptr: Some(dst),
930            size,
931            timestamp: Instant::now(),
932            event_handle: None,
933        };
934
935        self.queue_manager.submit_operation(queue_id, operation)?;
936        self.stats.memory_transfers += 1;
937        Ok(())
938    }
939
940    /// Create SYCL context
941    pub fn create_context(&mut self) -> Result<u32, OneApiError> {
942        let context_id = self.contexts.len() as u32;
943
944        let context = SyclContext {
945            handle: std::ptr::null_mut(), // Would be actual SYCL context
946            device_id: self.config.device_id,
947            device_properties: self.device_properties.clone(),
948            created_at: Instant::now(),
949            queues: Vec::new(),
950            usm_allocations: HashMap::new(),
951        };
952
953        self.contexts.insert(context_id, context);
954        Ok(context_id)
955    }
956
957    /// Create queue
958    pub fn create_queue(
959        &mut self,
960        properties: Option<SyclQueueProperties>,
961    ) -> Result<u32, OneApiError> {
962        self.queue_manager.create_queue(properties)
963    }
964
965    /// Destroy queue
966    pub fn destroy_queue(&mut self, queue_id: u32) -> Result<(), OneApiError> {
967        self.queue_manager.destroy_queue(queue_id)
968    }
969
970    /// Wait for all queues
971    pub fn wait_all(&mut self) -> Result<(), OneApiError> {
972        let queue_ids: Vec<u32> = self.queue_manager.queues.iter().map(|q| q.id).collect();
973        for queue_id in queue_ids {
974            self.queue_manager.wait_for_queue(queue_id)?;
975        }
976        Ok(())
977    }
978
979    /// Get device properties
980    pub fn get_device_properties(&self) -> &SyclDeviceProperties {
981        &self.device_properties
982    }
983
984    /// Get statistics
985    pub fn get_stats(&self) -> &OneApiStats {
986        &self.stats
987    }
988
989    /// Query USM pointer information
990    pub fn query_usm_ptr(&self, ptr: *mut c_void) -> Result<UsmAllocation, OneApiError> {
991        // In a real implementation, this would query the actual USM pointer
992        // For now, return default information
993        Ok(UsmAllocation {
994            ptr,
995            size: 0, // Would need to track actual size
996            usm_kind: UsmKind::Device,
997            allocated_at: Instant::now(),
998            device_id: self.config.device_id,
999            alignment: 64,
1000        })
1001    }
1002}
1003
1004// Safety: OneApiMemoryBackend manages OneAPI/SYCL GPU memory pointers via *mut c_void.
1005// While raw pointers are not Send/Sync by default, it's safe to share across threads
1006// when protected by Arc<Mutex<>> because:
1007// 1. All pointers point to SYCL USM memory managed by the OneAPI runtime
1008// 2. The Mutex provides exclusive access for all mutable operations
1009// 3. No thread-local state is maintained
1010unsafe impl Send for OneApiMemoryBackend {}
1011unsafe impl Sync for OneApiMemoryBackend {}
1012
1013/// OneAPI errors
1014#[derive(Debug, Clone)]
1015pub enum OneApiError {
1016    DeviceNotFound(String),
1017    OutOfMemory(String),
1018    InvalidPointer(String),
1019    InvalidQueue(String),
1020    QueueFull(String),
1021    UnsupportedOperation(String),
1022    UnsupportedMemoryType(String),
1023    ContextCreationFailed(String),
1024    KernelSubmissionFailed(String),
1025    SynchronizationFailed(String),
1026    InternalError(String),
1027}
1028
1029impl std::fmt::Display for OneApiError {
1030    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1031        match self {
1032            OneApiError::DeviceNotFound(msg) => write!(f, "Device not found: {}", msg),
1033            OneApiError::OutOfMemory(msg) => write!(f, "Out of memory: {}", msg),
1034            OneApiError::InvalidPointer(msg) => write!(f, "Invalid pointer: {}", msg),
1035            OneApiError::InvalidQueue(msg) => write!(f, "Invalid queue: {}", msg),
1036            OneApiError::QueueFull(msg) => write!(f, "Queue full: {}", msg),
1037            OneApiError::UnsupportedOperation(msg) => write!(f, "Unsupported operation: {}", msg),
1038            OneApiError::UnsupportedMemoryType(msg) => {
1039                write!(f, "Unsupported memory type: {}", msg)
1040            }
1041            OneApiError::ContextCreationFailed(msg) => {
1042                write!(f, "Context creation failed: {}", msg)
1043            }
1044            OneApiError::KernelSubmissionFailed(msg) => {
1045                write!(f, "Kernel submission failed: {}", msg)
1046            }
1047            OneApiError::SynchronizationFailed(msg) => write!(f, "Synchronization failed: {}", msg),
1048            OneApiError::InternalError(msg) => write!(f, "Internal error: {}", msg),
1049        }
1050    }
1051}
1052
1053impl std::error::Error for OneApiError {}
1054
1055/// Thread-safe OneAPI backend wrapper
1056pub struct ThreadSafeOneApiBackend {
1057    backend: Arc<Mutex<OneApiMemoryBackend>>,
1058}
1059
1060impl ThreadSafeOneApiBackend {
1061    pub fn new(config: OneApiConfig) -> Result<Self, OneApiError> {
1062        let backend = OneApiMemoryBackend::new(config)?;
1063        Ok(Self {
1064            backend: Arc::new(Mutex::new(backend)),
1065        })
1066    }
1067
1068    pub fn allocate(
1069        &self,
1070        size: usize,
1071        memory_type: OneApiMemoryType,
1072    ) -> Result<*mut c_void, OneApiError> {
1073        let mut backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
1074        backend.allocate(size, memory_type)
1075    }
1076
1077    pub fn free(&self, ptr: *mut c_void, memory_type: OneApiMemoryType) -> Result<(), OneApiError> {
1078        let mut backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
1079        backend.free(ptr, memory_type)
1080    }
1081
1082    pub fn get_stats(&self) -> OneApiStats {
1083        let backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
1084        backend.get_stats().clone()
1085    }
1086}
1087
1088#[cfg(test)]
1089mod tests {
1090    use super::*;
1091
1092    /// Regression test for F26: a zero-size request must not reach
1093    /// `std::alloc::alloc` (unsound for a zero-size layout), and an
1094    /// out-of-range alignment must be an honest error, not silent UB.
1095    #[test]
1096    fn sim_alloc_zero_size_is_a_safe_sentinel_not_a_ub_call() {
1097        let ptr = sim_alloc(0, 64).expect("zero-size request must succeed");
1098        assert!(!ptr.is_null());
1099        sim_dealloc(ptr);
1100    }
1101
1102    #[test]
1103    fn sim_alloc_rejects_unsupported_alignment() {
1104        assert!(sim_alloc(16, 3).is_err(), "3 is not a power of two");
1105        assert!(
1106            sim_alloc(16, 512).is_err(),
1107            "512 exceeds this simulated backend's supported alignment"
1108        );
1109    }
1110
1111    /// A real allocation must be readable/writable across its full size and
1112    /// must free through the same layout it was allocated with.
1113    #[test]
1114    fn sim_alloc_real_allocation_round_trips_and_frees_cleanly() {
1115        for (size, align) in [(1usize, 8usize), (7, 16), (256, 64), (4096, 128)] {
1116            let ptr = sim_alloc(size, align).expect("allocation must succeed") as *mut u8;
1117            assert!(!ptr.is_null());
1118            assert_eq!(
1119                (ptr as usize) % align,
1120                0,
1121                "returned pointer does not honour the requested alignment"
1122            );
1123            unsafe {
1124                for i in 0..size {
1125                    ptr.add(i).write(0xAB);
1126                }
1127                for i in 0..size {
1128                    assert_eq!(ptr.add(i).read(), 0xAB);
1129                }
1130                sim_dealloc(ptr as *mut c_void);
1131            }
1132        }
1133    }
1134
1135    #[test]
1136    fn sim_dealloc_null_is_a_no_op() {
1137        sim_dealloc(std::ptr::null_mut());
1138    }
1139
1140    #[test]
1141    fn test_oneapi_backend_creation() {
1142        let config = OneApiConfig::default();
1143        let backend = OneApiMemoryBackend::new(config);
1144        assert!(backend.is_ok());
1145    }
1146
1147    #[test]
1148    fn test_memory_pool() {
1149        let mut pool = OneApiMemoryPool::new(OneApiMemoryType::Device, 1024 * 1024);
1150        let ptr = pool.allocate(1024);
1151        assert!(ptr.is_ok());
1152
1153        let ptr = ptr.expect("unwrap failed");
1154        let result = pool.free(ptr);
1155        assert!(result.is_ok());
1156    }
1157
1158    #[test]
1159    fn test_sycl_queue_manager() {
1160        let mut manager = SyclQueueManager::new(SyclQueueConfig::default());
1161        let queue_id = manager.create_queue(None);
1162        assert!(queue_id.is_ok());
1163
1164        let queue_id = queue_id.expect("unwrap failed");
1165        let result = manager.destroy_queue(queue_id);
1166        assert!(result.is_ok());
1167    }
1168
1169    #[test]
1170    fn test_thread_safe_backend() {
1171        let config = OneApiConfig::default();
1172        let backend = ThreadSafeOneApiBackend::new(config);
1173        assert!(backend.is_ok());
1174
1175        let backend = backend.expect("unwrap failed");
1176        let stats = backend.get_stats();
1177        assert_eq!(stats.total_allocations, 0);
1178    }
1179}