Skip to main content

cubecl_cpp/metal/
dialect.rs

1use super::{
2    AddressSpace, Extension,
3    arch::MetalArchitecture,
4    extension::{format_fast_recip, format_ffs, format_hypot, format_mulhi, format_rhypot},
5    format_erf, format_global_binding_arg, format_metal_builtin_binding_arg, format_safe_tanh,
6};
7use crate::{
8    Dialect,
9    shared::{
10        self, Builtin, Component, CubeIndexFlags, DialectBindings, DialectCubeBuiltins,
11        DialectIncludes, DialectInstructions, DialectProcessors, DialectTypes,
12        DialectWarpReduceCompiler, DialectWmmaCompiler, Elem, Flags, FmtLeft, FragmentIdent,
13        FragmentLayout, FragmentType, Instruction, Item, KernelArg, ManualMma, SharedMemory,
14        SupportedMmaCombinations, Value, WarpInstruction, WmmaInstruction, wmma_api_base,
15    },
16};
17use core::panic;
18use cubecl_core::ir::{self as gpu, features::MmaConfig};
19use std::fmt::Display;
20
21#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
22pub struct MslDialect {}
23
24// Base dialect
25
26impl Dialect for MslDialect {
27    type Architecture = MetalArchitecture;
28}
29
30impl MslDialect {
31    fn warp_op_vectorized(
32        f: &mut core::fmt::Formatter<'_>,
33        input: &Value<Self>,
34        out: &Value<Self>,
35        simd_op_prefix: &str,
36        simd_op_suffix: &str,
37    ) -> core::fmt::Result {
38        let out = out.fmt_left();
39        // No simd reduction for bfloat; reduce in float and cast back.
40        let (open, in_open, in_close, close) = if matches!(input.item().elem(), Elem::BF16) {
41            ("bfloat(", "float(", ")", ")")
42        } else {
43            ("", "", "", "")
44        };
45
46        if let Item::Vector(_, vectorization) = input.item() {
47            f.write_fmt(format_args!("{out} = {} {{", input.item()))?;
48
49            for k in 0..vectorization {
50                let comma = if k + 1 < vectorization { "," } else { "" };
51                writeln!(
52                    f,
53                    "{open}{simd_op_prefix}{in_open}{input}.i_{k}{in_close}{simd_op_suffix}{close}{comma}"
54                )?;
55            }
56
57            f.write_fmt(format_args!("}};\n"))
58        } else {
59            writeln!(
60                f,
61                "{out} = {open}{simd_op_prefix}{in_open}{input}{in_close}{simd_op_suffix}{close};"
62            )
63        }
64    }
65
66    fn warp_shuffle(
67        f: &mut core::fmt::Formatter<'_>,
68        op: &str,
69        val: &str,
70        elem: &Elem<Self>,
71        arg: &str,
72    ) -> core::fmt::Result {
73        // No simd_shuffle for bfloat; route it through a same-width ushort.
74        if matches!(elem, Elem::BF16) {
75            write!(f, "as_type<bfloat>({op}(as_type<ushort>({val}), {arg}))")
76        } else {
77            write!(f, "{op}({val}, {arg})")
78        }
79    }
80}
81
82impl DialectWarpReduceCompiler<Self> for MslDialect {
83    fn warp_reduce_sum(
84        f: &mut core::fmt::Formatter<'_>,
85        input: &Value<Self>,
86        out: &Value<Self>,
87    ) -> core::fmt::Result {
88        Self::warp_op_vectorized(f, input, out, "simd_sum(", ")")
89    }
90    fn warp_reduce_prod(
91        f: &mut core::fmt::Formatter<'_>,
92        input: &Value<Self>,
93        out: &Value<Self>,
94    ) -> core::fmt::Result {
95        Self::warp_op_vectorized(f, input, out, "simd_product(", ")")
96    }
97    fn warp_reduce_max(
98        f: &mut core::fmt::Formatter<'_>,
99        input: &Value<Self>,
100        out: &Value<Self>,
101    ) -> core::fmt::Result {
102        Self::warp_op_vectorized(f, input, out, "simd_max(", ")")
103    }
104    fn warp_reduce_min(
105        f: &mut core::fmt::Formatter<'_>,
106        input: &Value<Self>,
107        out: &Value<Self>,
108    ) -> core::fmt::Result {
109        Self::warp_op_vectorized(f, input, out, "simd_min(", ")")
110    }
111    fn warp_reduce_all(
112        f: &mut core::fmt::Formatter<'_>,
113        input: &Value<Self>,
114        out: &Value<Self>,
115    ) -> core::fmt::Result {
116        Self::warp_op_vectorized(f, input, out, "simd_and(", "? 1u : 0u) != 0u")
117    }
118    fn warp_reduce_any(
119        f: &mut core::fmt::Formatter<'_>,
120        input: &Value<Self>,
121        out: &Value<Self>,
122    ) -> core::fmt::Result {
123        Self::warp_op_vectorized(f, input, out, "simd_or(", "? 1u : 0u) != 0u")
124    }
125    fn warp_reduce_sum_inclusive(
126        f: &mut core::fmt::Formatter<'_>,
127        input: &Value<Self>,
128        out: &Value<Self>,
129    ) -> core::fmt::Result {
130        Self::warp_op_vectorized(f, input, out, "simd_prefix_inclusive_sum(", ")")
131    }
132    fn warp_reduce_prod_inclusive(
133        f: &mut core::fmt::Formatter<'_>,
134        input: &Value<Self>,
135        out: &Value<Self>,
136    ) -> core::fmt::Result {
137        Self::warp_op_vectorized(f, input, out, "simd_prefix_inclusive_product(", ")")
138    }
139    fn warp_reduce_sum_exclusive(
140        f: &mut core::fmt::Formatter<'_>,
141        input: &Value<Self>,
142        out: &Value<Self>,
143    ) -> core::fmt::Result {
144        Self::warp_op_vectorized(f, input, out, "simd_prefix_exclusive_sum(", ")")
145    }
146    fn warp_reduce_prod_exclusive(
147        f: &mut core::fmt::Formatter<'_>,
148        input: &Value<Self>,
149        out: &Value<Self>,
150    ) -> core::fmt::Result {
151        Self::warp_op_vectorized(f, input, out, "simd_prefix_exclusive_product(", ")")
152    }
153}
154
155// Includes
156
157impl DialectIncludes<Self> for MslDialect {
158    type Extension = Extension<Self>;
159
160    fn compile_includes(f: &mut std::fmt::Formatter<'_>, _flags: &Flags<Self>) -> std::fmt::Result {
161        write!(
162            f,
163            "
164#include <metal_stdlib>
165using namespace metal;
166"
167        )?;
168        Ok(())
169    }
170
171    fn compile_extensions(
172        f: &mut std::fmt::Formatter<'_>,
173        extensions: &[Self::Extension],
174    ) -> std::fmt::Result {
175        for extension in extensions {
176            match extension {
177                Extension::Erf(input, output) => format_erf::<Self>(f, input, output)?,
178                Extension::Ffs(elem) => format_ffs(f, elem)?,
179                Extension::MulHi(elem) => format_mulhi(f, elem)?,
180                Extension::SafeTanh(item) => format_safe_tanh::<Self>(f, item)?,
181                Extension::Hypot(elem) => format_hypot::<Self>(f, elem)?,
182                Extension::Rhypot(elem) => format_rhypot::<Self>(f, elem)?,
183                Extension::FastRecip => format_fast_recip(f)?,
184                Extension::NoExtension => {}
185            }
186        }
187        Ok(())
188    }
189
190    fn register_instruction_extension(
191        extensions: &mut Vec<Self::Extension>,
192        instruction: &Instruction<Self>,
193    ) {
194        let mut register_extension = |extension: Self::Extension| {
195            if !extensions.contains(&extension) {
196                extensions.push(extension);
197            }
198        };
199        #[allow(clippy::single_match)]
200        match instruction {
201            shared::Instruction::<Self>::Erf(instruction) => {
202                register_extension(Extension::Erf(
203                    instruction.input.elem(),
204                    instruction.out.elem(),
205                ));
206            }
207            shared::Instruction::<Self>::FindFirstSet(instruction) => {
208                let input_elem = instruction.input.elem();
209                match input_elem {
210                    Elem::U32 | Elem::U64 => {
211                        register_extension(Extension::Ffs(instruction.input.elem()));
212                    }
213                    Elem::I32 => {
214                        register_extension(Extension::Ffs(Elem::<Self>::U32));
215                        register_extension(Extension::Ffs(instruction.input.elem()));
216                    }
217                    Elem::I64 => {
218                        register_extension(Extension::Ffs(Elem::<Self>::U64));
219                        register_extension(Extension::Ffs(instruction.input.elem()));
220                    }
221                    _ => {
222                        register_extension(Extension::Ffs(Elem::<Self>::U32));
223                    }
224                }
225            }
226            shared::Instruction::<Self>::HiMul(instruction) => {
227                register_extension(Extension::MulHi(instruction.out.elem()));
228            }
229            shared::Instruction::<Self>::Tanh(instruction) => {
230                register_extension(Extension::SafeTanh(instruction.input.item()));
231            }
232            shared::Instruction::<Self>::Hypot(instruction) => {
233                // For half types, the Binary impl casts to float, so we need float hypot
234                let elem = match instruction.out.elem() {
235                    Elem::F16 | Elem::F16x2 | Elem::BF16 | Elem::BF16x2 => Elem::F32,
236                    other => other,
237                };
238                register_extension(Extension::Hypot(elem));
239            }
240            shared::Instruction::<Self>::Rhypot(instruction) => {
241                // For half types, the Binary impl casts to float, so we need float rhypot
242                let elem = match instruction.out.elem() {
243                    Elem::F16 | Elem::F16x2 | Elem::BF16 | Elem::BF16x2 => Elem::F32,
244                    other => other,
245                };
246                register_extension(Extension::Rhypot(elem));
247            }
248            shared::Instruction::<Self>::FastRecip(_) => {
249                register_extension(Extension::FastRecip);
250            }
251            _ => {}
252        }
253    }
254
255    fn register_warp_instruction_extension(
256        _extensions: &mut Vec<Self::Extension>,
257        _instruction: &WarpInstruction<Self>,
258    ) {
259    }
260}
261
262// Types
263
264impl DialectTypes<Self> for MslDialect {
265    fn item_can_be_optimized() -> bool {
266        false
267    }
268
269    fn compile_type_definitions(
270        f: &mut std::fmt::Formatter<'_>,
271        items: &std::collections::HashSet<crate::shared::Item<Self>>,
272        scalars: &[(Elem<Self>, usize)],
273        info: &cubecl_core::Info,
274        flags: &Flags<Self>,
275    ) -> std::fmt::Result {
276        for item in items.iter() {
277            if let Item::Vector(inner, vectorization) = item {
278                let alignment = item.size();
279                if *vectorization > 1 {
280                    write!(
281                        f,
282                        "
283struct alignas({alignment}) {item} {{"
284                    )?;
285
286                    for i in 0..*vectorization {
287                        write!(
288                            f,
289                            "
290    {inner} i_{i};"
291                        )?;
292                    }
293
294                    f.write_str("\n};\n")?;
295                }
296            }
297        }
298
299        shared::type_info_definition_sized(f, info, scalars, flags.address_type)?;
300        Ok(())
301    }
302
303    fn compile_elem(
304        f: &mut std::fmt::Formatter<'_>,
305        elem: &shared::Elem<Self>,
306        _words: bool,
307    ) -> std::fmt::Result {
308        // we always use the word form of types
309        match elem {
310            shared::Elem::FP4(_)
311            | shared::Elem::FP4x2(_)
312            | shared::Elem::FP6(_)
313            | shared::Elem::FP6x2(_)
314            | shared::Elem::FP8(_)
315            | shared::Elem::FP8x2(_) => f.write_str("#error FP4/FP6/FP8 not supported in Metal\n"),
316            shared::Elem::F16 => f.write_str("half"),
317            shared::Elem::F16x2 => f.write_str("#error type F162 not supported!\n"),
318            shared::Elem::F32 => f.write_str("float"),
319            shared::Elem::F64 => f.write_str("#error type double not supported!\n"),
320            shared::Elem::BF16 => f.write_str("bfloat"),
321            shared::Elem::BF16x2 => f.write_str("#error type BF162 not supported!\n"),
322            shared::Elem::TF32 => f.write_str("float"),
323            shared::Elem::I8 => f.write_str("char"),
324            shared::Elem::I16 => f.write_str("short"),
325            shared::Elem::I32 => f.write_str("int"),
326            shared::Elem::I64 => f.write_str("long"),
327            shared::Elem::U8 => f.write_str("uchar"),
328            shared::Elem::U16 => f.write_str("ushort"),
329            shared::Elem::U32 => f.write_str("uint"),
330            shared::Elem::U64 => f.write_str("ulong"),
331            shared::Elem::Bool => f.write_str("bool"),
332            shared::Elem::None => f.write_str("<none>"),
333            shared::Elem::_Dialect(_) => Ok(()),
334        }
335    }
336
337    fn compile_item(f: &mut std::fmt::Formatter<'_>, item: &Item<Self>) -> std::fmt::Result {
338        match item {
339            Item::Scalar(elem) => write!(f, "{elem}"),
340            Item::Vector(inner, vectorization) => {
341                Self::compile_item(f, inner.as_ref())?;
342                write!(f, "_{vectorization}")
343            }
344            Item::NativeVector(elem, vectorization) => {
345                Self::compile_elem(f, elem, true)?;
346                write!(f, "{vectorization}")
347            }
348            Item::Atomic(inner) => {
349                write!(f, "atomic_{inner}")
350            }
351            Item::Pointer(inner, class) => {
352                let address_space = match class {
353                    // Atomics always need mutable (device) access, even on read-only
354                    // bindings, because MSL forbids `const`-qualified `atomic<T>` pointers.
355                    shared::PointerClass::Global(_)
356                        if matches!(inner.value_ty(), Item::Atomic(_)) =>
357                    {
358                        AddressSpace::Device
359                    }
360                    shared::PointerClass::Global(vis) => (*vis).into(),
361                    shared::PointerClass::Shared => AddressSpace::ThreadGroup,
362                    shared::PointerClass::Local => AddressSpace::Thread,
363                };
364                write!(f, "{address_space} ")?;
365                match inner.as_ref() {
366                    Item::DynamicArray(inner) => write!(f, "{inner}*"),
367                    other => write!(f, "{other}*"),
368                }
369            }
370            Item::Array(inner, size) => {
371                write!(f, "array<{inner}, {size}>")
372            }
373            Item::DynamicArray(inner) => {
374                write!(f, "{inner}*")
375            }
376            Item::Fragment(fragment_type) => write!(f, "{fragment_type}"),
377            Item::Barrier(_) | Item::BarrierToken(_) => {
378                unimplemented!("metal doesn't support barrier object")
379            }
380            Item::TensorMap => unimplemented!("TensorMap not supported on Metal"),
381        }
382    }
383
384    fn address_space_for_value(value: &Value<Self>) -> String {
385        format!("{} ", AddressSpace::from(value))
386    }
387
388    fn compile_local_memory_qualifier(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389        write!(f, "thread")
390    }
391
392    fn compile_shared_memory_declaration(
393        f: &mut std::fmt::Formatter<'_>,
394        shared: &SharedMemory<Self>,
395    ) -> std::fmt::Result {
396        let SharedMemory { ptr, offset, .. } = shared;
397        let ptr_ty = ptr.item();
398        let size_bytes = shared.size();
399        writeln!(f, "// Shared value size: {size_bytes} bytes")?;
400        writeln!(
401            f,
402            "{ptr_ty} {ptr} = reinterpret_cast<{ptr_ty}>(&dynamic_shared_mem[{offset}]);"
403        )
404    }
405}
406
407// Kernel argument bindings
408
409impl DialectBindings<Self> for MslDialect {
410    fn compile_kernel_signature(
411        f: &mut std::fmt::Formatter<'_>,
412        kernel_name: &str,
413        tensor_maps: &[KernelArg<Self>],
414        buffers: &[KernelArg<Self>],
415        flags: &Flags<Self>,
416    ) -> std::fmt::Result {
417        write!(
418            (f),
419            "
420[[kernel]]
421void {kernel_name}("
422        )?;
423        // Global bindings args
424        let mut buffer_idx = 0;
425        debug_assert!(
426            tensor_maps.is_empty(),
427            "Tensor maps aren't supported for metal"
428        );
429        for b in buffers.iter() {
430            format_global_binding_arg(b, &mut buffer_idx, f)?;
431        }
432
433        if flags.has_info {
434            let comma = if buffer_idx > 0 { "," } else { "" };
435            let (address_space, val) = match flags.has_dynamic_meta {
436                true => (AddressSpace::ConstDevice, "info_st* info_ptr"),
437                false => (AddressSpace::Constant, "info_st& info"),
438            };
439            let attribute = address_space.attribute();
440
441            write!(f, "{comma}\n    {address_space} {val}",)?;
442            // attribute
443            attribute.indexed_fmt(buffer_idx, f)?;
444            buffer_idx += 1;
445        }
446
447        // Global metal builtins args
448        let builtins = vec![
449            (
450                flags.indexes.absolute_pos_tuple,
451                Builtin::<Self>::AbsolutePosBaseName,
452            ),
453            (
454                flags.indexes.cube_dim_tuple,
455                Builtin::<Self>::CubeDimBaseName,
456            ),
457            (
458                flags.indexes.cube_count_tuple,
459                Builtin::<Self>::CubeCountBaseName,
460            ),
461            (flags.indexes.unit_pos, Builtin::<Self>::UnitPos),
462            (
463                flags.indexes.unit_pos_tuple,
464                Builtin::<Self>::UnitPosBaseName,
465            ),
466            (
467                flags.indexes.cube_pos_tuple,
468                Builtin::<Self>::CubePosBaseName,
469            ),
470            (flags.indexes.unit_pos_plane, Builtin::<Self>::UnitPosPlane),
471            (flags.indexes.plane_dim, Builtin::<Self>::PlaneDim),
472            (flags.indexes.plane_pos, Builtin::<Self>::PlanePos),
473        ];
474        let comma = buffer_idx > 0;
475        builtins
476            .iter()
477            .filter(|(cond, _)| *cond)
478            .try_for_each(|(_, val)| format_metal_builtin_binding_arg(f, val, comma))?;
479        f.write_str("\n)")
480    }
481
482    fn compile_bindings_body(
483        f: &mut std::fmt::Formatter<'_>,
484        body: &shared::Body<Self>,
485    ) -> std::fmt::Result {
486        if !body.shared_memories.is_empty() {
487            let size = body
488                .shared_memories
489                .iter()
490                .map(|it| it.offset + it.size())
491                .max()
492                .unwrap();
493
494            writeln!(f, "threadgroup uchar dynamic_shared_mem[{size}];",)?;
495        }
496        if body.info_by_ptr && body.has_dynamic_meta {
497            let address_space = AddressSpace::ConstDevice;
498            writeln!(f, "const {address_space} info_st& info = *info_ptr;")?;
499            writeln!(
500                f,
501                "const {address_space} {addr}* dynamic_meta = reinterpret_cast<const {address_space} {addr}*>(
502                    reinterpret_cast<const {address_space} char*>(info_ptr) + sizeof(info_st)
503                );\n",
504                addr = body.address_type,
505            )?;
506        }
507        Ok(())
508    }
509}
510
511// Cube builtins dialect
512
513impl DialectCubeBuiltins<Self> for MslDialect {
514    /// Metal exposes the unit plane position as a native built-in.
515    fn builtin_rules(flags: &CubeIndexFlags) -> CubeIndexFlags {
516        let absolute_pos = flags.absolute_pos;
517        let cube_count = flags.cube_count;
518        let cube_dim = flags.cube_dim;
519        let cube_pos = flags.cube_pos;
520        let plane_dim_checked = flags.plane_dim_checked;
521        let plane_index = flags.plane_pos;
522        let unit_pos = flags.unit_pos;
523        let absolute_pos_tuple = flags.absolute_pos_tuple || absolute_pos;
524        let cube_count_tuple = flags.cube_count_tuple || cube_count || cube_pos || absolute_pos;
525        let cube_dim_tuple = flags.cube_dim_tuple || cube_dim || absolute_pos || plane_dim_checked;
526        let cube_pos_tuple = flags.cube_pos_tuple || cube_pos;
527        let cluster_pos = flags.cluster_pos;
528        let plane_dim = flags.plane_dim || plane_dim_checked || plane_index;
529        let unit_pos_plane = flags.unit_pos_plane || plane_index;
530        let unit_pos_tuple = flags.unit_pos_tuple || unit_pos;
531        CubeIndexFlags {
532            absolute_pos_tuple,
533            absolute_pos,
534            cube_count_tuple,
535            cube_count,
536            cube_dim_tuple,
537            cube_dim,
538            cube_pos_tuple,
539            cube_pos,
540            plane_dim,
541            plane_dim_checked,
542            plane_pos: plane_index,
543            unit_pos_tuple,
544            unit_pos,
545            unit_pos_plane,
546            cluster_pos,
547        }
548    }
549
550    fn compile_absolute_pos_tuple_computation(
551        _f: &mut std::fmt::Formatter<'_>,
552    ) -> std::fmt::Result {
553        // no need to compute it on metal as there is a built-in for it
554        Ok(())
555    }
556
557    fn compile_absolute_pos_base_name(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
558        f.write_str("thread_pos_in_grid")
559    }
560
561    fn compile_absolute_pos(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
562        f.write_str("thread_index_in_grid")
563    }
564
565    fn compile_absolute_pos_x(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
566        Self::compile_absolute_pos_base_name(f)?;
567        write!(f, ".x")
568    }
569
570    fn compile_absolute_pos_y(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
571        Self::compile_absolute_pos_base_name(f)?;
572        write!(f, ".y")
573    }
574
575    fn compile_absolute_pos_z(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
576        Self::compile_absolute_pos_base_name(f)?;
577        write!(f, ".z")
578    }
579
580    fn compile_cube_count_base_name(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
581        f.write_str("threadgroups_per_grid")
582    }
583
584    fn compile_cube_count(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
585        f.write_str("total_threadgroups_in_grid")
586    }
587
588    fn compile_cube_count_x(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
589        Self::compile_cube_count_base_name(f)?;
590        write!(f, ".x")
591    }
592
593    fn compile_cube_count_y(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
594        Self::compile_cube_count_base_name(f)?;
595        write!(f, ".y")
596    }
597
598    fn compile_cube_count_z(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
599        Self::compile_cube_count_base_name(f)?;
600        write!(f, ".z")
601    }
602
603    fn compile_cube_dim_base_name(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
604        f.write_str("threads_per_threadgroup")
605    }
606
607    fn compile_cube_dim(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
608        f.write_str("total_thread_in_threadgroup")
609    }
610
611    fn compile_cube_dim_x(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
612        Self::compile_cube_dim_base_name(f)?;
613        write!(f, ".x")
614    }
615
616    fn compile_cube_dim_y(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
617        Self::compile_cube_dim_base_name(f)?;
618        write!(f, ".y")
619    }
620
621    fn compile_cube_dim_z(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
622        Self::compile_cube_dim_base_name(f)?;
623        write!(f, ".z")
624    }
625
626    fn compile_cube_pos_base_name(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
627        f.write_str("threadgroup_pos_in_grid")
628    }
629
630    fn compile_cube_pos(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631        f.write_str("threadgroup_index_in_grid")
632    }
633
634    fn compile_cube_pos_x(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
635        Self::compile_cube_pos_base_name(f)?;
636        write!(f, ".x")
637    }
638
639    fn compile_cube_pos_y(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
640        Self::compile_cube_pos_base_name(f)?;
641        write!(f, ".y")
642    }
643
644    fn compile_cube_pos_z(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
645        Self::compile_cube_pos_base_name(f)?;
646        write!(f, ".z")
647    }
648
649    fn compile_unit_pos_computation(_f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
650        // no need to compute it on metal as there is a built-in for it
651        Ok(())
652    }
653
654    fn compile_unit_pos_base_name(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
655        f.write_str("thread_pos_in_threadgroup")
656    }
657
658    fn compile_unit_pos(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
659        f.write_str("thread_index_in_threadgroup")
660    }
661
662    fn compile_unit_pos_x(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
663        Self::compile_unit_pos_base_name(f)?;
664        write!(f, ".x")
665    }
666
667    fn compile_unit_pos_y(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
668        Self::compile_unit_pos_base_name(f)?;
669        write!(f, ".y")
670    }
671
672    fn compile_unit_pos_z(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
673        Self::compile_unit_pos_base_name(f)?;
674        write!(f, ".z")
675    }
676
677    fn compile_plane_dim(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
678        f.write_str("simd_size")
679    }
680
681    fn compile_plane_dim_checked(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
682        f.write_str("threads_per_simdgroup_checked")
683    }
684
685    fn compile_plane_pos(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
686        f.write_str("simd_group_id")
687    }
688
689    fn compile_unit_pos_plane(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
690        f.write_str("simd_lane_id")
691    }
692}
693
694// Instructions
695
696impl DialectInstructions<Self> for MslDialect {
697    // atomics
698    fn compile_atomic_add(
699        f: &mut std::fmt::Formatter<'_>,
700        lhs: &Value<Self>,
701        rhs: &Value<Self>,
702        out: &Value<Self>,
703    ) -> std::fmt::Result {
704        let out = out.fmt_left();
705        writeln!(
706            f,
707            "{out} = atomic_fetch_add_explicit({lhs}, {rhs}, memory_order_relaxed);"
708        )
709    }
710
711    fn compile_atomic_and(
712        f: &mut std::fmt::Formatter<'_>,
713        lhs: &Value<Self>,
714        rhs: &Value<Self>,
715        out: &Value<Self>,
716    ) -> std::fmt::Result {
717        let out = out.fmt_left();
718        writeln!(
719            f,
720            "{out} = atomic_fetch_and_explicit({lhs}, {rhs}, memory_order_relaxed);"
721        )
722    }
723
724    fn compile_atomic_cas(
725        f: &mut std::fmt::Formatter<'_>,
726        input: &Value<Self>,
727        cmp: &Value<Self>,
728        val: &Value<Self>,
729        out: &Value<Self>,
730    ) -> std::fmt::Result {
731        let expected_name = format!("{out}_expected");
732        let out_item = out.item();
733        writeln!(f, "{out_item} {expected_name} = {cmp};")?;
734        writeln!(
735            f,
736            "atomic_compare_exchange_weak_explicit({input}, &{expected_name}, {val}, memory_order_relaxed, memory_order_relaxed);"
737        )?;
738        let out = out.fmt_left();
739        writeln!(f, "{out} = {expected_name};")
740    }
741
742    fn compile_atomic_load(
743        f: &mut std::fmt::Formatter<'_>,
744        input: &Value<Self>,
745        out: &Value<Self>,
746    ) -> std::fmt::Result {
747        let out = out.fmt_left();
748        writeln!(
749            f,
750            "{out} = atomic_load_explicit({input}, memory_order_relaxed);"
751        )
752    }
753
754    fn compile_atomic_max(
755        f: &mut std::fmt::Formatter<'_>,
756        lhs: &Value<Self>,
757        rhs: &Value<Self>,
758        out: &Value<Self>,
759    ) -> std::fmt::Result {
760        let out = out.fmt_left();
761        writeln!(
762            f,
763            "{out} = atomic_fetch_max_explicit({lhs}, {rhs}, memory_order_relaxed);"
764        )
765    }
766
767    fn compile_atomic_min(
768        f: &mut std::fmt::Formatter<'_>,
769        lhs: &Value<Self>,
770        rhs: &Value<Self>,
771        out: &Value<Self>,
772    ) -> std::fmt::Result {
773        let out = out.fmt_left();
774        writeln!(
775            f,
776            "{out} = atomic_fetch_min_explicit({lhs}, {rhs}, memory_order_relaxed);"
777        )
778    }
779
780    fn compile_atomic_or(
781        f: &mut std::fmt::Formatter<'_>,
782        lhs: &Value<Self>,
783        rhs: &Value<Self>,
784        out: &Value<Self>,
785    ) -> std::fmt::Result {
786        let out = out.fmt_left();
787        writeln!(
788            f,
789            "{out} = atomic_fetch_or_explicit({lhs}, {rhs}, memory_order_relaxed);"
790        )
791    }
792
793    fn compile_atomic_store(
794        f: &mut std::fmt::Formatter<'_>,
795        input: &Value<Self>,
796        out: &Value<Self>,
797    ) -> std::fmt::Result {
798        writeln!(
799            f,
800            "atomic_store_explicit({out}, {input}, memory_order_relaxed);"
801        )
802    }
803
804    fn compile_atomic_sub(
805        f: &mut std::fmt::Formatter<'_>,
806        lhs: &Value<Self>,
807        rhs: &Value<Self>,
808        out: &Value<Self>,
809    ) -> std::fmt::Result {
810        let out = out.fmt_left();
811        writeln!(
812            f,
813            "{out} = atomic_fetch_sub_explicit({lhs}, {rhs}, memory_order_relaxed);"
814        )
815    }
816
817    fn compile_atomic_swap(
818        f: &mut std::fmt::Formatter<'_>,
819        lhs: &Value<Self>,
820        rhs: &Value<Self>,
821        out: &Value<Self>,
822    ) -> std::fmt::Result {
823        let out = out.fmt_left();
824        writeln!(
825            f,
826            "{out} = atomic_exchange_explicit({lhs}, {rhs}, memory_order_relaxed);"
827        )
828    }
829
830    fn compile_atomic_xor(
831        f: &mut std::fmt::Formatter<'_>,
832        lhs: &Value<Self>,
833        rhs: &Value<Self>,
834        out: &Value<Self>,
835    ) -> std::fmt::Result {
836        let out = out.fmt_left();
837        writeln!(
838            f,
839            "{out} = atomic_fetch_xor_explicit({lhs}, {rhs}, memory_order_relaxed);"
840        )
841    }
842
843    fn compile_saturating_add(
844        f: &mut std::fmt::Formatter<'_>,
845        lhs: impl Display,
846        rhs: impl Display,
847        _item: Item<Self>,
848    ) -> std::fmt::Result {
849        write!(f, "addsat({lhs}, {rhs})")
850    }
851
852    fn compile_saturating_sub(
853        f: &mut std::fmt::Formatter<'_>,
854        lhs: impl Display,
855        rhs: impl Display,
856        _item: Item<Self>,
857    ) -> std::fmt::Result {
858        write!(f, "subsat({lhs}, {rhs})")
859    }
860
861    // debug
862    fn compile_instruction_printf(
863        f: &mut std::fmt::Formatter<'_>,
864        format_string: &str,
865        args: &[Value<Self>],
866    ) -> std::fmt::Result {
867        let args = args.iter().map(|arg| format!("{arg}")).collect::<Vec<_>>();
868        let args = match args.is_empty() {
869            true => "".to_string(),
870            false => format!(", {}", args.join(",")),
871        };
872        writeln!(f, "os_log_default.log({format_string:?}{args});")
873    }
874
875    // logs
876    fn compile_instruction_log1p_scalar<T: Component<Self>>(
877        f: &mut std::fmt::Formatter<'_>,
878        input: T,
879    ) -> std::fmt::Result {
880        match input.elem() {
881            Elem::F16 | Elem::F16x2 | Elem::BF16 | Elem::BF16x2 => {
882                write!(f, "log(half(1.0f) + {input})")
883            }
884            _ => write!(f, "log(1.0f + {input})"),
885        }
886    }
887
888    // exp
889    fn compile_instruction_expm1_scalar<T: Component<Self>>(
890        f: &mut std::fmt::Formatter<'_>,
891        input: T,
892    ) -> std::fmt::Result {
893        // MSL has no `expm1`. The naive `exp(x) - 1` loses all precision near zero
894        // (catastrophic cancellation), so use the stable identity
895        // `expm1(x) = (exp(x) - 1) * x / log(exp(x))`, where `x / log(exp(x))` is a
896        // unit-valued correction. With `u = exp(x)`, three boundary regimes need
897        // explicit handling (in order):
898        //   * `u == 1`   (x near 0)   → `x`     (avoids the `0/0` ratio)
899        //   * `isinf(u)` (x large +)  → `u`     (else `(inf-1)*x/log(inf)` is NaN)
900        //   * `u == 0`   (x large -)  → `u - 1` (else `x / log(0)` collapses it)
901        // `precise::` pins `exp`/`log` accurate regardless of the kernel's math mode.
902        let elem = input.elem();
903        match elem {
904            // The Unary impl casts half/bfloat to float, so operate in float and
905            // cast the result back.
906            Elem::F16 | Elem::F16x2 | Elem::BF16 | Elem::BF16x2 => {
907                write!(
908                    f,
909                    "{elem}(precise::exp(float({input})) == 1.0f ? float({input}) : (isinf(precise::exp(float({input}))) ? precise::exp(float({input})) : (precise::exp(float({input})) == 0.0f ? precise::exp(float({input})) - 1.0f : (precise::exp(float({input})) - 1.0f) * float({input}) / precise::log(precise::exp(float({input}))))))"
910                )
911            }
912            _ => write!(
913                f,
914                "(precise::exp({input}) == 1.0f ? {input} : (isinf(precise::exp({input})) ? precise::exp({input}) : (precise::exp({input}) == 0.0f ? precise::exp({input}) - 1.0f : (precise::exp({input}) - 1.0f) * {input} / precise::log(precise::exp({input})))))"
915            ),
916        }
917    }
918
919    // sync
920    fn compile_instruction_sync_threads(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
921        writeln!(f, "threadgroup_barrier(mem_flags::mem_threadgroup);")
922    }
923
924    fn compile_instruction_sync_warp(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
925        writeln!(f, "simdgroup_barrier(mem_flags::mem_none);")
926    }
927
928    fn compile_instruction_thread_fence(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
929        writeln!(f, "threadgroup_thread_fence(mem_flags::mem_device);")
930    }
931
932    // trigo
933    fn compile_instruction_tanh_scalar<T: Component<Self>>(
934        f: &mut std::fmt::Formatter<'_>,
935        input: T,
936    ) -> std::fmt::Result {
937        write!(f, "safe_tanh_scalar({input})")
938    }
939
940    // unary
941    fn compile_instruction_find_first_set<T: Component<Self>>(
942        f: &mut std::fmt::Formatter<'_>,
943        input: T,
944        out_elem: Elem<Self>,
945    ) -> std::fmt::Result {
946        write!(f, "{out_elem}(")?;
947        match input.elem() {
948            Elem::I32 | Elem::U32 => write!(f, "__ffs({input})"),
949            Elem::I64 | Elem::U64 => write!(f, "__ffsll({input})"),
950            _ => write!(f, "__ffs({}({input}))", Elem::<Self>::I32),
951        }?;
952        write!(f, ")")
953    }
954
955    fn compile_instruction_leading_zeros_scalar<T: Component<Self>>(
956        f: &mut std::fmt::Formatter<'_>,
957        input: T,
958        out_elem: Elem<Self>,
959    ) -> std::fmt::Result {
960        write!(f, "{out_elem}(clz({input}))")
961    }
962
963    fn compile_instruction_trailing_zeros_scalar<T: Component<Self>>(
964        f: &mut std::fmt::Formatter<'_>,
965        input: T,
966        out_elem: Elem<Self>,
967    ) -> std::fmt::Result {
968        write!(f, "{out_elem}(ctz({input}))")
969    }
970
971    fn compile_instruction_popcount_scalar<T: Component<Self>>(
972        f: &mut std::fmt::Formatter<'_>,
973        input: T,
974        out_elem: Elem<Self>,
975    ) -> std::fmt::Result {
976        write!(f, "{out_elem}(")?;
977        match input.elem() {
978            Elem::I32 | Elem::U32 | Elem::I64 | Elem::U64 => write!(f, "popcount({input})"),
979            _ => write!(f, "popcount({})", shared::unary::zero_extend(input)),
980        }?;
981        write!(f, ")")
982    }
983
984    fn compile_instruction_reverse_bits_scalar<T: Component<Self>>(
985        f: &mut std::fmt::Formatter<'_>,
986        input: T,
987        out_elem: Elem<Self>,
988    ) -> std::fmt::Result {
989        write!(f, "{out_elem}(")?;
990        match out_elem {
991            Elem::I32 | Elem::U32 | Elem::I64 | Elem::U64 => write!(f, "reverse_bits({input})"),
992            _ => write!(
993                f,
994                "reverse_bits({}) >> {}",
995                shared::unary::zero_extend(input),
996                (size_of::<u32>() - out_elem.size()) * 8
997            ),
998        }?;
999        write!(f, ")")
1000    }
1001
1002    // others
1003    fn compile_instruction_max_function_name(
1004        f: &mut std::fmt::Formatter<'_>,
1005        _item: Item<Self>,
1006    ) -> std::fmt::Result {
1007        write!(f, "max")
1008    }
1009
1010    fn compile_instruction_min_function_name(
1011        f: &mut std::fmt::Formatter<'_>,
1012        _item: Item<Self>,
1013    ) -> std::fmt::Result {
1014        write!(f, "min")
1015    }
1016
1017    fn compile_instruction_powf(
1018        f: &mut std::fmt::Formatter<'_>,
1019        lhs: &str,
1020        rhs: &str,
1021        elem: Elem<Self>,
1022    ) -> std::fmt::Result {
1023        write!(f, "pow({lhs}, {elem}({rhs}))")
1024    }
1025
1026    fn compile_instruction_hypot(
1027        f: &mut std::fmt::Formatter<'_>,
1028        lhs: &str,
1029        rhs: &str,
1030        _elem: Elem<Self>,
1031    ) -> std::fmt::Result {
1032        write!(f, "hypot({lhs}, {rhs})")
1033    }
1034
1035    fn compile_instruction_rhypot(
1036        f: &mut std::fmt::Formatter<'_>,
1037        lhs: &str,
1038        rhs: &str,
1039        _elem: Elem<Self>,
1040    ) -> std::fmt::Result {
1041        write!(f, "rhypot({lhs}, {rhs})")
1042    }
1043
1044    fn compile_instruction_half_function_name_prefix() -> &'static str {
1045        ""
1046    }
1047
1048    fn compile_instruction_half2_function_name_prefix() -> &'static str {
1049        ""
1050    }
1051
1052    fn compile_fast_math_function_name(name: &'static str) -> &'static str {
1053        // `__frcp_rn` has no native `fast::` form, so it uses the `fast_recip` helper.
1054        match name {
1055            "__expf" => "fast::exp",
1056            "__logf" => "fast::log",
1057            "__sinf" => "fast::sin",
1058            "__cosf" => "fast::cos",
1059            "__fsqrt_rn" => "fast::sqrt",
1060            "__frsqrt_rn" => "fast::rsqrt",
1061            "__tanhf" => "fast::tanh",
1062            "__fdividef" => "fast::divide",
1063            "__powf" => "fast::pow",
1064            "__frcp_rn" => "fast_recip",
1065            other => other,
1066        }
1067    }
1068
1069    // Warp
1070    fn compile_warp_shuffle(
1071        f: &mut std::fmt::Formatter<'_>,
1072        val: &str,
1073        elem: &Elem<Self>,
1074        source: &str,
1075    ) -> std::fmt::Result {
1076        Self::warp_shuffle(f, "simd_shuffle", val, elem, source)
1077    }
1078
1079    fn compile_warp_shuffle_xor(
1080        f: &mut std::fmt::Formatter<'_>,
1081        val: &str,
1082        elem: &Elem<Self>,
1083        offset: &str,
1084    ) -> std::fmt::Result {
1085        Self::warp_shuffle(f, "simd_shuffle_xor", val, elem, offset)
1086    }
1087
1088    fn compile_warp_shuffle_up(
1089        f: &mut std::fmt::Formatter<'_>,
1090        val: &str,
1091        elem: &Elem<Self>,
1092        offset: &str,
1093    ) -> std::fmt::Result {
1094        Self::warp_shuffle(f, "simd_shuffle_up", val, elem, offset)
1095    }
1096
1097    fn compile_warp_shuffle_down(
1098        f: &mut std::fmt::Formatter<'_>,
1099        val: &str,
1100        elem: &Elem<Self>,
1101        offset: &str,
1102    ) -> std::fmt::Result {
1103        Self::warp_shuffle(f, "simd_shuffle_down", val, elem, offset)
1104    }
1105
1106    fn compile_warp_all<T: Component<Self>>(
1107        f: &mut std::fmt::Formatter<'_>,
1108        input: &T,
1109    ) -> std::fmt::Result {
1110        write!(f, "simd_all({input})")
1111    }
1112
1113    fn compile_warp_any<T: Component<Self>>(
1114        f: &mut std::fmt::Formatter<'_>,
1115        input: &T,
1116    ) -> std::fmt::Result {
1117        write!(f, "simd_any({input})")
1118    }
1119
1120    fn compile_warp_ballot(
1121        f: &mut std::fmt::Formatter<'_>,
1122        input: &Value<Self>,
1123        out_elem: &Elem<Self>,
1124    ) -> std::fmt::Result {
1125        write!(f, "{out_elem}(uint64_t(simd_ballot({input})))")
1126    }
1127
1128    fn compile_unreachable(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1129        write!(f, "__builtin_unreachable();")
1130    }
1131}
1132
1133// Coop Matrices dialect
1134
1135impl DialectWmmaCompiler<Self> for MslDialect {
1136    fn compile_wmma_includes(
1137        f: &mut std::fmt::Formatter<'_>,
1138        _flags: &Flags<Self>,
1139    ) -> std::fmt::Result {
1140        writeln!(f, "#include <metal_simdgroup_matrix>")
1141    }
1142
1143    fn compile_wmma_local_variables(_f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1144        // not used
1145        Ok(())
1146    }
1147
1148    fn compile_wmma_fragment_declaration(
1149        f: &mut std::fmt::Formatter<'_>,
1150        val: &crate::shared::Value<MslDialect>,
1151        ty: &crate::shared::Item<MslDialect>,
1152    ) -> std::fmt::Result {
1153        wmma_api_base::compile_fragment_declaration(f, val, ty)
1154    }
1155
1156    fn compile_wwma_fragment_ident(
1157        _f: &mut std::fmt::Formatter<'_>,
1158        _ident: &FragmentIdent<Self>,
1159    ) -> std::fmt::Result {
1160        // not used
1161        Ok(())
1162    }
1163
1164    fn compile_wmma_fragment_layout(
1165        _f: &mut std::fmt::Formatter<'_>,
1166        _layout: &FragmentLayout<Self>,
1167    ) -> std::fmt::Result {
1168        // not used
1169        Ok(())
1170    }
1171
1172    fn compile_wmma_fragment(
1173        f: &mut std::fmt::Formatter<'_>,
1174        fragment: &FragmentType<Self>,
1175    ) -> std::fmt::Result {
1176        let ty = fragment.elem;
1177        // currently as of Metal 3.2 only fragments of 8x8x8 are supported
1178        let m = fragment.m;
1179        let n = fragment.n;
1180        let k = fragment.k;
1181        if m != 8 || n != 8 || k != 8 {
1182            panic!("{m}x{n}x{k} fragments not supported. Only 8x8x8 fragments are supported.");
1183        }
1184        write!(f, "simdgroup_{ty}8x8")
1185    }
1186
1187    fn compile_wmma_instruction(
1188        f: &mut std::fmt::Formatter<'_>,
1189        instruction: &WmmaInstruction<Self>,
1190    ) -> std::fmt::Result {
1191        match instruction {
1192            WmmaInstruction::Fill { frag, value } => {
1193                match *frag.item().value_ty() {
1194                    Item::Fragment { .. } => {
1195                        let ty = frag.elem();
1196                        // Only 8x8x8 fragments are supported. Check is done at fragment compilation time.
1197                        writeln!(
1198                            f,
1199                            "*{frag} = make_filled_simdgroup_matrix<{ty}, 8, 8>({value});"
1200                        )
1201                    }
1202                    _ => panic!("should be a fragment"),
1203                }
1204            }
1205            WmmaInstruction::Load {
1206                frag,
1207                ptr,
1208                stride,
1209                layout: _layout,
1210            } => {
1211                let transpose = match *frag.item().value_ty() {
1212                    Item::Fragment(inner) => match inner.layout {
1213                        Some(FragmentLayout::RowMajor) => false,
1214                        Some(FragmentLayout::ColMajor) => true,
1215                        _ => false,
1216                    },
1217                    _ => panic!("should be a fragment"),
1218                };
1219                if let Item::Vector(..) = *ptr.item().value_ty() {
1220                    let elem_ptr = ptr.item().as_scalar();
1221                    writeln!(
1222                        f,
1223                        "simdgroup_load(*{frag}, ({elem_ptr})({ptr}), {stride}, 0, {transpose});"
1224                    )
1225                } else {
1226                    writeln!(
1227                        f,
1228                        "simdgroup_load(*{frag}, {ptr}, {stride}, 0, {transpose});"
1229                    )
1230                }
1231            }
1232            WmmaInstruction::Execute {
1233                frag_a: a,
1234                frag_b: b,
1235                frag_c: c,
1236                frag_d: d,
1237                ..
1238            } => {
1239                writeln!(f, "simdgroup_multiply_accumulate(*{d}, {a}, {b}, {c});")
1240            }
1241            WmmaInstruction::Store {
1242                frag,
1243                stride,
1244                destination,
1245                layout: _layout,
1246            } => {
1247                let item = destination.item();
1248                let mut reinterpret_cast = item.vectorization() > 1;
1249                let elem = match item.value_ty().elem() {
1250                    Elem::BF16 => {
1251                        reinterpret_cast = true;
1252                        Elem::F16
1253                    }
1254                    _ => *item.elem(),
1255                };
1256                let scalar_ptr = item.as_scalar().with_elem(elem);
1257                if reinterpret_cast {
1258                    writeln!(
1259                        f,
1260                        "simdgroup_store({frag}, reinterpret_cast<{scalar_ptr}>({destination}), {stride});"
1261                    )
1262                } else {
1263                    writeln!(f, "simdgroup_store({frag}, {destination}, {stride});")
1264                }?;
1265                writeln!(f, "simdgroup_barrier(mem_flags::mem_none);")
1266            }
1267            WmmaInstruction::Cast { input, output } => {
1268                writeln!(f, "simdgroup_barrier(mem_flags::mem_none);")?;
1269                let ty = match *output.item().value_ty() {
1270                    Item::Fragment(frag) => frag.elem,
1271                    _ => panic!("should be a fragment"),
1272                };
1273                match ty {
1274                    Elem::BF16 => {
1275                        let addr_space = Self::address_space_for_value(output);
1276                        let elem = Elem::<Self>::F16;
1277                        // TODO: to test with benchmarks
1278
1279                        writeln!(
1280                            f,
1281                            "for(int e=0; e<8; e++) {{
1282    {ty} elem = {ty}({input}.thread_elements()[e]);
1283    {output}->thread_elements()[e] = *reinterpret_cast<{addr_space}{elem} *>(&elem);
1284}}"
1285                        )
1286                    }
1287                    _ => {
1288                        writeln!(
1289                            f,
1290                            "for(int e=0; e<8; e++) {{
1291    {output}->thread_elements()[e] = {ty}({input}.thread_elements()[e]);
1292}}"
1293                        )
1294                    }
1295                }
1296            }
1297            WmmaInstruction::ExecuteManual {
1298                shape,
1299                frag_a,
1300                frag_b,
1301                frag_c,
1302                frag_d,
1303            } => {
1304                Self::compile_manual_mma(f, ManualMma::new(*shape, frag_a, frag_b, frag_c, frag_d))
1305            }
1306            WmmaInstruction::ExecuteScaled {
1307                shape,
1308                frag_a,
1309                frag_b,
1310                frag_c,
1311                frag_d,
1312                scales_a,
1313                scales_b,
1314                scales_factor,
1315            } => Self::compile_scaled_mma(
1316                f,
1317                ManualMma::new(*shape, frag_a, frag_b, frag_c, frag_d),
1318                *scales_a,
1319                *scales_b,
1320                *scales_factor,
1321            ),
1322            WmmaInstruction::LdMatrix { .. } | WmmaInstruction::StMatrix { .. } => {
1323                f.write_str("#error WmmaInstruction Ld & St Matrix not supported on Metal\n")
1324            }
1325        }
1326    }
1327
1328    fn compile_manual_mma(
1329        f: &mut std::fmt::Formatter<'_>,
1330        _mma: shared::ManualMma<Self>,
1331    ) -> std::fmt::Result {
1332        f.write_str("#error manual mma not supported on Metal\n")
1333    }
1334
1335    fn compile_scaled_mma(
1336        f: &mut std::fmt::Formatter<'_>,
1337        _mma: shared::ManualMma<Self>,
1338        _scales_a: Value<Self>,
1339        _scales_b: Value<Self>,
1340        _scales_factor: u32,
1341    ) -> std::fmt::Result {
1342        f.write_str("#error scaled mma not supported on Metal\n")
1343    }
1344
1345    fn supported_wmma_combinations(_arch: &MetalArchitecture) -> SupportedMmaCombinations {
1346        let types = vec![
1347            (
1348                gpu::ElemType::Float(gpu::FloatKind::F16).into(),
1349                gpu::ElemType::Float(gpu::FloatKind::F16).into(),
1350                gpu::ElemType::Float(gpu::FloatKind::F16).into(),
1351            ),
1352            (
1353                gpu::ElemType::Float(gpu::FloatKind::F16).into(),
1354                gpu::ElemType::Float(gpu::FloatKind::F16).into(),
1355                gpu::ElemType::Float(gpu::FloatKind::F32).into(),
1356            ),
1357            (
1358                gpu::ElemType::Float(gpu::FloatKind::BF16).into(),
1359                gpu::ElemType::Float(gpu::FloatKind::BF16).into(),
1360                gpu::ElemType::Float(gpu::FloatKind::BF16).into(),
1361            ),
1362            (
1363                gpu::ElemType::Float(gpu::FloatKind::F32).into(),
1364                gpu::ElemType::Float(gpu::FloatKind::F32).into(),
1365                gpu::ElemType::Float(gpu::FloatKind::F32).into(),
1366            ),
1367        ];
1368        types
1369            .into_iter()
1370            .map(|(a_type, b_type, cd_type)| MmaConfig {
1371                a_type,
1372                b_type,
1373                cd_type,
1374                m: 8,
1375                n: 8,
1376                k: 8,
1377            })
1378            .collect()
1379    }
1380
1381    fn supported_mma_combinations(_arch: &MetalArchitecture) -> SupportedMmaCombinations {
1382        Vec::new()
1383    }
1384}
1385
1386// Coop Matrices dialect
1387
1388impl DialectProcessors<Self> for MslDialect {
1389    fn processors() -> Vec<Box<dyn gpu::Processor>> {
1390        Vec::new()
1391    }
1392}