1use std::collections::HashMap;
35use std::ffi::c_void;
36use std::sync::{Arc, Mutex};
37use std::time::{Duration, Instant};
38
39const SIM_ALLOC_ALIGN: usize = 256;
46
47fn sim_alloc(size: usize, align: usize) -> Result<*mut c_void, MetalError> {
63 if !align.is_power_of_two() || align > SIM_ALLOC_ALIGN {
64 return Err(MetalError::AllocationFailed(format!(
65 "unsupported allocation alignment {align}: this simulated backend supports \
66 power-of-two alignments up to {SIM_ALLOC_ALIGN} bytes"
67 )));
68 }
69 if size == 0 {
70 return Ok(SIM_ALLOC_ALIGN as *mut c_void);
71 }
72 let total = SIM_ALLOC_ALIGN.checked_add(size).ok_or_else(|| {
73 MetalError::AllocationFailed(format!(
74 "{size}-byte request overflows the allocator's size limit"
75 ))
76 })?;
77 let layout = std::alloc::Layout::from_size_align(total, SIM_ALLOC_ALIGN)
81 .map_err(|e| MetalError::AllocationFailed(format!("invalid allocation layout: {e}")))?;
82 let base = unsafe { std::alloc::alloc(layout) };
85 if base.is_null() {
86 return Err(MetalError::AllocationFailed(format!(
87 "allocator returned null for a {size}-byte request"
88 )));
89 }
90 unsafe { (base as *mut usize).write(size) };
94 Ok(unsafe { base.add(SIM_ALLOC_ALIGN) } as *mut c_void)
98}
99
100fn sim_dealloc(ptr: *mut c_void) {
109 if ptr.is_null() || (ptr as usize) == SIM_ALLOC_ALIGN {
110 return;
111 }
112 let base = unsafe { (ptr as *mut u8).sub(SIM_ALLOC_ALIGN) };
114 let size = unsafe { (base as *const usize).read() };
116 if let Ok(layout) = std::alloc::Layout::from_size_align(SIM_ALLOC_ALIGN + size, SIM_ALLOC_ALIGN)
117 {
118 unsafe { std::alloc::dealloc(base, layout) };
121 }
122}
123
124pub struct MetalMemoryBackend {
126 config: MetalConfig,
128 device_properties: MetalDeviceProperties,
130 memory_pools: HashMap<MetalMemoryType, MetalMemoryPool>,
132 stats: MetalStats,
134 command_manager: MetalCommandManager,
136}
137
138#[derive(Debug, Clone)]
140pub struct MetalConfig {
141 pub device_id: u32,
143 pub enable_private_memory: bool,
145 pub enable_shared_memory: bool,
147 pub enable_managed_memory: bool,
149 pub enable_memory_pools: bool,
151 pub enable_async_ops: bool,
153 pub pool_growth_size: usize,
155 pub enable_memoryless_targets: bool,
157 pub max_command_queues: u32,
159 pub enable_mps: bool,
161 pub enable_heap_allocation: bool,
163}
164
165impl Default for MetalConfig {
166 fn default() -> Self {
167 Self {
168 device_id: 0,
169 enable_private_memory: true,
170 enable_shared_memory: true,
171 enable_managed_memory: true,
172 enable_memory_pools: true,
173 enable_async_ops: true,
174 pool_growth_size: 64 * 1024 * 1024, enable_memoryless_targets: false,
176 max_command_queues: 8,
177 enable_mps: true,
178 enable_heap_allocation: true,
179 }
180 }
181}
182
183#[derive(Debug, Clone)]
185pub struct MetalDeviceProperties {
186 pub device_id: u32,
187 pub name: String,
188 pub device_type: MetalDeviceType,
189 pub family: MetalGPUFamily,
190 pub max_threads_per_threadgroup: u32,
191 pub threadgroup_memory_length: u32,
192 pub max_buffer_length: usize,
193 pub max_texture_size_2d: u32,
194 pub max_texture_size_3d: u32,
195 pub unified_memory: bool,
196 pub discrete_memory: bool,
197 pub low_power: bool,
198 pub headless: bool,
199 pub supports_shader_debugging: bool,
200 pub supports_function_pointers: bool,
201 pub supports_dynamic_libraries: bool,
202 pub supports_render_dynamic_libraries: bool,
203 pub recommended_max_working_set_size: usize,
204 pub max_transfer_rate: u64,
205 pub has_unified_memory: bool,
206}
207
208#[derive(Debug, Clone, PartialEq)]
210pub enum MetalDeviceType {
211 Integrated,
212 Discrete,
213 External,
214 Virtual,
215}
216
217#[derive(Debug, Clone, PartialEq)]
219pub enum MetalGPUFamily {
220 Apple1, Apple2, Apple3, Apple4, Apple5, Apple6, Apple7, Apple8, Apple9, Mac1, Mac2, }
232
233#[derive(Debug, Clone, PartialEq, Eq, Hash)]
235pub enum MetalMemoryType {
236 Private, Shared, Managed, Memoryless, }
241
242pub struct MetalDevice {
244 pub handle: *mut c_void,
246 pub device_id: u32,
248 pub properties: MetalDeviceProperties,
250 pub created_at: Instant,
252 pub command_queues: Vec<MetalCommandQueue>,
254 pub heaps: HashMap<usize, MetalHeap>,
256 pub resources: HashMap<*mut c_void, MetalResource>,
258}
259
260pub struct MetalCommandQueue {
262 pub handle: *mut c_void,
264 pub id: u32,
266 pub label: Option<String>,
268 pub created_at: Instant,
270 pub command_buffers: std::collections::VecDeque<MetalCommandBuffer>,
272 pub priority: MetalQueuePriority,
274}
275
276#[derive(Debug, Clone, PartialEq)]
278pub enum MetalQueuePriority {
279 High,
280 Normal,
281 Low,
282 Background,
283}
284
285#[derive(Debug, Clone)]
287pub struct MetalCommandBuffer {
288 pub buffer_id: u32,
289 pub commands: Vec<MetalCommand>,
290 pub timestamp: Instant,
291 pub committed: bool,
292 pub completed: bool,
293}
294
295#[derive(Debug, Clone)]
297pub enum MetalCommand {
298 BlitCommand {
299 src_buffer: *mut c_void,
300 dst_buffer: *mut c_void,
301 size: usize,
302 },
303 ComputeCommand {
304 kernel_id: u32,
305 threadgroup_size: (u32, u32, u32),
306 threadgroups: (u32, u32, u32),
307 },
308 RenderCommand {
309 render_pass: u32,
310 },
311 MemoryBarrier,
312}
313
314pub struct MetalMemoryPool {
316 memory_type: MetalMemoryType,
318 current_size: usize,
320 max_size: usize,
322 used_size: usize,
324 free_blocks: std::collections::VecDeque<MetalMemoryBlock>,
326 allocated_blocks: HashMap<*mut c_void, MetalMemoryBlock>,
328 storage_mode: MetalStorageMode,
330 cache_mode: MetalCacheMode,
332}
333
334#[derive(Debug, Clone)]
336pub struct MetalMemoryBlock {
337 pub ptr: *mut c_void,
338 pub size: usize,
339 pub memory_type: MetalMemoryType,
340 pub allocated_at: Instant,
341 pub last_access: Option<Instant>,
342 pub ref_count: u32,
343 pub storage_mode: MetalStorageMode,
344 pub cache_mode: MetalCacheMode,
345 pub gpu_address: Option<u64>,
346}
347
348#[derive(Debug, Clone, PartialEq)]
350pub enum MetalStorageMode {
351 Shared, Managed, Private, Memoryless, }
356
357#[derive(Debug, Clone, PartialEq)]
359pub enum MetalCacheMode {
360 DefaultCache,
361 WriteCombined,
362}
363
364pub struct MetalHeap {
366 pub handle: *mut c_void,
368 pub id: usize,
370 pub size: usize,
372 pub used_size: usize,
374 pub storage_mode: MetalStorageMode,
376 pub cpu_cache_mode: MetalCacheMode,
378 pub resources: HashMap<*mut c_void, MetalResource>,
380}
381
382#[derive(Debug, Clone)]
384pub struct MetalResource {
385 pub ptr: *mut c_void,
386 pub size: usize,
387 pub resource_type: MetalResourceType,
388 pub storage_mode: MetalStorageMode,
389 pub allocated_at: Instant,
390 pub heap_offset: Option<usize>,
391}
392
393#[derive(Debug, Clone, PartialEq)]
395pub enum MetalResourceType {
396 Buffer,
397 Texture1D,
398 Texture2D,
399 Texture3D,
400 TextureCube,
401}
402
403impl MetalMemoryPool {
404 pub fn new(memory_type: MetalMemoryType, max_size: usize) -> Self {
405 let (storage_mode, cache_mode) = match memory_type {
406 MetalMemoryType::Private => (MetalStorageMode::Private, MetalCacheMode::DefaultCache),
407 MetalMemoryType::Shared => (MetalStorageMode::Shared, MetalCacheMode::DefaultCache),
408 MetalMemoryType::Managed => (MetalStorageMode::Managed, MetalCacheMode::DefaultCache),
409 MetalMemoryType::Memoryless => {
410 (MetalStorageMode::Memoryless, MetalCacheMode::DefaultCache)
411 }
412 };
413
414 Self {
415 memory_type,
416 current_size: 0,
417 max_size,
418 used_size: 0,
419 free_blocks: std::collections::VecDeque::new(),
420 allocated_blocks: HashMap::new(),
421 storage_mode,
422 cache_mode,
423 }
424 }
425
426 pub fn allocate(&mut self, size: usize) -> Result<*mut c_void, MetalError> {
428 for i in 0..self.free_blocks.len() {
430 if self.free_blocks[i].size >= size {
431 let Some(mut block) = self.free_blocks.remove(i) else {
432 continue;
433 };
434
435 if block.size > size * 2 {
437 let remaining_block = MetalMemoryBlock {
438 ptr: unsafe { block.ptr.add(size) },
439 size: block.size - size,
440 memory_type: block.memory_type.clone(),
441 allocated_at: block.allocated_at,
442 last_access: None,
443 ref_count: 0,
444 storage_mode: block.storage_mode.clone(),
445 cache_mode: block.cache_mode.clone(),
446 gpu_address: None,
447 };
448 self.free_blocks.push_back(remaining_block);
449 block.size = size;
450 }
451
452 block.last_access = Some(Instant::now());
453 block.ref_count = 1;
454
455 let ptr = block.ptr;
456 self.allocated_blocks.insert(ptr, block);
457 self.used_size += size;
458
459 return Ok(ptr);
460 }
461 }
462
463 if self.current_size + size > self.max_size {
465 return Err(MetalError::OutOfMemory(
466 "Pool size limit exceeded".to_string(),
467 ));
468 }
469
470 let ptr = self.metal_allocate(size)?;
471 let block = MetalMemoryBlock {
472 ptr,
473 size,
474 memory_type: self.memory_type.clone(),
475 allocated_at: Instant::now(),
476 last_access: Some(Instant::now()),
477 ref_count: 1,
478 storage_mode: self.storage_mode.clone(),
479 cache_mode: self.cache_mode.clone(),
480 gpu_address: Some(ptr as u64), };
482
483 self.allocated_blocks.insert(ptr, block);
484 self.current_size += size;
485 self.used_size += size;
486
487 Ok(ptr)
488 }
489
490 pub fn free(&mut self, ptr: *mut c_void) -> Result<(), MetalError> {
492 if let Some(block) = self.allocated_blocks.remove(&ptr) {
493 self.used_size -= block.size;
494
495 self.free_blocks.push_back(MetalMemoryBlock {
497 ptr: block.ptr,
498 size: block.size,
499 memory_type: block.memory_type,
500 allocated_at: block.allocated_at,
501 last_access: None,
502 ref_count: 0,
503 storage_mode: block.storage_mode,
504 cache_mode: block.cache_mode,
505 gpu_address: block.gpu_address,
506 });
507
508 self.coalesce_free_blocks();
510
511 Ok(())
512 } else {
513 Err(MetalError::InvalidPointer(
514 "Pointer not found in pool".to_string(),
515 ))
516 }
517 }
518
519 fn coalesce_free_blocks(&mut self) {
520 let mut blocks: Vec<MetalMemoryBlock> = self.free_blocks.drain(..).collect();
522 blocks.sort_by_key(|block| block.ptr as usize);
523
524 let mut coalesced = Vec::new();
525 let mut current_block: Option<MetalMemoryBlock> = None;
526
527 for block in blocks {
528 match current_block.take() {
529 None => current_block = Some(block),
530 Some(mut prev_block) => {
531 let prev_end = prev_block.ptr as usize + prev_block.size;
532 let block_start = block.ptr as usize;
533
534 if prev_end == block_start && prev_block.memory_type == block.memory_type {
535 prev_block.size += block.size;
537 current_block = Some(prev_block);
538 } else {
539 coalesced.push(prev_block);
540 current_block = Some(block);
541 }
542 }
543 }
544 }
545
546 if let Some(block) = current_block {
547 coalesced.push(block);
548 }
549
550 self.free_blocks = coalesced.into();
551 }
552
553 fn metal_allocate(&self, size: usize) -> Result<*mut c_void, MetalError> {
554 let alignment = match self.memory_type {
556 MetalMemoryType::Private => 64, MetalMemoryType::Shared => 16, MetalMemoryType::Managed => 16, MetalMemoryType::Memoryless => 64, };
561
562 match self.memory_type {
563 MetalMemoryType::Private => sim_alloc(size, alignment), MetalMemoryType::Shared => sim_alloc(size, alignment), MetalMemoryType::Managed => sim_alloc(size, alignment), MetalMemoryType::Memoryless => {
567 if size > 8 * 1024 * 1024 {
569 return Err(MetalError::UnsupportedOperation(
571 "Memoryless allocation too large".to_string(),
572 ));
573 }
574 sim_alloc(size, alignment)
575 }
576 }
577 }
578}
579
580pub struct MetalCommandManager {
582 queues: Vec<MetalCommandQueue>,
584 next_queue_id: u32,
586 next_buffer_id: u32,
588 config: MetalCommandConfig,
590}
591
592#[derive(Debug, Clone)]
594pub struct MetalCommandConfig {
595 pub max_command_buffers_per_queue: usize,
596 pub enable_command_buffer_reuse: bool,
597 pub enable_parallel_encoding: bool,
598}
599
600impl Default for MetalCommandConfig {
601 fn default() -> Self {
602 Self {
603 max_command_buffers_per_queue: 64,
604 enable_command_buffer_reuse: true,
605 enable_parallel_encoding: true,
606 }
607 }
608}
609
610impl MetalCommandManager {
611 pub fn new(config: MetalCommandConfig) -> Self {
612 Self {
613 queues: Vec::new(),
614 next_queue_id: 0,
615 next_buffer_id: 0,
616 config,
617 }
618 }
619
620 pub fn create_command_queue(
622 &mut self,
623 label: Option<String>,
624 priority: MetalQueuePriority,
625 ) -> Result<u32, MetalError> {
626 let queue_id = self.next_queue_id;
627 self.next_queue_id += 1;
628
629 let queue = MetalCommandQueue {
630 handle: std::ptr::null_mut(),
631 id: queue_id,
632 label,
633 created_at: Instant::now(),
634 command_buffers: std::collections::VecDeque::new(),
635 priority,
636 };
637
638 self.queues.push(queue);
639 Ok(queue_id)
640 }
641
642 pub fn create_command_buffer(&mut self, queue_id: u32) -> Result<u32, MetalError> {
644 if let Some(queue) = self.queues.iter_mut().find(|q| q.id == queue_id) {
645 if queue.command_buffers.len() >= self.config.max_command_buffers_per_queue {
646 return Err(MetalError::QueueFull("Command queue is full".to_string()));
647 }
648
649 let buffer_id = self.next_buffer_id;
650 self.next_buffer_id += 1;
651
652 let command_buffer = MetalCommandBuffer {
653 buffer_id,
654 commands: Vec::new(),
655 timestamp: Instant::now(),
656 committed: false,
657 completed: false,
658 };
659
660 queue.command_buffers.push_back(command_buffer);
661 Ok(buffer_id)
662 } else {
663 Err(MetalError::InvalidQueue("Queue not found".to_string()))
664 }
665 }
666
667 pub fn add_command(
669 &mut self,
670 queue_id: u32,
671 buffer_id: u32,
672 command: MetalCommand,
673 ) -> Result<(), MetalError> {
674 if let Some(queue) = self.queues.iter_mut().find(|q| q.id == queue_id) {
675 if let Some(buffer) = queue
676 .command_buffers
677 .iter_mut()
678 .find(|b| b.buffer_id == buffer_id)
679 {
680 if buffer.committed {
681 return Err(MetalError::InvalidOperation(
682 "Command buffer already committed".to_string(),
683 ));
684 }
685 buffer.commands.push(command);
686 Ok(())
687 } else {
688 Err(MetalError::InvalidCommandBuffer(
689 "Command buffer not found".to_string(),
690 ))
691 }
692 } else {
693 Err(MetalError::InvalidQueue("Queue not found".to_string()))
694 }
695 }
696
697 pub fn commit_command_buffer(
699 &mut self,
700 queue_id: u32,
701 buffer_id: u32,
702 ) -> Result<(), MetalError> {
703 if let Some(queue) = self.queues.iter_mut().find(|q| q.id == queue_id) {
704 if let Some(buffer) = queue
705 .command_buffers
706 .iter_mut()
707 .find(|b| b.buffer_id == buffer_id)
708 {
709 buffer.committed = true;
710 buffer.completed = true;
717 Ok(())
718 } else {
719 Err(MetalError::InvalidCommandBuffer(
720 "Command buffer not found".to_string(),
721 ))
722 }
723 } else {
724 Err(MetalError::InvalidQueue("Queue not found".to_string()))
725 }
726 }
727
728 pub fn wait_until_completed(
730 &mut self,
731 queue_id: u32,
732 buffer_id: u32,
733 ) -> Result<(), MetalError> {
734 if let Some(queue) = self.queues.iter().find(|q| q.id == queue_id) {
735 if queue
736 .command_buffers
737 .iter()
738 .any(|b| b.buffer_id == buffer_id)
739 {
740 Ok(())
748 } else {
749 Err(MetalError::InvalidCommandBuffer(
750 "Command buffer not found".to_string(),
751 ))
752 }
753 } else {
754 Err(MetalError::InvalidQueue("Queue not found".to_string()))
755 }
756 }
757}
758
759#[derive(Debug, Clone, Default)]
761pub struct MetalStats {
762 pub total_allocations: u64,
763 pub total_deallocations: u64,
764 pub bytes_allocated: u64,
765 pub bytes_deallocated: u64,
766 pub private_memory_used: usize,
767 pub shared_memory_used: usize,
768 pub managed_memory_used: usize,
769 pub command_buffers_created: u64,
770 pub command_buffers_completed: u64,
771 pub compute_commands: u64,
772 pub blit_commands: u64,
773 pub render_commands: u64,
774 pub average_allocation_time: Duration,
775 pub peak_memory_usage: usize,
776}
777
778impl MetalMemoryBackend {
779 pub fn new(config: MetalConfig) -> Result<Self, MetalError> {
781 let device_properties = Self::query_device_properties(config.device_id)?;
783
784 let mut memory_pools = HashMap::new();
786 if config.enable_memory_pools {
787 let pool_size = device_properties.recommended_max_working_set_size / 4;
788
789 if config.enable_private_memory {
790 memory_pools.insert(
791 MetalMemoryType::Private,
792 MetalMemoryPool::new(MetalMemoryType::Private, pool_size),
793 );
794 }
795
796 if config.enable_shared_memory {
797 memory_pools.insert(
798 MetalMemoryType::Shared,
799 MetalMemoryPool::new(MetalMemoryType::Shared, pool_size),
800 );
801 }
802
803 if config.enable_managed_memory {
804 memory_pools.insert(
805 MetalMemoryType::Managed,
806 MetalMemoryPool::new(MetalMemoryType::Managed, pool_size),
807 );
808 }
809 }
810
811 let command_manager = MetalCommandManager::new(MetalCommandConfig::default());
812
813 Ok(Self {
814 config,
815 device_properties,
816 memory_pools,
817 stats: MetalStats::default(),
818 command_manager,
819 })
820 }
821
822 fn query_device_properties(device_id: u32) -> Result<MetalDeviceProperties, MetalError> {
824 Ok(MetalDeviceProperties {
826 device_id,
827 name: "Apple M1 Pro".to_string(),
828 device_type: MetalDeviceType::Integrated,
829 family: MetalGPUFamily::Apple7,
830 max_threads_per_threadgroup: 1024,
831 threadgroup_memory_length: 32768,
832 max_buffer_length: 2 * 1024 * 1024 * 1024, max_texture_size_2d: 16384,
834 max_texture_size_3d: 2048,
835 unified_memory: true,
836 discrete_memory: false,
837 low_power: false,
838 headless: false,
839 supports_shader_debugging: true,
840 supports_function_pointers: true,
841 supports_dynamic_libraries: true,
842 supports_render_dynamic_libraries: true,
843 recommended_max_working_set_size: 32 * 1024 * 1024 * 1024, max_transfer_rate: 400_000_000_000, has_unified_memory: true,
846 })
847 }
848
849 pub fn allocate(
851 &mut self,
852 size: usize,
853 memory_type: MetalMemoryType,
854 ) -> Result<*mut c_void, MetalError> {
855 let start_time = Instant::now();
856
857 let ptr = if self.config.enable_memory_pools {
858 if let Some(pool) = self.memory_pools.get_mut(&memory_type) {
859 pool.allocate(size)?
860 } else {
861 return Err(MetalError::UnsupportedMemoryType(
862 "Memory type not supported".to_string(),
863 ));
864 }
865 } else {
866 self.direct_allocate(size, memory_type.clone())?
868 };
869
870 self.stats.total_allocations += 1;
872 self.stats.bytes_allocated += size as u64;
873
874 match memory_type {
875 MetalMemoryType::Private => self.stats.private_memory_used += size,
876 MetalMemoryType::Shared => self.stats.shared_memory_used += size,
877 MetalMemoryType::Managed => self.stats.managed_memory_used += size,
878 _ => {}
879 }
880
881 let allocation_time = start_time.elapsed();
882 let total_time = self.stats.average_allocation_time.as_nanos() as u64
883 * (self.stats.total_allocations - 1)
884 + allocation_time.as_nanos() as u64;
885 self.stats.average_allocation_time =
886 Duration::from_nanos(total_time / self.stats.total_allocations);
887
888 let current_usage = self.stats.private_memory_used
889 + self.stats.shared_memory_used
890 + self.stats.managed_memory_used;
891 if current_usage > self.stats.peak_memory_usage {
892 self.stats.peak_memory_usage = current_usage;
893 }
894
895 Ok(ptr)
896 }
897
898 fn direct_allocate(
899 &self,
900 size: usize,
901 memory_type: MetalMemoryType,
902 ) -> Result<*mut c_void, MetalError> {
903 let alignment = match memory_type {
904 MetalMemoryType::Private => 64,
905 MetalMemoryType::Shared => 16,
906 MetalMemoryType::Managed => 16,
907 MetalMemoryType::Memoryless => 64,
908 };
909
910 match memory_type {
912 MetalMemoryType::Private => sim_alloc(size, alignment),
913 MetalMemoryType::Shared => sim_alloc(size, alignment),
914 MetalMemoryType::Managed => sim_alloc(size, alignment),
915 MetalMemoryType::Memoryless => {
916 if size > 8 * 1024 * 1024 {
917 return Err(MetalError::UnsupportedOperation(
918 "Memoryless allocation too large".to_string(),
919 ));
920 }
921 sim_alloc(size, alignment)
922 }
923 }
924 }
925
926 pub fn free(
928 &mut self,
929 ptr: *mut c_void,
930 memory_type: MetalMemoryType,
931 ) -> Result<(), MetalError> {
932 if self.config.enable_memory_pools {
933 if let Some(pool) = self.memory_pools.get_mut(&memory_type) {
934 pool.free(ptr)?;
935 } else {
936 return Err(MetalError::UnsupportedMemoryType(
937 "Memory type not supported".to_string(),
938 ));
939 }
940 } else {
941 sim_dealloc(ptr);
945 }
946
947 self.stats.total_deallocations += 1;
948 Ok(())
949 }
950
951 pub fn blit_copy(
953 &mut self,
954 src: *const c_void,
955 dst: *mut c_void,
956 size: usize,
957 queue_id: u32,
958 ) -> Result<(), MetalError> {
959 let buffer_id = self.command_manager.create_command_buffer(queue_id)?;
960 let command = MetalCommand::BlitCommand {
961 src_buffer: src as *mut c_void,
962 dst_buffer: dst,
963 size,
964 };
965
966 self.command_manager
967 .add_command(queue_id, buffer_id, command)?;
968 self.command_manager
969 .commit_command_buffer(queue_id, buffer_id)?;
970 self.command_manager
971 .wait_until_completed(queue_id, buffer_id)?;
972
973 self.stats.blit_commands += 1;
974 Ok(())
975 }
976
977 pub fn create_command_queue(
979 &mut self,
980 label: Option<String>,
981 priority: MetalQueuePriority,
982 ) -> Result<u32, MetalError> {
983 self.command_manager.create_command_queue(label, priority)
984 }
985
986 pub fn get_device_properties(&self) -> &MetalDeviceProperties {
988 &self.device_properties
989 }
990
991 pub fn get_stats(&self) -> &MetalStats {
993 &self.stats
994 }
995
996 pub fn wait_until_idle(&mut self) -> Result<(), MetalError> {
998 Ok(())
1006 }
1007}
1008
1009unsafe impl Send for MetalMemoryBackend {}
1016unsafe impl Sync for MetalMemoryBackend {}
1017
1018#[derive(Debug, Clone)]
1020pub enum MetalError {
1021 DeviceNotFound(String),
1022 OutOfMemory(String),
1023 InvalidPointer(String),
1024 InvalidQueue(String),
1025 InvalidCommandBuffer(String),
1026 QueueFull(String),
1027 InvalidOperation(String),
1028 UnsupportedOperation(String),
1029 UnsupportedMemoryType(String),
1030 AllocationFailed(String),
1031 InternalError(String),
1032}
1033
1034impl std::fmt::Display for MetalError {
1035 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1036 match self {
1037 MetalError::DeviceNotFound(msg) => write!(f, "Device not found: {}", msg),
1038 MetalError::OutOfMemory(msg) => write!(f, "Out of memory: {}", msg),
1039 MetalError::InvalidPointer(msg) => write!(f, "Invalid pointer: {}", msg),
1040 MetalError::InvalidQueue(msg) => write!(f, "Invalid queue: {}", msg),
1041 MetalError::InvalidCommandBuffer(msg) => write!(f, "Invalid command buffer: {}", msg),
1042 MetalError::QueueFull(msg) => write!(f, "Queue full: {}", msg),
1043 MetalError::InvalidOperation(msg) => write!(f, "Invalid operation: {}", msg),
1044 MetalError::UnsupportedOperation(msg) => write!(f, "Unsupported operation: {}", msg),
1045 MetalError::UnsupportedMemoryType(msg) => write!(f, "Unsupported memory type: {}", msg),
1046 MetalError::AllocationFailed(msg) => write!(f, "Allocation failed: {}", msg),
1047 MetalError::InternalError(msg) => write!(f, "Internal error: {}", msg),
1048 }
1049 }
1050}
1051
1052impl std::error::Error for MetalError {}
1053
1054pub struct ThreadSafeMetalBackend {
1056 backend: Arc<Mutex<MetalMemoryBackend>>,
1057}
1058
1059impl ThreadSafeMetalBackend {
1060 pub fn new(config: MetalConfig) -> Result<Self, MetalError> {
1061 let backend = MetalMemoryBackend::new(config)?;
1062 Ok(Self {
1063 backend: Arc::new(Mutex::new(backend)),
1064 })
1065 }
1066
1067 pub fn allocate(
1068 &self,
1069 size: usize,
1070 memory_type: MetalMemoryType,
1071 ) -> Result<*mut c_void, MetalError> {
1072 let mut backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
1073 backend.allocate(size, memory_type)
1074 }
1075
1076 pub fn free(&self, ptr: *mut c_void, memory_type: MetalMemoryType) -> Result<(), MetalError> {
1077 let mut backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
1078 backend.free(ptr, memory_type)
1079 }
1080
1081 pub fn get_stats(&self) -> MetalStats {
1082 let backend = self.backend.lock().unwrap_or_else(|e| e.into_inner());
1083 backend.get_stats().clone()
1084 }
1085}
1086
1087#[cfg(test)]
1088mod tests {
1089 use super::*;
1090
1091 #[test]
1095 fn sim_alloc_zero_size_is_a_safe_sentinel_not_a_ub_call() {
1096 let ptr = sim_alloc(0, 64).expect("zero-size request must succeed");
1097 assert!(!ptr.is_null());
1098 sim_dealloc(ptr);
1099 }
1100
1101 #[test]
1102 fn sim_alloc_rejects_unsupported_alignment() {
1103 assert!(sim_alloc(16, 3).is_err(), "3 is not a power of two");
1104 assert!(
1105 sim_alloc(16, 512).is_err(),
1106 "512 exceeds this simulated backend's supported alignment"
1107 );
1108 }
1109
1110 #[test]
1113 fn sim_alloc_real_allocation_round_trips_and_frees_cleanly() {
1114 for (size, align) in [(1usize, 16usize), (7, 16), (256, 64), (4096, 64)] {
1115 let ptr = sim_alloc(size, align).expect("allocation must succeed") as *mut u8;
1116 assert!(!ptr.is_null());
1117 assert_eq!(
1118 (ptr as usize) % align,
1119 0,
1120 "returned pointer does not honour the requested alignment"
1121 );
1122 unsafe {
1123 for i in 0..size {
1124 ptr.add(i).write(0xAB);
1125 }
1126 for i in 0..size {
1127 assert_eq!(ptr.add(i).read(), 0xAB);
1128 }
1129 sim_dealloc(ptr as *mut c_void);
1130 }
1131 }
1132 }
1133
1134 #[test]
1135 fn sim_dealloc_null_is_a_no_op() {
1136 sim_dealloc(std::ptr::null_mut());
1137 }
1138
1139 #[test]
1140 fn test_metal_backend_creation() {
1141 let config = MetalConfig::default();
1142 let backend = MetalMemoryBackend::new(config);
1143 assert!(backend.is_ok());
1144 }
1145
1146 #[test]
1147 fn test_memory_pool() {
1148 let mut pool = MetalMemoryPool::new(MetalMemoryType::Private, 1024 * 1024);
1149 let ptr = pool.allocate(1024);
1150 assert!(ptr.is_ok());
1151
1152 let ptr = ptr.expect("unwrap failed");
1153 let result = pool.free(ptr);
1154 assert!(result.is_ok());
1155 }
1156
1157 #[test]
1158 fn test_command_manager() {
1159 let mut manager = MetalCommandManager::new(MetalCommandConfig::default());
1160 let queue_id =
1161 manager.create_command_queue(Some("test".to_string()), MetalQueuePriority::Normal);
1162 assert!(queue_id.is_ok());
1163
1164 let queue_id = queue_id.expect("unwrap failed");
1165 let buffer_id = manager.create_command_buffer(queue_id);
1166 assert!(buffer_id.is_ok());
1167 }
1168
1169 #[test]
1170 fn test_thread_safe_backend() {
1171 let config = MetalConfig::default();
1172 let backend = ThreadSafeMetalBackend::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}