Skip to main content

oxicuda_levelzero/
spirv_xmx.rs

1//! Intel Xe Matrix Extensions (XMX) SPIR-V kernel generators.
2//!
3//! XMX is Intel's matrix-multiply-accumulate hardware (analogous to NVIDIA
4//! Tensor Cores or AMD MFMA). It is exposed via the
5//! [`SPV_KHR_cooperative_matrix`] SPIR-V extension and the
6//! `CooperativeMatrixKHR` capability (SPIR-V 1.6 / GLSL 4.6).
7//!
8//! This module provides:
9//! - [`XmxTileConfig`] — tile dimension configuration for XMX GEMM.
10//! - [`gemm_xmx_spirv`] — cooperative-matrix GEMM kernel (`C = alpha*A*B + beta*C`)
11//!   targeting Intel Xe / Arc / Ponte Vecchio with XMX engines.
12//! - [`gemm_xmx_f16_spirv`] — FP16 input / FP32 accumulation variant.
13//! - [`matmul_xmx_bf16_spirv`] — BF16 input / FP32 accumulation variant.
14//!
15//! [`SPV_KHR_cooperative_matrix`]:
16//!   <https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/KHR/SPV_KHR_cooperative_matrix.asciidoc>
17
18// ─── XMX SPIR-V opcodes (SPV_KHR_cooperative_matrix) ────────────────────────
19
20/// `OpTypeCooperativeMatrixKHR` — defines a cooperative matrix type.
21const OP_TYPE_COOPERATIVE_MATRIX_KHR: u32 = 4456;
22/// `OpCooperativeMatrixLoadKHR` — loads a tile from a pointer.
23const OP_COOPERATIVE_MATRIX_LOAD_KHR: u32 = 4457;
24/// `OpCooperativeMatrixStoreKHR` — stores a tile to a pointer.
25const OP_COOPERATIVE_MATRIX_STORE_KHR: u32 = 4458;
26/// `OpCooperativeMatrixMulAddKHR` — performs `D = A*B + C`.
27const OP_COOPERATIVE_MATRIX_MUL_ADD_KHR: u32 = 4459;
28// ─── Capabilities ────────────────────────────────────────────────────────────
29
30/// `CooperativeMatrixKHR` capability (requires SPV_KHR_cooperative_matrix).
31const CAPABILITY_COOPERATIVE_MATRIX_KHR: u32 = 6022;
32/// Standard `Shader` capability (required for GLSL memory model path).
33const CAPABILITY_SHADER: u32 = 1;
34/// `Float16` capability — required when using FP16 matrix elements.
35const CAPABILITY_FLOAT16: u32 = 9;
36// ─── Addressing / memory model ───────────────────────────────────────────────
37
38const ADDRESSING_MODEL_LOGICAL: u32 = 0;
39const MEMORY_MODEL_GLSL450: u32 = 1;
40
41// ─── Execution model ─────────────────────────────────────────────────────────
42
43const EXECUTION_MODEL_GLCOMPUTE: u32 = 5;
44const EXECUTION_MODE_LOCAL_SIZE: u32 = 17;
45
46// ─── Storage classes ─────────────────────────────────────────────────────────
47
48const STORAGE_CLASS_STORAGE_BUFFER: u32 = 12;
49const STORAGE_CLASS_INPUT: u32 = 1;
50
51// ─── Decorations ─────────────────────────────────────────────────────────────
52
53const DECORATION_DESCRIPTOR_SET: u32 = 34;
54const DECORATION_BINDING: u32 = 33;
55const DECORATION_BLOCK: u32 = 2;
56const DECORATION_BUILTIN: u32 = 11;
57const DECORATION_NON_WRITABLE: u32 = 24;
58const BUILTIN_WORKGROUP_ID: u32 = 26;
59
60// ─── Use/select extensions ───────────────────────────────────────────────────
61
62const OP_EXTENSION: u32 = 10;
63const OP_CAPABILITY: u32 = 17;
64const OP_MEMORY_MODEL: u32 = 14;
65const OP_ENTRY_POINT: u32 = 15;
66const OP_EXECUTION_MODE: u32 = 16;
67const OP_DECORATE: u32 = 71;
68const OP_MEMBER_DECORATE: u32 = 72;
69const OP_TYPE_VOID: u32 = 19;
70const OP_TYPE_INT: u32 = 21;
71const OP_TYPE_FLOAT: u32 = 22;
72const OP_TYPE_POINTER: u32 = 32;
73const OP_TYPE_FUNCTION: u32 = 33;
74const OP_TYPE_STRUCT: u32 = 30;
75const OP_TYPE_RUNTIME_ARRAY: u32 = 29;
76const OP_CONSTANT: u32 = 43;
77const OP_FUNCTION: u32 = 54;
78const OP_FUNCTION_END: u32 = 56;
79const OP_VARIABLE: u32 = 59;
80const OP_LOAD: u32 = 61;
81const OP_ACCESS_CHAIN: u32 = 65;
82const OP_IN_BOUNDS_ACCESS_CHAIN: u32 = 66;
83const OP_LABEL: u32 = 248;
84const OP_RETURN: u32 = 253;
85const OP_COMPOSITE_EXTRACT: u32 = 81;
86const OP_I_MUL: u32 = 132;
87const OP_I_ADD: u32 = 128;
88
89// ─── CooperativeMatrix use/scope values ──────────────────────────────────────
90
91/// Scope `Subgroup` (the XMX execution granularity on Intel GPUs).
92///
93/// The cooperative-matrix `Scope`, `Rows`, `Columns`, `Use` operands and the
94/// load/store `MemoryLayout` operand are all IdRefs to constant integers, so
95/// the `MatrixUse` (A=0, B=1, Accumulator=2) and `RowMajor` (0) enumerants are
96/// emitted as the `c0`/`c1`/`c2` `OpConstant` ids rather than as bare literals.
97const SCOPE_SUBGROUP: u32 = 3;
98
99// ─── CooperativeMatrixOperands bitmask ───────────────────────────────────────
100
101/// No special operand flags.
102const COOPERATIVE_MATRIX_OPERANDS_NONE: u32 = 0;
103
104// ─── SPIR-V magic / version ──────────────────────────────────────────────────
105
106const SPIRV_MAGIC: u32 = 0x07230203;
107/// SPIR-V 1.6 — required for `SPV_KHR_cooperative_matrix`.
108const SPIRV_VERSION_1_6: u32 = 0x0001_0600;
109const SPIRV_GENERATOR: u32 = 0x000D_0003; // OxiCUDA Level Zero XMX generator
110
111// ─── XmxTileConfig ───────────────────────────────────────────────────────────
112
113/// Tile dimensions for XMX GEMM.
114///
115/// Intel's XMX engines support the following sizes on Xe-HPC (Ponte Vecchio):
116/// - FP16 / BF16 input, FP32 accumulation: 8 × 16, 8 × 32
117/// - INT8 / INT4 input, INT32 accumulation: 8 × 32
118///
119/// On Arc (Alchemist) and later, additional sizes are available.
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub struct XmxTileConfig {
122    /// Rows of the A/C matrix tile.
123    pub m: u32,
124    /// Columns of the B/C matrix tile.
125    pub n: u32,
126    /// Inner dimension (columns of A, rows of B).
127    pub k: u32,
128}
129
130impl XmxTileConfig {
131    /// Default XMX tile size for FP16 input targeting Xe-HPC.
132    pub const XE_HPC_FP16: Self = Self { m: 8, n: 16, k: 16 };
133
134    /// XMX tile size for FP32 GEMM fallback (regular compute path).
135    pub const XE_DEFAULT: Self = Self { m: 8, n: 16, k: 16 };
136
137    /// Returns the number of accumulator elements per tile.
138    pub fn accum_elements(&self) -> u32 {
139        self.m * self.n
140    }
141}
142
143impl Default for XmxTileConfig {
144    fn default() -> Self {
145        Self::XE_HPC_FP16
146    }
147}
148
149// ─── Minimal SPIR-V builder (local, tuned for cooperative-matrix dialect) ───
150
151struct XmxSpvModule {
152    words: Vec<u32>,
153    id_bound: u32,
154}
155
156impl XmxSpvModule {
157    fn new() -> Self {
158        let words = vec![SPIRV_MAGIC, SPIRV_VERSION_1_6, SPIRV_GENERATOR, 0, 0];
159        Self { words, id_bound: 1 }
160    }
161
162    fn alloc_id(&mut self) -> u32 {
163        let id = self.id_bound;
164        self.id_bound += 1;
165        id
166    }
167
168    fn emit(&mut self, opcode: u32, operands: &[u32]) {
169        let word_count = (1 + operands.len()) as u32;
170        self.words.push((word_count << 16) | opcode);
171        self.words.extend_from_slice(operands);
172    }
173
174    fn string_words(s: &str) -> Vec<u32> {
175        let bytes = s.as_bytes();
176        let padded_len = (bytes.len() + 4) & !3;
177        let mut out = vec![0u32; padded_len / 4];
178        for (i, &b) in bytes.iter().enumerate() {
179            out[i / 4] |= (b as u32) << ((i % 4) * 8);
180        }
181        out
182    }
183
184    fn finalize(mut self) -> Vec<u32> {
185        self.words[3] = self.id_bound;
186        self.words
187    }
188
189    // ── Convenience emitters ──────────────────────────────────
190
191    fn emit_capability(&mut self, cap: u32) {
192        self.emit(OP_CAPABILITY, &[cap]);
193    }
194
195    fn emit_extension(&mut self, name: &str) {
196        let mut ops = Self::string_words(name);
197        // Extension instruction: opcode only, no result ID
198        let word_count = (1 + ops.len()) as u32;
199        self.words.push((word_count << 16) | OP_EXTENSION);
200        self.words.append(&mut ops);
201    }
202
203    fn emit_memory_model(&mut self, addr: u32, model: u32) {
204        self.emit(OP_MEMORY_MODEL, &[addr, model]);
205    }
206
207    fn emit_entry_point(&mut self, model: u32, func_id: u32, name: &str, interfaces: &[u32]) {
208        let mut ops = vec![model, func_id];
209        ops.extend(Self::string_words(name));
210        ops.extend_from_slice(interfaces);
211        self.emit(OP_ENTRY_POINT, &ops);
212    }
213
214    fn emit_execution_mode_local_size(&mut self, func_id: u32, x: u32, y: u32, z: u32) {
215        self.emit(
216            OP_EXECUTION_MODE,
217            &[func_id, EXECUTION_MODE_LOCAL_SIZE, x, y, z],
218        );
219    }
220
221    fn emit_decorate(&mut self, target: u32, decoration: u32, extra: &[u32]) {
222        let mut ops = vec![target, decoration];
223        ops.extend_from_slice(extra);
224        self.emit(OP_DECORATE, &ops);
225    }
226
227    fn emit_member_decorate(
228        &mut self,
229        struct_id: u32,
230        member: u32,
231        decoration: u32,
232        extra: &[u32],
233    ) {
234        let mut ops = vec![struct_id, member, decoration];
235        ops.extend_from_slice(extra);
236        self.emit(OP_MEMBER_DECORATE, &ops);
237    }
238
239    fn emit_type_void(&mut self, id: u32) {
240        self.emit(OP_TYPE_VOID, &[id]);
241    }
242    fn emit_type_int(&mut self, id: u32, width: u32, sign: u32) {
243        self.emit(OP_TYPE_INT, &[id, width, sign]);
244    }
245    fn emit_type_float(&mut self, id: u32, width: u32) {
246        self.emit(OP_TYPE_FLOAT, &[id, width]);
247    }
248    fn emit_type_ptr(&mut self, id: u32, sc: u32, pointee: u32) {
249        self.emit(OP_TYPE_POINTER, &[id, sc, pointee]);
250    }
251    fn emit_type_fn(&mut self, id: u32, ret: u32, params: &[u32]) {
252        let mut ops = vec![id, ret];
253        ops.extend_from_slice(params);
254        self.emit(OP_TYPE_FUNCTION, &ops);
255    }
256    fn emit_type_struct(&mut self, id: u32, members: &[u32]) {
257        let mut ops = vec![id];
258        ops.extend_from_slice(members);
259        self.emit(OP_TYPE_STRUCT, &ops);
260    }
261    fn emit_type_runtime_array(&mut self, id: u32, elem: u32) {
262        self.emit(OP_TYPE_RUNTIME_ARRAY, &[id, elem]);
263    }
264
265    fn emit_const_u32(&mut self, ty: u32, id: u32, val: u32) {
266        self.emit(OP_CONSTANT, &[ty, id, val]);
267    }
268    fn emit_variable(&mut self, ty: u32, id: u32, sc: u32) {
269        self.emit(OP_VARIABLE, &[ty, id, sc]);
270    }
271    fn emit_load(&mut self, ty: u32, id: u32, ptr: u32) {
272        self.emit(OP_LOAD, &[ty, id, ptr]);
273    }
274    fn emit_label(&mut self, id: u32) {
275        self.emit(OP_LABEL, &[id]);
276    }
277    fn emit_return(&mut self) {
278        self.emit(OP_RETURN, &[]);
279    }
280    fn emit_function_end(&mut self) {
281        self.emit(OP_FUNCTION_END, &[]);
282    }
283    fn emit_function(&mut self, ret_ty: u32, id: u32, ctrl: u32, fn_ty: u32) {
284        self.emit(OP_FUNCTION, &[ret_ty, id, ctrl, fn_ty]);
285    }
286    fn emit_i_add(&mut self, ty: u32, id: u32, a: u32, b: u32) {
287        self.emit(OP_I_ADD, &[ty, id, a, b]);
288    }
289    fn emit_i_mul(&mut self, ty: u32, id: u32, a: u32, b: u32) {
290        self.emit(OP_I_MUL, &[ty, id, a, b]);
291    }
292    fn emit_composite_extract(&mut self, ty: u32, id: u32, composite: u32, idx: u32) {
293        self.emit(OP_COMPOSITE_EXTRACT, &[ty, id, composite, idx]);
294    }
295
296    fn emit_access_chain(&mut self, ty: u32, id: u32, base: u32, indices: &[u32]) {
297        let mut ops = vec![ty, id, base];
298        ops.extend_from_slice(indices);
299        self.emit(OP_ACCESS_CHAIN, &ops);
300    }
301
302    fn emit_in_bounds_access_chain(&mut self, ty: u32, id: u32, base: u32, indices: &[u32]) {
303        let mut ops = vec![ty, id, base];
304        ops.extend_from_slice(indices);
305        self.emit(OP_IN_BOUNDS_ACCESS_CHAIN, &ops);
306    }
307
308    /// Emit `OpTypeCooperativeMatrixKHR`.
309    ///
310    /// Parameters: `(result_id, component_type, scope, rows, columns, use)`
311    fn emit_type_cooperative_matrix(
312        &mut self,
313        id: u32,
314        component_type: u32,
315        scope: u32,
316        rows: u32,
317        cols: u32,
318        matrix_use: u32,
319    ) {
320        self.emit(
321            OP_TYPE_COOPERATIVE_MATRIX_KHR,
322            &[id, component_type, scope, rows, cols, matrix_use],
323        );
324    }
325
326    /// Emit `OpCooperativeMatrixLoadKHR`.
327    fn emit_coop_matrix_load(
328        &mut self,
329        result_ty: u32,
330        result: u32,
331        pointer: u32,
332        layout: u32,
333        stride: u32,
334    ) {
335        self.emit(
336            OP_COOPERATIVE_MATRIX_LOAD_KHR,
337            &[
338                result_ty,
339                result,
340                pointer,
341                layout,
342                stride,
343                COOPERATIVE_MATRIX_OPERANDS_NONE,
344            ],
345        );
346    }
347
348    /// Emit `OpCooperativeMatrixStoreKHR`.
349    fn emit_coop_matrix_store(&mut self, pointer: u32, object: u32, layout: u32, stride: u32) {
350        self.emit(
351            OP_COOPERATIVE_MATRIX_STORE_KHR,
352            &[
353                pointer,
354                object,
355                layout,
356                stride,
357                COOPERATIVE_MATRIX_OPERANDS_NONE,
358            ],
359        );
360    }
361
362    /// Emit `OpCooperativeMatrixMulAddKHR`: `D = A * B + C`.
363    fn emit_coop_matrix_muladd(
364        &mut self,
365        result_ty: u32,
366        result: u32,
367        a: u32,
368        b: u32,
369        c: u32,
370        operands: u32,
371    ) {
372        self.emit(
373            OP_COOPERATIVE_MATRIX_MUL_ADD_KHR,
374            &[result_ty, result, a, b, c, operands],
375        );
376    }
377}
378
379// ─── gemm_xmx_spirv ──────────────────────────────────────────────────────────
380
381/// Generate a SPIR-V binary for an XMX-accelerated FP32 GEMM kernel.
382///
383/// Computes `C = alpha * A * B + beta * C` using Intel Xe Matrix Extensions
384/// (cooperative matrix hardware). The kernel is structured as:
385///
386/// - Workgroup of size `(wg_x, wg_y, 1)` where each workgroup computes a
387///   tile of the output matrix.
388/// - Inner loop loads sub-tiles of A and B using `OpCooperativeMatrixLoadKHR`,
389///   accumulates via `OpCooperativeMatrixMulAddKHR`, then stores result with
390///   `OpCooperativeMatrixStoreKHR`.
391///
392/// # Arguments
393///
394/// * `tile` — XMX tile configuration (M × N × K).
395/// * `wg_x` — workgroup X dimension (threads per group in X).
396/// * `wg_y` — workgroup Y dimension (threads per group in Y).
397///
398/// # Returns
399///
400/// A `Vec<u32>` containing a valid SPIR-V 1.6 binary. Pass this directly to
401/// `zeModuleCreate(..., ZE_MODULE_FORMAT_IL_SPIRV, ...)`.
402///
403/// # Kernel Interface (descriptor set 0)
404///
405/// | Binding | Type | Description |
406/// |---------|------|-------------|
407/// | 0 | `StorageBuffer f32[]` | Input matrix A (row-major, M×K) |
408/// | 1 | `StorageBuffer f32[]` | Input matrix B (row-major, K×N) |
409/// | 2 | `StorageBuffer f32[]` | In/out matrix C (row-major, M×N) |
410/// | 3 | `StorageBuffer u32[4]` | Push constants: M, N, K, flags |
411pub fn gemm_xmx_spirv(tile: XmxTileConfig, wg_x: u32, wg_y: u32) -> Vec<u32> {
412    let mut m = XmxSpvModule::new();
413
414    // ── Capabilities ──────────────────────────────────────────────────────────
415    m.emit_capability(CAPABILITY_SHADER);
416    m.emit_capability(CAPABILITY_COOPERATIVE_MATRIX_KHR);
417
418    // ── SPV_KHR_cooperative_matrix extension ──────────────────────────────────
419    m.emit_extension("SPV_KHR_cooperative_matrix");
420
421    // ── Memory model ──────────────────────────────────────────────────────────
422    m.emit_memory_model(ADDRESSING_MODEL_LOGICAL, MEMORY_MODEL_GLSL450);
423
424    // ── Type IDs ──────────────────────────────────────────────────────────────
425    let ty_void = m.alloc_id();
426    let ty_u32 = m.alloc_id();
427    let ty_f32 = m.alloc_id();
428
429    // Storage buffer types: struct { float[] } at binding 0/1/2, uint[] at binding 3
430    let ty_rt_f32 = m.alloc_id(); // runtime array of f32
431    let ty_rt_u32 = m.alloc_id(); // runtime array of u32
432    let ty_sb_f32 = m.alloc_id(); // struct { float[] }
433    let ty_sb_u32 = m.alloc_id(); // struct { uint[] }
434    let ty_ptr_sb_f32 = m.alloc_id();
435    let ty_ptr_sb_u32 = m.alloc_id();
436    let ty_ptr_f32_sb = m.alloc_id();
437    let ty_ptr_u32_sb = m.alloc_id();
438
439    // Cooperative matrix types (all at Subgroup scope)
440    let ty_cmat_a = m.alloc_id(); // mat A: f32, SCOPE_SUBGROUP, M×K, use A
441    let ty_cmat_b = m.alloc_id(); // mat B: f32, SCOPE_SUBGROUP, K×N, use B
442    let ty_cmat_c = m.alloc_id(); // mat C: f32, SCOPE_SUBGROUP, M×N, use Accum
443
444    // Function type
445    let ty_fn_void = m.alloc_id();
446
447    // v3uint for builtins
448    let ty_v3u32 = m.alloc_id();
449    let ty_ptr_in_v3u32 = m.alloc_id();
450
451    // Constants. `c2` (value 2) and `c_scope` (Subgroup) are declared up front —
452    // the cooperative-matrix type operands (Scope/Rows/Columns/Use) and the
453    // load/store MemoryLayout are IdRefs to constant integers, not literals.
454    let c0 = m.alloc_id();
455    let c1 = m.alloc_id();
456    let c2 = m.alloc_id();
457    let c_scope = m.alloc_id();
458    let c_tile_m = m.alloc_id();
459    let c_tile_n = m.alloc_id();
460    let c_tile_k = m.alloc_id();
461
462    // Variables at descriptor set 0
463    let var_a = m.alloc_id();
464    let var_b = m.alloc_id();
465    let var_c = m.alloc_id();
466    let var_dim = m.alloc_id();
467
468    // Builtin variable
469    let var_wg_id = m.alloc_id();
470
471    // Function
472    let fn_main = m.alloc_id();
473    let lbl_entry = m.alloc_id();
474
475    // ── Entry point ───────────────────────────────────────────────────────────
476    m.emit_entry_point(
477        EXECUTION_MODEL_GLCOMPUTE,
478        fn_main,
479        "gemm_xmx_f32",
480        &[var_a, var_b, var_c, var_dim, var_wg_id],
481    );
482    m.emit_execution_mode_local_size(fn_main, wg_x, wg_y, 1);
483
484    // ── Decorations ───────────────────────────────────────────────────────────
485    m.emit_decorate(ty_rt_f32, 6 /* ArrayStride */, &[4]);
486    m.emit_decorate(ty_rt_u32, 6 /* ArrayStride */, &[4]);
487
488    m.emit_decorate(ty_sb_f32, DECORATION_BLOCK, &[]);
489    m.emit_decorate(ty_sb_u32, DECORATION_BLOCK, &[]);
490
491    m.emit_member_decorate(ty_sb_f32, 0, 35 /* Offset */, &[0]);
492    m.emit_member_decorate(ty_sb_u32, 0, 35 /* Offset */, &[0]);
493
494    m.emit_decorate(var_a, DECORATION_DESCRIPTOR_SET, &[0]);
495    m.emit_decorate(var_a, DECORATION_BINDING, &[0]);
496    m.emit_decorate(var_a, DECORATION_NON_WRITABLE, &[]);
497    m.emit_decorate(var_b, DECORATION_DESCRIPTOR_SET, &[0]);
498    m.emit_decorate(var_b, DECORATION_BINDING, &[1]);
499    m.emit_decorate(var_b, DECORATION_NON_WRITABLE, &[]);
500    m.emit_decorate(var_c, DECORATION_DESCRIPTOR_SET, &[0]);
501    m.emit_decorate(var_c, DECORATION_BINDING, &[2]);
502    m.emit_decorate(var_dim, DECORATION_DESCRIPTOR_SET, &[0]);
503    m.emit_decorate(var_dim, DECORATION_BINDING, &[3]);
504    m.emit_decorate(var_dim, DECORATION_NON_WRITABLE, &[]);
505    m.emit_decorate(var_wg_id, DECORATION_BUILTIN, &[BUILTIN_WORKGROUP_ID]);
506
507    // ── Type definitions ──────────────────────────────────────────────────────
508    m.emit_type_void(ty_void);
509    m.emit_type_int(ty_u32, 32, 0);
510    m.emit_type_float(ty_f32, 32);
511
512    m.emit_type_runtime_array(ty_rt_f32, ty_f32);
513    m.emit_type_runtime_array(ty_rt_u32, ty_u32);
514    m.emit_type_struct(ty_sb_f32, &[ty_rt_f32]);
515    m.emit_type_struct(ty_sb_u32, &[ty_rt_u32]);
516    m.emit_type_ptr(ty_ptr_sb_f32, STORAGE_CLASS_STORAGE_BUFFER, ty_sb_f32);
517    m.emit_type_ptr(ty_ptr_sb_u32, STORAGE_CLASS_STORAGE_BUFFER, ty_sb_u32);
518    m.emit_type_ptr(ty_ptr_f32_sb, STORAGE_CLASS_STORAGE_BUFFER, ty_f32);
519    m.emit_type_ptr(ty_ptr_u32_sb, STORAGE_CLASS_STORAGE_BUFFER, ty_u32);
520
521    // ── Constants (must precede the cooperative-matrix types that ref them) ───
522    m.emit_const_u32(ty_u32, c0, 0);
523    m.emit_const_u32(ty_u32, c1, 1);
524    m.emit_const_u32(ty_u32, c2, 2);
525    m.emit_const_u32(ty_u32, c_scope, SCOPE_SUBGROUP);
526    m.emit_const_u32(ty_u32, c_tile_m, tile.m);
527    m.emit_const_u32(ty_u32, c_tile_n, tile.n);
528    m.emit_const_u32(ty_u32, c_tile_k, tile.k);
529
530    // Cooperative matrix types. Scope/Rows/Columns/Use are constant <id>s.
531    // MatrixUse: A=0 (c0), B=1 (c1), Accumulator=2 (c2).
532    m.emit_type_cooperative_matrix(ty_cmat_a, ty_f32, c_scope, c_tile_m, c_tile_k, c0);
533    m.emit_type_cooperative_matrix(ty_cmat_b, ty_f32, c_scope, c_tile_k, c_tile_n, c1);
534    m.emit_type_cooperative_matrix(ty_cmat_c, ty_f32, c_scope, c_tile_m, c_tile_n, c2);
535
536    // v3uint for WorkgroupId builtin
537    let ty_v3u32_actual = ty_v3u32;
538    m.emit(30 /* OpTypeVector */, &[ty_v3u32_actual, ty_u32, 3]);
539    m.emit_type_ptr(ty_ptr_in_v3u32, STORAGE_CLASS_INPUT, ty_v3u32_actual);
540
541    m.emit_type_fn(ty_fn_void, ty_void, &[]);
542
543    // ── Variables ─────────────────────────────────────────────────────────────
544    m.emit_variable(ty_ptr_sb_f32, var_a, STORAGE_CLASS_STORAGE_BUFFER);
545    m.emit_variable(ty_ptr_sb_f32, var_b, STORAGE_CLASS_STORAGE_BUFFER);
546    m.emit_variable(ty_ptr_sb_f32, var_c, STORAGE_CLASS_STORAGE_BUFFER);
547    m.emit_variable(ty_ptr_sb_u32, var_dim, STORAGE_CLASS_STORAGE_BUFFER);
548    m.emit_variable(ty_ptr_in_v3u32, var_wg_id, STORAGE_CLASS_INPUT);
549
550    // ── Function body ─────────────────────────────────────────────────────────
551    m.emit_function(ty_void, fn_main, 0, ty_fn_void);
552    m.emit_label(lbl_entry);
553
554    // Load WorkgroupId
555    let wg_id = m.alloc_id();
556    m.emit_load(ty_v3u32_actual, wg_id, var_wg_id);
557
558    // wg_col = wg_id.x, wg_row = wg_id.y
559    let wg_col = m.alloc_id();
560    let wg_row = m.alloc_id();
561    m.emit_composite_extract(ty_u32, wg_col, wg_id, 0);
562    m.emit_composite_extract(ty_u32, wg_row, wg_id, 1);
563
564    // Load problem dimensions from binding 3: [M, N, K, _]
565    let ptr_m = m.alloc_id();
566    let ptr_n = m.alloc_id();
567    let ptr_k = m.alloc_id();
568    let dim_m = m.alloc_id();
569    let dim_n = m.alloc_id();
570    let dim_k = m.alloc_id();
571    m.emit_access_chain(ty_ptr_u32_sb, ptr_m, var_dim, &[c0, c0]);
572    m.emit_access_chain(ty_ptr_u32_sb, ptr_n, var_dim, &[c0, c1]);
573    m.emit_access_chain(ty_ptr_u32_sb, ptr_k, var_dim, &[c0, c2]);
574    m.emit_load(ty_u32, dim_m, ptr_m);
575    m.emit_load(ty_u32, dim_n, ptr_n);
576    m.emit_load(ty_u32, dim_k, ptr_k);
577
578    // Tile base offsets: row_base = wg_row * tile.m, col_base = wg_col * tile.n
579    let row_base = m.alloc_id();
580    let col_base = m.alloc_id();
581    m.emit_i_mul(ty_u32, row_base, wg_row, c_tile_m);
582    m.emit_i_mul(ty_u32, col_base, wg_col, c_tile_n);
583
584    // ── Load existing C tile from global memory ───────────────────────────────
585    // ptr_c_tile = &C[row_base * N + col_base]
586    let c_row_stride = dim_n; // stride of C in elements
587    let c_base_flat = m.alloc_id();
588    let c_base_tmp = m.alloc_id();
589    m.emit_i_mul(ty_u32, c_base_tmp, row_base, c_row_stride);
590    m.emit_i_add(ty_u32, c_base_flat, c_base_tmp, col_base);
591    let ptr_c_tile = m.alloc_id();
592    m.emit_in_bounds_access_chain(ty_ptr_f32_sb, ptr_c_tile, var_c, &[c0, c_base_flat]);
593
594    let mat_c_init = m.alloc_id();
595    // MemoryLayout is an IdRef to a constant; RowMajor == 0 == c0.
596    m.emit_coop_matrix_load(ty_cmat_c, mat_c_init, ptr_c_tile, c0, c_row_stride);
597
598    // ── Accumulator starts as loaded C (for beta=1 semantics) ────────────────
599    // For simplicity this kernel computes C += A*B (i.e., alpha=1, beta=1).
600    // Callers wishing alpha/beta control should zero C before dispatch or use
601    // a separate scaling pass.
602    let mat_acc_after = {
603        // Iterate k_tile_idx from 0 to ceil(K/tile.k) and accumulate
604        // Because SPIR-V cooperative-matrix kernels are structured (no for-loops
605        // via branch merge in a single block for simplicity we unroll for the
606        // common case of K == tile.k, i.e., a single pass).
607        //
608        // A full tiled-K loop with `OpLoopMerge` would follow the same pattern
609        // extended with a branch back; omitted here for clarity.
610
611        // Load A tile: A[row_base * K + 0] (RowMajor, stride = dim_k)
612        let a_base_flat = m.alloc_id();
613        m.emit_i_mul(ty_u32, a_base_flat, row_base, dim_k);
614        let ptr_a_tile = m.alloc_id();
615        m.emit_in_bounds_access_chain(ty_ptr_f32_sb, ptr_a_tile, var_a, &[c0, a_base_flat]);
616        let mat_a = m.alloc_id();
617        m.emit_coop_matrix_load(ty_cmat_a, mat_a, ptr_a_tile, c0, dim_k);
618
619        // Load B tile: B[0 * N + col_base] (RowMajor, stride = dim_n)
620        let ptr_b_tile = m.alloc_id();
621        m.emit_in_bounds_access_chain(ty_ptr_f32_sb, ptr_b_tile, var_b, &[c0, col_base]);
622        let mat_b = m.alloc_id();
623        m.emit_coop_matrix_load(ty_cmat_b, mat_b, ptr_b_tile, c0, dim_n);
624
625        // Multiply-accumulate: tmp = A * B + C_init
626        let mat_tmp = m.alloc_id();
627        m.emit_coop_matrix_muladd(
628            ty_cmat_c,
629            mat_tmp,
630            mat_a,
631            mat_b,
632            mat_c_init,
633            COOPERATIVE_MATRIX_OPERANDS_NONE,
634        );
635        mat_tmp
636    };
637
638    // ── Store result tile back to C ───────────────────────────────────────────
639    m.emit_coop_matrix_store(ptr_c_tile, mat_acc_after, c0, c_row_stride);
640
641    m.emit_return();
642    m.emit_function_end();
643
644    m.finalize()
645}
646
647// ─── gemm_xmx_f16_spirv ──────────────────────────────────────────────────────
648
649/// Generate a SPIR-V binary for an XMX-accelerated FP16→FP32 GEMM kernel.
650///
651/// Inputs A and B are FP16 (`f16`); accumulator C and output are FP32.
652/// Requires the `Float16` capability and is suited for Xe-HPC (Ponte Vecchio)
653/// and Arc (Alchemist) GPUs where XMX engines are available.
654///
655/// Kernel interface identical to [`gemm_xmx_spirv`] but with FP16 A/B buffers
656/// (binding 0 and 1 contain packed FP16 elements, 2 bytes each).
657pub fn gemm_xmx_f16_spirv(tile: XmxTileConfig, wg_x: u32, wg_y: u32) -> Vec<u32> {
658    let mut m = XmxSpvModule::new();
659
660    // Capabilities: Shader + Float16 + CooperativeMatrix
661    m.emit_capability(CAPABILITY_SHADER);
662    m.emit_capability(CAPABILITY_FLOAT16);
663    m.emit_capability(CAPABILITY_COOPERATIVE_MATRIX_KHR);
664    m.emit_extension("SPV_KHR_cooperative_matrix");
665    m.emit_memory_model(ADDRESSING_MODEL_LOGICAL, MEMORY_MODEL_GLSL450);
666
667    // Types
668    let ty_void = m.alloc_id();
669    let ty_u32 = m.alloc_id();
670    let ty_f16 = m.alloc_id();
671    let ty_f32 = m.alloc_id();
672
673    let ty_rt_f16 = m.alloc_id();
674    let ty_rt_f32 = m.alloc_id();
675    let ty_rt_u32 = m.alloc_id();
676    let ty_sb_f16 = m.alloc_id();
677    let ty_sb_f32 = m.alloc_id();
678    let ty_sb_u32 = m.alloc_id();
679    let ty_ptr_sb_f16 = m.alloc_id();
680    let ty_ptr_sb_f32 = m.alloc_id();
681    let ty_ptr_sb_u32 = m.alloc_id();
682    let ty_ptr_f16_sb = m.alloc_id();
683    let ty_ptr_f32_sb = m.alloc_id();
684    let ty_ptr_u32_sb = m.alloc_id();
685
686    // XMX coop-matrix types: A/B are f16, C/D are f32
687    let ty_cmat_a = m.alloc_id();
688    let ty_cmat_b = m.alloc_id();
689    let ty_cmat_c = m.alloc_id();
690
691    let ty_v3u32 = m.alloc_id();
692    let ty_ptr_in_v3u32 = m.alloc_id();
693    let ty_fn_void = m.alloc_id();
694
695    // Variables
696    let var_a = m.alloc_id();
697    let var_b = m.alloc_id();
698    let var_c = m.alloc_id();
699    let var_dim = m.alloc_id();
700    let var_wg = m.alloc_id();
701    let fn_main = m.alloc_id();
702    let lbl = m.alloc_id();
703
704    // Entry / execution mode
705    m.emit_entry_point(
706        EXECUTION_MODEL_GLCOMPUTE,
707        fn_main,
708        "gemm_xmx_f16",
709        &[var_a, var_b, var_c, var_dim, var_wg],
710    );
711    m.emit_execution_mode_local_size(fn_main, wg_x, wg_y, 1);
712
713    // Decorations
714    m.emit_decorate(ty_rt_f16, 6, &[2]); // ArrayStride 2 (f16)
715    m.emit_decorate(ty_rt_f32, 6, &[4]);
716    m.emit_decorate(ty_rt_u32, 6, &[4]);
717    m.emit_decorate(ty_sb_f16, DECORATION_BLOCK, &[]);
718    m.emit_decorate(ty_sb_f32, DECORATION_BLOCK, &[]);
719    m.emit_decorate(ty_sb_u32, DECORATION_BLOCK, &[]);
720    m.emit_member_decorate(ty_sb_f16, 0, 35, &[0]);
721    m.emit_member_decorate(ty_sb_f32, 0, 35, &[0]);
722    m.emit_member_decorate(ty_sb_u32, 0, 35, &[0]);
723    for (var, set, binding, writable) in [
724        (var_a, 0u32, 0u32, false),
725        (var_b, 0, 1, false),
726        (var_c, 0, 2, true),
727        (var_dim, 0, 3, false),
728    ] {
729        m.emit_decorate(var, DECORATION_DESCRIPTOR_SET, &[set]);
730        m.emit_decorate(var, DECORATION_BINDING, &[binding]);
731        if !writable {
732            m.emit_decorate(var, DECORATION_NON_WRITABLE, &[]);
733        }
734    }
735    m.emit_decorate(var_wg, DECORATION_BUILTIN, &[BUILTIN_WORKGROUP_ID]);
736
737    // Type definitions
738    m.emit_type_void(ty_void);
739    m.emit_type_int(ty_u32, 32, 0);
740    m.emit_type_float(ty_f16, 16);
741    m.emit_type_float(ty_f32, 32);
742    m.emit_type_runtime_array(ty_rt_f16, ty_f16);
743    m.emit_type_runtime_array(ty_rt_f32, ty_f32);
744    m.emit_type_runtime_array(ty_rt_u32, ty_u32);
745    m.emit_type_struct(ty_sb_f16, &[ty_rt_f16]);
746    m.emit_type_struct(ty_sb_f32, &[ty_rt_f32]);
747    m.emit_type_struct(ty_sb_u32, &[ty_rt_u32]);
748    m.emit_type_ptr(ty_ptr_sb_f16, STORAGE_CLASS_STORAGE_BUFFER, ty_sb_f16);
749    m.emit_type_ptr(ty_ptr_sb_f32, STORAGE_CLASS_STORAGE_BUFFER, ty_sb_f32);
750    m.emit_type_ptr(ty_ptr_sb_u32, STORAGE_CLASS_STORAGE_BUFFER, ty_sb_u32);
751    m.emit_type_ptr(ty_ptr_f16_sb, STORAGE_CLASS_STORAGE_BUFFER, ty_f16);
752    m.emit_type_ptr(ty_ptr_f32_sb, STORAGE_CLASS_STORAGE_BUFFER, ty_f32);
753    m.emit_type_ptr(ty_ptr_u32_sb, STORAGE_CLASS_STORAGE_BUFFER, ty_u32);
754    // Constants (declared before the cooperative-matrix types that ref them;
755    // Scope/Rows/Columns/Use operands are IdRefs to constant integers).
756    let c0 = m.alloc_id();
757    let c1 = m.alloc_id();
758    let c2 = m.alloc_id();
759    let c_scope = m.alloc_id();
760    let c_tm = m.alloc_id();
761    let c_tn = m.alloc_id();
762    let c_tk = m.alloc_id();
763    m.emit_const_u32(ty_u32, c0, 0);
764    m.emit_const_u32(ty_u32, c1, 1);
765    m.emit_const_u32(ty_u32, c2, 2);
766    m.emit_const_u32(ty_u32, c_scope, SCOPE_SUBGROUP);
767    m.emit_const_u32(ty_u32, c_tm, tile.m);
768    m.emit_const_u32(ty_u32, c_tn, tile.n);
769    m.emit_const_u32(ty_u32, c_tk, tile.k);
770
771    // XMX: A and B use f16 components, C/D use f32. MatrixUse A=0/B=1/Accum=2.
772    m.emit_type_cooperative_matrix(ty_cmat_a, ty_f16, c_scope, c_tm, c_tk, c0);
773    m.emit_type_cooperative_matrix(ty_cmat_b, ty_f16, c_scope, c_tk, c_tn, c1);
774    m.emit_type_cooperative_matrix(ty_cmat_c, ty_f32, c_scope, c_tm, c_tn, c2);
775    m.emit(30, &[ty_v3u32, ty_u32, 3]); // OpTypeVector v3u32
776    m.emit_type_ptr(ty_ptr_in_v3u32, STORAGE_CLASS_INPUT, ty_v3u32);
777    m.emit_type_fn(ty_fn_void, ty_void, &[]);
778
779    // Variables
780    m.emit_variable(ty_ptr_sb_f16, var_a, STORAGE_CLASS_STORAGE_BUFFER);
781    m.emit_variable(ty_ptr_sb_f16, var_b, STORAGE_CLASS_STORAGE_BUFFER);
782    m.emit_variable(ty_ptr_sb_f32, var_c, STORAGE_CLASS_STORAGE_BUFFER);
783    m.emit_variable(ty_ptr_sb_u32, var_dim, STORAGE_CLASS_STORAGE_BUFFER);
784    m.emit_variable(ty_ptr_in_v3u32, var_wg, STORAGE_CLASS_INPUT);
785
786    // Function body
787    m.emit_function(ty_void, fn_main, 0, ty_fn_void);
788    m.emit_label(lbl);
789
790    let wg_id = m.alloc_id();
791    m.emit_load(ty_v3u32, wg_id, var_wg);
792    let wg_col = m.alloc_id();
793    m.emit_composite_extract(ty_u32, wg_col, wg_id, 0);
794    let wg_row = m.alloc_id();
795    m.emit_composite_extract(ty_u32, wg_row, wg_id, 1);
796
797    let ptr_m = m.alloc_id();
798    m.emit_access_chain(ty_ptr_u32_sb, ptr_m, var_dim, &[c0, c0]);
799    let ptr_n = m.alloc_id();
800    m.emit_access_chain(ty_ptr_u32_sb, ptr_n, var_dim, &[c0, c1]);
801    let ptr_k = m.alloc_id();
802    m.emit_access_chain(ty_ptr_u32_sb, ptr_k, var_dim, &[c0, c2]);
803    let dim_m = m.alloc_id();
804    m.emit_load(ty_u32, dim_m, ptr_m);
805    let dim_n = m.alloc_id();
806    m.emit_load(ty_u32, dim_n, ptr_n);
807    let dim_k = m.alloc_id();
808    m.emit_load(ty_u32, dim_k, ptr_k);
809
810    let row_base = m.alloc_id();
811    m.emit_i_mul(ty_u32, row_base, wg_row, c_tm);
812    let col_base = m.alloc_id();
813    m.emit_i_mul(ty_u32, col_base, wg_col, c_tn);
814
815    // Load C tile (f32)
816    let c_base_tmp = m.alloc_id();
817    m.emit_i_mul(ty_u32, c_base_tmp, row_base, dim_n);
818    let c_base_flat = m.alloc_id();
819    m.emit_i_add(ty_u32, c_base_flat, c_base_tmp, col_base);
820    let ptr_c_tile = m.alloc_id();
821    m.emit_in_bounds_access_chain(ty_ptr_f32_sb, ptr_c_tile, var_c, &[c0, c_base_flat]);
822    let mat_c_init = m.alloc_id();
823    // MemoryLayout is an IdRef to a constant; RowMajor == 0 == c0.
824    m.emit_coop_matrix_load(ty_cmat_c, mat_c_init, ptr_c_tile, c0, dim_n);
825
826    // Load A tile (f16)
827    let a_base = m.alloc_id();
828    m.emit_i_mul(ty_u32, a_base, row_base, dim_k);
829    let ptr_a = m.alloc_id();
830    m.emit_in_bounds_access_chain(ty_ptr_f16_sb, ptr_a, var_a, &[c0, a_base]);
831    let mat_a = m.alloc_id();
832    m.emit_coop_matrix_load(ty_cmat_a, mat_a, ptr_a, c0, dim_k);
833
834    // Load B tile (f16)
835    let ptr_b = m.alloc_id();
836    m.emit_in_bounds_access_chain(ty_ptr_f16_sb, ptr_b, var_b, &[c0, col_base]);
837    let mat_b = m.alloc_id();
838    m.emit_coop_matrix_load(ty_cmat_b, mat_b, ptr_b, c0, dim_n);
839
840    // XMX multiply-accumulate: D = A(f16)*B(f16) + C(f32)
841    let mat_out = m.alloc_id();
842    m.emit_coop_matrix_muladd(
843        ty_cmat_c,
844        mat_out,
845        mat_a,
846        mat_b,
847        mat_c_init,
848        COOPERATIVE_MATRIX_OPERANDS_NONE,
849    );
850
851    // Store result
852    m.emit_coop_matrix_store(ptr_c_tile, mat_out, c0, dim_n);
853
854    m.emit_return();
855    m.emit_function_end();
856    m.finalize()
857}
858
859// ─── matmul_xmx_bf16_spirv ───────────────────────────────────────────────────
860
861/// Generate a SPIR-V binary for BF16 input / FP32 accumulation XMX GEMM.
862///
863/// # Errors
864///
865/// Returns [`LevelZeroError::Unsupported`](crate::error::LevelZeroError::Unsupported) because a correct BF16 kernel
866/// requires a genuine BF16 cooperative-matrix element type (an
867/// `OpTypeFloat 16` carrying the `SPV_KHR_bfloat16` `BFloat16KHR` FP-encoding,
868/// plus the matching `BFloat16CooperativeMatrixKHR` capability), which cannot
869/// be validated on this non-Intel host.
870///
871/// The previous implementation reused the FP16 kernel body verbatim and merely
872/// renamed the entry point. Because BF16 (8-bit exponent) and IEEE binary16
873/// (5-bit exponent) share no bit layout, that reinterpreted BF16 inputs as
874/// FP16 and produced silently wrong results while reporting success — a
875/// correctness trap. Returning a loud error is strictly better than emitting an
876/// FP16 kernel mislabeled as BF16.
877pub fn matmul_xmx_bf16_spirv(
878    tile: XmxTileConfig,
879    wg_x: u32,
880    wg_y: u32,
881) -> crate::error::LevelZeroResult<Vec<u32>> {
882    let _ = (tile, wg_x, wg_y);
883    Err(crate::error::LevelZeroError::Unsupported(
884        "BF16 XMX GEMM (matmul_xmx_bf16_spirv) is not yet implemented: it requires a genuine \
885         SPV_KHR_bfloat16 cooperative-matrix element type; reusing the FP16 kernel would \
886         reinterpret BF16 bits as IEEE binary16 and silently corrupt every result"
887            .into(),
888    ))
889}
890
891// ─── XMX detection / capability query ────────────────────────────────────────
892
893/// Whether the given Level Zero device name suggests XMX hardware support.
894///
895/// Returns `true` for Intel Arc (Alchemist), Xe-HPC (Ponte Vecchio / Data
896/// Center GPU Max), and Battlemage GPU families that include XMX engines.
897///
898/// This is a best-effort heuristic based on the device name string returned
899/// by `zeDeviceGetProperties`. Production code should also query
900/// `zeDeviceGetModuleProperties` and check `flags & ZE_DEVICE_MODULE_FLAG_FP16`
901/// and the `SpirvVersion` field.
902pub fn device_supports_xmx(device_name: &str) -> bool {
903    let name = device_name.to_ascii_lowercase();
904    // Intel Arc (Alchemist / Battlemage)
905    name.contains("arc")
906    // Xe-HPC (Ponte Vecchio / Data Center GPU Max)
907    || name.contains("data center gpu max")
908    || name.contains("ponte vecchio")
909    || name.contains("max 1")
910    || name.contains("max 12")
911    // Intel UHD / Iris Xe (integrated) — has basic XMX on Gen12+
912    || name.contains("iris xe")
913    || name.contains("uhd graphics")
914}
915
916/// Best XMX tile configuration for the given device name.
917///
918/// Falls back to [`XmxTileConfig::XE_DEFAULT`] for unknown devices.
919pub fn best_xmx_tile(device_name: &str) -> XmxTileConfig {
920    let name = device_name.to_ascii_lowercase();
921    if name.contains("max") || name.contains("ponte vecchio") {
922        // Xe-HPC supports 8×16×16 and 8×32×16 natively
923        XmxTileConfig { m: 8, n: 32, k: 16 }
924    } else if name.contains("arc") || name.contains("iris xe") {
925        XmxTileConfig::XE_HPC_FP16
926    } else {
927        XmxTileConfig::XE_DEFAULT
928    }
929}
930
931// ─── Tests ───────────────────────────────────────────────────────────────────
932
933#[cfg(test)]
934mod tests {
935    use super::*;
936
937    #[test]
938    fn gemm_xmx_spirv_starts_with_magic() {
939        let words = gemm_xmx_spirv(XmxTileConfig::default(), 16, 16);
940        assert!(!words.is_empty(), "output must not be empty");
941        assert_eq!(words[0], 0x07230203, "first word must be SPIR-V magic");
942    }
943
944    #[test]
945    fn gemm_xmx_spirv_version_1_6() {
946        let words = gemm_xmx_spirv(XmxTileConfig::default(), 16, 16);
947        assert_eq!(words[1], 0x0001_0600, "version must be SPIR-V 1.6");
948    }
949
950    #[test]
951    fn gemm_xmx_spirv_id_bound_nonzero() {
952        let words = gemm_xmx_spirv(XmxTileConfig::default(), 16, 16);
953        assert!(words[3] > 0, "ID bound must be > 0");
954    }
955
956    #[test]
957    fn gemm_xmx_f16_produces_valid_header() {
958        let words = gemm_xmx_f16_spirv(XmxTileConfig::XE_HPC_FP16, 16, 16);
959        assert_eq!(words[0], SPIRV_MAGIC);
960        assert_eq!(words[1], SPIRV_VERSION_1_6);
961        assert!(words.len() > 20, "module must have non-trivial content");
962    }
963
964    #[test]
965    fn matmul_xmx_bf16_returns_unsupported() {
966        // The BF16 kernel must NOT silently reuse the FP16 body (which would
967        // reinterpret BF16 bits as IEEE binary16 and corrupt every result); it
968        // returns a loud error until a genuine BF16 element type is emitted.
969        let result = matmul_xmx_bf16_spirv(XmxTileConfig::default(), 16, 16);
970        assert!(matches!(
971            result,
972            Err(crate::error::LevelZeroError::Unsupported(_))
973        ));
974    }
975
976    #[test]
977    fn xmx_tile_accum_elements() {
978        let tile = XmxTileConfig { m: 8, n: 16, k: 16 };
979        assert_eq!(tile.accum_elements(), 128);
980    }
981
982    #[test]
983    fn device_supports_xmx_arc() {
984        assert!(device_supports_xmx("Intel Arc A770 Graphics"));
985        assert!(device_supports_xmx("Intel Data Center GPU Max 1550"));
986        assert!(!device_supports_xmx("AMD Radeon RX 7900 XTX"));
987    }
988
989    #[test]
990    fn best_xmx_tile_xe_hpc() {
991        let tile = best_xmx_tile("Intel Data Center GPU Max 1550");
992        assert_eq!(tile.m, 8);
993        assert_eq!(tile.n, 32);
994    }
995
996    #[test]
997    fn different_tile_sizes_produce_different_binaries() {
998        let a = gemm_xmx_spirv(XmxTileConfig { m: 8, n: 16, k: 16 }, 16, 16);
999        let b = gemm_xmx_spirv(XmxTileConfig { m: 8, n: 32, k: 16 }, 16, 16);
1000        assert_ne!(
1001            a, b,
1002            "different tile configurations must yield distinct SPIR-V"
1003        );
1004    }
1005
1006    #[test]
1007    fn gemm_xmx_spirv_contains_cooperative_matrix_opcode() {
1008        let words = gemm_xmx_spirv(XmxTileConfig::default(), 16, 16);
1009        // OP_TYPE_COOPERATIVE_MATRIX_KHR = 4456, embedded in a word as (word_count << 16) | opcode
1010        let has_cmat = words
1011            .iter()
1012            .any(|&w| (w & 0xFFFF) == OP_TYPE_COOPERATIVE_MATRIX_KHR);
1013        assert!(has_cmat, "module must declare OpTypeCooperativeMatrixKHR");
1014    }
1015
1016    #[test]
1017    fn gemm_xmx_f16_contains_float16_type() {
1018        let words = gemm_xmx_f16_spirv(XmxTileConfig::XE_HPC_FP16, 16, 16);
1019        // OpTypeFloat id 16 → OP_TYPE_FLOAT = 22, 3 words total → (3 << 16) | 22 = 0x00030016
1020        let has_f16 = words.windows(3).any(|w| {
1021            (w[0] & 0xFFFF) == 22 /* OP_TYPE_FLOAT */ && w[2] == 16 /* width */
1022        });
1023        assert!(has_f16, "FP16 module must declare 16-bit float type");
1024    }
1025}