1use crate::context::GpuContext;
7use crate::error::{GpuError, GpuResult};
8use std::collections::HashMap;
9use tracing::{debug, info};
10
11#[derive(Debug, Clone)]
13pub struct MetalOptimizationConfig {
14 pub enable_mps: bool,
16 pub enable_unified_memory: bool,
18 pub enable_threadgroup_memory: bool,
20 pub enable_argument_buffers: bool,
22 pub threadgroup_size: (u32, u32, u32),
24}
25
26impl Default for MetalOptimizationConfig {
27 fn default() -> Self {
28 Self {
29 enable_mps: true,
30 enable_unified_memory: true,
31 enable_threadgroup_memory: true,
32 enable_argument_buffers: true,
33 threadgroup_size: (256, 1, 1),
34 }
35 }
36}
37
38pub struct MetalFeatureDetector {
40 features: MetalFeatures,
41}
42
43#[derive(Debug, Clone)]
44pub struct MetalFeatures {
45 pub family: MetalFamily,
47 pub unified_memory: bool,
49 pub mps_support: bool,
51 pub max_threadgroup_memory: u64,
53 pub simd_width: u32,
55 pub argument_buffers: bool,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum MetalFamily {
61 Apple7,
63 Apple8,
64 Apple9,
65 Mac2,
67 Mac3,
68 Mac4,
69 Unknown,
71}
72
73impl Default for MetalFeatures {
74 fn default() -> Self {
75 Self {
76 family: MetalFamily::Mac2,
77 unified_memory: true,
78 mps_support: true,
79 max_threadgroup_memory: 32 * 1024,
80 simd_width: 32,
81 argument_buffers: true,
82 }
83 }
84}
85
86impl MetalFeatureDetector {
87 pub fn new(context: &GpuContext) -> Self {
89 let features = Self::detect_features(context);
90 info!(
91 "Metal features: family={:?}, unified_memory={}, mps={}, simd_width={}",
92 features.family, features.unified_memory, features.mps_support, features.simd_width
93 );
94
95 Self { features }
96 }
97
98 pub fn features(&self) -> &MetalFeatures {
100 &self.features
101 }
102
103 fn detect_features(context: &GpuContext) -> MetalFeatures {
104 let adapter_info = context.adapter_info();
105 let name = adapter_info.name.to_lowercase();
106
107 let family = if name.contains("m1")
108 || name.contains("m2")
109 || name.contains("m3")
110 || name.contains("m4")
111 {
112 if name.contains("m4") {
113 MetalFamily::Mac4
114 } else if name.contains("m3") {
115 MetalFamily::Mac3
116 } else {
117 MetalFamily::Mac2
118 }
119 } else if name.contains("apple") {
120 MetalFamily::Apple7
121 } else {
122 MetalFamily::Unknown
123 };
124
125 let unified_memory = matches!(
126 family,
127 MetalFamily::Mac2 | MetalFamily::Mac3 | MetalFamily::Mac4
128 );
129
130 MetalFeatures {
131 family,
132 unified_memory,
133 mps_support: true,
134 max_threadgroup_memory: 32 * 1024,
135 simd_width: 32,
136 argument_buffers: true,
137 }
138 }
139}
140
141pub struct MetalShaderOptimizer {
143 features: MetalFeatures,
144 config: MetalOptimizationConfig,
145}
146
147impl MetalShaderOptimizer {
148 pub fn new(features: MetalFeatures, config: MetalOptimizationConfig) -> Self {
150 Self { features, config }
151 }
152
153 pub fn optimize_shader(&self, shader_code: &str) -> String {
155 let mut optimized = shader_code.to_string();
156
157 if !optimized.contains("SIMD_WIDTH") {
159 let simd_decl = format!("\nconst SIMD_WIDTH: u32 = {}u;\n", self.features.simd_width);
160 optimized.insert_str(0, &simd_decl);
161 }
162
163 if self.config.enable_threadgroup_memory {
165 optimized.push_str(Self::threadgroup_memory_helpers());
166 }
167
168 if self.config.enable_unified_memory && self.features.unified_memory {
170 optimized.insert_str(0, "\n// Unified Memory Optimized for Apple Silicon\n");
171 }
172
173 optimized
174 }
175
176 pub fn calculate_threadgroup_size(&self, num_elements: u64) -> (u32, u32, u32) {
178 let max_threads = 1024; if num_elements <= max_threads as u64 {
181 return (num_elements as u32, 1, 1);
182 }
183
184 self.config.threadgroup_size
186 }
187
188 fn threadgroup_memory_helpers() -> &'static str {
189 r#"
190// Metal threadgroup memory helpers
191// In WGSL, this uses workgroup memory
192
193// Threadgroup barrier
194fn threadgroup_barrier() {
195 workgroupBarrier();
196}
197
198// Threadgroup memory fence
199fn threadgroup_memory_fence() {
200 storageBarrier();
201}
202"#
203 }
204}
205
206pub struct MetalPerformanceShaders {
208 available_kernels: HashMap<String, MPSKernel>,
209}
210
211#[derive(Debug, Clone)]
212struct MPSKernel {
213 name: String,
214 kernel_type: MPSKernelType,
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
218pub enum MPSKernelType {
219 MatrixMultiplication,
221 Convolution,
223 ImageFilter,
225 Reduction,
227 NeuralNetwork,
229}
230
231impl MetalPerformanceShaders {
232 pub fn new() -> Self {
234 let mut available_kernels = HashMap::new();
235
236 available_kernels.insert(
238 "matmul".to_string(),
239 MPSKernel {
240 name: "matmul".to_string(),
241 kernel_type: MPSKernelType::MatrixMultiplication,
242 },
243 );
244
245 available_kernels.insert(
246 "conv2d".to_string(),
247 MPSKernel {
248 name: "conv2d".to_string(),
249 kernel_type: MPSKernelType::Convolution,
250 },
251 );
252
253 available_kernels.insert(
254 "reduce_sum".to_string(),
255 MPSKernel {
256 name: "reduce_sum".to_string(),
257 kernel_type: MPSKernelType::Reduction,
258 },
259 );
260
261 Self { available_kernels }
262 }
263
264 pub fn is_available(&self, name: &str) -> bool {
266 self.available_kernels.contains_key(name)
267 }
268
269 pub fn get_kernel(&self, name: &str) -> Option<&MPSKernel> {
271 self.available_kernels.get(name)
272 }
273
274 pub fn list_kernels(&self) -> Vec<String> {
276 self.available_kernels.keys().cloned().collect()
277 }
278
279 pub fn generate_mps_shader(&self, kernel_type: MPSKernelType) -> String {
281 match kernel_type {
282 MPSKernelType::MatrixMultiplication => self.generate_matmul_shader(),
283 MPSKernelType::Convolution => self.generate_conv_shader(),
284 MPSKernelType::ImageFilter => self.generate_filter_shader(),
285 MPSKernelType::Reduction => self.generate_reduction_shader(),
286 MPSKernelType::NeuralNetwork => self.generate_nn_shader(),
287 }
288 }
289
290 fn generate_matmul_shader(&self) -> String {
291 r#"
292// Metal-optimized matrix multiplication
293@group(0) @binding(0) var<storage, read> matrix_a: array<f32>;
294@group(0) @binding(1) var<storage, read> matrix_b: array<f32>;
295@group(0) @binding(2) var<storage, read_write> matrix_c: array<f32>;
296
297struct MatmulParams {
298 m: u32,
299 n: u32,
300 k: u32,
301}
302
303@group(1) @binding(0) var<uniform> params: MatmulParams;
304
305@compute @workgroup_size(16, 16)
306fn matmul(@builtin(global_invocation_id) global_id: vec3<u32>) {
307 let row = global_id.x;
308 let col = global_id.y;
309
310 if (row >= params.m || col >= params.n) {
311 return;
312 }
313
314 var sum = 0.0;
315 for (var i = 0u; i < params.k; i++) {
316 let a_val = matrix_a[row * params.k + i];
317 let b_val = matrix_b[i * params.n + col];
318 sum += a_val * b_val;
319 }
320
321 matrix_c[row * params.n + col] = sum;
322}
323"#
324 .to_string()
325 }
326
327 fn generate_conv_shader(&self) -> String {
328 r#"
329// Metal-optimized 2D convolution
330@group(0) @binding(0) var<storage, read> input: array<f32>;
331@group(0) @binding(1) var<storage, read> kernel: array<f32>;
332@group(0) @binding(2) var<storage, read_write> output: array<f32>;
333
334struct ConvParams {
335 width: u32,
336 height: u32,
337 kernel_size: u32,
338}
339
340@group(1) @binding(0) var<uniform> params: ConvParams;
341
342@compute @workgroup_size(16, 16)
343fn conv2d(@builtin(global_invocation_id) global_id: vec3<u32>) {
344 let x = global_id.x;
345 let y = global_id.y;
346
347 if (x >= params.width || y >= params.height) {
348 return;
349 }
350
351 var sum = 0.0;
352 let half_k = params.kernel_size / 2u;
353
354 for (var ky = 0u; ky < params.kernel_size; ky++) {
355 for (var kx = 0u; kx < params.kernel_size; kx++) {
356 let ix = i32(x) + i32(kx) - i32(half_k);
357 let iy = i32(y) + i32(ky) - i32(half_k);
358
359 if (ix >= 0 && ix < i32(params.width) && iy >= 0 && iy < i32(params.height)) {
360 let input_idx = u32(iy) * params.width + u32(ix);
361 let kernel_idx = ky * params.kernel_size + kx;
362 sum += input[input_idx] * kernel[kernel_idx];
363 }
364 }
365 }
366
367 output[y * params.width + x] = sum;
368}
369"#
370 .to_string()
371 }
372
373 fn generate_filter_shader(&self) -> String {
391 r#"
392// Metal-style separable Gaussian filter (two passes share this layout).
393@group(0) @binding(0) var<storage, read> src: array<f32>;
394@group(0) @binding(1) var<storage, read> weights: array<f32>;
395@group(0) @binding(2) var<storage, read_write> dst: array<f32>;
396
397struct FilterParams {
398 width: u32,
399 height: u32,
400 radius: u32,
401 _pad: u32,
402}
403
404@group(1) @binding(0) var<uniform> params: FilterParams;
405
406@compute @workgroup_size(16, 16)
407fn gaussian_horizontal(@builtin(global_invocation_id) global_id: vec3<u32>) {
408 let x = global_id.x;
409 let y = global_id.y;
410 if (x >= params.width || y >= params.height) {
411 return;
412 }
413 let r = i32(params.radius);
414 var acc = 0.0;
415 for (var k = -r; k <= r; k = k + 1) {
416 var sx = i32(x) + k;
417 if (sx < 0) { sx = 0; }
418 if (sx > i32(params.width) - 1) { sx = i32(params.width) - 1; }
419 let w = weights[u32(k + r)];
420 acc = acc + w * src[y * params.width + u32(sx)];
421 }
422 dst[y * params.width + x] = acc;
423}
424
425@compute @workgroup_size(16, 16)
426fn gaussian_vertical(@builtin(global_invocation_id) global_id: vec3<u32>) {
427 let x = global_id.x;
428 let y = global_id.y;
429 if (x >= params.width || y >= params.height) {
430 return;
431 }
432 let r = i32(params.radius);
433 var acc = 0.0;
434 for (var k = -r; k <= r; k = k + 1) {
435 var sy = i32(y) + k;
436 if (sy < 0) { sy = 0; }
437 if (sy > i32(params.height) - 1) { sy = i32(params.height) - 1; }
438 let w = weights[u32(k + r)];
439 acc = acc + w * src[u32(sy) * params.width + x];
440 }
441 dst[y * params.width + x] = acc;
442}
443"#
444 .to_string()
445 }
446
447 fn generate_reduction_shader(&self) -> String {
465 r#"
466// Metal-style two-pass workgroup reduction.
467@group(0) @binding(0) var<storage, read> input: array<f32>;
468@group(0) @binding(1) var<storage, read_write> partials: array<f32>;
469
470struct ReduceParams {
471 n: u32,
472 _pad0: u32,
473 _pad1: u32,
474 _pad2: u32,
475}
476
477@group(1) @binding(0) var<uniform> params: ReduceParams;
478
479const REDUCE_WG: u32 = 256u;
480var<workgroup> red_scratch: array<f32, 256>;
481
482@compute @workgroup_size(256)
483fn reduce_sum(@builtin(global_invocation_id) gid: vec3<u32>,
484 @builtin(local_invocation_index) lid: u32,
485 @builtin(workgroup_id) wid: vec3<u32>) {
486 var v = 0.0;
487 if (gid.x < params.n) { v = input[gid.x]; }
488 red_scratch[lid] = v;
489 workgroupBarrier();
490 var stride = REDUCE_WG / 2u;
491 loop {
492 if (stride == 0u) { break; }
493 if (lid < stride) {
494 red_scratch[lid] = red_scratch[lid] + red_scratch[lid + stride];
495 }
496 workgroupBarrier();
497 stride = stride / 2u;
498 }
499 if (lid == 0u) { partials[wid.x] = red_scratch[0]; }
500}
501
502@compute @workgroup_size(256)
503fn reduce_min(@builtin(global_invocation_id) gid: vec3<u32>,
504 @builtin(local_invocation_index) lid: u32,
505 @builtin(workgroup_id) wid: vec3<u32>) {
506 var v = 3.4e38;
507 if (gid.x < params.n) { v = input[gid.x]; }
508 red_scratch[lid] = v;
509 workgroupBarrier();
510 var stride = REDUCE_WG / 2u;
511 loop {
512 if (stride == 0u) { break; }
513 if (lid < stride) {
514 red_scratch[lid] = min(red_scratch[lid], red_scratch[lid + stride]);
515 }
516 workgroupBarrier();
517 stride = stride / 2u;
518 }
519 if (lid == 0u) { partials[wid.x] = red_scratch[0]; }
520}
521
522@compute @workgroup_size(256)
523fn reduce_max(@builtin(global_invocation_id) gid: vec3<u32>,
524 @builtin(local_invocation_index) lid: u32,
525 @builtin(workgroup_id) wid: vec3<u32>) {
526 var v = -3.4e38;
527 if (gid.x < params.n) { v = input[gid.x]; }
528 red_scratch[lid] = v;
529 workgroupBarrier();
530 var stride = REDUCE_WG / 2u;
531 loop {
532 if (stride == 0u) { break; }
533 if (lid < stride) {
534 red_scratch[lid] = max(red_scratch[lid], red_scratch[lid + stride]);
535 }
536 workgroupBarrier();
537 stride = stride / 2u;
538 }
539 if (lid == 0u) { partials[wid.x] = red_scratch[0]; }
540}
541"#
542 .to_string()
543 }
544
545 fn generate_nn_shader(&self) -> String {
559 r#"
560// Metal-style fused ReLU + tiled GEMM: C = relu(A * B).
561@group(0) @binding(0) var<storage, read> mat_a: array<f32>;
562@group(0) @binding(1) var<storage, read> mat_b: array<f32>;
563@group(0) @binding(2) var<storage, read_write> mat_c: array<f32>;
564
565struct NnDims {
566 M: u32,
567 N: u32,
568 K: u32,
569 _pad: u32,
570}
571
572@group(1) @binding(0) var<uniform> dims: NnDims;
573
574const TILE: u32 = 16u;
575var<workgroup> tile_a: array<f32, 256>;
576var<workgroup> tile_b: array<f32, 256>;
577
578@compute @workgroup_size(16, 16)
579fn matmul_relu(@builtin(global_invocation_id) gid: vec3<u32>,
580 @builtin(local_invocation_id) lid: vec3<u32>) {
581 let row = gid.y;
582 let col = gid.x;
583 let num_tiles = (dims.K + TILE - 1u) / TILE;
584
585 var sum = 0.0;
586 for (var t = 0u; t < num_tiles; t = t + 1u) {
587 let a_col = t * TILE + lid.x;
588 if (row < dims.M && a_col < dims.K) {
589 tile_a[lid.y * TILE + lid.x] = mat_a[row * dims.K + a_col];
590 } else {
591 tile_a[lid.y * TILE + lid.x] = 0.0;
592 }
593 let b_row = t * TILE + lid.y;
594 if (b_row < dims.K && col < dims.N) {
595 tile_b[lid.y * TILE + lid.x] = mat_b[b_row * dims.N + col];
596 } else {
597 tile_b[lid.y * TILE + lid.x] = 0.0;
598 }
599 workgroupBarrier();
600
601 for (var k = 0u; k < TILE; k = k + 1u) {
602 sum = sum + tile_a[lid.y * TILE + k] * tile_b[k * TILE + lid.x];
603 }
604 workgroupBarrier();
605 }
606
607 if (row < dims.M && col < dims.N) {
608 mat_c[row * dims.N + col] = max(sum, 0.0);
609 }
610}
611"#
612 .to_string()
613 }
614}
615
616impl Default for MetalPerformanceShaders {
617 fn default() -> Self {
618 Self::new()
619 }
620}
621
622pub struct UnifiedMemoryOptimizer {
624 enabled: bool,
625}
626
627impl UnifiedMemoryOptimizer {
628 pub fn new(enabled: bool) -> Self {
630 Self { enabled }
631 }
632
633 pub fn is_enabled(&self) -> bool {
635 self.enabled
636 }
637
638 pub fn optimize_allocation(&self, size: u64) -> AllocationHint {
640 if !self.enabled {
641 return AllocationHint::Standard;
642 }
643
644 if size < 1024 * 1024 {
646 AllocationHint::Shared
648 } else {
649 AllocationHint::Private
651 }
652 }
653
654 pub fn optimize_access_pattern(&self, width: u32, height: u32) -> AccessPattern {
656 AccessPattern {
657 width,
658 height,
659 prefer_linear: true,
660 prefer_tiled: false,
661 }
662 }
663}
664
665#[derive(Debug, Clone, Copy, PartialEq, Eq)]
666pub enum AllocationHint {
667 Standard,
669 Shared,
671 Private,
673 Managed,
675}
676
677#[derive(Debug, Clone)]
678pub struct AccessPattern {
679 pub width: u32,
681 pub height: u32,
683 pub prefer_linear: bool,
685 pub prefer_tiled: bool,
687}
688
689pub struct ArgumentBufferManager {
691 buffers: HashMap<u32, ArgumentBuffer>,
692 next_id: u32,
693}
694
695#[derive(Debug, Clone)]
696struct ArgumentBuffer {
697 id: u32,
698 name: String,
699 arguments: Vec<ArgumentDescriptor>,
700}
701
702#[derive(Debug, Clone)]
703struct ArgumentDescriptor {
704 name: String,
705 binding: u32,
706 arg_type: ArgumentType,
707}
708
709#[derive(Debug, Clone, Copy, PartialEq, Eq)]
710pub enum ArgumentType {
711 Buffer,
713 Texture,
715 Sampler,
717}
718
719impl ArgumentBufferManager {
720 pub fn new() -> Self {
722 Self {
723 buffers: HashMap::new(),
724 next_id: 0,
725 }
726 }
727
728 pub fn create(&mut self, name: String) -> u32 {
730 let id = self.next_id;
731 self.next_id += 1;
732
733 self.buffers.insert(
734 id,
735 ArgumentBuffer {
736 id,
737 name: name.clone(),
738 arguments: Vec::new(),
739 },
740 );
741
742 debug!("Created argument buffer '{}' (ID: {})", name, id);
743
744 id
745 }
746
747 pub fn add_argument(
753 &mut self,
754 buffer_id: u32,
755 name: String,
756 binding: u32,
757 arg_type: ArgumentType,
758 ) -> GpuResult<()> {
759 let buffer = self
760 .buffers
761 .get_mut(&buffer_id)
762 .ok_or_else(|| GpuError::invalid_buffer("Argument buffer not found"))?;
763
764 buffer.arguments.push(ArgumentDescriptor {
765 name: name.clone(),
766 binding,
767 arg_type,
768 });
769
770 debug!(
771 "Added argument '{}' to buffer {} at binding {}",
772 name, buffer_id, binding
773 );
774
775 Ok(())
776 }
777
778 pub fn get(&self, buffer_id: u32) -> Option<&ArgumentBuffer> {
780 self.buffers.get(&buffer_id)
781 }
782
783 pub fn destroy(&mut self, buffer_id: u32) {
785 if let Some(buffer) = self.buffers.remove(&buffer_id) {
786 debug!("Destroyed argument buffer '{}'", buffer.name);
787 }
788 }
789}
790
791impl Default for ArgumentBufferManager {
792 fn default() -> Self {
793 Self::new()
794 }
795}
796
797pub struct ThreadgroupMemoryAllocator {
799 max_memory: u64,
800 allocated: u64,
801 allocations: HashMap<u32, ThreadgroupAllocation>,
802 next_id: u32,
803}
804
805#[derive(Debug, Clone)]
806struct ThreadgroupAllocation {
807 id: u32,
808 size: u64,
809 offset: u64,
810}
811
812impl ThreadgroupMemoryAllocator {
813 pub fn new(max_memory: u64) -> Self {
815 Self {
816 max_memory,
817 allocated: 0,
818 allocations: HashMap::new(),
819 next_id: 0,
820 }
821 }
822
823 pub fn allocate(&mut self, size: u64) -> GpuResult<u32> {
829 if self.allocated + size > self.max_memory {
830 return Err(GpuError::out_of_memory(
831 size,
832 self.max_memory - self.allocated,
833 ));
834 }
835
836 let id = self.next_id;
837 self.next_id += 1;
838
839 let offset = self.allocated;
840 self.allocated += size;
841
842 self.allocations
843 .insert(id, ThreadgroupAllocation { id, size, offset });
844
845 debug!(
846 "Allocated {} bytes of threadgroup memory at offset {}",
847 size, offset
848 );
849
850 Ok(id)
851 }
852
853 pub fn free(&mut self, id: u32) -> GpuResult<()> {
859 let alloc = self
860 .allocations
861 .remove(&id)
862 .ok_or_else(|| GpuError::invalid_buffer("Allocation not found"))?;
863
864 self.allocated = self.allocated.saturating_sub(alloc.size);
865
866 debug!("Freed {} bytes of threadgroup memory", alloc.size);
867
868 Ok(())
869 }
870
871 pub fn usage(&self) -> (u64, u64) {
873 (self.allocated, self.max_memory)
874 }
875
876 pub fn reset(&mut self) {
878 self.allocations.clear();
879 self.allocated = 0;
880 }
881}
882
883pub struct SimdGroupOperations;
894
895impl SimdGroupOperations {
896 pub fn simd_shuffle_shader(native: bool) -> String {
898 if native {
899 r#"
900// Native SIMD-group shuffle == WGSL subgroup shuffle (Features::SUBGROUP).
901fn simd_shuffle(value: f32, lane: u32, lid: u32, n: u32) -> f32 { return subgroupShuffle(value, lane); }
902fn simd_shuffle_down(value: f32, delta: u32, lid: u32, n: u32) -> f32 { return subgroupShuffleDown(value, delta); }
903fn simd_shuffle_up(value: f32, delta: u32, lid: u32, n: u32) -> f32 { return subgroupShuffleUp(value, delta); }
904fn simd_shuffle_xor(value: f32, mask: u32, lid: u32, n: u32) -> f32 { return subgroupShuffleXor(value, mask); }
905"#
906 .to_string()
907 } else {
908 r#"
909// Emulated SIMD-group shuffle — workgroup-shared memory + workgroupBarrier().
910// Out-of-range source lanes return the caller's own value.
911var<workgroup> simd_shfl_scratch: array<f32, 256>;
912fn simd_shuffle(value: f32, lane: u32, lid: u32, n: u32) -> f32 {
913 simd_shfl_scratch[lid] = value;
914 workgroupBarrier();
915 var idx = lane;
916 if (idx >= n) { idx = lid; }
917 let result = simd_shfl_scratch[idx];
918 workgroupBarrier();
919 return result;
920}
921fn simd_shuffle_down(value: f32, delta: u32, lid: u32, n: u32) -> f32 {
922 simd_shfl_scratch[lid] = value;
923 workgroupBarrier();
924 var idx = lid + delta;
925 if (idx >= n) { idx = lid; }
926 let result = simd_shfl_scratch[idx];
927 workgroupBarrier();
928 return result;
929}
930fn simd_shuffle_up(value: f32, delta: u32, lid: u32, n: u32) -> f32 {
931 simd_shfl_scratch[lid] = value;
932 workgroupBarrier();
933 var idx = lid;
934 if (lid >= delta) { idx = lid - delta; }
935 let result = simd_shfl_scratch[idx];
936 workgroupBarrier();
937 return result;
938}
939fn simd_shuffle_xor(value: f32, mask: u32, lid: u32, n: u32) -> f32 {
940 simd_shfl_scratch[lid] = value;
941 workgroupBarrier();
942 var idx = lid ^ mask;
943 if (idx >= n) { idx = lid; }
944 let result = simd_shfl_scratch[idx];
945 workgroupBarrier();
946 return result;
947}
948"#
949 .to_string()
950 }
951 }
952
953 pub fn simd_reduce_shader(native: bool) -> String {
960 if native {
961 r#"
962// Native SIMD-group reduction == WGSL subgroup reduction (Features::SUBGROUP).
963fn simd_sum(value: f32, lid: u32, n: u32) -> f32 { return subgroupAdd(value); }
964fn simd_max(value: f32, lid: u32, n: u32) -> f32 { return subgroupMax(value); }
965fn simd_min(value: f32, lid: u32, n: u32) -> f32 { return subgroupMin(value); }
966fn simd_product(value: f32, lid: u32, n: u32) -> f32 { return subgroupMul(value); }
967"#
968 .to_string()
969 } else {
970 r#"
971// Emulated SIMD-group reduction — workgroup-wide tree reduction via shared
972// memory + workgroupBarrier(); result broadcast to every invocation.
973var<workgroup> simd_red_scratch: array<f32, 256>;
974fn simd_sum(value: f32, lid: u32, n: u32) -> f32 {
975 simd_red_scratch[lid] = value;
976 workgroupBarrier();
977 var stride = 1u;
978 loop {
979 if (stride >= n) { break; }
980 let idx = lid * stride * 2u;
981 if (idx + stride < n) {
982 simd_red_scratch[idx] = simd_red_scratch[idx] + simd_red_scratch[idx + stride];
983 }
984 stride = stride * 2u;
985 workgroupBarrier();
986 }
987 let result = simd_red_scratch[0];
988 workgroupBarrier();
989 return result;
990}
991fn simd_max(value: f32, lid: u32, n: u32) -> f32 {
992 simd_red_scratch[lid] = value;
993 workgroupBarrier();
994 var stride = 1u;
995 loop {
996 if (stride >= n) { break; }
997 let idx = lid * stride * 2u;
998 if (idx + stride < n) {
999 simd_red_scratch[idx] = max(simd_red_scratch[idx], simd_red_scratch[idx + stride]);
1000 }
1001 stride = stride * 2u;
1002 workgroupBarrier();
1003 }
1004 let result = simd_red_scratch[0];
1005 workgroupBarrier();
1006 return result;
1007}
1008fn simd_min(value: f32, lid: u32, n: u32) -> f32 {
1009 simd_red_scratch[lid] = value;
1010 workgroupBarrier();
1011 var stride = 1u;
1012 loop {
1013 if (stride >= n) { break; }
1014 let idx = lid * stride * 2u;
1015 if (idx + stride < n) {
1016 simd_red_scratch[idx] = min(simd_red_scratch[idx], simd_red_scratch[idx + stride]);
1017 }
1018 stride = stride * 2u;
1019 workgroupBarrier();
1020 }
1021 let result = simd_red_scratch[0];
1022 workgroupBarrier();
1023 return result;
1024}
1025fn simd_product(value: f32, lid: u32, n: u32) -> f32 {
1026 simd_red_scratch[lid] = value;
1027 workgroupBarrier();
1028 var stride = 1u;
1029 loop {
1030 if (stride >= n) { break; }
1031 let idx = lid * stride * 2u;
1032 if (idx + stride < n) {
1033 simd_red_scratch[idx] = simd_red_scratch[idx] * simd_red_scratch[idx + stride];
1034 }
1035 stride = stride * 2u;
1036 workgroupBarrier();
1037 }
1038 let result = simd_red_scratch[0];
1039 workgroupBarrier();
1040 return result;
1041}
1042"#
1043 .to_string()
1044 }
1045 }
1046
1047 pub fn native_subgroups(context: &GpuContext) -> bool {
1053 context
1054 .device()
1055 .features()
1056 .contains(wgpu::Features::SUBGROUP)
1057 }
1058}
1059
1060#[cfg(test)]
1061mod tests {
1062 use super::*;
1063
1064 #[test]
1065 fn test_metal_features() {
1066 let features = MetalFeatures::default();
1067 assert_eq!(features.simd_width, 32);
1068 assert!(features.unified_memory);
1069 assert!(features.mps_support);
1070 }
1071
1072 #[test]
1073 fn test_metal_performance_shaders() {
1074 let mps = MetalPerformanceShaders::new();
1075 assert!(mps.is_available("matmul"));
1076 assert!(mps.is_available("conv2d"));
1077
1078 let kernels = mps.list_kernels();
1079 assert!(!kernels.is_empty());
1080 }
1081
1082 #[test]
1083 fn test_unified_memory_optimizer() {
1084 let optimizer = UnifiedMemoryOptimizer::new(true);
1085 assert!(optimizer.is_enabled());
1086
1087 let hint = optimizer.optimize_allocation(512 * 1024);
1088 assert_eq!(hint, AllocationHint::Shared);
1089
1090 let hint = optimizer.optimize_allocation(2 * 1024 * 1024);
1091 assert_eq!(hint, AllocationHint::Private);
1092 }
1093
1094 #[test]
1095 fn test_argument_buffer_manager() {
1096 let mut manager = ArgumentBufferManager::new();
1097
1098 let buffer_id = manager.create("test_args".to_string());
1099 manager
1100 .add_argument(buffer_id, "input".to_string(), 0, ArgumentType::Buffer)
1101 .expect("Failed to add argument");
1102
1103 let buffer = manager.get(buffer_id).expect("Buffer not found");
1104 assert_eq!(buffer.arguments.len(), 1);
1105 }
1106
1107 #[test]
1108 fn test_threadgroup_memory_allocator() {
1109 let mut allocator = ThreadgroupMemoryAllocator::new(32 * 1024);
1110
1111 let id1 = allocator.allocate(1024).expect("Failed to allocate");
1112 let _id2 = allocator.allocate(2048).expect("Failed to allocate");
1113
1114 let (used, total) = allocator.usage();
1115 assert_eq!(used, 3072);
1116 assert_eq!(total, 32 * 1024);
1117
1118 allocator.free(id1).expect("Failed to free");
1119 let (used, _) = allocator.usage();
1120 assert_eq!(used, 2048);
1121 }
1122}